use native union type syntax

This commit is contained in:
ducklet 2023-02-02 23:46:02 +01:00
parent 13b65103fd
commit 3320d53eda
8 changed files with 43 additions and 45 deletions

View file

@ -4,7 +4,7 @@ import logging
import re import re
import threading import threading
from pathlib import Path from pathlib import Path
from typing import Any, Iterable, Literal, Optional, Type, TypeVar, Union from typing import Any, Iterable, Literal, Type, TypeVar
import sqlalchemy import sqlalchemy
from databases import Database from databases import Database
@ -26,7 +26,7 @@ from .types import ULID
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
T = TypeVar("T") T = TypeVar("T")
_shared_connection: Optional[Database] = None _shared_connection: Database | None = None
async def open_connection_pool() -> None: async def open_connection_pool() -> None:
@ -131,7 +131,7 @@ async def apply_db_patches(db: Database):
await db.execute("vacuum") await db.execute("vacuum")
async def get_import_progress() -> Optional[Progress]: async def get_import_progress() -> Progress | None:
"""Return the latest import progress.""" """Return the latest import progress."""
return await get(Progress, type="import-imdb-movies", order_by="started DESC") return await get(Progress, type="import-imdb-movies", order_by="started DESC")
@ -244,7 +244,7 @@ ModelType = TypeVar("ModelType")
async def get( async def get(
model: Type[ModelType], *, order_by: str = None, **kwds model: Type[ModelType], *, order_by: str = None, **kwds
) -> Optional[ModelType]: ) -> ModelType | None:
"""Load a model instance from the database. """Load a model instance from the database.
Passing `kwds` allows to filter the instance to load. You have to encode the Passing `kwds` allows to filter the instance to load. You have to encode the
@ -415,7 +415,7 @@ async def find_ratings(
limit_rows: int = 10, limit_rows: int = 10,
user_ids: Iterable[str] = [], user_ids: Iterable[str] = [],
): ):
values: dict[str, Union[int, str]] = { values: dict[str, int | str] = {
"limit_rows": limit_rows, "limit_rows": limit_rows,
} }
@ -598,7 +598,7 @@ async def find_movies(
include_unrated: bool = False, include_unrated: bool = False,
user_ids: list[ULID] = [], user_ids: list[ULID] = [],
) -> Iterable[tuple[Movie, list[Rating]]]: ) -> Iterable[tuple[Movie, list[Rating]]]:
values: dict[str, Union[int, str]] = { values: dict[str, int | str] = {
"limit_rows": limit_rows, "limit_rows": limit_rows,
"skip_rows": skip_rows, "skip_rows": skip_rows,
} }

View file

@ -2,7 +2,6 @@ import logging
import re import re
from collections import namedtuple from collections import namedtuple
from datetime import datetime from datetime import datetime
from typing import Optional, Tuple
from urllib.parse import urljoin from urllib.parse import urljoin
from . import db from . import db
@ -153,7 +152,7 @@ def movie_and_rating_from_item(item) -> tuple[Movie, Rating]:
ForgedRequest = namedtuple("ForgedRequest", "url headers") ForgedRequest = namedtuple("ForgedRequest", "url headers")
async def parse_page(url) -> Tuple[list[Rating], Optional[str]]: async def parse_page(url) -> tuple[list[Rating], str | None]:
ratings = [] ratings = []
soup = soup_from_url(url) soup = soup_from_url(url)

View file

@ -4,7 +4,7 @@ import logging
from dataclasses import dataclass, fields from dataclasses import dataclass, fields
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from typing import Generator, Literal, Optional, Type, TypeVar, overload from typing import Generator, Literal, Type, TypeVar, overload
from . import config, db, request from . import config, db, request
from .db import add_or_update_many_movies from .db import add_or_update_many_movies
@ -27,10 +27,10 @@ class BasicRow:
primaryTitle: str primaryTitle: str
originalTitle: str originalTitle: str
isAdult: bool isAdult: bool
startYear: Optional[int] startYear: int | None
endYear: Optional[int] endYear: int | None
runtimeMinutes: Optional[int] runtimeMinutes: int | None
genres: Optional[set[str]] genres: set[str] | None
@classmethod @classmethod
def from_row(cls, row): def from_row(cls, row):

