Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.7
hooks:
- id: ruff
- id: ruff-check
args: [ --fix, --exit-non-zero-on-fix ]
types_or: [ python, pyi, jupyter ]
- id: ruff-format
Expand Down
23 changes: 18 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
FROM python:3.12-slim-bookworm
FROM ghcr.io/astral-sh/uv:python3.14-alpine AS builder

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

RUN pip install -r requirements.txt
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.14-alpine

WORKDIR /pesu-auth

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

ENV PATH="/pesu-auth/.venv/bin:$PATH"
Comment thread
aditeyabaral marked this conversation as resolved.

CMD ["python", "-m", "app.app"]
90 changes: 45 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ following commands to start the API.

### Running without Docker

If you don't have Docker installed, you can run the API natively. Ensure you have Python 3.11 or higher
If you don't have Docker installed, you can run the API natively. Ensure you have Python 3.12 or higher
installed on your system. We recommend using a package manager like [`uv`](https://docs.astral.sh/uv/) to manage
dependencies.

1. Create a virtual environment using and activate it. Then, install the dependencies using the following commands.

```bash
uv venv --python=3.11
uv venv --python=3.12
source .venv/bin/activate
uv sync
```
Expand Down 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
20 changes: 14 additions & 6 deletions app/app.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
"""FastAPI Entrypoint for PESUAuth API."""

from __future__ import annotations

import argparse
import asyncio
import datetime
import logging
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from importlib.metadata import version
from typing import TYPE_CHECKING
from zoneinfo import ZoneInfo

import pytz
import uvicorn
from fastapi import BackgroundTasks, FastAPI
from fastapi.exceptions import RequestValidationError
from fastapi.requests import Request
from fastapi.responses import JSONResponse, RedirectResponse

if TYPE_CHECKING:
from collections.abc import AsyncIterator

from fastapi.requests import Request

from pydantic import ValidationError

from app.docs import authenticate_docs, health_docs, readme_docs
from app.exceptions.base import PESUAcademyError
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 +84,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 +224,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
12 changes: 6 additions & 6 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,13 +79,13 @@
"section": "C",
"email": "johndoe@gmail.com",
"phone": "1234567890",
"campus_code": 1,
"campusCode": 1,
"campus": "RR",
},
},
},
"authentication_with_kycas": {
"summary": 'Authentication with "Know Your Class and Section endpoint"',
"summary": 'Authentication with "Know Your Class and Section" data"',
"value": {
"status": True,
"message": "Login successful.",
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
Loading
Loading