Skip to content

Commit bafa19e

Browse files
Fix typings and add typing and lint workflow (#1)
* fix typings and add typing and lint workflow * resolve single -> double quotes * missed incorrect type * missed stragglers * more stragglers * add lock file for version hashing
1 parent a74a66c commit bafa19e

File tree

7 files changed

+947
-31
lines changed

7 files changed

+947
-31
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Type Coverage and Linting
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
types: [opened, reopened, synchronize]
11+
12+
jobs:
13+
check:
14+
runs-on: ubuntu-latest
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
python-version: ["3.11", "3.x"]
19+
20+
name: "Type Coverage and Linting @ ${{ matrix.python-version }}"
21+
steps:
22+
- name: "Checkout Repository"
23+
uses: actions/checkout@v3
24+
with:
25+
fetch-depth: 0
26+
27+
- name: "Load cached poetry installation @ ${{ matrix.python-version }}"
28+
id: cached-poetry
29+
uses: actions/cache@v3
30+
with:
31+
path: ~/.local
32+
key: poetry-0
33+
34+
- name: "Setup Poetry @ ${{ matrix.python-version }}"
35+
if: steps.cached-poetry.outputs.cache-hit != 'true'
36+
uses: snok/install-poetry@v1
37+
with:
38+
version: latest
39+
virtualenvs-create: true
40+
virtualenvs-in-project: true
41+
virtualenvs-path: .venv
42+
43+
- name: "Setup Python @ ${{ matrix.python-version }}"
44+
id: setup-python
45+
uses: actions/setup-python@v4
46+
with:
47+
python-version: "${{ matrix.python-version }}"
48+
cache: "poetry"
49+
50+
- name: "Load cached venv @ ${{ matrix.python-version }}"
51+
id: cached-pip-wheels
52+
uses: actions/cache@v3
53+
with:
54+
path: .venv/
55+
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
56+
57+
- name: "Install Python deps @ ${{ matrix.python-version }}"
58+
if: ${{ steps.cached-pip-wheels.outputs.cache-hit != 'true' }}
59+
id: install-deps
60+
run: |
61+
poetry install --no-interaction
62+
63+
- name: Activate venv @ ${{ matrix.python-version }}
64+
run: |
65+
echo "$(poetry env info --path)/bin" >> $GITHUB_PATH
66+
67+
- name: "Run Pyright @ ${{ matrix.python-version }}"
68+
uses: jakebailey/pyright-action@v1
69+
with:
70+
warnings: false
71+
no-comments: ${{ matrix.python-version != '3.x' }}
72+
73+
- name: Lint
74+
if: ${{ always() && steps.install-deps.outcome == 'success' }}
75+
uses: github/super-linter/slim@v4
76+
env:
77+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
78+
DEFAULT_BRANCH: main
79+
VALIDATE_ALL_CODEBASE: false
80+
VALIDATE_PYTHON_BLACK: true
81+
VALIDATE_PYTHON_ISORT: true
82+
LINTER_RULES_PATH: /
83+
PYTHON_ISORT_CONFIG_FILE: pyproject.toml
84+
PYTHON_BLACK_CONFIG_FILE: pyproject.toml

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,4 @@ cython_debug/
160160
#.idea/
161161

162162
# Config
163-
config.toml
163+
config.toml

core/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@
2121
SOFTWARE.
2222
"""
2323
import logging
24+
from typing import TextIO
2425

2526
from .config import config
2627
from .logger import ColourFormatter
2728
from .utils import *
2829

2930

3031
# Setup root logging formatter...
31-
handler: logging.StreamHandler = logging.StreamHandler()
32+
handler: logging.StreamHandler[TextIO] = logging.StreamHandler()
3233
handler.setFormatter(ColourFormatter())
3334

3435
logger: logging.Logger = logging.getLogger()

core/logger.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Copyright (c) 2015-present Rapptz
55
66
Permission is hereby granted, free of charge, to any person obtaining a
7-
copy of this software and associated documentation files (the "Software"),
7+
copy of this software and associated documentation files (the 'Software'),
88
to deal in the Software without restriction, including without limitation
99
the rights to use, copy, modify, merge, publish, distribute, sublicense,
1010
and/or sell copies of the Software, and to permit persons to whom the
@@ -13,7 +13,7 @@
1313
The above copyright notice and this permission notice shall be included in
1414
all copies or substantial portions of the Software.
1515
16-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
1717
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1818
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1919
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
@@ -24,17 +24,16 @@
2424
import logging
2525

2626

27-
__all__ = ('ColourFormatter', )
27+
__all__ = ('ColourFormatter',)
2828

2929

3030
class ColourFormatter(logging.Formatter):
31-
3231
# ANSI codes are a bit weird to decipher if you're unfamiliar with them, so here's a refresher
3332
# It starts off with a format like \x1b[XXXm where XXX is a semicolon separated list of commands
3433
# The important ones here relate to colour.
3534
# 30-37 are black, red, green, yellow, blue, magenta, cyan and white in that order
3635
# 40-47 are the same except for the background
37-
# 90-97 are the same but "bright" foreground
36+
# 90-97 are the same but 'bright' foreground
3837
# 100-107 are the same as the bright ones but for the background.
3938
# 1 means bold, 2 means dim, 0 means reset, and 4 means underline.
4039

@@ -57,7 +56,7 @@ class ColourFormatter(logging.Formatter):
5756
for level, colour in LEVEL_COLOURS
5857
}
5958

60-
def format(self, record):
59+
def format(self, record: logging.LogRecord) -> str:
6160
formatter = self.FORMATS.get(record.levelno)
6261
if formatter is None:
6362
formatter = self.FORMATS[logging.DEBUG]

core/utils.py

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"""
2323
import asyncio
2424
import inspect
25-
from typing import Any, Awaitable, Callable, Iterator, Self
25+
from collections.abc import Callable, Coroutine, Iterator
26+
from typing import Any, Self, TypeAlias
2627

2728
from starlette.requests import Request
2829
from starlette.responses import Response
@@ -35,12 +36,13 @@
3536
'View',
3637
)
3738

