Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit 002acd3

Browse files
committed
Fix: mypy failures.
1 parent 72fa0c3 commit 002acd3

File tree

8 files changed

+54
-37
lines changed

8 files changed

+54
-37
lines changed

bookserver/internal/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ def canonicalize_tz(tstring: str) -> str:
3131
"""
3232
x = re.search(r"\((.*)\)", tstring)
3333
if x:
34-
x = x.group(1)
35-
y = x.split()
34+
z = x.group(1)
35+
y = z.split()
3636
if len(y) == 1:
3737
return tstring
3838
else:

bookserver/routers/assessment.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,16 @@
1515
#
1616
# Standard library
1717
# ----------------
18-
import datetime
18+
# None.
1919

2020
# Third-party imports
2121
# -------------------
22-
from dateutil.parser import parse
2322
from fastapi import APIRouter
2423

2524
# Local application imports
2625
# -------------------------
2726
from ..applogger import rslogger
2827
from ..crud import create_useinfo_entry, fetch_last_answer_table_entry # noqa F401
29-
from ..internal.utils import canonicalize_tz
3028
from ..schemas import AssessmentRequest, LogItem, LogItemIncoming # noqa F401
3129

3230
# Routing
@@ -49,20 +47,6 @@ async def get_assessment_results(request_data: AssessmentRequest):
4947
# else:
5048
# sid = auth.user.username
5149

52-
# :index:`todo`: **This whole thing is messy - get the deadline from the assignment in the db.**
53-
if request_data.deadline:
54-
try:
55-
deadline = parse(canonicalize_tz(request_data.deadline))
56-
tzoff = session.timezoneoffset if session.timezoneoffset else 0
57-
deadline = deadline + datetime.timedelta(hours=float(tzoff))
58-
deadline = deadline.replace(tzinfo=None)
59-
except Exception:
60-
# TODO: can this enclose just the parse code? Or can an error be raised in other cases?
61-
rslogger.error(f"Bad Timezone - {request_data.deadline}")
62-
deadline = datetime.datetime.utcnow()
63-
else:
64-
request_data.deadline = datetime.datetime.utcnow()
65-
6650
# Identify the correct event and query the database so we can load it from the server
6751

6852
row = await fetch_last_answer_table_entry(request_data)

bookserver/routers/rslogging.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
# log_book_event endpoint
3737
# -----------------------
3838
@router.post("/bookevent")
39-
async def log_book_event(entry: LogItemIncoming):
39+
async def log_book_event(entry: LogItem):
4040
"""
4141
This endpoint is called to log information for nearly every click that happens in the textbook.
4242
It uses the ``LogItem`` object to define the JSON payload it gets from a page of a book.

bookserver/schemas.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@
1010
#
1111
# Standard library
1212
# ----------------
13-
from datetime import datetime
14-
from typing import Container, Optional, Type
13+
from datetime import datetime, timedelta
14+
from dateutil.parser import parse
15+
from typing import Container, Optional, Type, Dict, Tuple, Union, Any
1516

1617
# Third-party imports
1718
# -------------------
18-
from pydantic import BaseModel, create_model, constr
19+
from pydantic import BaseModel, create_model, constr, validator, Field
1920

2021
# Local application imports
2122
# -------------------------
2223
from . import models
24+
from .internal.utils import canonicalize_tz
2325

2426

2527
# Schema generation
@@ -36,13 +38,13 @@ def sqlalchemy_to_pydantic(
3638
# The base class from which the Pydantic model will inherit.
3739
base: Optional[Type] = None,
3840
# SQLAlchemy fields to exclude from the resulting schema, provided as a sequence of field names.
39-
exclude: Container[str] = []
41+
exclude: Container[str] = [],
4042
) -> Type[BaseModel]:
4143

4244
# If provided an ORM model, get the underlying Table object.
4345
db_model = getattr(db_model, "__table__", db_model)
4446

45-
fields = {}
47+
fields: Dict[str, Union[Tuple[str, Any], Type]] = {}
4648
for column in db_model.columns:
4749
# Determine the name of this column.
4850
name = column.key
@@ -84,8 +86,6 @@ class LogItemIncoming(BaseModel):
8486
"""
8587

8688
# FIXME: Use max lengths for strings based on the actual lengths from the database using `Pydantic constraints <https://pydantic-docs.helpmanual.io/usage/types/#constrained-types>`_. Is there any way to query the database for these, instead of manually keeping them in sync?
87-
#
88-
# Even better, we should introspect the database and auto-convert this to Pydantic. See the idea at https://github.com/tiangolo/pydantic-sqlalchemy/blob/master/pydantic_sqlalchemy/main.py. See also https://github.com/kolypto/py-sa2schema. Other ideas: https://docs.sqlalchemy.org/en/14/core/reflection.html,
8989
event: str
9090
act: str
9191
div_id: str
@@ -107,19 +107,30 @@ class LogItem(LogItemIncoming):
107107
This may seem like overkill but it illustrates a point. The schema for the incoming log data will not contain a timestamp. We could make it optional there, but then that would imply that it is optional which it most certainly is not. We could add timestamp as part of a LogItemCreate class similar to how password is handled in the tutorial: https://fastapi.tiangolo.com/tutorial/sql-databases/ But there is no security reason to exclude timestamp. So I think this is a reasonable compromise.
108108
"""
109109

