Skip to content

Commit db2ece6

Browse files
committed
Update deps; improve README
Signed-off-by: Will Killian <william.killian@outlook.com>
1 parent 21aabb2 commit db2ece6

12 files changed

Lines changed: 664 additions & 486 deletions

File tree

.github/workflows/linting.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@ on:
1111
jobs:
1212
lint:
1313
runs-on: ubuntu-latest
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
python-version: ["3.12", "3.13"]
1418
steps:
1519
- name: Checkout
1620
uses: actions/checkout@v6
1721

1822
- name: Set up Python
1923
uses: actions/setup-python@v6
2024
with:
21-
python-version: '3.12'
25+
python-version: ${{ matrix.python-version }}
2226

2327
- name: Install uv
2428
uses: astral-sh/setup-uv@v7
@@ -31,14 +35,18 @@ jobs:
3135

3236
test:
3337
runs-on: ubuntu-latest
38+
strategy:
39+
fail-fast: false
40+
matrix:
41+
python-version: ["3.12", "3.13"]
3442
steps:
3543
- name: Checkout
36-
uses: actions/checkout@v5
44+
uses: actions/checkout@v6
3745

3846
- name: Set up Python
3947
uses: actions/setup-python@v6
4048
with:
41-
python-version: '3.12'
49+
python-version: ${{ matrix.python-version }}
4250

4351
- name: Install uv
4452
uses: astral-sh/setup-uv@v7

AGENT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ skills/ # task playbooks (SKILL.md per subfolder) for assistants and
4545

4646
1. **`Course` naming**: `scheduler.config` uses `Course` as a **course-id string** type in JSON config. `scheduler.models` defines a **`Course` class** (credits, meetings, etc.). `CourseInstance.course` is the model; use **`.course.course_id`** for the config-style id. (See README “Note on naming”.)
4747
2. **Generated artifacts**: After changing **`server.py`** or API-facing models, refresh **`fern/openapi.json`**. After **`CombinedConfig`** / config models change, refresh **`fern/docs/assets/combined-config.schema.json`**. After public **docstrings** change, refresh **`fern/docs/pages/python/reference.mdx`** — see CONTRIBUTING.
48-
3. **Style**: **Ruff** is authoritative (`pyproject.toml`: line length **120**, `py312`). CONTRIBUTING’s “88 / Black” note is outdated relative to the repo config — follow **`pyproject.toml`**.
48+
3. **Style**: **Ruff** is authoritative (`pyproject.toml`: line length **120**, `py312`). CONTRIBUTING matches this; when in doubt follow **`pyproject.toml`**.
4949

5050
## Skills
5151

CONTRIBUTING.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,10 @@ source .venv/bin/activate # On macOS/Linux
7272
# OR
7373
.venv\Scripts\activate # On Windows
7474

75-
# Install dependencies
76-
pip install -r requirements.txt
75+
# Install the package (editable). There is no checked-in requirements.txt.
7776
pip install -e .
77+
# Dev tools (pytest, ruff, ty, …) are listed under [dependency-groups] dev in pyproject.toml;
78+
# install the ones you need with pip, or prefer uv sync.
7879
```
7980

8081
### 3. Install Development Dependencies
@@ -225,7 +226,7 @@ We follow [PEP 8](https://pep8.org/) with some modifications enforced by our lin
225226

226227
**Key Standards:**
227228
- Use 4 spaces for indentation (no tabs)
228-
- Maximum line length: 88 characters (enforced by Black)
229+
- Maximum line length: **120** characters (enforced by **Ruff** format; see `[tool.ruff]` in `pyproject.toml`)
229230
- Use descriptive variable and function names
230231
- Add type hints for all function parameters and return values
231232
- Use f-strings for string formatting (Python 3.6+)

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ curl -X POST "http://localhost:8000/schedules/{schedule_id}/next"
8989
curl -X GET "http://localhost:8000/schedules/{schedule_id}/count"
9090
```
9191

92+
### Server deployment and security
93+
94+
The REST API is convenient for local use and trusted integrations. For production or internet-facing deployments:
95+
96+
- **No built-in authentication** — use a reverse proxy, API gateway, or private network; do not expose the process directly without controls you trust.
97+
- **In-memory sessions** — active schedule sessions are lost on restart and are not shared across multiple server processes.
98+
- **CORS** — set the `CORS_ORIGINS` environment variable to a comma-separated list of allowed origins when browsers must send credentials. If unset, the server allows all origins without credentials (typical for local development).
99+
100+
See [SECURITY.md](SECURITY.md) for how to report vulnerabilities.
101+
92102
## Documentation
93103

94104
**Published docs (configuration, Python API, REST API, development):**

