Skip to content

Commit 73e55ac

Browse files
committed
Add type hints and mypy
1 parent c6af913 commit 73e55ac

File tree

15 files changed

+236
-158
lines changed

15 files changed

+236
-158
lines changed

.github/workflows/python-app.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,27 @@ jobs:
3232
- name: Lint with pylint
3333
run: |
3434
pylint packet
35+
36+
typecheck:
37+
runs-on: ubuntu-latest
38+
39+
strategy:
40+
matrix:
41+
python-version: [3.9]
42+
43+
steps:
44+
- name: Install ldap dependencies
45+
run: sudo apt-get update && sudo apt-get install libldap2-dev libsasl2-dev
46+
- uses: actions/checkout@v2
47+
- name: Set up Python ${{ matrix.python-version }}
48+
uses: actions/setup-python@v2
49+
with:
50+
python-version: ${{ matrix.python-version }}
51+
- name: Install dependencies
52+
run: |
53+
python -m pip install --upgrade pip
54+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
55+
- name: Typecheck with mypy
56+
run: |
57+
# Disabled error codes to discard errors from imports
58+
mypy --disable-error-code import --disable-error-code name-defined --disallow-untyped-defs --exclude routes packet

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,14 @@ All DB commands are from the `Flask-Migrate` library and are used to configure D
115115
docs [here](https://flask-migrate.readthedocs.io/en/latest/) for details.
116116

117117
## Code standards
118-
This project is configured to use Pylint. Commits will be pylinted by GitHub actions and if the score drops your build will
119-
fail blocking you from merging. To make your life easier just run it before making a PR.
118+
This project is configured to use Pylint and mypy. Commits will be pylinted and typechecked by GitHub actions and if the
119+
score drops your build will fail blocking you from merging. To make your life easier just run it before making a PR.
120120

121-
To run pylint use this command:
121+
To run pylint and mypy use these commands:
122122
```bash
123123
pylint packet/routes packet
124+
mypy --disable-error-code import --disable-error-code name-defined --disallow-untyped-defs --exclude routes packet
124125
```
125126

126127
All python files should have a top-level docstring explaining the contents of the file and complex functions should
127-
have docstrings explaining any non-obvious portions.
128+
have docstrings explaining any non-obvious portions. Functions should have type annotations.

packet/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
from .git import get_version
2323

24-
app = Flask(__name__)
24+
app: Flask = Flask(__name__)
2525
gzip = Gzip(app)
2626

2727
# Load default configuration and any environment variable overrides

packet/commands.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66

77
from secrets import token_hex
8-
from datetime import datetime, time
8+
from datetime import datetime, time, date
99
import csv
1010
import click
1111

@@ -15,7 +15,7 @@
1515

1616

1717
@app.cli.command('create-secret')
18-
def create_secret():
18+
def create_secret() -> None:
1919
"""
2020
Generates a securely random token. Useful for creating a value for use in the "SECRET_KEY" config setting.
2121
"""
@@ -28,13 +28,13 @@ def create_secret():
2828

2929

3030
class CSVFreshman:
31-
def __init__(self, row):
31+
def __init__(self, row: list[str]) -> None:
3232
self.name = row[0].strip()
3333
self.rit_username = row[3].strip()
3434
self.onfloor = row[1].strip() == 'TRUE'
3535

3636

37-
def parse_csv(freshmen_csv):
37+
def parse_csv(freshmen_csv: str) -> dict[str, CSVFreshman]:
3838
print('Parsing file...')
3939
try:
4040
with open(freshmen_csv, newline='') as freshmen_csv_file:
@@ -44,7 +44,7 @@ def parse_csv(freshmen_csv):
4444
raise e
4545

4646

47-
def input_date(prompt):
47+
def input_date(prompt: str) -> date:
4848
while True:
4949
try:
5050
date_str = input(prompt + ' (format: MM/DD/YYYY): ')
@@ -55,7 +55,7 @@ def input_date(prompt):
5555

5656
@app.cli.command('sync-freshmen')
5757
@click.argument('freshmen_csv')
58-
def sync_freshmen(freshmen_csv):
58+
def sync_freshmen(freshmen_csv: str) -> None:
5959
"""
6060
Updates the freshmen entries in the DB to match the given CSV.
6161
"""
@@ -68,7 +68,7 @@ def sync_freshmen(freshmen_csv):
6868

6969
@app.cli.command('create-packets')
7070
@click.argument('freshmen_csv')
71-
def create_packets(freshmen_csv):
71+
def create_packets(freshmen_csv: str) -> None:
7272
"""
7373
Creates a new packet season for each of the freshmen in the given CSV.
7474
"""
@@ -84,7 +84,7 @@ def create_packets(freshmen_csv):
8484

8585

8686
@app.cli.command('ldap-sync')
87-
def ldap_sync():
87+
def ldap_sync() -> None:
8888
"""
8989
Updates the upper and misc sigs in the DB to match ldap.
9090
"""
@@ -97,7 +97,7 @@ def ldap_sync():
9797
help='The file to write to. If no file provided, output is sent to stdout.')
9898
@click.option('--csv/--no-csv', 'use_csv', required=False, default=False, help='Format output as comma separated list.')
9999
@click.option('--date', 'date_str', required=False, default='', help='Packet end date in the format MM/DD/YYYY.')
100-
def fetch_results(file_path, use_csv, date_str):
100+
def fetch_results(file_path: str, use_csv: bool, date_str: str) -> None:
101101
"""
102102
Fetches and prints the results from a given packet season.
103103
"""
@@ -150,7 +150,7 @@ def fetch_results(file_path, use_csv, date_str):
150150