View file

@ -9,7 +9,6 @@ from typing import (
ClassVar, ClassVar,
Container, Container,
Literal, Literal,
Optional,
Type, Type,
TypeVar, TypeVar,
Union, Union,
@ -25,7 +24,7 @@ JSONObject = dict[str, JSON]
T = TypeVar("T") T = TypeVar("T")
def annotations(tp: Type) -> Optional[tuple]: def annotations(tp: Type) -> tuple | None:
return tp.__metadata__ if hasattr(tp, "__metadata__") else None return tp.__metadata__ if hasattr(tp, "__metadata__") else None
@ -61,7 +60,7 @@ def is_optional(tp: Type) -> bool:
return len(args) == 2 and type(None) in args return len(args) == 2 and type(None) in args
def optional_type(tp: Type) -> Optional[Type]: def optional_type(tp: Type) -> Type | None:
"""Return the wrapped type from an optional type. """Return the wrapped type from an optional type.
For example this will return `int` for `Optional[int]`. For example this will return `int` for `Optional[int]`.
@ -206,7 +205,7 @@ class Progress:
type: str = None type: str = None
state: str = None state: str = None
started: datetime = field(default_factory=utcnow) started: datetime = field(default_factory=utcnow)
stopped: Optional[str] = None stopped: str | None = None
@property @property
def _state(self) -> dict: def _state(self) -> dict:
@ -243,15 +242,15 @@ class Movie:
id: ULID = field(default_factory=ULID) id: ULID = field(default_factory=ULID)
title: str = None # canonical title (usually English) title: str = None # canonical title (usually English)
original_title: Optional[ original_title: str | None = (
str None # original title (usually transscribed to latin script)
] = None # original title (usually transscribed to latin script) )
release_year: int = None # canonical release date release_year: int = None # canonical release date
media_type: str = None media_type: str = None
imdb_id: str = None imdb_id: str = None
imdb_score: Optional[int] = None # range: [0,100] imdb_score: int | None = None # range: [0,100]
imdb_votes: Optional[int] = None imdb_votes: int | None = None
runtime: Optional[int] = None # minutes runtime: int | None = None # minutes
genres: set[str] = None genres: set[str] = None
created: datetime = field(default_factory=utcnow) created: datetime = field(default_factory=utcnow)
updated: datetime = field(default_factory=utcnow) updated: datetime = field(default_factory=utcnow)
@ -292,7 +291,7 @@ dataclass containing the ID of the linked data.
The contents of the Relation are ignored or discarded when using The contents of the Relation are ignored or discarded when using
`asplain`, `fromplain`, and `validate`. `asplain`, `fromplain`, and `validate`.
""" """
Relation = Annotated[Optional[T], _RelationSentinel] Relation = Annotated[T | None, _RelationSentinel]
@dataclass @dataclass
@ -309,8 +308,8 @@ class Rating:
score: int = None # range: [0,100] score: int = None # range: [0,100]
rating_date: datetime = None rating_date: datetime = None
favorite: Optional[bool] = None favorite: bool | None = None
finished: Optional[bool] = None finished: bool | None = None
def __eq__(self, other): def __eq__(self, other):
"""Return wether two Ratings are equal. """Return wether two Ratings are equal.
@ -342,11 +341,11 @@ class User:
secret: str = None secret: str = None
groups: list[dict[str, str]] = field(default_factory=list) groups: list[dict[str, str]] = field(default_factory=list)
def has_access(self, group_id: Union[ULID, str], access: Access = "r"): def has_access(self, group_id: ULID | str, access: Access = "r"):
group_id = group_id if isinstance(group_id, str) else str(group_id) group_id = group_id if isinstance(group_id, str) else str(group_id)
return any(g["id"] == group_id and access == g["access"] for g in self.groups) return any(g["id"] == group_id and access == g["access"] for g in self.groups)
def set_access(self, group_id: Union[ULID, str], access: Access): def set_access(self, group_id: ULID | str, access: Access):
group_id = group_id if isinstance(group_id, str) else str(group_id) group_id = group_id if isinstance(group_id, str) else str(group_id)
for g in self.groups: for g in self.groups:
if g["id"] == group_id: if g["id"] == group_id:

View file

@ -11,7 +11,7 @@ from hashlib import md5
from pathlib import Path from pathlib import Path
from random import random from random import random
from time import sleep, time from time import sleep, time
from typing import Callable, Optional, Union from typing import Callable
import bs4 import bs4
import requests import requests
@ -142,7 +142,7 @@ class RedirectError(RuntimeError):
super().__init__(f"Redirected: {from_url} -> {to_url}") super().__init__(f"Redirected: {from_url} -> {to_url}")
def cache_path(req) -> Optional[Path]: def cache_path(req) -> Path | None:
if not config.cachedir: if not config.cachedir:
return return
sig = repr(req.url) # + repr(sorted(req.headers.items())) sig = repr(req.url) # + repr(sorted(req.headers.items()))
@ -215,7 +215,7 @@ def last_modified_from_file(path: Path):
def download( def download(
url: str, url: str,
file_path: Union[Path, str] = None, file_path: Path | str = None,
*, *,
replace_existing: bool = None, replace_existing: bool = None,
only_if_newer: bool = False, only_if_newer: bool = False,

View file

@ -1,5 +1,5 @@
import re import re
from typing import Union, cast from typing import cast
import ulid import ulid
from ulid.hints import Buffer from ulid.hints import Buffer
@ -16,7 +16,7 @@ class ULID(ulid.ULID):
_pattern = re.compile(r"^[0-9A-HJKMNP-TV-Z]{26}$") _pattern = re.compile(r"^[0-9A-HJKMNP-TV-Z]{26}$")
def __init__(self, buffer: Union[Buffer, ulid.ULID, str, None] = None): def __init__(self, buffer: Buffer | ulid.ULID | str | None = None):
if isinstance(buffer, str): if isinstance(buffer, str):
if not self._pattern.search(buffer): if not self._pattern.search(buffer):
raise ValueError("Invalid ULID.") raise ValueError("Invalid ULID.")

View file

@ -2,7 +2,7 @@ import asyncio
import logging import logging
import secrets import secrets
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
from typing import Literal, Optional, overload from typing import Literal, overload
from starlette.applications import Starlette from starlette.applications import Starlette
from starlette.authentication import ( from starlette.authentication import (
@ -97,7 +97,7 @@ def yearcomp(s: str):
return comp, int(s) return comp, int(s)
def as_int(x, *, max: int = None, min: Optional[int] = 1, default: int = None): def as_int(x, *, max: int = None, min: int | None = 1, default: int = None):
try: try:
if not isinstance(x, int): if not isinstance(x, int):
x = int(x) x = int(x)
@ -158,7 +158,7 @@ def is_admin(request):
return "admin" in request.auth.scopes return "admin" in request.auth.scopes
async def auth_user(request) -> Optional[User]: async def auth_user(request) -> User | None:
if not isinstance(request.user, AuthedUser): if not isinstance(request.user, AuthedUser):
return return

View file

@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Container, Iterable, Optional from typing import Container, Iterable
from . import imdb, models from . import imdb, models
@ -10,14 +10,14 @@ Score100 = int # [0, 100]
@dataclass @dataclass
class Rating: class Rating:
canonical_title: str canonical_title: str
imdb_score: Optional[Score100] imdb_score: Score100 | None
imdb_votes: Optional[int] imdb_votes: int | None
media_type: str media_type: str
movie_imdb_id: str movie_imdb_id: str
original_title: Optional[str] original_title: str | None
release_year: int release_year: int
user_id: Optional[str] user_id: str | None
user_score: Optional[Score100] user_score: Score100 | None
@classmethod @classmethod
def from_movie(cls, movie: models.Movie, *, rating: models.Rating = None): def from_movie(cls, movie: models.Movie, *, rating: models.Rating = None):
@ -37,11 +37,11 @@ class Rating:
@dataclass @dataclass
class RatingAggregate: class RatingAggregate:
canonical_title: str canonical_title: str
imdb_score: Optional[Score100] imdb_score: Score100 | None
imdb_votes: Optional[int] imdb_votes: int | None
link: URL link: URL
media_type: str media_type: str
original_title: Optional[str] original_title: str | None
user_scores: list[Score100] user_scores: list[Score100]
year: int year: int