Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
264fdb3
rewrite: replace snake case keys with camel case
aditeyabaral May 22, 2026
0a1628b
add: tests for new behavior
aditeyabaral May 22, 2026
4eba6d7
chore: upgrade target versions to python 3.14
aditeyabaral May 22, 2026
83a387d
update: tests
aditeyabaral May 22, 2026
dcbdfe0
chore: bump version
aditeyabaral May 22, 2026
cfe81f1
feat: read app version from pyproject.toml
aditeyabaral May 22, 2026
47b69f6
fix: Dockerfile
aditeyabaral May 22, 2026
e71903d
chore: update uv.lock with Python 3.14 resolution markers
aditeyabaral May 22, 2026
c53cea4
chore: migrate Dockerfile to uv multi-stage build
aditeyabaral May 22, 2026
d828b1a
chore: align build versions to Python 3.12 and add kycas to benchmark…
aditeyabaral May 22, 2026
c2d3cb1
chore: restore Python version matrix for lint and pre-commit CI
aditeyabaral May 23, 2026
2c092fd
chore: replace pytz with stdlib zoneinfo
aditeyabaral May 23, 2026
482ca2d
chore: upgrade all dependencies to latest versions
aditeyabaral May 23, 2026
f20659d
chore: bump minimum version bounds in pyproject.toml to current latest
aditeyabaral May 23, 2026
fa36704
chore: migrate dev deps from optional-dependencies to dependency-groups
aditeyabaral May 23, 2026
f77f84f
fix: run pre-commit via uv run to use venv PATH
aditeyabaral May 23, 2026
92279a2
fix: install all dependency groups in pre-commit CI
aditeyabaral May 23, 2026
d3ef90f
docs: update .github docs for Python 3.12, uv dependency-groups, and …
aditeyabaral May 23, 2026
141b74d
chore: bump Python target to 3.14 and fix stale version references
aditeyabaral May 24, 2026
02ecc26
chore: minor string update
aditeyabaral May 24, 2026
7b10344
chore: switch Docker base images to python3.14-alpine
aditeyabaral May 24, 2026
720166e
fix: resolve ruff TC import-in-type-checking-block errors
aditeyabaral May 24, 2026
d12a6e8
refactor: make ProfileField Literal the source of truth for allowed f…
aditeyabaral May 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ projects.

### Prerequisites

- Python 3.11 or higher
- Python 3.12 or higher
- Git
- Docker

Expand All @@ -83,14 +83,14 @@ projects.
1. **Create and activate a virtual environment:**

```bash
uv venv --python 3.11
uv venv --python 3.12
source .venv/bin/activate
```

1. **Install dependencies:**

```bash
uv sync --all-extras
uv sync --all-groups
```

### Set Up Environment Variables
Expand Down Expand Up @@ -130,9 +130,8 @@ suite automatically before every commit.
The following checks are enforced:

- ✅ `ruff` for linting and formatting (with auto-fix)
- ✅ `blacken-docs` to format code blocks inside Markdown files
- ✅ `pyupgrade` to upgrade syntax to Python 3.9+
- ✅ `end-of-file-fixer`, `trailing-whitespace`, `check-yaml`, `check-toml`, `requirements-txt-fixer` for formatting
- ✅ `mdformat` to format Markdown files (with GFM support)
- ✅ `end-of-file-fixer`, `trailing-whitespace`, `check-yaml`, `check-toml`, `requirements-txt-fixer`, `check-added-large-files` for formatting
- ✅ `name-tests-test` to enforce test naming conventions
- ✅ `debug-statements` to prevent committed `print()` or `pdb`
- ✅ A local `pytest` hook that runs the full test suite
Expand Down Expand Up @@ -259,7 +258,7 @@ To keep the codebase clean and maintainable, please follow these conventions:
- Write clean, readable code
- Use meaningful variable and function names
- Avoid large functions; keep logic modular and composable
- Use Python 3.11+ syntax when appropriate (e.g., `match`, `|` union types)
- Use Python 3.12+ syntax when appropriate (e.g., `match`, `|` union types)
- Keep imports sorted and remove unused ones (handled automatically via `ruff`)

### 📝 Docstrings & Comments
Expand Down
4 changes: 2 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ Please provide a concise summary of the changes:

### 📊 Benchmarks & Analysis

