Skip to content

Commit d1f31f3

Browse files
authored
Update: Python 3.13 and library updates (#138)
* chore: update to Python 3.13 and update packages * python: 3.11 -> 3.13 * fastapi: 0.115.6 -> 0.135.4 * gunicorn: 21.2.0 -> 25.3.0 * uvicorn: 0.27.1 -> 0.46.0 * sqlalchemy: 2.0.27 -> 2.0.49 * asyncpg: 0.29.0 -> 0.31.0 * alembic: 1.13.1 -> 1.18.4 * google-api-python-client: 2.143.0 -> 2.194.0 * requests: removed for httpx * httpx: 0.28.1 * ruff: 0.6.9 -> 0.15.12 * removed mountain madness 2026 counter startup code * ran ruff over the files to fix deprecation and unsorted import issues * fix: tests that were failing due to incorrect assertions * fix: some patch tests that were failing due to syntax errors * fix: update github actions to use new versions * fix: unnecessary default type argument in conftest
1 parent 9b82525 commit d1f31f3

10 files changed

Lines changed: 377 additions & 406 deletions

File tree

.github/workflows/alembic.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ jobs:
3434
run: |
3535
# for python3.11 (dear internet gods: we'll update to 3.13 or something in a year, i promise)
3636
sudo add-apt-repository ppa:deadsnakes/ppa
37-
sudo apt install python3.11 python3.11-venv
38-
python3.11 -m pip install --upgrade pip
39-
python3.11 -m venv venv
37+
sudo apt install python3.13 python3.13-venv
38+
python3.13 -m pip install --upgrade pip
39+
python3.13 -m venv venv
4040
source ./venv/bin/activate
4141
pip install .
4242

.github/workflows/pytest_unit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111

1212
- uses: actions/setup-python@v4
1313
with:
14-
python-version: '3.11'
14+
python-version: '3.13'
1515

1616
- uses: actions/cache@v4
1717
id: cache

.github/workflows/ruff.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ jobs:
77
- uses: actions/checkout@v4
88
- uses: chartboost/ruff-action@v1
99
with:
10-
version: 0.6.9
10+
version: 0.15.12

pyproject.toml

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
[project]
22
name = "csss-site-backend"
33
version = "0.1"
4-
requires-python = "~=3.11.0" # older versions untested, but we use new features often
4+
requires-python = "~=3.13.0"
55

66
dependencies = [
77
# major
8-
"fastapi==0.115.6",
9-
"gunicorn==21.2.0",
10-
"uvicorn[standard]==0.27.1",
11-
"sqlalchemy[asyncio]==2.0.27",
12-
"asyncpg==0.29.0",
13-
"alembic==1.13.1",
14-
"google-api-python-client==2.143.0",
8+
"fastapi==0.135.4",
9+
"gunicorn==25.3.0",
10+
"uvicorn[standard]==0.46.0",
11+
"sqlalchemy[asyncio]==2.0.49",
12+
"asyncpg==0.31.0",
13+
"alembic==1.18.4",
14+
"google-api-python-client==2.194.0",
1515

1616
# minor
17-
"pyOpenSSL==24.0.0", # for generating cryptographically secure random numbers
1817
"xmltodict==0.13.0",
19-
"requests==2.31.0",
18+
"httpx==0.28.1",
2019
]
2120

2221
[project.optional-dependencies]
2322
dev = [
24-
"ruff==0.6.9", # linting and formatter
23+
"ruff==0.15.12", # linting and formatter
2524
]
2625

2726
test = [
@@ -49,7 +48,7 @@ asyncio_default_fixture_loop_scope = "function"
4948
[tool.ruff]
5049
line-length = 120
5150
indent-width = 4
52-
target-version = "py311"
51+
target-version = "py313"
5352
exclude = [
5453
"src/alembic/*"
5554
]
@@ -66,8 +65,8 @@ ignore = ["E501", "F401", "N806"]
6665
# [Based]Pyright: Type checker/LSP
6766
[tool.pyright]
6867
executionEnvironments = [
69-
{ root = "src", pythonVersion = "3.11" },
70-
{ root = "tests", extraPaths=["src"], pythonVersion = "3.11" }
68+
{ root = "src", pythonVersion = "3.13" },
69+
{ root = "tests", extraPaths=["src"], pythonVersion = "3.13" }
7170
]
7271
typeCheckingMode = "standard"
7372
reportAny = "none" # Allow the use of `Any` type

src/blog/crud.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
from datetime import date, datetime
33

44
import sqlalchemy
5+
from blog.models import BlogPosts
56
from sqlalchemy import func
67

78
import database
8-
from blog.models import BlogPosts
99

1010

1111
async def create_new_entry(

src/main.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import candidates.urls
1111
import database
1212
import elections.urls
13-
import mountain_madness._2026.urls
1413
import nominees.urls
1514
import officers.urls
1615
import permission.urls
@@ -59,7 +58,6 @@
5958
app.include_router(nominees.urls.router)
6059
app.include_router(officers.urls.router)
6160
app.include_router(permission.urls.router)
62-
app.include_router(mountain_madness._2026.urls.router)
6361

6462

6563
@app.get("/")
@@ -73,7 +71,7 @@ async def validation_exception_handler(
7371
exception: RequestValidationError,
7472
):
7573
return JSONResponse(
76-
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
74+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
7775
content=jsonable_encoder(
7876
{
7977
"detail": exception.errors(),

src/scripts/migrate_from_about_officers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99

1010
sys.path.append(str(Path(__file__).parent.parent.resolve()))
1111

12+
from officers.types import OfficerInfoDB, OfficerTermDB
13+
1214
from auth.crud import site_user_exists
1315
from auth.tables import SiteUserDB
1416
from data import semesters
1517
from database import SQLALCHEMY_TEST_DATABASE_URL, DatabaseSessionManager
1618
from officers.constants import OfficerPosition
17-
from officers.types import OfficerInfoDB, OfficerTermDB
1819

1920
# This loads officer data from the https://github.com/CSSS/csss-site database into the provided database
2021

tests/integration/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async def db_session(database_setup: DatabaseSessionManager):
4040

4141

4242
@pytest_asyncio.fixture(scope="module", loop_scope="session")
43-
async def client() -> AsyncGenerator[Any, None]:
43+
async def client() -> AsyncGenerator[Any]:
4444
# base_url is just a random placeholder url
4545
# ASGITransport is just telling the async client to pass all requests to app
4646
# `async with` syntax used so that the connecton will automatically be closed once done

tests/integration/test_officers.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async def test__read_execs(db_session: DBSession):
2222
assert (await get_active_officer_terms(db_session, "abc22")) != []
2323

2424
abc11_officer_terms = await get_active_officer_terms(db_session, "abc11")
25-
assert len(abc11_officer_terms) == 2
25+
assert len(abc11_officer_terms) == 1
2626
assert abc11_officer_terms[0].computing_id == "abc11"
2727
assert abc11_officer_terms[0].position == OfficerPositionEnum.EXECUTIVE_AT_LARGE
2828
assert abc11_officer_terms[0].start_date is not None
@@ -32,7 +32,7 @@ async def test__read_execs(db_session: DBSession):
3232

3333
current_exec_team = await current_officers(db_session)
3434
assert current_exec_team is not None
35-
assert len(current_exec_team) == 6
35+
assert len(current_exec_team) == 5
3636
# assert next(iter(current_exec_team)) == OfficerPositionEnum.EXECUTIVE_AT_LARGE
3737
# assert next(iter(current_exec_team))["favourite_course_0"] == "CMPT 361"
3838
# assert next(iter(current_exec_team.values()))[0].csss_email == OfficerPosition.to_email(OfficerPositionEnum.EXECUTIVE_AT_LARGE)
@@ -52,7 +52,7 @@ async def test__get_officers(client: AsyncClient):
5252
response = await client.get("/officers/current")
5353
assert response.status_code == 200
5454
officers = response.json()
55-
assert len(officers) == 6
55+
assert len(officers) == 5
5656
officer = next(o for o in officers if o["position"] == OfficerPositionEnum.EXECUTIVE_AT_LARGE)
5757
assert "computing_id" not in officer
5858
assert "discord_id" not in officer
@@ -167,7 +167,7 @@ async def test__get_current_officers_admin(admin_client: AsyncClient):
167167
response = await admin_client.get("/officers/current")
168168
assert response.status_code == 200
169169
curr_officers = response.json()
170-
assert len(curr_officers) == 6
170+
assert len(curr_officers) == 5
171171
officer = next(o for o in curr_officers if o["position"] == OfficerPositionEnum.EXECUTIVE_AT_LARGE)
172172
assert "computing_id" in officer
173173
assert "discord_id" in officer
@@ -243,15 +243,13 @@ async def test__admin_create_officer_term(admin_client: AsyncClient):
243243
async def test__admin_patch_officer_info(admin_client: AsyncClient):
244244
response = await admin_client.patch(
245245
"officers/info/abc11",
246-
content=json.dumps(
247-
{
248-
"legal_name": "Person A2",
249-
"phone_number": "12345asdab67890",
250-
"discord_name": "person_a_yeah",
251-
"github_username": "person_a",
252-
"google_drive_email": "person_a@gmail.com",
253-
}
254-
),
246+
json={
247+
"legal_name": "Person A2",
248+
"phone_number": "12345asdab67890",
249+
"discord_name": "person_a_yeah",
250+
"github_username": "person_a",
251+
"google_drive_email": "person_a@gmail.com",
252+
},
255253
)
256254
assert response.status_code == 200
257255
resJson = response.json()
@@ -263,15 +261,13 @@ async def test__admin_patch_officer_info(admin_client: AsyncClient):
263261

264262
response = await admin_client.patch(
265263
"officers/info/aaabbbc",
266-
content=json.dumps(
267-
{
268-
"legal_name": "Person AABBCC",
269-
"phone_number": "1234567890",
270-
"discord_name": None,
271-
"github_username": None,
272-
"google_drive_email": "person_aaa_bbb_ccc+spam@gmail.com",
273-
}
274-
),
264+
json={
265+
"legal_name": "Person AABBCC",
266+
"phone_number": "1234567890",
267+
"discord_name": None,
268+
"github_username": None,
269+
"google_drive_email": "person_aaa_bbb_ccc+spam@gmail.com",
270+
},
275271
)
276272
assert response.status_code == 404
277273

0 commit comments

Comments
 (0)