110-
timestamp: datetime
110+
timestamp: datetime = Field(default_factory=datetime.utcnow)
111111

112-
class Config:
113-
orm_mode = True
114-
# this tells pydantic to try read anything with attributes we give it as a model
115-
# TODO: This doesn't make sense to me -- we're not using an ORM. What does this do?
112+
@validator("timestamp")
113+
def str_to_datetime(cls, value: str) -> datetime:
114+
# TODO: this code probably doesn't work.
115+
try:
116+
deadline = parse(canonicalize_tz(value))
117+
# TODO: session isn't defined. Here's a temporary fix
118+
# tzoff = session.timezoneoffset if session.timezoneoffset else 0
119+
tzoff = 0
120+
deadline = deadline + timedelta(hours=float(tzoff))
121+
deadline = deadline.replace(tzinfo=None)
122+
except Exception:
123+
# TODO: can this enclose just the parse code? Or can an error be raised in other cases?
124+
raise ValueError(f"Bad Timezone - {value}")
125+
return deadline
116126

117127

118128
class AssessmentRequest(BaseModel):
119129
course: str
120130
div_id: str
121131
event: str
122132
sid: Optional[str] = None
133+
# See `Field with dynamic default value <https://pydantic-docs.helpmanual.io/usage/models/#required-optional-fields>`_.
123134
deadline: Optional[str] = None
124135

125136

bookserver/session.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,22 @@
88
# OR in a header to be validated. If the token is valid then the user will be looked
99
# up in the database using the ``load_user`` function in this file.
1010
# see `./routers/auth.py` for more detail.
11+
12+
# Imports
13+
# =======
14+
# These are listed in the order prescribed by `PEP 8`_.
1115
#
12-
from bookserver.config import settings
16+
# Standard library
17+
# ----------------
18+
from typing import Optional
19+
20+
# Third-party imports
21+
# -------------------
1322
from fastapi_login import LoginManager
23+
24+
# Local application imports
25+
# -------------------------
26+
from .config import settings
1427
from . import schemas
1528
from .crud import fetch_user
1629
from .applogger import rslogger
@@ -21,7 +34,7 @@
2134

2235

2336
@auth_manager.user_loader
24-
async def load_user(user_id: str) -> schemas.User:
37+
async def load_user(user_id: str) -> Optional[schemas.User]:
2538
"""
2639
fetch a user object from the database. This is designed to work with the
2740
original web2py auth_user schema but make it easier to migrate to a new

conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def excluder(app: Sphinx, config: Config):
345345
# The path must start in the `srcdir <https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.srcdir>`_.
346346
root_path = Path(app.srcdir)
347347
# This is slightly inefficient, since it doesn't use the existing excludes to avoid searching already-excluded values.
348-
app.config.exclude_patterns += [
348+
app.config.exclude_patterns += [ # type: ignore
349349
# Paths must be relative to the srcdir.
350350
x.relative_to(root_path).as_posix()
351351
for x in root_path.glob(pattern)

mypy.ini

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
[mypy]
2727
# See `files <https://mypy.readthedocs.io/en/stable/config_file.html#confval-files>`_.
2828
files = .
29-
exclude = ^_build/
29+
exclude = (^_build/)|(^alembic/)
3030

3131
; These libraries lack annotations. `Ignore missing imports <https://mypy.readthedocs.io/en/latest/config_file.html#import-discovery>`_.
3232
[mypy-alembic.*]
@@ -35,5 +35,14 @@ ignore_missing_imports = True
3535
[mypy-CodeChat.*]
3636
ignore_missing_imports = True
3737

38+
[mypy-fastapi_login.*]
39+
ignore_missing_imports = True
40+
41+
[mypy-pydal.validators.*]
42+
ignore_missing_imports = True
43+
3844
[mypy-sqlalchemy.*]
3945
ignore_missing_imports = True
46+
47+
[mypy-uvicorn.*]
48+
ignore_missing_imports = True

pre_commit_check.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def checks():
3434
"black --check .",
3535
# Do this next -- it should be easy to fix most of these.
3636
"flake8 .",
37-
##"mypy",
37+
"mypy",
3838
# Next, check the docs. Again, these only require fixes to comments, and should still be relatively easy to correct.
3939
#
4040
# Force a `full build <https://www.sphinx-doc.org/en/master/man/sphinx-build.html>`_:

0 commit comments

Comments
 (0)