- [ ] `scripts/benchmark_auth.py` – Performance or latency measurement changes
- [ ] `scripts/analyze_benchmark.py` – Benchmark result analysis changes
- [ ] `scripts/benchmark/benchmark_requests.py` – Performance or latency measurement changes
- [ ] `scripts/benchmark/analyze_benchmark.py` – Benchmark result analysis changes
- [ ] `scripts/run_tests.py` – Custom test runner logic or behavior updates

## 📸 Screenshots / API Demos (if applicable)
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
max-parallel: 5
matrix:
python-version: [ "3.11", "3.12", "3.13" ]
python-version: [ "3.12", "3.13", "3.14" ]

steps:
- uses: actions/checkout@v4
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/pre-commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
max-parallel: 1
matrix:
python-version: [ "3.11", "3.12", "3.13" ]
python-version: [ "3.12", "3.13", "3.14" ]

env:
TEST_NAME: ${{ secrets.TEST_NAME }}
Expand All @@ -34,8 +34,8 @@ jobs:

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .[dev]
pip install uv
uv sync --all-groups

- name: Run pre-commit hooks
run: pre-commit run --all-files
run: uv run pre-commit run --all-files
21 changes: 17 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder

WORKDIR /pesu-auth

ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy

COPY pyproject.toml uv.lock README.md ./
RUN uv sync --no-dev --frozen --no-install-project

COPY app ./app
RUN uv sync --no-dev --frozen

FROM python:3.12-slim-bookworm

COPY app /app
COPY README.md /README.md
COPY requirements.txt /requirements.txt
WORKDIR /pesu-auth

COPY --from=builder /pesu-auth/.venv /pesu-auth/.venv
COPY app ./app

RUN pip install -r requirements.txt
ENV PATH="/pesu-auth/.venv/bin:$PATH"
Comment thread
aditeyabaral marked this conversation as resolved.

CMD ["python", "-m", "app.app"]
86 changes: 43 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,61 +111,61 @@ object, with the user's profile information if requested.

#### Request Parameters

| **Parameter** | **Optional** | **Type** | **Default** | **Description** |
| ----------------------------- | ------------ | ----------- | ----------- | ----------------------------------------------------------------------------------------------- |
| `username` | No | `str` | | The user's SRN or PRN |
| `password` | No | `str` | | The user's password |
| `profile` | Yes | `boolean` | `False` | Whether to fetch profile information |
| `know_your_class_and_section` | Yes | `boolean` | `False` | Whether to fetch data from PESU's "Know Your Class and Section" information |
| `fields` | Yes | `list[str]` | `None` | Which fields to fetch from the profile information. If not provided, all fields will be fetched |
| **Parameter** | **Optional** | **Type** | **Default** | **Description** |
| ------------------------- | ------------ | ----------- | ----------- | ----------------------------------------------------------------------------------------------- |
| `username` | No | `str` | | The user's SRN or PRN |
| `password` | No | `str` | | The user's password |
| `profile` | Yes | `boolean` | `False` | Whether to fetch profile information |
| `knowYourClassAndSection` | Yes | `boolean` | `False` | Whether to fetch data from PESU's "Know Your Class and Section" information |
| `fields` | Yes | `list[str]` | `None` | Which fields to fetch from the profile information. If not provided, all fields will be fetched |

#### Response Object

On authentication, it returns the following parameters in a JSON object. If the authentication was successful and
profile data was requested, the response's `profile` key will store a dictionary with a user's profile information.
**On an unsuccessful sign-in, this field will not exist**.

| **Field** | **Type** | **Description** |
| ----------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------- |
| `status` | `boolean` | A flag indicating whether the overall request was successful |
| `profile` | `ProfileObject` | A nested map storing the profile information, returned only if requested |
| `know_your_class_and_section` | `KnowYourClassAndSectionObject` | A nested map storing the profile information from PESU's "Know Your Class and Section" endpoint |
| `message` | `str` | A message that provides information corresponding to the status |
| `timestamp` | `datetime` | A timezone offset timestamp indicating the time of authentication |
| **Field** | **Type** | **Description** |
| ------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------- |
| `status` | `boolean` | A flag indicating whether the overall request was successful |
| `profile` | `ProfileObject` | A nested map storing the profile information, returned only if requested |
| `knowYourClassAndSection` | `KnowYourClassAndSectionObject` | A nested map storing the profile information from PESU's "Know Your Class and Section" endpoint |
| `message` | `str` | A message that provides information corresponding to the status |
| `timestamp` | `datetime` | A timezone offset timestamp indicating the time of authentication |

