Skip to content

Commit e97c996

Browse files
committed
Try to make ty mostly happy
1 parent ed383be commit e97c996

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1760
-1173
lines changed

Tekst-API/openapi.json

Lines changed: 140 additions & 325 deletions
Large diffs are not rendered by default.

Tekst-API/tekst/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from importlib import metadata
22

33

4-
_package_metadata = metadata.metadata(__package__)
4+
_package_metadata = metadata.metadata(__package__ or __name__)
55

66
# whyyyyy
77
_project_urls = {

Tekst-API/tekst/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ async def _export(
123123
export_format=fmt,
124124
)
125125
except TekstHTTPException as e:
126-
if e.detail.detail.key == "unsupportedExportFormat":
126+
if "unsupportedExportFormat" in str(e.detail):
127127
click.echo(
128128
f"Resource {res_id_str} does not support export format {fmt}",
129129
err=True,

Tekst-API/tekst/app.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from tekst import db, search
1414
from tekst.config import TekstConfig, get_config
1515
from tekst.db import migrations
16-
from tekst.errors import TekstHTTPException
16+
from tekst.errors import TekstErrorModel, TekstHTTPException
1717
from tekst.logs import log
1818
from tekst.middlewares import CookieTypeChoiceMiddleware
1919
from tekst.models.platform import PlatformState
@@ -117,14 +117,22 @@ async def lifespan(app: FastAPI):
117117
async def custom_http_exception_handler(request: Request, exc: Exception):
118118
# force garbage collection due to issues with custom exception handler
119119
# (see https://github.com/fastapi/fastapi/discussions/9145#discussioncomment-7254388)
120+
121+
# TODO To anyone touching this in the future:
122+
# The ignored concerns of ty might be justified or not.
123+
# I wasn't able to figure it out.
124+
# As it works fine, I'm leaving it as is for now. I am sorry.
125+
120126
gc.collect()
121127
return await http_exception_handler(
122128
request,
123129
HTTPException(
124130
status_code=exc.status_code,
125-
detail=exc.detail.model_dump().get("detail", None),
126-
headers=exc.headers,
131+
detail=exc.detail.model_dump().get("detail", None)
132+
if isinstance(exc.detail, TekstErrorModel)
133+
else None,
134+
headers=exc.headers, # ty:ignore[invalid-argument-type]
127135
)
128136
if isinstance(exc, TekstHTTPException)
129-
else exc,
137+
else exc, # ty:ignore[invalid-argument-type]
130138
)

Tekst-API/tekst/auth.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@
66
from datetime import UTC, datetime
77
from typing import Annotated, Any
88

9-
import fastapi_users.models as fapi_users_models
10-
119
from beanie import Document, PydanticObjectId
1210
from beanie.operators import Eq, Or, Pull, Set, Size
1311
from fastapi import (
14-
APIRouter,
1512
Depends,
1613
FastAPI,
1714
HTTPException,
@@ -23,6 +20,7 @@
2320
BaseUserManager,
2421
FastAPIUsers,
2522
InvalidPasswordException,
23+
models,
2624
)
2725
from fastapi_users.authentication import (
2826
AuthenticationBackend,
@@ -76,7 +74,7 @@ class Settings(BeanieBaseAccessToken.Settings):
7674
cookie_path=_cfg.api_path or "/",
7775
cookie_secure=not _cfg.dev_mode,
7876
cookie_httponly=True,
79-
cookie_samesite="Lax",
77+
cookie_samesite="lax",
8078
)
8179

8280
_bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
@@ -117,7 +115,7 @@ def _get_jwt_strategy() -> JWTStrategy:
117115
return JWTStrategy(
118116
secret=_cfg.security.secret,
119117
lifetime_seconds=_cfg.security.auth_jwt_lifetime,
120-
token_audience="tekst:jwt",
118+
token_audience=["tekst:jwt"],
121119
)
122120

123121

@@ -359,7 +357,12 @@ async def validate_password(
359357
reason="Password should not contain e-mail address"
360358
)
361359

362-
async def create(self, user_create, **kwargs) -> fapi_users_models.UP:
360+
async def create(
361+
self,
362+
user_create: UserCreate,
363+
safe: bool = False,
364+
request: Request | None = None,
365+
) -> models.UP:
363366
"""
364367
Overrides FastAPI-User's BaseUserManager's create method to check if the
365368
username already exists and respond with a meaningful HTTP exception.
@@ -374,7 +377,7 @@ async def create(self, user_create, **kwargs) -> fapi_users_models.UP:
374377
status_code=status.HTTP_400_BAD_REQUEST,
375378
detail="REGISTER_USERNAME_ALREADY_EXISTS",
376379
)
377-
return await super().create(user_create, **kwargs)
380+
return await super().create(user_create, safe, request) # ty:ignore[invalid-return-type]
378381

379382

380383
async def get_user_manager(user_db=Depends(get_user_db)):
@@ -390,7 +393,7 @@ async def get_user_manager(user_db=Depends(get_user_db)):
390393
)
391394

392395

393-
def setup_auth_routes(app: FastAPI) -> list[APIRouter]:
396+
def setup_auth_routes(app: FastAPI) -> None:
394397
# cookie auth
395398
if _cfg.security.enable_cookie_auth:
396399
app.include_router(
@@ -437,7 +440,7 @@ def setup_auth_routes(app: FastAPI) -> list[APIRouter]:
437440
app.include_router(
438441
_fastapi_users.get_users_router(
439442
UserRead,
440-
UserUpdate,
443+
UserUpdate, # ty:ignore[invalid-argument-type] # this is correct
441444
requires_verification=not _cfg.security.closed_mode,
442445
),
443446
prefix="/users",

Tekst-API/tekst/config.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from humps import camelize
1414
from pydantic import (
1515
BaseModel,
16+
BeforeValidator,
1617
ConfigDict,
1718
DirectoryPath,
1819
EmailStr,
@@ -24,7 +25,7 @@
2425
from pydantic_settings import BaseSettings, SettingsConfigDict
2526

2627
from tekst import package_metadata
27-
from tekst.types import EmptyStrToNone, HttpUrl, MultiLineString, SingleLineString
28+
from tekst.types import FalsyToNone, HttpUrl, MultiLineString, SingleLineString
2829

2930

3031
_DEV_MODE: bool = bool(os.environ.get("TEKST_DEV_MODE", False))
@@ -93,13 +94,13 @@ class MongoDBConfig(ConfigSubSection):
9394
str | None,
9495
StringConstraints(max_length=64),
9596
SingleLineString,
96-
EmptyStrToNone,
97+
FalsyToNone,
9798
] = None
9899
password: Annotated[
99100
str | None,
100101
StringConstraints(max_length=64),
101102
SingleLineString,
102-
EmptyStrToNone,
103+
FalsyToNone,
103104
] = None
104105
name: str = "tekst"
105106
unicode_nf: Literal["NFC", "NFKC", "NFD", "NFKD"] | None = "NFC"
@@ -181,7 +182,7 @@ class SecurityConfig(ConfigSubSection):
181182
str | None,
182183
StringConstraints(max_length=64),
183184
SingleLineString,
184-
EmptyStrToNone,
185+
FalsyToNone,
185186
] = None
186187
auth_cookie_lifetime: Annotated[
187188
int,
@@ -211,13 +212,13 @@ class SecurityConfig(ConfigSubSection):
211212
str | None,
212213
StringConstraints(max_length=256),
213214
SingleLineString,
214-
EmptyStrToNone,
215+
FalsyToNone,
215216
] = None
216217
init_admin_password: Annotated[
217218
str | None,
218219
StringConstraints(max_length=256),
219220
SingleLineString,
220-
EmptyStrToNone,
221+
FalsyToNone,
221222
] = None
222223

223224

@@ -229,18 +230,18 @@ class EMailConfig(ConfigSubSection):
229230
StringConstraints(max_length=256),
230231
SingleLineString,
231232
] = "127.0.0.1"
232-
smtp_port: int | None = 25
233+
smtp_port: int = 25
233234
smtp_user: Annotated[
234235
str | None,
235236
StringConstraints(max_length=256),
236237
SingleLineString,
237-
EmptyStrToNone,
238+
FalsyToNone,
238239
] = None
239240
smtp_password: Annotated[
240241
str | None,
241242
StringConstraints(max_length=256),
242243
SingleLineString,
243-
EmptyStrToNone,
244+
FalsyToNone,
244245
] = None
245246
smtp_starttls: bool = True
246247
from_address: str = "noreply@example-tekst-instance.org"
@@ -262,39 +263,39 @@ class ApiDocConfig(ConfigSubSection):
262263
str | None,
263264
StringConstraints(max_length=256),
264265
SingleLineString,
265-
EmptyStrToNone,
266+
FalsyToNone,
266267
] = None
267268
description: Annotated[
268269
str | None,
269270
StringConstraints(max_length=4096),
270271
MultiLineString,
271-
EmptyStrToNone,
272+
FalsyToNone,
272273
] = None
273-
terms_url: Annotated[HttpUrl | None, EmptyStrToNone] = None
274+
terms_url: Annotated[HttpUrl | None, FalsyToNone] = None
274275
contact_name: Annotated[
275276
str | None,
276277
StringConstraints(max_length=64),
277278
SingleLineString,
278-
EmptyStrToNone,
279+
FalsyToNone,
279280
] = None
280281
contact_email: Annotated[
281282
EmailStr | None,
282-
EmptyStrToNone,
283+
FalsyToNone,
283284
] = None
284-
contact_url: Annotated[HttpUrl | None, EmptyStrToNone] = None
285+
contact_url: Annotated[HttpUrl | None, FalsyToNone] = None
285286
license_name: Annotated[
286287
str | None,
287288
StringConstraints(max_length=32),
288289
SingleLineString,
289-
EmptyStrToNone,
290+
FalsyToNone,
290291
] = None
291292
license_id: Annotated[
292293
str | None,
293294
StringConstraints(max_length=32),
294295
SingleLineString,
295-
EmptyStrToNone,
296+
FalsyToNone,
296297
] = None
297-
license_url: Annotated[HttpUrl | None, EmptyStrToNone] = None
298+
license_url: Annotated[HttpUrl | None, FalsyToNone] = None
298299

299300

300301
class CORSConfig(ConfigSubSection):
@@ -352,7 +353,10 @@ class TekstConfig(BaseSettings):
352353
auto_migrate: bool = False
353354
xsrf: bool = True
354355

355-
temp_files_dir: DirectoryPath = Path("/tmp/tekst_tmp")
356+
temp_files_dir: Annotated[
357+
DirectoryPath,
358+
BeforeValidator(lambda v: Path(v) if isinstance(v, str) else v),
359+
] = Path("/tmp/tekst_tmp")
356360

357361
# config sub sections
358362
db: MongoDBConfig = MongoDBConfig() # MongoDB-related config

Tekst-API/tekst/db/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ async def get_db_status() -> dict[str, Any] | None:
5151

5252

5353
def get_db(
54-
db_client: Database = get_db_client(), cfg: TekstConfig = get_config()
54+
db_client: DatabaseClient = get_db_client(), cfg: TekstConfig = get_config()
5555
) -> Database:
5656
return db_client.get_database(
5757
cfg.db.name,
@@ -79,8 +79,12 @@ async def init_odm(db: Database = get_db()) -> None:
7979
]
8080
# add all resource types' resource and content document models
8181
for lt_class in resource_types_mgr.get_all().values():
82-
models.append(lt_class.resource_model().document_model())
83-
models.append(lt_class.content_model().document_model())
82+
res_doc_model = lt_class.resource_model().document_model()
83+
cnt_doc_model = lt_class.content_model().document_model()
84+
assert isinstance(res_doc_model, ResourceBaseDocument)
85+
assert isinstance(cnt_doc_model, ContentBaseDocument)
86+
models.append(res_doc_model)
87+
models.append(cnt_doc_model)
8488
# init beanie ODM
8589
await init_beanie(
8690
database=db,

Tekst-API/tekst/db/migrations/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ async def migrate() -> None:
105105
for mig_ver in pending:
106106
log.info(f"Migrating DB from {curr_db_version} to {str(mig_ver)}...")
107107
try:
108-
await pending[mig_ver](db)
108+
await pending[mig_ver](db) # ty:ignore[invalid-await] # TODO: look into what ty wants here!
109109
except Exception as e: # pragma: no cover
110110
log.error(
111111
f"Failed migrating DB from {curr_db_version} to {str(mig_ver)}: {e}"

Tekst-API/tekst/db/migrations/migration_0_3_0a0.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ async def migration(db: Database) -> None:
1313
"position": corr["position"],
1414
}
1515
)
16+
if loc is None:
17+
raise ValueError(f"Location not found for correction {corr['_id']}")
1618
await db.corrections.update_one(
1719
{"_id": corr["_id"]}, {"$set": {"location_id": loc["_id"]}}
1820
)

Tekst-API/tekst/logs.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from logging import config
44
from time import perf_counter, process_time
5-
from typing import Literal
5+
from typing import Any, Literal
66
from uuid import uuid4
77

88
from tekst.config import TekstConfig, get_config
@@ -95,15 +95,12 @@ def filter(self, record): # pragma: no cover
9595
},
9696
}
9797

98-
_LOG_LEVELS = {
99-
"DEBUG": logging.getLevelName("DEBUG"),
100-
"INFO": logging.getLevelName("INFO"),
101-
"WARNING": logging.getLevelName("WARNING"),
102-
"ERROR": logging.getLevelName("ERROR"),
103-
"CRITICAL": logging.getLevelName("CRITICAL"),
104-
}
10598

106-
LogLevelString = Literal[tuple(_LOG_LEVELS.keys())]
99+
def _get_lvl_name(level: str, fallback_level: str) -> Any:
100+
return logging.getLevelName(level) or logging.getLevelName(fallback_level)
101+
102+
103+
LogLevelString = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
107104

108105
config.dictConfig(_LOGGING_CONFIG)
109106
log = logging.getLogger("tekst")
@@ -120,7 +117,7 @@ def log_op_start(
120117
use_process_time: bool = False,
121118
) -> str:
122119
global _running_ops
123-
level_code = _LOG_LEVELS.get(level, _LOG_LEVELS["DEBUG"])
120+
level_code = _get_lvl_name(level, "DEBUG")
124121
op_id = str(uuid4())
125122
start_t = process_time() if use_process_time else perf_counter()
126123
_running_ops[op_id] = (label, start_t, level_code, use_process_time)
@@ -145,7 +142,7 @@ def log_op_end(
145142
if not failed:
146143
log.log(level_code, f"Finished: {label} [{dur:.2f}s]")
147144
else:
148-
level_code = _LOG_LEVELS.get(failed_level, _LOG_LEVELS["ERROR"])
145+
level_code = _get_lvl_name(failed_level, "ERROR")
149146
failed_msg = f" – {failed_msg}" if failed_msg else ""
150147
log.log(level_code, f"Failed: {label} [{dur:.2f}s]{failed_msg}")
151148
return dur

0 commit comments

Comments
 (0)