151151
@app.cli.command('extend-packet')
152152
@click.argument('packet_id')
153-
def extend_packet(packet_id):
153+
def extend_packet(packet_id: int) -> None:
154154
"""
155155
Extends the given packet by setting a new end date.
156156
"""
@@ -168,7 +168,7 @@ def extend_packet(packet_id):
168168
print('Packet successfully extended')
169169

170170

171-
def remove_sig(packet_id, username, is_member):
171+
def remove_sig(packet_id: int, username: str, is_member: bool) -> None:
172172
packet = Packet.by_id(packet_id)
173173

174174
if not packet.is_open():
@@ -200,7 +200,7 @@ def remove_sig(packet_id, username, is_member):
200200
@app.cli.command('remove-member-sig')
201201
@click.argument('packet_id')
202202
@click.argument('member')
203-
def remove_member_sig(packet_id, member):
203+
def remove_member_sig(packet_id: int, member: str) -> None:
204204
"""
205205
Removes the given member's signature from the given packet.
206206
:param member: The member's CSH username
@@ -211,7 +211,7 @@ def remove_member_sig(packet_id, member):
211211
@app.cli.command('remove-freshman-sig')
212212
@click.argument('packet_id')
213213
@click.argument('freshman')
214-
def remove_freshman_sig(packet_id, freshman):
214+
def remove_freshman_sig(packet_id: int, freshman: str) -> None:
215215
"""
216216
Removes the given freshman's signature from the given packet.
217217
:param freshman: The freshman's RIT username

packet/context_processors.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,23 @@
55
import urllib
66
from functools import lru_cache
77
from datetime import datetime
8+
from typing import Callable
89

9-
from packet.models import Freshman
10+
from packet.models import Freshman, UpperSignature
1011
from packet import app, ldap
1112

1213

1314
# pylint: disable=bare-except
1415
@lru_cache(maxsize=128)
15-
def get_csh_name(username):
16+
def get_csh_name(username: str) -> str:
1617
try:
1718
member = ldap.get_member(username)
1819
return member.cn + ' (' + member.uid + ')'
1920
except:
2021
return username
2122

2223

23-
def get_roles(sig):
24+
def get_roles(sig: UpperSignature) -> dict[str, str]:
2425
"""
2526
Converts a signature's role fields to a dict for ease of access.
2627
:return: A dictionary of role short names to role long names
@@ -45,7 +46,7 @@ def get_roles(sig):
4546

4647
# pylint: disable=bare-except
4748
@lru_cache(maxsize=256)
48-
def get_rit_name(username):
49+
def get_rit_name(username: str) -> str:
4950
try:
5051
freshman = Freshman.query.filter_by(rit_username=username).first()
5152
return freshman.name + ' (' + username + ')'
@@ -55,7 +56,7 @@ def get_rit_name(username):
5556

5657
# pylint: disable=bare-except
5758
@lru_cache(maxsize=256)
58-
def get_rit_image(username):
59+
def get_rit_image(username: str) -> str:
5960
if username:
6061
addresses = [username + '@rit.edu', username + '@g.rit.edu']
6162
for addr in addresses:
@@ -69,15 +70,15 @@ def get_rit_image(username):
6970
return 'https://www.gravatar.com/avatar/freshmen?d=mp&f=y'
7071

7172

72-
def log_time(label):
73+
def log_time(label: str) -> None:
7374
"""
7475
Used during debugging to log timestamps while rendering templates
7576
"""
7677
print(label, datetime.now())
7778

7879

7980
@app.context_processor
80-
def utility_processor():
81+
def utility_processor() -> dict[str, Callable]:
8182
return dict(
8283
get_csh_name=get_csh_name, get_rit_name=get_rit_name, get_rit_image=get_rit_image, log_time=log_time,
8384
get_roles=get_roles

packet/git.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
import subprocess
44

5-
def get_short_sha(commit_ish: str = 'HEAD'):
5+
def get_short_sha(commit_ish: str = 'HEAD') -> str:
66
"""
77
Get the short hash of a commit-ish
88
Returns '' if unfound
@@ -14,7 +14,7 @@ def get_short_sha(commit_ish: str = 'HEAD'):
1414
except subprocess.CalledProcessError:
1515
return ''
1616

17-
def get_tag(commit_ish: str = 'HEAD'):
17+
def get_tag(commit_ish: str = 'HEAD') -> str:
1818
"""
1919
Get the name of the tag at a given commit-ish
2020
Returns '' if untagged
@@ -26,7 +26,7 @@ def get_tag(commit_ish: str = 'HEAD'):
2626
except subprocess.CalledProcessError:
2727
return ''
2828

29-
def get_version(commit_ish: str = 'HEAD'):
29+
def get_version(commit_ish: str = 'HEAD') -> str:
3030
"""
3131
Get the version string of a commit-ish
3232

0 commit comments

Comments
 (0)