##### `ProfileObject`

This object contains the user's profile information, which is returned only if the `profile` parameter is set to `True`.
If the authentication fails, this field will not be present in the response.

| **Field** | **Description** |
| ------------- | ------------------------------------------------------ |
| `name` | Name of the user |
| `prn` | PRN of the user |
| `srn` | SRN of the user |
| `program` | Academic program that the user is enrolled into |
| `branch` | Complete name of the branch that the user is pursuing |
| `semester` | Current semester that the user is in |
| `section` | Section of the user |
| `email` | Email address of the user registered with PESU |
| `phone` | Phone number of the user registered with PESU |
| `campus_code` | The integer code of the campus (1 for RR and 2 for EC) |
| `campus` | Abbreviation of the user's campus name |
| **Field** | **Description** |
| ------------ | ------------------------------------------------------ |
| `name` | Name of the user |
| `prn` | PRN of the user |
| `srn` | SRN of the user |
| `program` | Academic program that the user is enrolled into |
| `branch` | Complete name of the branch that the user is pursuing |
| `semester` | Current semester that the user is in |
| `section` | Section of the user |
| `email` | Email address of the user registered with PESU |
| `phone` | Phone number of the user registered with PESU |
| `campusCode` | The integer code of the campus (1 for RR and 2 for EC) |
| `campus` | Abbreviation of the user's campus name |

#### `KnowYourClassAndSectionObject`

| **Field** | **Description** |
| ---------------- | ------------------------------------------------------------------------ |
| `prn` | PRN of the user |
| `srn` | SRN of the user |
| `name` | Name of the user |
| `semester` | Current semester that the user is in |
| `section` | Section of the user |
| `cycle` | Physics Cycle or Chemistry Cycle, if the user is in first year |
| `department` | Abbreviation of the branch along with the campus the user is studying in |
| `branch` | Abbreviation of the branch that the user is pursuing |
| `institute_name` | The name of the campus that the user is studying in |
| `error` | The error name and stack trace, if an error occurs |
| **Field** | **Description** |
| --------------- | ------------------------------------------------------------------------ |
| `prn` | PRN of the user |
| `srn` | SRN of the user |
| `name` | Name of the user |
| `semester` | Current semester that the user is in |
| `section` | Section of the user |
| `cycle` | Physics Cycle or Chemistry Cycle, if the user is in first year |
| `department` | Abbreviation of the branch along with the campus the user is studying in |
| `branch` | Abbreviation of the branch that the user is pursuing |
| `instituteName` | The name of the campus that the user is studying in |
| `error` | The error name and stack trace, if an error occurs |

### `/health`

Expand Down Expand Up @@ -199,7 +199,7 @@ data = {
"username": "your SRN or PRN here",
"password": "your password here",
"profile": True, # Optional, defaults to False
'know_your_class_and_section': True, # Optional, defaults to False
'knowYourClassAndSection': True, # Optional, defaults to False
}

response = requests.post("http://localhost:5000/authenticate", json=data)
Expand All @@ -221,11 +221,11 @@ print(response.json())
"section": "NA",
"email": "johnnyblaze@gmail.com",
"phone": "1234567890",
"campus_code": 1,
"campusCode": 1,
"campus": "RR"
},
"message": "Login successful.",
"know_your_class_and_section": {
"knowYourClassAndSection": {
"prn": "PES1201800001",
"srn": "PES1201800001",
"name": "JOHNNY BLAZE",
Expand All @@ -234,7 +234,7 @@ print(response.json())
"cycle": "NA",
"department": "CSE(EC Campus)",
"branch": "CSE",
"institute_name": "PES University (Electronic City)"
"instituteName": "PES University (Electronic City)"
},
"timestamp": "2024-07-28 22:30:10.103368+05:30"
}
Expand Down
9 changes: 5 additions & 4 deletions app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
import logging
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from importlib.metadata import version
from zoneinfo import ZoneInfo