39+
ResponseType: TypeAlias = Coroutine[Any, Any, Response]
3840

39-
class _Route:
4041

41-
def __init__(self, **kwargs) -> None:
42+
class _Route:
43+
def __init__(self, **kwargs: Any) -> None:
4244
self._path: str = kwargs['path']
43-
self._coro: Callable[[Any, Request], Awaitable[Response]] = kwargs['coro']
45+
self._coro: Callable[[Any, Request], ResponseType] = kwargs['coro']
4446
self._methods: list[str] = kwargs['methods']
4547
self._prefix: bool = kwargs['prefix']
4648

@@ -53,31 +55,29 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
5355
await response(scope, receive, send)
5456

5557

56-
def route(path: str, /, *, methods: list[str] | None = None, prefix: bool = True) -> Callable[..., _Route]:
58+
def route(path: str, /, *, methods: list[str] = ['GET'], prefix: bool = True) -> Callable[..., _Route]:
5759
"""Decorator which allows a coroutine to be turned into a `starlette.routing.Route` inside a `core.View`.
5860
5961
Parameters
6062
----------
6163
path: str
6264
The path to this route. By default, the path is prefixed with the View class name.
6365
methods: list[str]
64-
The allowed methods for this route. If this is None, this route will default to 'GET'. Defaults to None.
66+
The allowed methods for this route. Defaults to ``['GET']``.
6567
prefix: bool
6668
Whether the route path should be prefixed with the View class name. Defaults to True.
6769
"""
6870

69-
if methods is None:
70-
methods = ['GET']
71-
72-
def decorator(coro: Callable[[Any, Request], Awaitable[Response]]) -> _Route:
71+
def decorator(coro: Callable[[Any, Request], ResponseType]) -> _Route:
7372
if not asyncio.iscoroutinefunction(coro):
74-
raise RuntimeError('Route must be a coroutine function.')
73+
raise RuntimeError('Route callback must be a coroutine function.')
7574

76-
disallowed: list[str] = ["get", "post", "put", "patch", "delete", "options"]
75+
disallowed: list[str] = ['get', 'post', 'put', 'patch', 'delete', 'options']
7776
if coro.__name__.lower() in disallowed:
78-
raise ValueError(f'Route coroutine must not be named any: {", ".join(disallowed)}')
77+
raise ValueError(f'Route callback function must not be named any: {", ".join(disallowed)}')
7978

8079
return _Route(path=path, coro=coro, methods=methods, prefix=prefix)
80+
8181
return decorator
8282

8383

@@ -106,9 +106,9 @@ async def hello_endpoint(self, request: Request) -> Response:
106106

107107
__routes__: list[Route]
108108

109-
def __new__(cls, *args, **kwargs) -> Self:
110-
self: Self = super().__new__(cls)
111-
name: str = cls.__name__
109+
def __new__(cls, *args: Any, **kwargs: Any) -> Self:
110+
self = super().__new__(cls)
111+
name = cls.__name__
112112

113113
self.__routes__ = []
114114

@@ -125,12 +125,9 @@ def __new__(cls, *args, **kwargs) -> Self:
125125
# Due to the way Starlette works, this allows us to have schema documentation...
126126
setattr(member, method, member._coro)
127127

128-
self.__routes__.append(Route(
129-
path=path,
130-
endpoint=member,
131-
methods=member._methods,
132-
name=f'{name}.{member._coro.__name__}'
133-
))
128+
self.__routes__.append(
129+
Route(path=path, endpoint=member, methods=member._methods, name=f'{name}.{member._coro.__name__}')
130+
)
134131

135132
return self
136133

@@ -143,5 +140,5 @@ def __getitem__(self, index: int) -> Route:
143140
def __len__(self) -> int:
144141
return len(self.__routes__)
145142

146-
def __iter__(self) -> Iterator:
143+
def __iter__(self) -> Iterator[Route]:
147144
return iter(self.__routes__)

0 commit comments

Comments
 (0)