SECURITY.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Security
2+
3+
## Reporting a vulnerability
4+
5+
Please **do not** open a public GitHub issue for security-sensitive reports.
6+
7+
Instead, use **[GitHub Security Advisories](https://github.com/mucsci/scheduler/security/advisories/new)** for this repository (or the Security tab → *Report a vulnerability*). We will work with you to understand and address the issue before any public disclosure.
8+
9+
## REST server expectations
10+
11+
The `scheduler-server` process does not implement API keys or user authentication. Deploy it only on trusted networks, or place it behind infrastructure that provides authentication, rate limiting, and appropriate request size limits. Schedule sessions are stored in memory and are cleared when the process stops.
12+
13+
For non-sensitive questions about deployment, open a regular GitHub issue on this repository.

pyproject.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ dev = [
4242
"httpx~=0.28",
4343
"prek~=0.3",
4444
"pytest-cov~=6.0",
45-
"ruff~=0.12",
46-
"pytest~=8.0",
45+
"ruff~=0.15",
46+
"pytest~=8.0",
4747
"twine~=6.2",
4848
"ty~=0.0",
49-
"pydoc-markdown>=4.8.2",
49+
"pydoc-markdown~=4.8",
5050
]
5151

5252
[tool.uv]
@@ -88,6 +88,9 @@ line-ending = "auto"
8888

8989
[tool.ty.rules]
9090
possibly-unresolved-reference = "warn"
91+
# Z3 / Starlette stubs and intentional test assignments produce noise at default severity.
92+
redundant-cast = "ignore"
93+
unused-type-ignore-comment = "ignore"
9194

9295
[tool.pytest.ini_options]
9396
testpaths = ["tests"]

src/scheduler/config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,13 +454,14 @@ def validate(self):
454454
errors = []
455455

456456
# Check that all days in time_slot_config are valid
457-
valid_days = {"MON", "TUE", "WED", "THU", "FRI"}
457+
weekdays: tuple[Day, ...] = ("MON", "TUE", "WED", "THU", "FRI")
458+
valid_days = frozenset(weekdays)
458459
for day in self.times:
459460
if day not in valid_days:
460461
errors.append(f"Invalid day '{day}' in time slot configuration")
461462

462463
# Check that there are time blocks for each day
463-
for day in valid_days:
464+
for day in weekdays:
464465
if day not in self.times or not self.times[day]:
465466
errors.append(f"No time blocks defined for {day}")
466467

src/scheduler/scheduler.py

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from functools import cache
99
from typing import cast
1010

11-
import z3 # type: ignore
11+
import z3
1212
from bidict import frozenbidict
1313
from pydantic import BaseModel
1414

@@ -607,10 +607,7 @@ def _build_faculty_constraints(self, z3_data: _Z3SortsAndConstants) -> list[z3.B
607607
)
608608
)
609609
# ensure that each faculty is assigned <= unique course limit
610-
limit = cast(
611-
z3.BoolRef,
612-
self._simplify(z3.Sum([z3.If(tc, 1, 0) for tc in teaches_course]) <= unique_limit),
613-
)
610+
limit = self._simplify(z3.Sum([z3.If(tc, 1, 0) for tc in teaches_course]) <= unique_limit)
614611
faculty_constraints.append(limit)
615612

616613
# Track whether the faculty teaches on a given day
@@ -623,43 +620,27 @@ def _build_faculty_constraints(self, z3_data: _Z3SortsAndConstants) -> list[z3.B
623620
slot_matches = [course.time == slot_const for slot_const in slot_constants]
624621
if slot_matches:
625622
course_day_assignments.append(
626-
cast(
627-
z3.BoolRef,
628-
self._simplify(
629-
z3.And(
630-
course.faculty == faculty_constant,
631-
z3.Or(slot_matches),
632-
)
633-
),
623+
self._simplify(
624+
z3.And(
625+
course.faculty == faculty_constant,
626+
z3.Or(slot_matches),
627+
)
634628
)
635629
)
636630
if course_day_assignments:
637-
day_indicator_map[day] = cast(
638-
z3.BoolRef,
639-
self._simplify(z3.Or(course_day_assignments)),
640-
)
631+
day_indicator_map[day] = self._simplify(z3.Or(course_day_assignments))
641632
else:
642633
day_indicator_map[day] = z3.BoolVal(False, ctx=self._ctx)
643634

644635
# Maximum-day constraint
645636
day_sum_terms = [z3.If(indicator, 1, 0) for indicator in day_indicator_map.values()]
646637
day_sum = z3.Sum(day_sum_terms) if day_sum_terms else z3.IntVal(0, ctx=self._ctx)
647-
faculty_constraints.append(
648-
cast(
649-
z3.BoolRef,
650-
self._simplify(day_sum <= max_days),
651-
)
652-
)
638+
faculty_constraints.append(self._simplify(day_sum <= max_days))
653639

654640
# Mandatory-day constraints
655641
for mandatory_day in mandatory_days:
656642
indicator = day_indicator_map.get(mandatory_day, z3.BoolVal(False, ctx=self._ctx))
657-
faculty_constraints.append(
658-
cast(
659-
z3.BoolRef,
660-
self._simplify(indicator),
661-
)
662-
)
643+
faculty_constraints.append(self._simplify(indicator))
663644

664645
return faculty_constraints
665646

src/scheduler/server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from concurrent.futures import Future, ThreadPoolExecutor
66
from contextlib import asynccontextmanager
77
from dataclasses import dataclass, field
8+
from typing import Any, cast
89

910
import click
1011
from fastapi import BackgroundTasks, FastAPI, HTTPException
@@ -373,7 +374,7 @@ async def lifespan(app: FastAPI):
373374
_cors_credentials = False
374375

375376
app.add_middleware(
376-
CORSMiddleware,
377+
cast(Any, CORSMiddleware),
377378
allow_origins=_cors_origins,
378379
allow_credentials=_cors_credentials,
379380
allow_methods=["*"],

0 commit comments

Comments
 (0)