Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,13 @@ jobs:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14-dev']
python-version: ['3.12', '3.13', '3.14']
exclude: [
# windows runners are pretty scarce, so let's only run lowest and highest python version
{platform: windows-latest, python-version: '3.11'},
{platform: windows-latest, python-version: '3.12'},
{platform: windows-latest, python-version: '3.13'},

# same, macos is a bit too slow and ubuntu covers python quirks well
{platform: macos-latest , python-version: '3.11' },
{platform: macos-latest , python-version: '3.12' },
{platform: macos-latest , python-version: '3.13' },
]

runs-on: ${{ matrix.platform }}
Expand All @@ -50,16 +48,16 @@ jobs:
# ugh https://github.com/actions/toolkit/blob/main/docs/commands.md#path-manipulation
- run: echo "$HOME/.local/bin" >> $GITHUB_PATH

- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
fetch-depth: 0 # nicer to have all git history when debugging/for tests

- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- uses: astral-sh/setup-uv@v5
- uses: astral-sh/setup-uv@v7
with:
enable-cache: false # we don't have lock files, so can't use them as cache key

Expand Down Expand Up @@ -100,16 +98,16 @@ jobs:
# ugh https://github.com/actions/toolkit/blob/main/docs/commands.md#path-manipulation
- run: echo "$HOME/.local/bin" >> $GITHUB_PATH

- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
fetch-depth: 0 # pull all commits to correctly infer vcs version

- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.12'

- uses: astral-sh/setup-uv@v5
- uses: astral-sh/setup-uv@v7
with:
enable-cache: false # we don't have lock files, so can't use them as cache key

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ dependencies = [
"kompress>=0.2.20240918" , # for transparent access to compressed files via pathlib.Path

]
requires-python = ">=3.10"
requires-python = ">=3.12"

