From 828819507296c7d79dc826f51956c72b3519f331 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 21:23:17 +0900 Subject: [PATCH 01/23] 2025-12-31 21:23:17 (Wed) > DW-Mac > derekwan From c4fc170962773a878b580896c31805b393eda2e1 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 21:26:33 +0900 Subject: [PATCH 02/23] 2025-12-31 21:26:33 (Wed) > DW-Mac > derekwan --- .bumpversion.toml | 13 +++ .github/workflows/pull-request.yaml | 118 ++++++++++++++++++++++ .github/workflows/pull-request.yml | 68 ------------- .github/workflows/{push.yml => push.yaml} | 10 +- .pre-commit-config.yaml | 109 ++++++++++++-------- README.md | 12 +-- pyproject.toml | 95 +---------------- pyrightconfig.json | 29 ++++++ pytest.toml | 39 +++++++ ruff.toml | 60 +++++++++++ src/tests/test_typing_funcs/no_future.py | 4 +- src/utilities/__init__.py | 2 +- src/utilities/parse.py | 2 +- src/utilities/sentinel.py | 4 +- src/utilities/sqlalchemy.py | 6 +- src/utilities/typing.py | 2 +- uv.lock | 6 +- 17 files changed, 355 insertions(+), 224 deletions(-) create mode 100644 .bumpversion.toml create mode 100644 .github/workflows/pull-request.yaml delete mode 100644 .github/workflows/pull-request.yml rename .github/workflows/{push.yml => push.yaml} (71%) create mode 100644 pyrightconfig.json create mode 100644 pytest.toml create mode 100644 ruff.toml diff --git a/.bumpversion.toml b/.bumpversion.toml new file mode 100644 index 0000000000..40adebd401 --- /dev/null +++ b/.bumpversion.toml @@ -0,0 +1,13 @@ +[tool.bumpversion] + allow_dirty = true + current_version = "0.174.21" + + [[tool.bumpversion.files]] + filename = "pyproject.toml" + replace = "version = \"{new_version}\"" + search = "version = \"{current_version}\"" + + [[tool.bumpversion.files]] + filename = "src/utilities/__init__.py" + replace = "__version__ = \"{new_version}\"" + search = "__version__ = \"{current_version}\"" diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml new file mode 100644 index 0000000000..e659ba3541 --- /dev/null +++ b/.github/workflows/pull-request.yaml @@ -0,0 +1,118 @@ +name: pull-request + +on: + pull_request: + branches: + - master + + schedule: + - cron: 0 0 * * * +jobs: + check-version-bumped: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + cache: pip + - run: curl -fsSL + https://raw.githubusercontent.com/dycw/remote-scripts/refs/heads/master/scripts/check-version-bumped + | sh -s + + ruff: + name: ruff + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: astral-sh/ruff-action@v3 + - run: ruff check --fix + - run: ruff format + + - name: Run 'ruff' + uses: dycw/action-ruff@latest + with: + token: ${{secrets.GITHUB_TOKEN}} + pyright: + name: pyright + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + version: latest + - uses: actions/setup-python@v6 + with: + python-version-file: .python-version + - run: uv sync + - run: uv run pyright + + - name: Run 'pyright' + uses: dycw/action-pyright@latest + with: + token: ${{secrets.GITHUB_TOKEN}} + python-version: "3.14" + test: + name: test / ${{ matrix.os }} / ${{ matrix.version }} + env: + CI: 1 + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, ubuntu-latest] + version: ["3.12", "3.13", "3.14"] + services: + redis: + image: ${{ matrix.os == 'ubuntu-latest' && 'redis/redis-stack:latest' || + '' }} + ports: + - 6379:6379 + steps: + - uses: actions/checkout@v6 + - uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + version: latest + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.version }} + - run: uv sync --locked + - run: uv run pytest --cov-report=term-missing:skip-covered -n=auto + timeout-minutes: 60 + pre-commit: + runs-on: ubuntu-latest + steps: + - name: Run 'pre-commit' + uses: dycw/action-pre-commit@latest + with: + token: ${{secrets.GITHUB_TOKEN}} + repos: |- + dycw/pre-commit-hook-nitpick + pre-commit/pre-commit-hooks + pytest: + env: + CI: "1" + name: pytest (${{matrix.os}}, ${{matrix.python-version}}, + ${{matrix.resolution}}) + runs-on: ${{matrix.os}} + steps: + - name: Run 'pytest' + uses: dycw/action-pytest@latest + with: + token: ${{secrets.GITHUB_TOKEN}} + python-version: ${{matrix.python-version}} + resolution: ${{matrix.resolution}} + strategy: + fail-fast: false + matrix: + os: + - macos-latest + - ubuntu-latest + python-version: + - "3.12" + - "3.13" + - "3.14" + resolution: + - highest + timeout-minutes: 10 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml deleted file mode 100644 index 1d89e858ff..0000000000 --- a/.github/workflows/pull-request.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: pull-request - -on: - pull_request: - branches: - - master - -jobs: - check-version-bumped: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 - with: - cache: pip - - run: curl -fsSL https://raw.githubusercontent.com/dycw/remote-scripts/refs/heads/master/scripts/check-version-bumped | sh -s - - ruff: - name: ruff - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: astral-sh/ruff-action@v3 - - run: ruff check --fix - - run: ruff format - - pyright: - name: pyright - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - version: latest - - uses: actions/setup-python@v5 - with: - python-version-file: .python-version - - run: uv sync - - run: uv run pyright - - test: - name: test / ${{ matrix.os }} / ${{ matrix.version }} - env: - CI: 1 - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [macos-latest, ubuntu-latest] - version: ["3.12", "3.13", "3.14"] - services: - redis: - image: ${{ matrix.os == 'ubuntu-latest' && 'redis/redis-stack:latest' || '' }} - ports: - - 6379:6379 - steps: - - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - version: latest - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.version }} - - run: uv sync --locked - - run: uv run pytest --cov-report=term-missing:skip-covered -n=auto - timeout-minutes: 60 diff --git a/.github/workflows/push.yml b/.github/workflows/push.yaml similarity index 71% rename from .github/workflows/push.yml rename to .github/workflows/push.yaml index ef791efdd1..45cbb9fac3 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yaml @@ -12,12 +12,16 @@ jobs: - uses: actions/checkout@v6 - uses: butlerlogic/action-autotag@1.1.2 # https://github.com/ButlerLogic/action-autotag/issues/45#issuecomment-1825726927 env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: strategy: regex root: pyproject.toml - regex_pattern: 'current_version = "(?\d+\.\d+\.\d+)"' + regex_pattern: current_version = "(?\d+\.\d+\.\d+)" + - name: Tag latest commit + uses: dycw/action-tag@latest + with: + token: ${{secrets.GITHUB_TOKEN}} publish: runs-on: ubuntu-latest needs: @@ -31,7 +35,7 @@ jobs: - uses: astral-sh/setup-uv@v7 with: enable-cache: true - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version-file: pyproject.toml - run: uv build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 995753d778..182d4418ef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,45 +1,35 @@ repos: - # fixers - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.10 + - repo: https://github.com/dycw/pre-commit-hook-nitpick + rev: 0.7.9 hooks: - - id: ruff-check - args: [--fix] - - id: ruff-format - - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.9.18 - hooks: - - id: uv-lock - args: [--upgrade] - - repo: https://github.com/compwa/taplo-pre-commit - rev: v0.9.3 - hooks: - - id: taplo-format - args: - [ - --option, - indent_tables=true, - --option, - indent_entries=true, - --option, - reorder_keys=true, - ] - - repo: https://github.com/dycw/pre-commit-hooks - rev: 0.13.26 - hooks: - - id: format-requirements - - id: replace-sequence-str - - id: run-bump-my-version - - repo: https://github.com/scop/pre-commit-shfmt - rev: v3.12.0-2 - hooks: - - id: shfmt - # linters - - repo: https://github.com/shellcheck-py/shellcheck-py - rev: v0.11.0.1 - hooks: - - id: shellcheck - # fixers/linters + - args: + - --description=Miscellaneous Python utilities + - --github--pull-request--pre-commit + - --github--pull-request--pyright + - --github--pull-request--pytest--os--macos + - --github--pull-request--pytest--os--ubuntu + - --github--pull-request--pytest--python-version--3-12 + - --github--pull-request--pytest--python-version--3-13 + - --github--pull-request--pytest--python-version--3-14 + - --github--pull-request--pytest--resolution--highest + - --github--pull-request--ruff + - --github--push--tag + - --package-name=dycw-utilities + - --pre-commit--prettier + - --pre-commit--ruff + - --pre-commit--taplo + - --pre-commit--uv + - --pyproject + - --pyright + - --pytest + - --pytest--asyncio + - --pytest--timeout=600 + - --python-version=3.12 + - --python-package-name=utilities + - --readme + - --repo-name=python-utilities + - --ruff + id: nitpick - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: @@ -50,8 +40,43 @@ repos: - id: detect-private-key - id: end-of-file-fixer - id: mixed-line-ending - args: [--fix=lf] + args: + - --fix=lf - id: no-commit-to-branch - id: pretty-format-json - args: [--autofix] + args: + - --autofix - id: trailing-whitespace + - repo: local + hooks: + - id: prettier + name: prettier + entry: npx prettier --write + language: system + types_or: + - markdown + - yaml + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.10 + hooks: + - id: ruff-check + args: + - --fix + - id: ruff-format + - repo: https://github.com/compwa/taplo-pre-commit + rev: v0.9.3 + hooks: + - id: taplo-format + args: + - --option + - indent_tables=true + - --option + - indent_entries=true + - --option + - reorder_keys=true + - repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.9.21 + hooks: + - id: uv-lock + args: + - --upgrade diff --git a/README.md b/README.md index 6981f24278..fae17c09bc 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,3 @@ -[![PyPI version](https://badge.fury.io/py/dycw-utilities.svg)](https://badge.fury.io/py/dycw-utilities) +# `python-utilities` -# `dycw-utilities` - -[All the Python functions I don't want to write twice.](https://github.com/nvim-lua/plenary.nvim) - -## Installation - -- `pip install dycw-utilities` - -or with [extras](https://github.com/dycw/python-utilities/blob/master/pyproject.toml). +Miscellaneous Python utilities diff --git a/pyproject.toml b/pyproject.toml index 146684e160..a25d5de936 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,7 @@ -# build-system [build-system] build-backend = "uv_build" requires = ["uv_build"] -# dependency groups [dependency-groups] aeventkit = ["aeventkit >=2.1.0, <2.2"] altair = ["altair >=5.5.0, <5.6"] @@ -27,6 +25,8 @@ "pyright[nodejs] >=1.1.407, <1.2", "pytest-cov >=7.0.0, <7.1", "pytest-timeout >=2.4.0, <2.5", + "dycw-utilities[test]", + "rich", ] fastapi = ["fastapi >=0.128.0, <0.129"] fastapi-test = ["httpx", "uvicorn"] @@ -89,7 +89,6 @@ tzdata = ["tzdata >=2025.3, <2025.4"] whenever-test = ["pathvalidate"] -# project [project] authors = [{ email = "d.wan@icloud.com", name = "Derek Wan" }] dependencies = [ @@ -98,10 +97,11 @@ "tzlocal >=5.3.1, <5.4", "whenever >=0.9.4, <0.10", ] + description = "Miscellaneous Python utilities" name = "dycw-utilities" readme = "README.md" requires-python = ">= 3.12" - version = "0.174.20" + version = "0.174.21" [project.entry-points.pytest11] pytest-randomly = "utilities.pytest_plugins.pytest_randomly" @@ -135,7 +135,7 @@ # bump-my-version [tool.bumpversion] allow_dirty = true - current_version = "0.174.20" + current_version = "0.174.21" [[tool.bumpversion.files]] filename = "src/utilities/__init__.py" @@ -212,18 +212,6 @@ # pytest [tool.pytest] - addopts = [ - "-ra", - "-vv", - "--color=auto", - "--durations=10", - "--durations-min=10", - "--timeout=600", - ] - asyncio_default_fixture_loop_scope = "function" - asyncio_mode = "auto" - collect_imported_tests = false - empty_parameter_set_mark = "fail_at_collect" filterwarnings = [ "error", "ignore: was delete before being closed:ResourceWarning", # sqlalchemy @@ -251,79 +239,6 @@ timeout = "600" xfail_strict = true - # ruff - [tool.ruff] - src = ["src"] - target-version = "py312" - unsafe-fixes = true - - [tool.ruff.format] - preview = true - skip-magic-trailing-comma = true - - [tool.ruff.lint] - explicit-preview-rules = true - fixable = ["ALL"] - ignore = [ - "ANN401", # any-type - "A005", # stdlib-module-shadowing - "ASYNC109", # async-function-with-timeout - "C901", # complex-structure - "CPY", # flake8-copyright - "D", # pydocstyle - "DOC", # pydoclint - "E501", # line-too-long - "PD", # pandas-vet - "PERF203", # try-except-in-loop - "PLC0415", # import-outside-top-level - "PLR0911", # too-many-return-statements - "PLR0912", # too-many-branches - "PLR0913", # too-many-arguments - "PLR0915", # too-many-statements - "PLR2004", # magic-value-comparison - "PT012", # pytest-raises-with-multiple-statements - "PT013", # pytest-incorrect-pytest-import - "S202", # tarfile-unsafe-members - "S310", # suspicious-url-open-usage - "S311", # suspicious-non-cryptographic-random-usage - "S602", # subprocess-popen-with-shell-equals-true - "S603", # subprocess-without-shell-equals-true - "S607", # start-process-with-partial-path - # preview - "S101", # assert - # formatter - "W191", # tab-indentation - "E111", # indentation-with-invalid-multiple - "E114", # indentation-with-invalid-multiple-comment - "E117", # over-indented - "COM812", # missing-trailing-comma - "COM819", # prohibited-trailing-comma - "ISC001", # single-line-implicit-string-concatenation - "ISC002", # multi-line-implicit-string-concatenation - ] - preview = true - select = [ - "ALL", - "RUF022", # unsorted-dunder-all - ] - - [tool.ruff.lint.extend-per-file-ignores] - "src/tests/**/*.py" = [ - "S101", # assert - "SLF001", # private-member-access - ] - "src/tests/test_typing_funcs/no_future.py" = [ - "I002", - ] # missing-required-import - - [tool.ruff.lint.flake8-tidy-imports] - ban-relative-imports = "all" - - [tool.ruff.lint.isort] - required-imports = ["from __future__ import annotations"] - split-on-trailing-comma = false - - # uv [tool.uv] default-groups = "all" diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000000..8e3afb1cef --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,29 @@ +{ + "deprecateTypingAliases": true, + "enableReachabilityAnalysis": false, + "include": [ + "src" + ], + "pythonVersion": "3.12", + "reportCallInDefaultInitializer": true, + "reportImplicitOverride": true, + "reportImplicitStringConcatenation": true, + "reportImportCycles": true, + "reportMissingSuperCall": true, + "reportMissingTypeArgument": false, + "reportMissingTypeStubs": false, + "reportPrivateImportUsage": false, + "reportPrivateUsage": false, + "reportPropertyTypeMismatch": true, + "reportUninitializedInstanceVariable": true, + "reportUnknownArgumentType": false, + "reportUnknownMemberType": false, + "reportUnknownParameterType": false, + "reportUnknownVariableType": false, + "reportUnnecessaryComparison": false, + "reportUnnecessaryTypeIgnoreComment": true, + "reportUnusedCallResult": true, + "reportUnusedImport": false, + "reportUnusedVariable": false, + "typeCheckingMode": "strict" +} diff --git a/pytest.toml b/pytest.toml new file mode 100644 index 0000000000..a44324fe05 --- /dev/null +++ b/pytest.toml @@ -0,0 +1,39 @@ +[pytest] + addopts = [ + "-ra", + "-vv", + "--color=auto", + "--durations=10", + "--durations-min=10", + ] + asyncio_default_fixture_loop_scope = "function" + asyncio_mode = "auto" + collect_imported_tests = false + empty_parameter_set_mark = "fail_at_collect" + filterwarnings = [ + "error", + "ignore: was delete before being closed:ResourceWarning", # sqlalchemy + "ignore:Exception ignored in.* :pytest.PytestUnraisableExceptionWarning", + "ignore:Exception in thread Thread-.*:pytest.PytestUnhandledThreadExceptionWarning", + "ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning", # jupyter + "ignore:ResourceTracker called reentrantly for resource cleanup, which is unsupported:UserWarning", + "ignore:The garbage collector is trying to clean up non-checked-in connection :RuntimeWarning", # sqlalchemy + "ignore:Using fork.* can cause Polars to deadlock in the child process:RuntimeWarning", # polars/pqdm + "ignore:coroutine 'AsyncConnection.close' was never awaited:RuntimeWarning", + "ignore:loop is closed:ResourceWarning", # redis + "ignore:unclosed :ResourceWarning", # redis + "ignore:unclosed :ResourceWarning", # redis + "ignore:unclosed Connection :ResourceWarning", # redis + "ignore:unclosed connection :ResourceWarning", # asyncpg + "ignore:unclosed database in :ResourceWarning", # sqlalchemy + "ignore:unclosed event loop <_UnixSelectorEventLoop .*>:ResourceWarning", # redis + "ignore:unclosed file <_io.*TextIOWrapper .*>:ResourceWarning", # logging + "ignore:unclosed transport <_SelectorSocketTransport .*>:ResourceWarning", # redis + "ignore:Do not expect file_or_dir in Namespace:UserWarning", # pytest + + ] + minversion = "9.0" + strict = true + testpaths = ["src/tests"] + timeout = "600" + xfail_strict = true diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000000..ba422b136a --- /dev/null +++ b/ruff.toml @@ -0,0 +1,60 @@ +target-version = "py314" +unsafe-fixes = true + +[format] + preview = true + skip-magic-trailing-comma = true + +[lint] + explicit-preview-rules = true + fixable = ["ALL"] + ignore = [ + "ANN401", + "ASYNC109", + "C901", + "CPY", + "D", + "E501", + "PD", + "PERF203", + "PLC0415", + "PLE1205", + "PLR0904", + "PLR0911", + "PLR0912", + "PLR0913", + "PLR0915", + "PLR2004", + "PT012", + "PT013", + "PYI041", + "S202", + "S310", + "S311", + "S602", + "S603", + "S607", + "W191", + "E111", + "E114", + "E117", + "COM812", + "COM819", + "ISC001", + "ISC002", + ] + preview = true + select = ["ALL", "RUF022", "RUF029"] + + [lint.extend-per-file-ignores] + "test_*.py" = ["S101", "SLF001"] + + [lint.flake8-bugbear] + extend-immutable-calls = ["typing.cast"] + + [lint.flake8-tidy-imports] + ban-relative-imports = "all" + + [lint.isort] + required-imports = ["from __future__ import annotations"] + split-on-trailing-comma = false diff --git a/src/tests/test_typing_funcs/no_future.py b/src/tests/test_typing_funcs/no_future.py index 97a4d0e0b8..92a30f5c49 100644 --- a/src/tests/test_typing_funcs/no_future.py +++ b/src/tests/test_typing_funcs/no_future.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from typing import Literal, NotRequired, TypedDict @@ -27,7 +29,7 @@ class DataClassNoFutureNestedInnerFirstOuter: @dataclass(kw_only=True) class DataClassNoFutureNestedOuterFirstOuter: - inner: "DataClassNoFutureNestedOuterFirstInner" + inner: DataClassNoFutureNestedOuterFirstInner @dataclass(kw_only=True) diff --git a/src/utilities/__init__.py b/src/utilities/__init__.py index b992bfe185..1fe4a1b471 100644 --- a/src/utilities/__init__.py +++ b/src/utilities/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -__version__ = "0.174.20" +__version__ = "0.174.21" diff --git a/src/utilities/parse.py b/src/utilities/parse.py index f98f490bed..ab91d55c4a 100644 --- a/src/utilities/parse.py +++ b/src/utilities/parse.py @@ -284,7 +284,7 @@ def _parse_object_extra(cls: Any, text: str, extra: ParseObjectExtra, /) -> Any: except KeyError: try: parser = one(p for c, p in extra.items() if is_subclass_gen(cls, c)) - except (OneEmptyError, TypeError): + except OneEmptyError, TypeError: raise _ParseObjectParseError(type_=cls, text=text) from None except OneNonUniqueError as error: raise _ParseObjectExtraNonUniqueError( diff --git a/src/utilities/sentinel.py b/src/utilities/sentinel.py index f17ddeaf17..58257bac6f 100644 --- a/src/utilities/sentinel.py +++ b/src/utilities/sentinel.py @@ -2,9 +2,7 @@ from dataclasses import dataclass from re import IGNORECASE, search -from typing import Any, override - -from typing_extensions import TypeIs +from typing import Any, TypeIs, override class _Meta(type): diff --git a/src/utilities/sqlalchemy.py b/src/utilities/sqlalchemy.py index e894995ad2..870c490c6e 100644 --- a/src/utilities/sqlalchemy.py +++ b/src/utilities/sqlalchemy.py @@ -119,7 +119,7 @@ def check_connect(engine: Engine, /) -> bool: try: with engine.connect() as conn: return bool(conn.execute(_SELECT).scalar_one()) - except (gaierror, ConnectionRefusedError, DatabaseError): # pragma: no cover + except gaierror, ConnectionRefusedError, DatabaseError: # pragma: no cover return False @@ -134,7 +134,7 @@ async def check_connect_async( try: async with timeout_td(timeout, error=error), engine.connect() as conn: return bool((await conn.execute(_SELECT)).scalar_one()) - except (gaierror, ConnectionRefusedError, DatabaseError, TimeoutError): + except gaierror, ConnectionRefusedError, DatabaseError, TimeoutError: return False @@ -834,7 +834,7 @@ def is_orm(obj: Any, /) -> TypeGuard[ORMInstOrClass]: if isinstance(obj, type): try: _ = class_mapper(cast("Any", obj)) - except (ArgumentError, UnmappedClassError): + except ArgumentError, UnmappedClassError: return False return True return is_orm(type(obj)) diff --git a/src/utilities/typing.py b/src/utilities/typing.py index b369225194..c01685b332 100644 --- a/src/utilities/typing.py +++ b/src/utilities/typing.py @@ -592,7 +592,7 @@ def _is_namedtuple_core(obj: Any, /) -> bool: """Check if an object is an instance of a dataclass.""" try: (base,) = obj.__orig_bases__ - except (AttributeError, ValueError): + except AttributeError, ValueError: return False return base is NamedTuple diff --git a/uv.lock b/uv.lock index 52979f784e..fcb4a39676 100644 --- a/uv.lock +++ b/uv.lock @@ -634,7 +634,7 @@ wheels = [ [[package]] name = "dycw-utilities" -version = "0.174.20" +version = "0.174.21" source = { editable = "." } dependencies = [ { name = "atomicwrites" }, @@ -706,9 +706,11 @@ dev = [ { name = "coloredlogs" }, { name = "coverage-conditional-plugin" }, { name = "dycw-pytest-only" }, + { name = "dycw-utilities", extra = ["test"] }, { name = "pyright", extra = ["nodejs"] }, { name = "pytest-cov" }, { name = "pytest-timeout" }, + { name = "rich" }, ] fastapi = [ { name = "fastapi" }, @@ -948,9 +950,11 @@ dev = [ { name = "coloredlogs", specifier = ">=15.0.1,<15.1" }, { name = "coverage-conditional-plugin", specifier = ">=0.9.0,<0.10" }, { name = "dycw-pytest-only", specifier = ">=2.1.1,<2.2" }, + { name = "dycw-utilities", extras = ["test"] }, { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.407,<1.2" }, { name = "pytest-cov", specifier = ">=7.0.0,<7.1" }, { name = "pytest-timeout", specifier = ">=2.4.0,<2.5" }, + { name = "rich" }, ] fastapi = [{ name = "fastapi", specifier = ">=0.128.0,<0.129" }] fastapi-test = [ From 0b77fbf2d8835ae905c4680b60e2ee229bfffeed Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 21:27:31 +0900 Subject: [PATCH 03/23] 2025-12-31 21:27:31 (Wed) > DW-Mac > derekwan --- .coveragerc.toml | 13 ++++++++++ .pre-commit-config.yaml | 1 + pyproject.toml | 56 ----------------------------------------- pytest.toml | 4 ++- 4 files changed, 17 insertions(+), 57 deletions(-) create mode 100644 .coveragerc.toml diff --git a/.coveragerc.toml b/.coveragerc.toml new file mode 100644 index 0000000000..237155ab72 --- /dev/null +++ b/.coveragerc.toml @@ -0,0 +1,13 @@ +[html] +directory = ".coverage/html" + +[report] +exclude_also = ["@overload", "if TYPE_CHECKING:"] +fail_under = 100.0 +skip_covered = true +skip_empty = true + +[run] +branch = true +data_file = ".coverage/data" +parallel = true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 182d4418ef..737a3f35fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,7 @@ repos: rev: 0.7.9 hooks: - args: + # - --coverage - --description=Miscellaneous Python utilities - --github--pull-request--pre-commit - --github--pull-request--pyright diff --git a/pyproject.toml b/pyproject.toml index a25d5de936..5e89cf9e0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -183,62 +183,6 @@ plugins = ["coverage_conditional_plugin"] source = ["src/utilities"] - # pyright - [tool.pyright] - deprecateTypingAliases = true - enableReachabilityAnalysis = false - ignore = ["**/_typeshed/**"] - pythonVersion = "3.12" - reportCallInDefaultInitializer = true - reportImplicitOverride = true - reportImplicitStringConcatenation = true - reportImportCycles = true - reportMissingSuperCall = true - reportMissingTypeArgument = false - reportMissingTypeStubs = false - reportPrivateUsage = false - reportPropertyTypeMismatch = true - reportUninitializedInstanceVariable = true - reportUnknownArgumentType = false - reportUnknownMemberType = false - reportUnknownParameterType = false - reportUnknownVariableType = false - reportUnnecessaryComparison = false - reportUnnecessaryTypeIgnoreComment = true - reportUnusedCallResult = true - reportUnusedImport = false - reportUnusedVariable = false - typeCheckingMode = "strict" - - # pytest - [tool.pytest] - filterwarnings = [ - "error", - "ignore: was delete before being closed:ResourceWarning", # sqlalchemy - "ignore:Exception ignored in.* :pytest.PytestUnraisableExceptionWarning", - "ignore:Exception in thread Thread-.*:pytest.PytestUnhandledThreadExceptionWarning", - "ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning", # jupyter - "ignore:ResourceTracker called reentrantly for resource cleanup, which is unsupported:UserWarning", - "ignore:The garbage collector is trying to clean up non-checked-in connection :RuntimeWarning", # sqlalchemy - "ignore:Using fork.* can cause Polars to deadlock in the child process:RuntimeWarning", # polars/pqdm - "ignore:coroutine 'AsyncConnection.close' was never awaited:RuntimeWarning", - "ignore:loop is closed:ResourceWarning", # redis - "ignore:unclosed :ResourceWarning", # redis - "ignore:unclosed :ResourceWarning", # redis - "ignore:unclosed Connection :ResourceWarning", # redis - "ignore:unclosed connection :ResourceWarning", # asyncpg - "ignore:unclosed database in :ResourceWarning", # sqlalchemy - "ignore:unclosed event loop <_UnixSelectorEventLoop .*>:ResourceWarning", # redis - "ignore:unclosed file <_io.*TextIOWrapper .*>:ResourceWarning", # logging - "ignore:unclosed transport <_SelectorSocketTransport .*>:ResourceWarning", # redis - "ignore:Do not expect file_or_dir in Namespace:UserWarning", # pytest - ] - minversion = "9.0" - strict = true - testpaths = ["src/tests"] - timeout = "600" - xfail_strict = true - [tool.uv] default-groups = "all" diff --git a/pytest.toml b/pytest.toml index a44324fe05..896ebc46ae 100644 --- a/pytest.toml +++ b/pytest.toml @@ -5,6 +5,9 @@ "--color=auto", "--durations=10", "--durations-min=10", + "--cov=utilities", + "--cov-config=.coveragerc.toml", + "--cov-report=html", ] asyncio_default_fixture_loop_scope = "function" asyncio_mode = "auto" @@ -30,7 +33,6 @@ "ignore:unclosed file <_io.*TextIOWrapper .*>:ResourceWarning", # logging "ignore:unclosed transport <_SelectorSocketTransport .*>:ResourceWarning", # redis "ignore:Do not expect file_or_dir in Namespace:UserWarning", # pytest - ] minversion = "9.0" strict = true From 9806dac83eb29e48833a51c496438818f2d139db Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 21:28:45 +0900 Subject: [PATCH 04/23] 2025-12-31 21:28:45 (Wed) > DW-Mac > derekwan --- .coveragerc.toml | 41 +++++++++++++++++++++++++++++------- pyproject.toml | 55 ------------------------------------------------ 2 files changed, 33 insertions(+), 63 deletions(-) diff --git a/.coveragerc.toml b/.coveragerc.toml index 237155ab72..14509433ef 100644 --- a/.coveragerc.toml +++ b/.coveragerc.toml @@ -1,13 +1,38 @@ +[coverage_conditional_plugin] + [coverage_conditional_plugin.rules] + skipif-ci = '"CI" in os_environ' + skipif-ci-and-mac = '("CI" in os_environ) and (sys_platform == "darwin")' + skipif-ci-and-not-linux = '("CI" in os_environ) and (sys_platform != "linux")' + skipif-ci-or-mac = '("CI" in os_environ) or (sys_platform == "darwin")' + skipif-linux = 'sys_platform == "linux"' + skipif-mac = 'sys_platform == "darwin"' + skipif-not-linux = 'sys_platform != "linux"' + skipif-not-macos = 'sys_platform != "darwin"' + skipif-not-windows = 'sys_platform != "windows"' + skipif-windows = 'sys_platform == "darwin"' + [html] -directory = ".coverage/html" + directory = ".coverage/html" [report] -exclude_also = ["@overload", "if TYPE_CHECKING:"] -fail_under = 100.0 -skip_covered = true -skip_empty = true + exclude_also = [ + "@overload", + "assert_never", + "case never:", + "if TYPE_CHECKING:", + ] + fail_under = 100.0 + skip_covered = true + skip_empty = true [run] -branch = true -data_file = ".coverage/data" -parallel = true + branch = true + data_file = ".coverage/data" + omit = [ + "src/utilities/__init__.py", + "src/utilities/pytest_plugins/*.py", + "src/utilities/streamlit.py", + ] + parallel = true + plugins = ["coverage_conditional_plugin"] + source = ["src/utilities"] diff --git a/pyproject.toml b/pyproject.toml index 5e89cf9e0d..40dad2fa56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,62 +127,7 @@ "testbook >=0.4.2, <0.5", ] - [project.scripts] - -# tool [tool] - - # bump-my-version - [tool.bumpversion] - allow_dirty = true - current_version = "0.174.21" - - [[tool.bumpversion.files]] - filename = "src/utilities/__init__.py" - replace = "__version__ = \"{new_version}\"" - search = "__version__ = \"{current_version}\"" - - # coverage - [tool.coverage] - [tool.coverage.coverage_conditional_plugin] - [tool.coverage.coverage_conditional_plugin.rules] - skipif-ci = '"CI" in os_environ' - skipif-ci-and-mac = '("CI" in os_environ) and (sys_platform == "darwin")' - skipif-ci-and-not-linux = '("CI" in os_environ) and (sys_platform != "linux")' - skipif-ci-or-mac = '("CI" in os_environ) or (sys_platform == "darwin")' - skipif-linux = 'sys_platform == "linux"' - skipif-mac = 'sys_platform == "darwin"' - skipif-not-linux = 'sys_platform != "linux"' - skipif-not-macos = 'sys_platform != "darwin"' - skipif-not-windows = 'sys_platform != "windows"' - skipif-windows = 'sys_platform == "darwin"' - - [tool.coverage.html] - directory = ".coverage/html" - - [tool.coverage.report] - exclude_also = [ - "@overload", - "assert_never", - "case never:", - "if TYPE_CHECKING:", - ] - fail_under = 100.0 - skip_covered = true - skip_empty = true - - [tool.coverage.run] - branch = true - data_file = ".coverage/data" - omit = [ - "src/utilities/__init__.py", - "src/utilities/pytest_plugins/*.py", - "src/utilities/streamlit.py", - ] - parallel = true - plugins = ["coverage_conditional_plugin"] - source = ["src/utilities"] - [tool.uv] default-groups = "all" From 70c087a14b179c0e74414f942a6a0bc2c35198a5 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 21:35:25 +0900 Subject: [PATCH 05/23] 2025-12-31 21:35:25 (Wed) > DW-Mac > derekwan --- src/tests/conftest.py | 3 +++ src/tests/test_asyncio.py | 7 +++++-- src/tests/test_atools.py | 6 ++++++ src/tests/test_contextlib.py | 3 +++ src/tests/test_errors.py | 5 ++++- src/utilities/pytest_regressions.py | 17 +++++++++++++++-- src/utilities/sentinel.py | 4 +++- 7 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/tests/conftest.py b/src/tests/conftest.py index 142a27266b..daeeaf3e03 100644 --- a/src/tests/conftest.py +++ b/src/tests/conftest.py @@ -1,5 +1,6 @@ from __future__ import annotations +from asyncio import sleep from contextlib import AbstractContextManager, suppress from logging import LogRecord, setLogRecordFactory from typing import TYPE_CHECKING @@ -118,6 +119,7 @@ async def test_async_engine( test_async_sqlite_engine: AsyncEngine, test_async_postgres_engine: AsyncEngine, ) -> AsyncEngine: + await sleep(0.0) dialect = request.param match dialect: case "sqlite": @@ -133,6 +135,7 @@ async def test_async_engine( async def test_async_sqlite_engine(*, tmp_path: Path) -> AsyncEngine: from utilities.sqlalchemy import create_engine + await sleep(0.0) db_path = tmp_path / "db.sqlite" return create_engine("sqlite+aiosqlite", database=str(db_path), async_=True) diff --git a/src/tests/test_asyncio.py b/src/tests/test_asyncio.py index 5b8a271341..e209781ce9 100644 --- a/src/tests/test_asyncio.py +++ b/src/tests/test_asyncio.py @@ -1,7 +1,7 @@ from __future__ import annotations import re -from asyncio import Queue, run +from asyncio import Queue, run, sleep from collections.abc import AsyncIterable, ItemsView, Iterable, KeysView, ValuesView from contextlib import asynccontextmanager from re import DOTALL, search @@ -203,6 +203,7 @@ async def test_sync(self, *, n: int) -> None: @given(n=integers(0, 10)) async def test_async(self, *, n: int) -> None: async def range_async(n: int, /) -> AsyncIterator[int]: + await sleep(0.0) for i in range(n): yield i @@ -220,6 +221,7 @@ async def test_create_task_context_coroutine(self) -> None: @asynccontextmanager async def yield_true() -> AsyncIterator[None]: + await sleep(0.0) nonlocal flag try: flag = True @@ -316,7 +318,7 @@ class CustomError(Exception): ... class TestGetCoroutineName: def test_main(self) -> None: async def func() -> None: - return None + await sleep(0.0) result = get_coroutine_name(func) expected = "func" @@ -369,6 +371,7 @@ async def test_error_non_unique(self, *, iterable: set[int]) -> None: def _lift[T](self, iterable: Iterable[T], /) -> AsyncIterable[T]: async def lifted() -> AsyncIterator[Any]: + await sleep(0.0) for i in iterable: yield i diff --git a/src/tests/test_atools.py b/src/tests/test_atools.py index ebdff6f9a2..d7408cdb12 100644 --- a/src/tests/test_atools.py +++ b/src/tests/test_atools.py @@ -1,5 +1,7 @@ from __future__ import annotations +from asyncio import sleep + from utilities.asyncio import sleep_td from utilities.atools import call_memoized, memoize from utilities.whenever import SECOND @@ -12,6 +14,7 @@ async def test_main(self) -> None: counter = 0 async def increment() -> int: + await sleep(0.0) nonlocal counter counter += 1 return counter @@ -24,6 +27,7 @@ async def test_refresh(self) -> None: counter = 0 async def increment() -> int: + await sleep(0.0) nonlocal counter counter += 1 return counter @@ -43,6 +47,7 @@ async def test_main(self) -> None: @memoize async def increment() -> int: + await sleep(0.0) nonlocal counter counter += 1 return counter @@ -56,6 +61,7 @@ async def test_with_arguments(self) -> None: @memoize(duration=_DELTA) async def increment() -> int: + await sleep(0.0) nonlocal counter counter += 1 return counter diff --git a/src/tests/test_contextlib.py b/src/tests/test_contextlib.py index cc30473090..1d6b99c6bb 100644 --- a/src/tests/test_contextlib.py +++ b/src/tests/test_contextlib.py @@ -55,6 +55,7 @@ async def _test_enhanced_async_context_manager_core( @enhanced_async_context_manager async def yield_marker() -> AsyncIterator[None]: + await asyncio.sleep(0.0) try: yield finally: @@ -142,6 +143,7 @@ async def test_async( sigterm=sigterm, ) async def yield_marker() -> AsyncIterator[None]: + await asyncio.sleep(0.0) try: yield finally: @@ -155,6 +157,7 @@ async def yield_marker() -> AsyncIterator[None]: def test_async_signature(self) -> None: @enhanced_async_context_manager async def yield_marker(x: int, y: int, /) -> AsyncIterator[int]: + await asyncio.sleep(0.0) yield x + y sig = set(signature(yield_marker).parameters) diff --git a/src/tests/test_errors.py b/src/tests/test_errors.py index 27e3454e80..20a5acb641 100644 --- a/src/tests/test_errors.py +++ b/src/tests/test_errors.py @@ -1,6 +1,6 @@ from __future__ import annotations -from asyncio import TaskGroup +from asyncio import TaskGroup, sleep from pytest import RaisesGroup, raises @@ -27,6 +27,7 @@ async def test_group(self) -> None: class CustomError(Exception): ... async def coroutine() -> None: + await sleep(0.0) raise CustomError with RaisesGroup(CustomError) as exc_info: @@ -58,11 +59,13 @@ async def test_group(self) -> None: class Custom1Error(Exception): ... async def coroutine1() -> None: + await sleep(0.0) raise Custom1Error class Custom2Error(Exception): ... async def coroutine2() -> None: + await sleep(0.0) msg = "message2" raise Custom2Error(msg) diff --git a/src/utilities/pytest_regressions.py b/src/utilities/pytest_regressions.py index 2b41248c87..ef867cf08b 100644 --- a/src/utilities/pytest_regressions.py +++ b/src/utilities/pytest_regressions.py @@ -1,15 +1,17 @@ from __future__ import annotations from contextlib import suppress +from dataclasses import dataclass from json import loads from pathlib import Path from shutil import copytree -from typing import TYPE_CHECKING, Any, assert_never +from typing import TYPE_CHECKING, Any, assert_never, override from pytest_regressions.file_regression import FileRegressionFixture from utilities.functions import ensure_str from utilities.operator import is_equal +from utilities.reprlib import get_repr if TYPE_CHECKING: from polars import DataFrame, Series @@ -73,7 +75,18 @@ def check( def _check_fn(self, path1: Path, path2: Path, /) -> None: left = loads(path1.read_text()) right = loads(path2.read_text()) - assert is_equal(left, right), f"{left=}, {right=}" + if not is_equal(left, right): + raise OrjsonRegressionError(left=left, right=right) + + +@dataclass(kw_only=True, slots=True) +class OrjsonRegressionError(Exception): + left: Any + right: Any + + @override + def __str__(self) -> str: + return f"Left must equal right; got {get_repr(self.left)} and {get_repr(self.right)}" ## diff --git a/src/utilities/sentinel.py b/src/utilities/sentinel.py index 58257bac6f..3b7a21b290 100644 --- a/src/utilities/sentinel.py +++ b/src/utilities/sentinel.py @@ -2,7 +2,9 @@ from dataclasses import dataclass from re import IGNORECASE, search -from typing import Any, TypeIs, override +from typing import Any, override + +from typing_extensions import TypeIs # noqa: UP035 class _Meta(type): From f5cf19591612b61cb0c4841673ed703b08c4bee8 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 21:41:09 +0900 Subject: [PATCH 06/23] 2025-12-31 21:41:09 (Wed) > DW-Mac > derekwan --- .pre-commit-config.yaml | 2 +- ruff.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 737a3f35fa..706cac0760 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,8 +25,8 @@ repos: - --pytest - --pytest--asyncio - --pytest--timeout=600 - - --python-version=3.12 - --python-package-name=utilities + - --python-version=3.12 - --readme - --repo-name=python-utilities - --ruff diff --git a/ruff.toml b/ruff.toml index ba422b136a..50ccc003a9 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,5 +1,5 @@ target-version = "py314" -unsafe-fixes = true +unsafe-fixes = false [format] preview = true From c083d7765689c66e6834bb82d86e688e0bc2f356 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 21:41:59 +0900 Subject: [PATCH 07/23] 2025-12-31 21:41:59 (Wed) > DW-Mac > derekwan --- ruff.toml | 4 ++-- src/utilities/parse.py | 2 +- src/utilities/sentinel.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ruff.toml b/ruff.toml index 50ccc003a9..8207af81a6 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,5 +1,5 @@ -target-version = "py314" -unsafe-fixes = false +target-version = "py312" +unsafe-fixes = true [format] preview = true diff --git a/src/utilities/parse.py b/src/utilities/parse.py index ab91d55c4a..f98f490bed 100644 --- a/src/utilities/parse.py +++ b/src/utilities/parse.py @@ -284,7 +284,7 @@ def _parse_object_extra(cls: Any, text: str, extra: ParseObjectExtra, /) -> Any: except KeyError: try: parser = one(p for c, p in extra.items() if is_subclass_gen(cls, c)) - except OneEmptyError, TypeError: + except (OneEmptyError, TypeError): raise _ParseObjectParseError(type_=cls, text=text) from None except OneNonUniqueError as error: raise _ParseObjectExtraNonUniqueError( diff --git a/src/utilities/sentinel.py b/src/utilities/sentinel.py index 3b7a21b290..f17ddeaf17 100644 --- a/src/utilities/sentinel.py +++ b/src/utilities/sentinel.py @@ -4,7 +4,7 @@ from re import IGNORECASE, search from typing import Any, override -from typing_extensions import TypeIs # noqa: UP035 +from typing_extensions import TypeIs class _Meta(type): From 02661b3404b7113afbb1dad46810ee52622a5532 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 21:42:13 +0900 Subject: [PATCH 08/23] 2025-12-31 21:42:13 (Wed) > DW-Mac > derekwan --- src/utilities/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities/typing.py b/src/utilities/typing.py index c01685b332..b369225194 100644 --- a/src/utilities/typing.py +++ b/src/utilities/typing.py @@ -592,7 +592,7 @@ def _is_namedtuple_core(obj: Any, /) -> bool: """Check if an object is an instance of a dataclass.""" try: (base,) = obj.__orig_bases__ - except AttributeError, ValueError: + except (AttributeError, ValueError): return False return base is NamedTuple From 48e3851ba562ff0e1098b2b587f89016231e3e45 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 21:55:00 +0900 Subject: [PATCH 09/23] 2025-12-31 21:55:00 (Wed) > DW-Mac > derekwan --- pyproject.toml | 14 ++++++++++ ...stOrjsonRegressionFixture__test_error.json | 1 + src/tests/test_pytest_regressions.py | 26 +++++++++++++------ src/utilities/pytest_regressions.py | 23 ++++++++++------ 4 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_error.json diff --git a/pyproject.toml b/pyproject.toml index 40dad2fa56..a3cbcc65ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,6 +128,20 @@ ] [tool] + # [tool.coverage] + # [tool.coverage.coverage_conditional_plugin] + # [tool.coverage.coverage_conditional_plugin.rules] + # skipif-ci = '"CI" in os_environ' + # skipif-ci-and-mac = '("CI" in os_environ) and (sys_platform == "darwin")' + # skipif-ci-and-not-linux = '("CI" in os_environ) and (sys_platform != "linux")' + # skipif-ci-or-mac = '("CI" in os_environ) or (sys_platform == "darwin")' + # skipif-linux = 'sys_platform == "linux"' + # skipif-mac = 'sys_platform == "darwin"' + # skipif-not-linux = 'sys_platform != "linux"' + # skipif-not-macos = 'sys_platform != "darwin"' + # skipif-not-windows = 'sys_platform != "windows"' + # skipif-windows = 'sys_platform == "darwin"' + [tool.uv] default-groups = "all" diff --git a/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_error.json b/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_error.json new file mode 100644 index 0000000000..c508d5366f --- /dev/null +++ b/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_error.json @@ -0,0 +1 @@ +false diff --git a/src/tests/test_pytest_regressions.py b/src/tests/test_pytest_regressions.py index e268680666..e35a61cc71 100644 --- a/src/tests/test_pytest_regressions.py +++ b/src/tests/test_pytest_regressions.py @@ -5,6 +5,7 @@ from hypothesis import HealthCheck, given, settings from hypothesis.strategies import sampled_from from polars import int_range +from pytest import raises from tests.test_typing_funcs.with_future import ( DataClassFutureInt, @@ -12,6 +13,7 @@ DataClassFutureNestedOuterFirstInner, DataClassFutureNestedOuterFirstOuter, ) +from utilities.pytest_regressions import OrjsonRegressionError if TYPE_CHECKING: from utilities.pytest_regressions import ( @@ -44,14 +46,6 @@ def test_series(self, *, polars_regression: PolarsRegressionFixture) -> None: class TestOrjsonRegressionFixture: - def test_dataclass_nested( - self, *, orjson_regression: OrjsonRegressionFixture - ) -> None: - obj = DataClassFutureNestedOuterFirstOuter( - inner=DataClassFutureNestedOuterFirstInner(int_=0) - ) - orjson_regression.check(obj) - def test_dataclass_int(self, *, orjson_regression: OrjsonRegressionFixture) -> None: obj = DataClassFutureInt(int_=0) orjson_regression.check(obj) @@ -66,3 +60,19 @@ def test_dataclass_literal( ) -> None: obj = DataClassFutureLiteral(truth=truth) orjson_regression.check(obj, suffix=truth) + + def test_dataclass_nested( + self, *, orjson_regression: OrjsonRegressionFixture + ) -> None: + obj = DataClassFutureNestedOuterFirstOuter( + inner=DataClassFutureNestedOuterFirstInner(int_=0) + ) + orjson_regression.check(obj) + + def test_error(self, *, orjson_regression: OrjsonRegressionFixture) -> None: + orjson_regression.check(False) # noqa: FBT003 + with raises( + OrjsonRegressionError, + match=r"Obtained object \(at '.+'\) and existing object \(at '.+'\) differ; got True and False", + ): + orjson_regression.check(True) # noqa: FBT003 diff --git a/src/utilities/pytest_regressions.py b/src/utilities/pytest_regressions.py index ef867cf08b..8e55f05f5b 100644 --- a/src/utilities/pytest_regressions.py +++ b/src/utilities/pytest_regressions.py @@ -72,21 +72,28 @@ def check( check_fn=self._check_fn, ) - def _check_fn(self, path1: Path, path2: Path, /) -> None: - left = loads(path1.read_text()) - right = loads(path2.read_text()) - if not is_equal(left, right): - raise OrjsonRegressionError(left=left, right=right) + def _check_fn(self, path_obtained: Path, path_existing: Path, /) -> None: + obtained = loads(path_obtained.read_text()) + existing = loads(path_existing.read_text()) + if not is_equal(obtained, existing): + raise OrjsonRegressionError( + path_obtained=path_obtained, + path_existing=path_existing, + obtained=obtained, + existing=existing, + ) @dataclass(kw_only=True, slots=True) class OrjsonRegressionError(Exception): - left: Any - right: Any + path_obtained: Path + path_existing: Path + obtained: Any + existing: Any @override def __str__(self) -> str: - return f"Left must equal right; got {get_repr(self.left)} and {get_repr(self.right)}" + return f"Obtained object (at {str(self.path_obtained)!r}) and existing object (at {str(self.path_existing)!r}) differ; got {get_repr(self.obtained)} and {get_repr(self.existing)}" ## From 16383d3db9ddb12d83c2471b9668645d34a1763b Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 21:57:20 +0900 Subject: [PATCH 10/23] 2025-12-31 21:57:20 (Wed) > DW-Mac > derekwan --- src/utilities/sqlalchemy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utilities/sqlalchemy.py b/src/utilities/sqlalchemy.py index 870c490c6e..e894995ad2 100644 --- a/src/utilities/sqlalchemy.py +++ b/src/utilities/sqlalchemy.py @@ -119,7 +119,7 @@ def check_connect(engine: Engine, /) -> bool: try: with engine.connect() as conn: return bool(conn.execute(_SELECT).scalar_one()) - except gaierror, ConnectionRefusedError, DatabaseError: # pragma: no cover + except (gaierror, ConnectionRefusedError, DatabaseError): # pragma: no cover return False @@ -134,7 +134,7 @@ async def check_connect_async( try: async with timeout_td(timeout, error=error), engine.connect() as conn: return bool((await conn.execute(_SELECT)).scalar_one()) - except gaierror, ConnectionRefusedError, DatabaseError, TimeoutError: + except (gaierror, ConnectionRefusedError, DatabaseError, TimeoutError): return False @@ -834,7 +834,7 @@ def is_orm(obj: Any, /) -> TypeGuard[ORMInstOrClass]: if isinstance(obj, type): try: _ = class_mapper(cast("Any", obj)) - except ArgumentError, UnmappedClassError: + except (ArgumentError, UnmappedClassError): return False return True return is_orm(type(obj)) From cb0a061e8767693b2b022a8d85933bd2e7e3551b Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 21:58:53 +0900 Subject: [PATCH 11/23] 2025-12-31 21:58:53 (Wed) > DW-Mac > derekwan --- .github/workflows/pull-request.yaml | 91 ++++++----------------------- .github/workflows/push.yaml | 29 --------- 2 files changed, 19 insertions(+), 101 deletions(-) diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index e659ba3541..6390630598 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -1,101 +1,41 @@ name: pull-request - on: pull_request: branches: - master - schedule: - cron: 0 0 * * * jobs: - check-version-bumped: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 - with: - cache: pip - - run: curl -fsSL - https://raw.githubusercontent.com/dycw/remote-scripts/refs/heads/master/scripts/check-version-bumped - | sh -s - - ruff: - name: ruff + pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: astral-sh/ruff-action@v3 - - run: ruff check --fix - - run: ruff format - - - name: Run 'ruff' - uses: dycw/action-ruff@latest + - name: Run 'pre-commit' + uses: dycw/action-pre-commit@latest with: token: ${{secrets.GITHUB_TOKEN}} + repos: |- + dycw/pre-commit-hook-nitpick + pre-commit/pre-commit-hooks pyright: - name: pyright runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - version: latest - - uses: actions/setup-python@v6 - with: - python-version-file: .python-version - - run: uv sync - - run: uv run pyright - - name: Run 'pyright' uses: dycw/action-pyright@latest with: token: ${{secrets.GITHUB_TOKEN}} - python-version: "3.14" - test: - name: test / ${{ matrix.os }} / ${{ matrix.version }} + python-version: "3.12" + pytest: env: - CI: 1 - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [macos-latest, ubuntu-latest] - version: ["3.12", "3.13", "3.14"] + CI: "1" + name: pytest (${{matrix.os}}, ${{matrix.python-version}}, + ${{matrix.resolution}}) + runs-on: ${{matrix.os}} services: redis: image: ${{ matrix.os == 'ubuntu-latest' && 'redis/redis-stack:latest' || '' }} ports: - 6379:6379 - steps: - - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - version: latest - - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.version }} - - run: uv sync --locked - - run: uv run pytest --cov-report=term-missing:skip-covered -n=auto - timeout-minutes: 60 - pre-commit: - runs-on: ubuntu-latest - steps: - - name: Run 'pre-commit' - uses: dycw/action-pre-commit@latest - with: - token: ${{secrets.GITHUB_TOKEN}} - repos: |- - dycw/pre-commit-hook-nitpick - pre-commit/pre-commit-hooks - pytest: - env: - CI: "1" - name: pytest (${{matrix.os}}, ${{matrix.python-version}}, - ${{matrix.resolution}}) - runs-on: ${{matrix.os}} steps: - name: Run 'pytest' uses: dycw/action-pytest@latest @@ -116,3 +56,10 @@ jobs: resolution: - highest timeout-minutes: 10 + ruff: + runs-on: ubuntu-latest + steps: + - name: Run 'ruff' + uses: dycw/action-ruff@latest + with: + token: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 45cbb9fac3..43eb7b29d4 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -1,42 +1,13 @@ name: push - on: push: branches: - master - jobs: tag: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: butlerlogic/action-autotag@1.1.2 # https://github.com/ButlerLogic/action-autotag/issues/45#issuecomment-1825726927 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - strategy: regex - root: pyproject.toml - regex_pattern: current_version = "(?\d+\.\d+\.\d+)" - - name: Tag latest commit uses: dycw/action-tag@latest with: token: ${{secrets.GITHUB_TOKEN}} - publish: - runs-on: ubuntu-latest - needs: - - tag - environment: - name: release - permissions: - id-token: write - steps: - - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - - uses: actions/setup-python@v6 - with: - python-version-file: pyproject.toml - - run: uv build - - run: uv publish From b49dc8e7695c02689de52b8051c9be833302f202 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 22:00:40 +0900 Subject: [PATCH 12/23] 2025-12-31 22:00:40 (Wed) > DW-Mac > derekwan --- ruff.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/ruff.toml b/ruff.toml index 8207af81a6..3cac3d1404 100644 --- a/ruff.toml +++ b/ruff.toml @@ -47,6 +47,7 @@ unsafe-fixes = true select = ["ALL", "RUF022", "RUF029"] [lint.extend-per-file-ignores] + "src/tests/test_typing_funcs/no_future.py" = ["I002"] "test_*.py" = ["S101", "SLF001"] [lint.flake8-bugbear] From 9eb27a7293b1886ab662c8208ba31b6e3078fc69 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 22:00:48 +0900 Subject: [PATCH 13/23] 2025-12-31 22:00:48 (Wed) > DW-Mac > derekwan --- src/tests/test_typing_funcs/no_future.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tests/test_typing_funcs/no_future.py b/src/tests/test_typing_funcs/no_future.py index 92a30f5c49..97a4d0e0b8 100644 --- a/src/tests/test_typing_funcs/no_future.py +++ b/src/tests/test_typing_funcs/no_future.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from dataclasses import dataclass from typing import Literal, NotRequired, TypedDict @@ -29,7 +27,7 @@ class DataClassNoFutureNestedInnerFirstOuter: @dataclass(kw_only=True) class DataClassNoFutureNestedOuterFirstOuter: - inner: DataClassNoFutureNestedOuterFirstInner + inner: "DataClassNoFutureNestedOuterFirstInner" @dataclass(kw_only=True) From 07eb87d078033439aa488c6e031af6d720873f94 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 22:01:32 +0900 Subject: [PATCH 14/23] 2025-12-31 22:01:32 (Wed) > DW-Mac > derekwan --- pyproject.toml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a3cbcc65ad..40dad2fa56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,20 +128,6 @@ ] [tool] - # [tool.coverage] - # [tool.coverage.coverage_conditional_plugin] - # [tool.coverage.coverage_conditional_plugin.rules] - # skipif-ci = '"CI" in os_environ' - # skipif-ci-and-mac = '("CI" in os_environ) and (sys_platform == "darwin")' - # skipif-ci-and-not-linux = '("CI" in os_environ) and (sys_platform != "linux")' - # skipif-ci-or-mac = '("CI" in os_environ) or (sys_platform == "darwin")' - # skipif-linux = 'sys_platform == "linux"' - # skipif-mac = 'sys_platform == "darwin"' - # skipif-not-linux = 'sys_platform != "linux"' - # skipif-not-macos = 'sys_platform != "darwin"' - # skipif-not-windows = 'sys_platform != "windows"' - # skipif-windows = 'sys_platform == "darwin"' - [tool.uv] default-groups = "all" From c2a307ee840cb694b8fbf6d00e2cd2ee6d758801 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 22:09:58 +0900 Subject: [PATCH 15/23] 2025-12-31 22:09:58 (Wed) > DW-Mac > derekwan --- src/tests/test_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/test_functions.py b/src/tests/test_functions.py index 1e1c4aaa73..5741fb6f5f 100644 --- a/src/tests/test_functions.py +++ b/src/tests/test_functions.py @@ -24,7 +24,7 @@ permutations, sampled_from, ) -from pytest import mark, param, raises +from pytest import approx, mark, param, raises from utilities.errors import ImpossibleCaseError from utilities.functions import ( @@ -717,7 +717,7 @@ class Example: attr: ClassVar[int] = n attrs = dict(yield_object_attributes(Example)) - assert len(attrs) == 29 + assert len(attrs) == approx(29, rel=0.1) assert attrs["attr"] == n From 09fa4ee6ab38d6efe31bc7ec35843bbe87f45186 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 22:16:06 +0900 Subject: [PATCH 16/23] 2025-12-31 22:16:06 (Wed) > DW-Mac > derekwan --- pytest.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pytest.toml b/pytest.toml index 896ebc46ae..7b4bb4cdee 100644 --- a/pytest.toml +++ b/pytest.toml @@ -5,9 +5,9 @@ "--color=auto", "--durations=10", "--durations-min=10", - "--cov=utilities", - "--cov-config=.coveragerc.toml", - "--cov-report=html", + # "--cov=utilities", + # "--cov-config=.coveragerc.toml", + # "--cov-report=html", ] asyncio_default_fixture_loop_scope = "function" asyncio_mode = "auto" From 0649df06349bcd1725585cda5e691e7c0e19f7b6 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 22:25:45 +0900 Subject: [PATCH 17/23] 2025-12-31 22:25:45 (Wed) > DW-Mac > derekwan --- pyproject.toml | 2 +- uv.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 40dad2fa56..dd44cc4b12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ [dependency-groups] aeventkit = ["aeventkit >=2.1.0, <2.2"] - altair = ["altair >=5.5.0, <5.6"] + altair = ["altair >=6.0.0, <6.1"] altair-test = ["polars", "img2pdf", "vl-convert-python"] atools = ["atools >=0.14.2, <0.15"] cachetools = ["cachetools >=6.2.4, <6.3"] diff --git a/uv.lock b/uv.lock index fcb4a39676..ab2a96f30c 100644 --- a/uv.lock +++ b/uv.lock @@ -151,18 +151,18 @@ wheels = [ [[package]] name = "altair" -version = "5.5.0" +version = "6.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2" }, { name = "jsonschema" }, { name = "narwhals" }, { name = "packaging" }, - { name = "typing-extensions", marker = "python_full_version < '3.14'" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/16/b1/f2969c7bdb8ad8bbdda031687defdce2c19afba2aa2c8e1d2a17f78376d8/altair-5.5.0.tar.gz", hash = "sha256:d960ebe6178c56de3855a68c47b516be38640b73fb3b5111c2a9ca90546dd73d", size = 705305, upload-time = "2024-11-23T23:39:58.542Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/c0/184a89bd5feba14ff3c41cfaf1dd8a82c05f5ceedbc92145e17042eb08a4/altair-6.0.0.tar.gz", hash = "sha256:614bf5ecbe2337347b590afb111929aa9c16c9527c4887d96c9bc7f6640756b4", size = 763834, upload-time = "2025-11-12T08:59:11.519Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/f3/0b6ced594e51cc95d8c1fc1640d3623770d01e4969d29c0bd09945fafefa/altair-5.5.0-py3-none-any.whl", hash = "sha256:91a310b926508d560fe0148d02a194f38b824122641ef528113d029fcd129f8c", size = 731200, upload-time = "2024-11-23T23:39:56.4Z" }, + { url = "https://files.pythonhosted.org/packages/db/33/ef2f2409450ef6daa61459d5de5c08128e7d3edb773fefd0a324d1310238/altair-6.0.0-py3-none-any.whl", hash = "sha256:09ae95b53d5fe5b16987dccc785a7af8588f2dca50de1e7a156efa8a461515f8", size = 795410, upload-time = "2025-11-12T08:59:09.804Z" }, ] [[package]] @@ -925,7 +925,7 @@ provides-extras = ["logging", "test"] [package.metadata.requires-dev] aeventkit = [{ name = "aeventkit", specifier = ">=2.1.0,<2.2" }] -altair = [{ name = "altair", specifier = ">=5.5.0,<5.6" }] +altair = [{ name = "altair", specifier = ">=6.0.0,<6.1" }] altair-test = [ { name = "img2pdf" }, { name = "polars" }, From 155d029b11eb701160de351ab203f9d1fc5bc147 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 22:28:19 +0900 Subject: [PATCH 18/23] 2025-12-31 22:28:19 (Wed) > DW-Mac > derekwan --- src/utilities/altair.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/utilities/altair.py b/src/utilities/altair.py index 05debce937..9b5dbe4a22 100644 --- a/src/utilities/altair.py +++ b/src/utilities/altair.py @@ -27,6 +27,7 @@ from utilities.functions import ensure_bytes, ensure_number from utilities.iterables import always_iterable from utilities.tempfile import TemporaryDirectory +from utilities.warnings import suppress_warnings if TYPE_CHECKING: from polars import DataFrame @@ -145,7 +146,9 @@ def plot_dataframes( ] zoom = selection_interval(bind="scales", encodings=["x"]) chart = ( - vconcat(*layers).add_params(zoom).resolve_scale(color="independent", x="shared") + vconcat_charts(*layers) + .add_params(zoom) + .resolve_scale(color="independent", x="shared") ) if title is not None: chart = chart.properties(title=title) @@ -297,7 +300,9 @@ def vconcat_charts(*charts: _ChartLike, width: int = _WIDTH) -> VConcatChart: charts_use = (c.properties(width=width) for c in charts) resize = selection_interval(bind="scales", encodings=["x"]) charts_use = (c.add_params(resize) for c in charts_use) - return vconcat(*charts_use).resolve_scale(color="independent", x="shared") + with suppress_warnings(category=UserWarning): + chart = vconcat(*charts_use) + return chart.resolve_scale(color="independent", x="shared") __all__ = [ From 1caa54f0f1aea422fa9751b0204813bc9a930d2d Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 22:29:52 +0900 Subject: [PATCH 19/23] 2025-12-31 22:29:52 (Wed) > DW-Mac > derekwan --- pytest.toml | 1 + src/utilities/altair.py | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pytest.toml b/pytest.toml index 7b4bb4cdee..f61c13ecb5 100644 --- a/pytest.toml +++ b/pytest.toml @@ -16,6 +16,7 @@ filterwarnings = [ "error", "ignore: was delete before being closed:ResourceWarning", # sqlalchemy + "ignore:Automatically deduplicated selection parameter with identical configuration:UserWarning", # altair "ignore:Exception ignored in.* :pytest.PytestUnraisableExceptionWarning", "ignore:Exception in thread Thread-.*:pytest.PytestUnhandledThreadExceptionWarning", "ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning", # jupyter diff --git a/src/utilities/altair.py b/src/utilities/altair.py index 9b5dbe4a22..5a046c5afe 100644 --- a/src/utilities/altair.py +++ b/src/utilities/altair.py @@ -27,7 +27,6 @@ from utilities.functions import ensure_bytes, ensure_number from utilities.iterables import always_iterable from utilities.tempfile import TemporaryDirectory -from utilities.warnings import suppress_warnings if TYPE_CHECKING: from polars import DataFrame @@ -300,9 +299,7 @@ def vconcat_charts(*charts: _ChartLike, width: int = _WIDTH) -> VConcatChart: charts_use = (c.properties(width=width) for c in charts) resize = selection_interval(bind="scales", encodings=["x"]) charts_use = (c.add_params(resize) for c in charts_use) - with suppress_warnings(category=UserWarning): - chart = vconcat(*charts_use) - return chart.resolve_scale(color="independent", x="shared") + return vconcat(*charts_use).resolve_scale(color="independent", x="shared") __all__ = [ From 667e1b57c846ef1c30277df9032d60e55bfe8d7b Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 23:43:40 +0900 Subject: [PATCH 20/23] 2025-12-31 23:43:40 (Wed) > DW-Mac > derekwan --- src/tests/test_aeventkit.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/tests/test_aeventkit.py b/src/tests/test_aeventkit.py index 580cdb61da..a8cd2732fe 100644 --- a/src/tests/test_aeventkit.py +++ b/src/tests/test_aeventkit.py @@ -44,12 +44,12 @@ def listener_sync() -> None: async def listener_async() -> None: nonlocal called called |= True - await sleep(0.01) + await sleep(0.0) _ = add_listener(event, listener_async) event.emit() - await sleep(0.01) + await sleep(0.0) assert called @given(root=temp_paths(), sync_or_async=sampled_from(["sync", "async"])) @@ -69,12 +69,12 @@ def listener_sync() -> None: ... case "async": async def listener_async() -> None: - await sleep(0.01) + await sleep(0.0) _ = add_listener(event, listener_async, logger=str(root)) event.emit(None) - await sleep(0.01) + await sleep(0.0) pattern = r"listener_a?sync\(\) takes 0 positional arguments but 1 was given" contents = buffer.getvalue() assert search(pattern, contents) @@ -105,14 +105,14 @@ async def listener_async(is_success: bool, /) -> None: # noqa: FBT001 if is_success: nonlocal called called |= True - await sleep(0.01) + await sleep(0.0) else: raise ValueError async def error_async(event: Event, exception: BaseException, /) -> None: nonlocal log log.add((event.name(), type(exception))) - await sleep(0.01) + await sleep(0.0) match case: case "sync": @@ -122,11 +122,11 @@ async def error_async(event: Event, exception: BaseException, /) -> None: case "async": _ = add_listener(event, listener_async, error=error_async) event.emit(True) # noqa: FBT003 - await sleep(0.01) + await sleep(0.0) assert called assert log == set() event.emit(False) # noqa: FBT003 - await sleep(0.01) + await sleep(0.0) assert log == {(name, ValueError)} @given( @@ -164,14 +164,14 @@ async def listener_async(is_success: bool, /) -> None: # noqa: FBT001 if is_success: nonlocal called called |= True - await sleep(0.01) + await sleep(0.0) else: raise ValueError async def error_async(event: Event, exception: BaseException, /) -> None: nonlocal log log.add((event.name(), type(exception))) - await sleep(0.01) + await sleep(0.0) match case: case "no/sync": @@ -191,11 +191,11 @@ async def error_async(event: Event, exception: BaseException, /) -> None: event, listener_async, error=error_async, ignore=ValueError ) event.emit(True) # noqa: FBT003 - await sleep(0.01) + await sleep(0.0) assert called assert log == set() event.emit(False) # noqa: FBT003 - await sleep(0.01) + await sleep(0.0) assert log == set() def test_decorators(self) -> None: @@ -227,7 +227,7 @@ def listener() -> None: async def error(event: Event, exception: BaseException, /) -> None: _ = (event, exception) - await sleep(0.01) + await sleep(0.0) with raises( LiftListenerError, From 71e0553bde3a2f9f0c2ac236126dba7246d9ac35 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Thu, 1 Jan 2026 00:11:14 +0900 Subject: [PATCH 21/23] 2026-01-01 00:11:14 (Thu) > DW-Mac > derekwan --- pyproject.toml | 1 - src/tests/test_aeventkit.py | 282 -------------------------- src/utilities/aeventkit.py | 389 ------------------------------------ uv.lock | 16 -- 4 files changed, 688 deletions(-) delete mode 100644 src/tests/test_aeventkit.py delete mode 100644 src/utilities/aeventkit.py diff --git a/pyproject.toml b/pyproject.toml index dd44cc4b12..65c88b3e72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,6 @@ requires = ["uv_build"] [dependency-groups] - aeventkit = ["aeventkit >=2.1.0, <2.2"] altair = ["altair >=6.0.0, <6.1"] altair-test = ["polars", "img2pdf", "vl-convert-python"] atools = ["atools >=0.14.2, <0.15"] diff --git a/src/tests/test_aeventkit.py b/src/tests/test_aeventkit.py deleted file mode 100644 index a8cd2732fe..0000000000 --- a/src/tests/test_aeventkit.py +++ /dev/null @@ -1,282 +0,0 @@ -from __future__ import annotations - -from asyncio import sleep -from collections.abc import Callable -from functools import wraps -from io import StringIO -from logging import StreamHandler, getLogger -from re import search -from typing import TYPE_CHECKING, Literal - -from eventkit import Event -from hypothesis import given -from hypothesis.strategies import sampled_from -from pytest import raises - -from utilities.aeventkit import ( - LiftedEvent, - LiftListenerError, - TypedEvent, - add_listener, - lift_listener, -) -from utilities.hypothesis import temp_paths, text_ascii - -if TYPE_CHECKING: - from pathlib import Path - - -class TestAddListener: - @given(sync_or_async=sampled_from(["sync", "async"])) - async def test_main(self, *, sync_or_async: Literal["sync", "async"]) -> None: - event = Event() - called = False - match sync_or_async: - case "sync": - - def listener_sync() -> None: - nonlocal called - called |= True - - _ = add_listener(event, listener_sync) - case "async": - - async def listener_async() -> None: - nonlocal called - called |= True - await sleep(0.0) - - _ = add_listener(event, listener_async) - - event.emit() - await sleep(0.0) - assert called - - @given(root=temp_paths(), sync_or_async=sampled_from(["sync", "async"])) - async def test_no_error_handler_but_run_into_error( - self, *, root: Path, sync_or_async: Literal["sync", "async"] - ) -> None: - logger = getLogger(str(root)) - logger.addHandler(StreamHandler(buffer := StringIO())) - event = Event() - - match sync_or_async: - case "sync": - - def listener_sync() -> None: ... - - _ = add_listener(event, listener_sync, logger=str(root)) - case "async": - - async def listener_async() -> None: - await sleep(0.0) - - _ = add_listener(event, listener_async, logger=str(root)) - - event.emit(None) - await sleep(0.0) - pattern = r"listener_a?sync\(\) takes 0 positional arguments but 1 was given" - contents = buffer.getvalue() - assert search(pattern, contents) - - @given( - name=text_ascii(min_size=1), case=sampled_from(["sync", "async/sync", "async"]) - ) - async def test_with_error_handler( - self, *, name: str, case: Literal["sync", "async/sync", "async"] - ) -> None: - event = Event(_name=name) - assert event.name() == name - called = False - log: set[tuple[str, type[BaseException]]] = set() - - def listener_sync(is_success: bool, /) -> None: # noqa: FBT001 - if is_success: - nonlocal called - called |= True - else: - raise ValueError - - def error_sync(event: Event, exception: BaseException, /) -> None: - nonlocal log - log.add((event.name(), type(exception))) - - async def listener_async(is_success: bool, /) -> None: # noqa: FBT001 - if is_success: - nonlocal called - called |= True - await sleep(0.0) - else: - raise ValueError - - async def error_async(event: Event, exception: BaseException, /) -> None: - nonlocal log - log.add((event.name(), type(exception))) - await sleep(0.0) - - match case: - case "sync": - _ = add_listener(event, listener_sync, error=error_sync) - case "async/sync": - _ = add_listener(event, listener_async, error=error_sync) - case "async": - _ = add_listener(event, listener_async, error=error_async) - event.emit(True) # noqa: FBT003 - await sleep(0.0) - assert called - assert log == set() - event.emit(False) # noqa: FBT003 - await sleep(0.0) - assert log == {(name, ValueError)} - - @given( - case=sampled_from([ - "no/sync", - "no/async", - "have/sync", - "have/async/sync", - "have/async", - ]) - ) - async def test_ignore( - self, - *, - case: Literal[ - "no/sync", "no/async", "have/sync", "have/async/sync", "have/async" - ], - ) -> None: - event = Event() - called = False - log: set[tuple[str, type[BaseException]]] = set() - - def listener_sync(is_success: bool, /) -> None: # noqa: FBT001 - if is_success: - nonlocal called - called |= True - else: - raise ValueError - - def error_sync(event: Event, exception: BaseException, /) -> None: - nonlocal log - log.add((event.name(), type(exception))) - - async def listener_async(is_success: bool, /) -> None: # noqa: FBT001 - if is_success: - nonlocal called - called |= True - await sleep(0.0) - else: - raise ValueError - - async def error_async(event: Event, exception: BaseException, /) -> None: - nonlocal log - log.add((event.name(), type(exception))) - await sleep(0.0) - - match case: - case "no/sync": - _ = add_listener(event, listener_sync, ignore=ValueError) - case "no/async": - _ = add_listener(event, listener_async, ignore=ValueError) - case "have/sync": - _ = add_listener( - event, listener_sync, error=error_sync, ignore=ValueError - ) - case "have/async/sync": - _ = add_listener( - event, listener_async, error=error_sync, ignore=ValueError - ) - case "have/async": - _ = add_listener( - event, listener_async, error=error_async, ignore=ValueError - ) - event.emit(True) # noqa: FBT003 - await sleep(0.0) - assert called - assert log == set() - event.emit(False) # noqa: FBT003 - await sleep(0.0) - assert log == set() - - def test_decorators(self) -> None: - event = Event() - counter = 0 - - def listener() -> None: - nonlocal counter - counter += 1 - - def increment[**P, R](func: Callable[P, R], /) -> Callable[P, R]: - @wraps(func) - def wrapped(*args: P.args, **kwargs: P.kwargs) -> R: - nonlocal counter - counter += 1 - return func(*args, **kwargs) - - return wrapped - - _ = add_listener(event, listener, decorators=increment) - event.emit() - assert counter == 2 - - -class TestLiftListener: - def test_error(self) -> None: - def listener() -> None: - pass - - async def error(event: Event, exception: BaseException, /) -> None: - _ = (event, exception) - await sleep(0.0) - - with raises( - LiftListenerError, - match=r"Synchronous listener .* cannot be paired with an asynchronous error handler .*", - ): - _ = lift_listener(listener, Event(), error=error) - - -class TestLiftedEvent: - def test_main(self) -> None: - event1 = Event() - counter = 0 - - def listener() -> None: - nonlocal counter - counter += 1 - - _ = event1.connect(listener) - event1.emit() - assert counter == 1 - - event2 = Event() - - class Example(LiftedEvent[Callable[[], None]]): ... - - lifted = Example(event=event2) - _ = lifted.connect(listener) - lifted.emit() - assert counter == 2 - - def incorrect(x: int, /) -> None: - assert x >= 0 - - _ = lifted.connect(incorrect) # pyright: ignore[reportArgumentType] - - -class TestTypedEvent: - def test_main(self) -> None: - class Example(TypedEvent[Callable[[int], None]]): ... - - event = Example() - - def correct(x: int, /) -> None: - assert x >= 0 - - _ = event.connect(correct) - - def incorrect(x: int, y: int, /) -> None: - assert x >= 0 - assert y >= 0 - - _ = event.connect(incorrect) # pyright: ignore[reportArgumentType] diff --git a/src/utilities/aeventkit.py b/src/utilities/aeventkit.py deleted file mode 100644 index 75ad0600d0..0000000000 --- a/src/utilities/aeventkit.py +++ /dev/null @@ -1,389 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from functools import wraps -from inspect import iscoroutinefunction -from typing import TYPE_CHECKING, Any, Self, assert_never, cast, override - -from eventkit import ( - Constant, - Count, - DropWhile, - Enumerate, - Event, - Filter, - Fork, - Iterate, - Map, - Pack, - Partial, - PartialRight, - Pluck, - Skip, - Star, - Take, - TakeUntil, - TakeWhile, - Timestamp, -) - -from utilities.functions import apply_decorators -from utilities.iterables import always_iterable -from utilities.logging import to_logger - -if TYPE_CHECKING: - from collections.abc import Callable - - from utilities.types import Coro, LoggerLike, MaybeCoro, MaybeIterable, TypeLike - - -## - - -def add_listener[E: Event, F: Callable]( - event: E, - listener: Callable[..., MaybeCoro[None]], - /, - *, - error: Callable[[Event, BaseException], MaybeCoro[None]] | None = None, - ignore: TypeLike[BaseException] | None = None, - logger: LoggerLike | None = None, - decorators: MaybeIterable[Callable[[F], F]] | None = None, - done: Callable[..., MaybeCoro[None]] | None = None, - keep_ref: bool = False, -) -> E: - """Connect a listener to an event.""" - lifted = lift_listener( - listener, - event, - error=error, - ignore=ignore, - logger=logger, - decorators=decorators, - ) - return cast("E", event.connect(lifted, done=done, keep_ref=keep_ref)) - - -## - - -@dataclass(repr=False, kw_only=True) -class LiftedEvent[F: Callable[..., MaybeCoro[None]]]: - """A lifted version of `Event`.""" - - event: Event - - def name(self) -> str: - return self.event.name() # pragma: no cover - - def done(self) -> bool: - return self.event.done() # pragma: no cover - - def set_done(self) -> None: - self.event.set_done() # pragma: no cover - - def value(self) -> Any: - return self.event.value() # pragma: no cover - - def connect[F2: Callable]( - self, - listener: F, - /, - *, - error: Callable[[Event, BaseException], MaybeCoro[None]] | None = None, - ignore: TypeLike[BaseException] | None = None, - logger: LoggerLike | None = None, - decorators: MaybeIterable[Callable[[F2], F2]] | None = None, - done: Callable[..., MaybeCoro[None]] | None = None, - keep_ref: bool = False, - ) -> Event: - return add_listener( - self.event, - listener, - error=error, - ignore=ignore, - logger=logger, - decorators=decorators, - done=done, - keep_ref=keep_ref, - ) - - def disconnect( - self, listener: Any, /, *, error: Any = None, done: Any = None - ) -> Any: - return self.event.disconnect( # pragma: no cover - listener, error=error, done=done - ) - - def disconnect_obj(self, obj: Any, /) -> None: - self.event.disconnect_obj(obj) # pragma: no cover - - def emit(self, *args: Any) -> None: - self.event.emit(*args) # pragma: no cover - - def emit_threadsafe(self, *args: Any) -> None: - self.event.emit_threadsafe(*args) # pragma: no cover - - def clear(self) -> None: - self.event.clear() # pragma: no cover - - def run(self) -> list[Any]: - return self.event.run() # pragma: no cover - - def pipe(self, *targets: Event) -> Event: - return self.event.pipe(*targets) # pragma: no cover - - def fork(self, *targets: Event) -> Fork: - return self.event.fork(*targets) # pragma: no cover - - def set_source(self, source: Any, /) -> None: - self.event.set_source(source) # pragma: no cover - - def _onFinalize(self, ref: Any) -> None: # noqa: N802 - self.event._onFinalize(ref) # noqa: SLF001 # pragma: no cover - - async def aiter(self, *, skip_to_last: bool = False, tuples: bool = False) -> Any: - async for i in self.event.aiter( # pragma: no cover - skip_to_last=skip_to_last, tuples=tuples - ): - yield i - - __iadd__ = connect - __isub__ = disconnect - __call__ = emit - __or__ = pipe - - @override - def __repr__(self) -> str: - return self.event.__repr__() # pragma: no cover - - def __len__(self) -> int: - return self.event.__len__() # pragma: no cover - - def __bool__(self) -> bool: - return self.event.__bool__() # pragma: no cover - - def __getitem__(self, fork_targets: Any, /) -> Fork: - return self.event.__getitem__(fork_targets) # pragma: no cover - - def __await__(self) -> Any: - return self.event.__await__() # pragma: no cover - - def __aiter__(self) -> Any: - return self.event.aiter() # pragma: no cover - - def __contains__(self, c: Any, /) -> bool: - return self.event.__contains__(c) # pragma: no cover - - @override - def __reduce__(self) -> Any: - return self.event.__reduce__() # pragma: no cover - - def filter(self, *, predicate: Any = bool) -> Filter: - return self.event.filter(predicate=predicate) # pragma: no cover - - def skip(self, *, count: int = 1) -> Skip: - return self.event.skip(count=count) # pragma: no cover - - def take(self, *, count: int = 1) -> Take: - return self.event.take(count=count) # pragma: no cover - - def takewhile(self, *, predicate: Any = bool) -> TakeWhile: - return self.event.takewhile(predicate=predicate) # pragma: no cover - - def dropwhile(self, *, predicate: Any = lambda x: not x) -> DropWhile: # pyright: ignore[reportUnknownLambdaType] - return self.event.dropwhile(predicate=predicate) # pragma: no cover - - def takeuntil(self, notifier: Event, /) -> TakeUntil: - return self.event.takeuntil(notifier) # pragma: no cover - - def constant(self, constant: Any, /) -> Constant: - return self.event.constant(constant) # pragma: no cover - - def iterate(self, it: Any, /) -> Iterate: - return self.event.iterate(it) # pragma: no cover - - def count(self, *, start: int = 0, step: int = 1) -> Count: - return self.event.count(start=start, step=step) # pragma: no cover - - def enumerate(self, *, start: int = 0, step: int = 1) -> Enumerate: - return self.event.enumerate(start=start, step=step) # pragma: no cover - - def timestamp(self) -> Timestamp: - return self.event.timestamp() # pragma: no cover - - def partial(self, *left_args: Any) -> Partial: - return self.event.partial(*left_args) # pragma: no cover - - def partial_right(self, *right_args: Any) -> PartialRight: - return self.event.partial_right(*right_args) # pragma: no cover - - def star(self) -> Star: - return self.event.star() # pragma: no cover - - def pack(self) -> Pack: - return self.event.pack() # pragma: no cover - - def pluck(self, *selections: int | str) -> Pluck: - return self.event.pluck(*selections) # pragma: no cover - - def map( - self, - func: Any, - /, - *, - timeout: float | None = None, - ordered: bool = True, - task_limit: int | None = None, - ) -> Map: - return self.event.map( # pragma: no cover - func, timeout=timeout, ordered=ordered, task_limit=task_limit - ) - - -## - - -class TypedEvent[F: Callable[..., MaybeCoro[None]]](Event): - """A typed version of `Event`.""" - - @override - def connect[F2: Callable]( - self, - listener: F, - error: Callable[[Self, BaseException], MaybeCoro[None]] | None = None, - done: Callable[[Self], MaybeCoro[None]] | None = None, - keep_ref: bool = False, - *, - ignore: TypeLike[BaseException] | None = None, - logger: LoggerLike | None = None, - decorators: MaybeIterable[Callable[[F2], F2]] | None = None, - ) -> Self: - lifted = lift_listener( - listener, - self, - error=cast( - "Callable[[Event, BaseException], MaybeCoro[None]] | None", error - ), - ignore=ignore, - logger=logger, - decorators=decorators, - ) - return cast( - "Self", super().connect(lifted, error=error, done=done, keep_ref=keep_ref) - ) - - -## - - -def lift_listener[F1: Callable[..., MaybeCoro[None]], F2: Callable]( - listener: F1, - event: Event, - /, - *, - error: Callable[[Event, BaseException], MaybeCoro[None]] | None = None, - ignore: TypeLike[BaseException] | None = None, - logger: LoggerLike | None = None, - decorators: MaybeIterable[Callable[[F2], F2]] | None = None, -) -> F1: - match error, bool(iscoroutinefunction(listener)): - case None, False: - listener_typed = cast("Callable[..., None]", listener) - - @wraps(listener) - def listener_no_error_sync(*args: Any, **kwargs: Any) -> None: - try: - listener_typed(*args, **kwargs) - except Exception as exc: # noqa: BLE001 - if (ignore is not None) and isinstance(exc, ignore): - return - to_logger(logger).exception("") - - lifted = listener_no_error_sync - - case None, True: - listener_typed = cast("Callable[..., Coro[None]]", listener) - - @wraps(listener) - async def listener_no_error_async(*args: Any, **kwargs: Any) -> None: - try: - await listener_typed(*args, **kwargs) - except Exception as exc: # noqa: BLE001 - if (ignore is not None) and isinstance(exc, ignore): - return - to_logger(logger).exception("") - - lifted = listener_no_error_async - case _, _: - match bool(iscoroutinefunction(listener)), bool(iscoroutinefunction(error)): - case False, False: - listener_typed = cast("Callable[..., None]", listener) - error_typed = cast("Callable[[Event, Exception], None]", error) - - @wraps(listener) - def listener_have_error_sync(*args: Any, **kwargs: Any) -> None: - try: - listener_typed(*args, **kwargs) - except Exception as exc: # noqa: BLE001 - if (ignore is not None) and isinstance(exc, ignore): - return - error_typed(event, exc) - - lifted = listener_have_error_sync - case False, True: - listener_typed = cast("Callable[..., None]", listener) - error_typed = cast( - "Callable[[Event, Exception], Coro[None]]", error - ) - raise LiftListenerError(listener=listener_typed, error=error_typed) - case True, _: - listener_typed = cast("Callable[..., Coro[None]]", listener) - - @wraps(listener) - async def listener_have_error_async( - *args: Any, **kwargs: Any - ) -> None: - try: - await listener_typed(*args, **kwargs) - except Exception as exc: # noqa: BLE001 - if (ignore is not None) and isinstance(exc, ignore): - return None - if iscoroutinefunction(error): - error_typed = cast( - "Callable[[Event, Exception], Coro[None]]", error - ) - return await error_typed(event, exc) - error_typed = cast( - "Callable[[Event, Exception], None]", error - ) - error_typed(event, exc) - - lifted = listener_have_error_async - case never: - assert_never(never) - case never: - assert_never(never) - - if decorators is not None: - lifted = apply_decorators(lifted, *always_iterable(decorators)) - return cast("F1", lifted) - - -@dataclass(kw_only=True, slots=True) -class LiftListenerError(Exception): - listener: Callable[..., None] - error: Callable[[Event, Exception], Coro[None]] - - @override - def __str__(self) -> str: - return f"Synchronous listener {self.listener} cannot be paired with an asynchronous error handler {self.error}" - - -__all__ = [ - "LiftListenerError", - "LiftedEvent", - "TypedEvent", - "add_listener", - "lift_listener", -] diff --git a/uv.lock b/uv.lock index ab2a96f30c..808daea7ed 100644 --- a/uv.lock +++ b/uv.lock @@ -7,18 +7,6 @@ resolution-markers = [ "python_full_version < '3.13'", ] -[[package]] -name = "aeventkit" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/8c/c08db1a1910f8d04ec6a524de522edd0bac181bdf94dbb01183f7685cd77/aeventkit-2.1.0.tar.gz", hash = "sha256:4e7d81bb0a67227121da50a23e19e5bbf13eded541a9f4857eeb6b7b857b738a", size = 24703, upload-time = "2025-06-22T15:54:03.961Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/8c/2a4b912b1afa201b25bdd0f5bccf96d5a8b5dccb6131316a8dd2d9cabcc1/aeventkit-2.1.0-py3-none-any.whl", hash = "sha256:962d43f79e731ac43527f2d0defeed118e6dbaa85f1487f5667540ebb8f00729", size = 26678, upload-time = "2025-06-22T15:54:02.141Z" }, -] - [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -666,9 +654,6 @@ test = [ ] [package.dev-dependencies] -aeventkit = [ - { name = "aeventkit" }, -] altair = [ { name = "altair" }, ] @@ -924,7 +909,6 @@ requires-dist = [ provides-extras = ["logging", "test"] [package.metadata.requires-dev] -aeventkit = [{ name = "aeventkit", specifier = ">=2.1.0,<2.2" }] altair = [{ name = "altair", specifier = ">=6.0.0,<6.1" }] altair-test = [ { name = "img2pdf" }, From c9d9c06b959897eba7dd8ca68079984d95490b56 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Thu, 1 Jan 2026 00:11:24 +0900 Subject: [PATCH 22/23] 2026-01-01 00:11:24 (Thu) > DW-Mac > derekwan --- .bumpversion.toml | 2 +- pyproject.toml | 2 +- src/utilities/__init__.py | 2 +- uv.lock | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.toml b/.bumpversion.toml index 40adebd401..9568c31fd1 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -1,6 +1,6 @@ [tool.bumpversion] allow_dirty = true - current_version = "0.174.21" + current_version = "0.175.0" [[tool.bumpversion.files]] filename = "pyproject.toml" diff --git a/pyproject.toml b/pyproject.toml index 65c88b3e72..11753129c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ name = "dycw-utilities" readme = "README.md" requires-python = ">= 3.12" - version = "0.174.21" + version = "0.175.0" [project.entry-points.pytest11] pytest-randomly = "utilities.pytest_plugins.pytest_randomly" diff --git a/src/utilities/__init__.py b/src/utilities/__init__.py index 1fe4a1b471..30ee604cb2 100644 --- a/src/utilities/__init__.py +++ b/src/utilities/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -__version__ = "0.174.21" +__version__ = "0.175.0" diff --git a/uv.lock b/uv.lock index 808daea7ed..324b119083 100644 --- a/uv.lock +++ b/uv.lock @@ -622,7 +622,7 @@ wheels = [ [[package]] name = "dycw-utilities" -version = "0.174.21" +version = "0.175.0" source = { editable = "." } dependencies = [ { name = "atomicwrites" }, From 5b4518ea38b2dd3920287cc828d49be498781ce3 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Thu, 1 Jan 2026 00:21:55 +0900 Subject: [PATCH 23/23] 2026-01-01 00:21:55 (Thu) > DW-Mac > derekwan --- pytest.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytest.toml b/pytest.toml index f61c13ecb5..c28abdc82f 100644 --- a/pytest.toml +++ b/pytest.toml @@ -17,8 +17,10 @@ "error", "ignore: was delete before being closed:ResourceWarning", # sqlalchemy "ignore:Automatically deduplicated selection parameter with identical configuration:UserWarning", # altair + "ignore:Do not expect file_or_dir in Namespace:UserWarning", # pytest "ignore:Exception ignored in.* :pytest.PytestUnraisableExceptionWarning", "ignore:Exception in thread Thread-.*:pytest.PytestUnhandledThreadExceptionWarning", + "ignore:Implicitly cleaning up <_TemporaryFileWrapper .*>:ResourceWarning", # tempfile "ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning", # jupyter "ignore:ResourceTracker called reentrantly for resource cleanup, which is unsupported:UserWarning", "ignore:The garbage collector is trying to clean up non-checked-in connection :RuntimeWarning", # sqlalchemy @@ -33,7 +35,6 @@ "ignore:unclosed event loop <_UnixSelectorEventLoop .*>:ResourceWarning", # redis "ignore:unclosed file <_io.*TextIOWrapper .*>:ResourceWarning", # logging "ignore:unclosed transport <_SelectorSocketTransport .*>:ResourceWarning", # redis - "ignore:Do not expect file_or_dir in Namespace:UserWarning", # pytest ] minversion = "9.0" strict = true