import pytz
import uvicorn
from fastapi import BackgroundTasks, FastAPI
from fastapi.exceptions import RequestValidationError
Expand All @@ -20,7 +21,7 @@
from app.models import RequestModel, ResponseModel
from app.pesu import PESUAcademy

IST = pytz.timezone("Asia/Kolkata")
IST = ZoneInfo("Asia/Kolkata")
CSRF_TOKEN_REFRESH_INTERVAL_SECONDS = 45 * 60
CSRF_TOKEN_REFRESH_LOCK = asyncio.Lock()

Expand Down Expand Up @@ -76,7 +77,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
app = FastAPI(
title="PESUAuth API",
description="A simple and lightweight API to authenticate PESU credentials using PESU Academy",
version="2.1.0",
version=version("pesu-auth"),
Comment thread
achyu-dev marked this conversation as resolved.
docs_url="/",
Comment thread
aditeyabaral marked this conversation as resolved.
lifespan=lifespan,
openapi_tags=[
Expand Down Expand Up @@ -216,7 +217,7 @@ async def authenticate(payload: RequestModel, background_tasks: BackgroundTasks)
try:
authentication_result = ResponseModel.model_validate(authentication_result)
logging.info(f"Returning auth result for user={username}: {authentication_result}")
authentication_result = authentication_result.model_dump(exclude_none=True)
authentication_result = authentication_result.model_dump(by_alias=True, exclude_none=True)
authentication_result["timestamp"] = current_time.isoformat()
return JSONResponse(
status_code=200,
Expand Down
10 changes: 5 additions & 5 deletions app/docs/authenticate.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"username": "PES1201800001",
"password": "mySecurePassword123",
"profile": True,
"know_your_class_and_section": True,
"knowYourClassAndSection": True,
},
},
"phone_auth_selective_fields": {
Expand Down Expand Up @@ -79,7 +79,7 @@
"section": "C",
"email": "johndoe@gmail.com",
"phone": "1234567890",
"campus_code": 1,
"campusCode": 1,
"campus": "RR",
},
},
Expand All @@ -100,10 +100,10 @@
"section": "C",
"email": "johndoe@gmail.com",
"phone": "1234567890",
"campus_code": 1,
"campusCode": 1,
"campus": "RR",
},
"know_your_class_and_section": {
"knowYourClassAndSection": {
Comment thread
achyu-dev marked this conversation as resolved.
"prn": "PESXXYYZZZZZ",
"srn": "PESXXUGYYZZZ",
"name": "John Doe",
Expand All @@ -112,7 +112,7 @@
"cycle": "NA",
"department": "Computer Science and Engineering",
"branch": "CSE",
"institute_name": "PES University",
"instituteName": "PES University",
},
},
},
Expand Down
3 changes: 2 additions & 1 deletion app/models/kycas.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""Model representing the "Know Your Class and Section" data returned after successful authentication."""

from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel


class KYCASModel(BaseModel):
"""Model representing the "Know Your Class and Section" data."""

model_config = ConfigDict(strict=True)
model_config = ConfigDict(strict=True, alias_generator=to_camel, populate_by_name=True)

prn: str | None = Field(
None,
Expand Down
3 changes: 2 additions & 1 deletion app/models/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel


class ProfileModel(BaseModel):
"""Model representing the user's profile data returned after successful authentication."""

model_config = ConfigDict(strict=True)
model_config = ConfigDict(strict=True, alias_generator=to_camel, populate_by_name=True)

name: str | None = Field(
None,
Expand Down
3 changes: 2 additions & 1 deletion app/models/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, field_validator
from pydantic.alias_generators import to_camel

from app.pesu import PESUAcademy


class RequestModel(BaseModel):
"""Model representing the student's authentication request."""

model_config = ConfigDict(strict=True)
model_config = ConfigDict(strict=True, alias_generator=to_camel, extra="forbid")

username: str = Field(
...,
Expand Down
3 changes: 2 additions & 1 deletion app/models/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
from datetime import datetime

from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel

from app.models import KYCASModel, ProfileModel


class ResponseModel(BaseModel):
"""Model representing the response after a student's authentication request."""

model_config = ConfigDict(strict=True)
model_config = ConfigDict(strict=True, alias_generator=to_camel, populate_by_name=True)

status: bool = Field(
...,
Expand Down
Loading
Loading