## these need to be set if you're planning to upload to pypi
description = "A Python interface to my life"
Expand Down Expand Up @@ -65,7 +65,7 @@ typecheck = [
{ include-group = "testing" },
"mypy",
"lxml", # for mypy coverage
"ty>=0.0.1a21",
"ty>=0.0.1a22",

"HPI[optional]",
"orgparse", # for my.core.orgmode
Expand Down
4 changes: 4 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ lint.ignore = [
"PLC0415", # "imports should be at the top level" -- not realistic

"ARG001", # ugh, kinda annoying when using pytest fixtures

# FIXME hmm. Need to figure out if cachew works fine with type = defined types before updating things..
"UP047", # non-pep695-generic-function
"UP040", # non-pep695-type-alias
]

lint.per-file-ignores."src/my/core/compat.py" = [
Expand Down
5 changes: 2 additions & 3 deletions src/my/arbtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@

from collections.abc import Iterable, Sequence
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from subprocess import PIPE, Popen

import ijson # type: ignore[import-untyped]

from my.core import Json, PathIsh, Stats, datetime_aware, get_files, stat
from my.core.compat import fromisoformat


def inputs() -> Sequence[Path]:
Expand Down Expand Up @@ -46,7 +46,6 @@ class Entry:
@property
def dt(self) -> datetime_aware:
# contains utc already
# TODO after python>=3.11, could just use fromisoformat
ds = self.json['date']
elen = 27
lds = len(ds)
Expand All @@ -57,7 +56,7 @@ def dt(self) -> datetime_aware:
# and sometimes more...
ds = ds[: elen - 1] + 'Z'

return fromisoformat(ds)
return datetime.fromisoformat(ds)

@property
def active(self) -> str | None:
Expand Down
3 changes: 1 addition & 2 deletions src/my/bumble/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Protocol
from typing import Protocol, assert_never

from more_itertools import unique_everseen

from my.core import Paths, Res, get_files
from my.core.compat import assert_never
from my.core.sqlite import select, sqlite_connection


Expand Down
4 changes: 2 additions & 2 deletions src/my/codeforces.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from collections.abc import Iterator, Sequence
from dataclasses import dataclass
from datetime import datetime, timezone
from datetime import UTC, datetime
from functools import cached_property
from pathlib import Path

Expand Down Expand Up @@ -45,7 +45,7 @@ def _parse_allcontests(self, p: Path) -> Iterator[Contest]:
for c in j['result']:
yield Contest(
contest_id=c['id'],
when=datetime.fromtimestamp(c['startTimeSeconds'], tz=timezone.utc),
when=datetime.fromtimestamp(c['startTimeSeconds'], tz=UTC),
name=c['name'],
)

Expand Down
21 changes: 7 additions & 14 deletions src/my/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
from collections.abc import Callable, Iterable, Sequence
from glob import glob as do_glob
from pathlib import Path
from typing import (
TYPE_CHECKING,
Generic,
TypeVar,
)
from typing import TYPE_CHECKING

from . import compat, warnings

Expand Down Expand Up @@ -106,30 +102,27 @@ def caller() -> str:
return tuple(paths)


_R = TypeVar('_R')


# https://stackoverflow.com/a/5192374/706389
# NOTE: it was added to stdlib in 3.9 and then deprecated in 3.11
# seems that the suggested solution is to use custom decorator?
class classproperty(Generic[_R]):
def __init__(self, f: Callable[..., _R]) -> None:
class classproperty[R]:
def __init__(self, f: Callable[..., R]) -> None:
self.f = f

def __get__(self, obj, cls) -> _R:
def __get__(self, obj, cls) -> R:
return self.f(cls)


def test_classproperty() -> None:
from .compat import assert_type
from typing import assert_type

class C:
@classproperty
def prop(cls) -> str:
return 'hello'

res = C.prop
assert_type(res, str)
assert_type(res, str) # ty: ignore[type-assertion-failure]
assert res == 'hello'


Expand Down Expand Up @@ -247,7 +240,7 @@ def asdict(*args, **kwargs):

tzdatetime = datetime_aware
else:
from .compat import Never
from typing import Never

# make these invalid during type check while working in runtime
Stats = Never
Expand Down
78 changes: 6 additions & 72 deletions src/my/core/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,91 +32,25 @@ def removeprefix(text: str, prefix: str) -> str:
def removesuffix(text: str, suffix: str) -> str:
return text.removesuffix(suffix)

##

## used to have compat function before 3.8 for these, keeping for runtime back compatibility
from bisect import bisect_left
from functools import cached_property
from types import NoneType

# used to have compat function before 3.9 for these, keeping for runtime back compatibility
##
## used to have compat function before 3.9 for these, keeping for runtime back compatibility
from typing import Literal, ParamSpec, Protocol, TypeAlias, TypedDict

_KwOnlyType = TypedDict("_KwOnlyType", {"kw_only": Literal[True]}) # noqa: UP013
KW_ONLY: _KwOnlyType = {"kw_only": True}
##

##

from datetime import datetime
## old compat for python <3.12
from datetime import datetime

if sys.version_info[:2] >= (3, 11):
fromisoformat = datetime.fromisoformat
else:
# fromisoformat didn't support Z as "utc" before 3.11
# https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat

def fromisoformat(date_string: str) -> datetime:
if date_string.endswith('Z'):
date_string = date_string[:-1] + '+00:00'
return datetime.fromisoformat(date_string)


def test_fromisoformat() -> None:
from datetime import timezone

# fmt: off
# feedbin has this format
assert fromisoformat('2020-05-01T10:32:02.925961Z') == datetime(
2020, 5, 1, 10, 32, 2, 925961, timezone.utc,
)

# polar has this format
assert fromisoformat('2018-11-28T22:04:01.304Z') == datetime(
2018, 11, 28, 22, 4, 1, 304000, timezone.utc,
)

# stackexchange, runnerup has this format
assert fromisoformat('2020-11-30T00:53:12Z') == datetime(
2020, 11, 30, 0, 53, 12, 0, timezone.utc,
)
# fmt: on

# arbtt has this format (sometimes less/more than 6 digits in milliseconds)
# TODO doesn't work atm, not sure if really should be supported...
# maybe should have flags for weird formats?
# assert isoparse('2017-07-18T18:59:38.21731Z') == datetime(
# 2017, 7, 18, 18, 59, 38, 217310, timezone.utc,
# )


if sys.version_info[:2] >= (3, 11):
from typing import Never, assert_never, assert_type
else:
from typing_extensions import Never, assert_never, assert_type


if sys.version_info[:2] >= (3, 11):
add_note = BaseException.add_note
else:

def add_note(e: BaseException, note: str) -> None:
"""
Backport of BaseException.add_note
"""

# The only (somewhat annoying) difference is it will log extra lines for notes past the main exception message:
# (i.e. line 2 here:)

# 1 [ERROR 2025-02-04 22:12:21] Main exception message
# 2 ^ extra note
# 3 Traceback (most recent call last):
# 4 File "run.py", line 19, in <module>
# 5 ee = test()
# 6 File "run.py", line 5, in test
# 7 raise RuntimeError("Main exception message")
# 8 RuntimeError: Main exception message
# 9 ^ extra note

args = e.args
if len(args) == 1 and isinstance(args[0], str):
e.args = (e.args[0] + '\n' + note,)
##
10 changes: 4 additions & 6 deletions src/my/core/freezer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

import dataclasses
import inspect
from typing import Any, Generic, TypeVar
from typing import Any

D = TypeVar('D')


def _freeze_dataclass(Orig: type[D]):
ofields = [(f.name, f.type, f) for f in dataclasses.fields(Orig)] # type: ignore[arg-type] # see https://github.com/python/typing_extensions/issues/115
def _freeze_dataclass(Orig: type):
ofields = [(f.name, f.type, f) for f in dataclasses.fields(Orig)]

# extract properties along with their types
props = list(inspect.getmembers(Orig, lambda o: isinstance(o, property)))
Expand All @@ -20,7 +18,7 @@ def _freeze_dataclass(Orig: type[D]):
return props, RRR


class Freezer(Generic[D]):
class Freezer[D]:
'''
Some magic which converts dataclass properties into fields.
It could be useful for better serialization, for performance, for using type as a schema.
Expand Down
Loading
Loading