Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Show when PESU Academy last responded in /health

Aperta
#170 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
62/100
Tipo di issue
Funzionalità
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
fastapi, python

Direzione di ricerca

After #168 merges, trace the authentication flow through app/app.py and app/pesu.py, including _upstream_call and its tests in tests/unit/test_metrics_instrumentation.py. Add the upstream timestamps to the health model, route, docs, and README, then run the focused tests and uv run python scripts/run_tests.py. Done means all listed status cases pass, /metrics is unchanged, and /health shows IST timestamps or null.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

enhancement good first issue pesuacademy

🔄 Updated for v5.0.0 (2026-10-08). Rewritten for 5.0.0. pesu-auth now signs in through PESU Academy's mobile API (#241): a login is at most two upstream calls (login, then profile_fetch), with no CSRF token, no background refresh and no redirects followed. So nothing calls PESU at startup any more, and both timestamps start as null until the first login. The walkthrough, the success/error rules and the tests are updated to match. The goal and the rules for what /health may show are unchanged.

🌱 This is a beginner-friendly issue, and a step up from #168. It isn't the easiest issue in the repo, and that's deliberate. Every contributor to pesu-auth should understand how a login actually flows through the code: how the login request reaches PESU Academy's mobile API, how the profile is fetched with the token it returns, and how each of those calls is timed and recorded. The best way to learn that is to change it. This issue walks you through the real authentication pipeline one step at a time. Ask questions in the comments at any point.

⚠️ Who can take this issue

  • PES University students only. This change sits in the login path and has to be tested end to end against PESU Academy with real PESU credentials, which only PES University students have.
  • Wait until you're assigned. Comment to ask for the issue, then wait for a maintainer to assign it to you before writing any code. PRs for unassigned issues are closed without review.
  • Do #168 first. This issue extends the HealthModel that #168 creates, so it can't start until #168 has merged.

The problem

pesu-auth has one dependency: PESU Academy. When a login fails, the first question is always "is pesu-auth broken, or is PESU Academy down?" Today nothing public can answer it:

  • /health only says that pesu-auth itself is running.
  • /metrics counts calls to PESU Academy, but those are totals since startup. They can't tell you when PESU last answered, and /metrics may need a token.

What you'll build

Add an upstream object to the /health response ("upstream" is the usual name for a service that we depend on):

"upstream": {
  "lastSuccessAt": "2024-07-28T22:29:51.004211+05:30",
  "lastErrorAt": "2024-07-28T21:02:13.772930+05:30"
}
Field What it means
lastSuccessAt The last time PESU Academy answered one of our requests normally. null if it never has yet.
lastErrorAt The last time PESU Academy couldn't be reached, or refused or failed one of our requests. null if that hasn't happened yet.

A reader compares the two. If lastErrorAt is recent and lastSuccessAt is old, PESU Academy is probably down.

Rules the solution must follow
  • Record, never probe. The timestamps are recorded as a side effect of calls pesu-auth already makes. /health must never call PESU Academy itself: it has to stay fast, and it mustn't fail just because PESU is slow.
  • Timestamps only. Never store usernames, error messages, status codes or exception text here. /health is public and unauthenticated.
  • It doesn't change health. status stays true and the HTTP code stays 200. If PESU Academy were down and /health returned an error, Render could restart pesu-auth, and restarting us doesn't fix PESU.
  • Timestamps, not a yes/no. During quiet hours there may be no logins for a while, so the values can be old, and after a restart both are null until the first login. A timestamp lets the reader see how old; a boolean would claim more than we know.

Step-by-step guide

Step 1: Follow a login through the code

Before changing anything, trace one /authenticate request from start to finish. Open these and read them in this order:

  1. app/app.py, the authenticate route. It validates the request and calls pesu_academy.authenticate(...).
  2. app/pesu.py, PESUAcademy.authenticate. It creates one httpx2.AsyncClient for this login only, and closes it in finally, however the login ends.
  3. _login. The username and password are POSTed to LOGIN_URL, PESU Academy's mobile login endpoint. A 401 means the credentials were wrong; any other non-200, or a response that doesn't say SUCCESS, is PESU failing (UpstreamError, a 502). A good response carries an access token.
  4. _fetch_profile, only if the caller asked for the profile. It POSTs PROFILE_FORM to DISPATCHER_URL with that access token. PESU can answer with an HTTP 200 that is really an error (its "error envelope", for example {"status": 403, "message": "Access Denied"} for a non-student account); that becomes ProfileFetchError.
  5. lifespan in app/app.py. Notice what isn't there: since 5.0.0, pesu-auth makes no call to PESU at startup or in the background.

Write down every place where pesu-auth talks to PESU Academy. You should find exactly two places in the code, and each is wrapped in async with _upstream_call(...). One place is always one HTTP request: the client doesn't follow redirects, so a redirect comes back as a 3xx response.

Step 2: Understand _upstream_call

_upstream_call, near the top of app/pesu.py, is the single gateway for every request to PESU Academy. Read it and its comments carefully. It:

  • times each call;
  • labels its outcome as success, error or cancelled;
  • records all of that as metrics, in a finally block, so it runs however the call ends.

Then read tests/unit/test_metrics_instrumentation.py, which tests it. Pay attention to test_a_failed_profile_status_is_still_a_successful_call: a 500 response from PESU is recorded with the outcome success. That's because _upstream_call treats "no exception was raised" as success, and the status code is checked later, by the caller. Keep this in mind for Step 3.

Step 3: Decide what counts as success and error

Every call through _upstream_call updates one of the timestamps, except cancelled calls (see the rules below). There are two kinds:

Operation When it happens
login Every /authenticate request that passes validation
profile_fetch Every /authenticate that asks for the profile, once the login has succeeded and returned an access token

For these fields, use these rules. They are not the same as the outcome label that already exists:

  • Success: PESU Academy sent a response with a status below 500, except 403 and 429.
    • A wrong password counts as success. PESU answered normally (a 401); the student just typed the wrong password. The fields answer "is PESU Academy reachable?", not "are logins working?".
    • So does an HTTP 200 carrying PESU's error envelope: PESU answered, it just declined that request.
  • Error: any of these:
    • an exception (a timeout or connection failure);
    • a response with a status of 500 or above;
    • a 403 Forbidden or 429 Too Many Requests HTTP status. PESU did answer, but it's refusing to serve us (blocked or rate limited), so for our purposes it's unreachable.
  • Ignored: cancelled. It means we stopped waiting (a client disconnected, or the app is shutting down), not that PESU failed. The existing comment in _upstream_call explains this.
Step 4: Record the timestamps
  1. Create a small class to hold the two timestamps, for example an UpstreamStatus with last_success_at and last_error_at (both starting as None) and a method to record each. PESUAcademy.__init__ creates it and keeps it in an attribute, next to self._metrics.
  2. Pass it to _upstream_call the same way metrics is passed, and update both call sites.
  3. Record inside _upstream_call, using the rules from Step 3. The finally block already knows the outcome and the response. Make sure a cancelled call records nothing.
  4. Don't store these in MetricsCollector. They aren't metrics, and /metrics must not change.

Two things to watch for:

  • Timezones. /health shows times in IST. Don't import IST from app/app.py into app/pesu.py: that would create a circular import. (app/pesu.py has its own IST, used for dates of birth; #230 plans to merge the two.) Store timezone-aware UTC times (datetime.now(UTC)), and convert them with .astimezone(IST) in the /health route.
  • Concurrency. Many requests run at the same time, but they all share one event loop, and assigning a single attribute can't be interrupted part-way through. So no lock is needed. Say so in a comment.
Step 5: Show it in /health
  • Add an UpstreamModel sub-model to app/models/health.py, following the style #168 used. Both fields are optional timestamps, and each field's description should say when it is null.
  • Read the values in the /health route in app/app.py, and expose them through a small public property or method on PESUAcademy, not by reaching into a private attribute.
  • Update the example in app/docs/health.py and the /health table in README.md.
Step 6: See it work

Start the app (uv run python -m app.app --debug) and open http://localhost:5000/health.

  1. Right after startup, both values are null: nothing has called PESU yet.

  2. Log in with a wrong password from Swagger at http://localhost:5000/. lastSuccessAt is set, because PESU answered.

  3. Log in with your real credentials, with profile: true. It moves forward again (twice: the login and the profile call).

  4. Turn off your internet and try to log in. This time lastErrorAt gets set. Swagger's page loads its scripts from the internet, so it won't work while you're offline. Send the request from a terminal instead:

    curl -X POST http://localhost:5000/authenticate \
      -H "Content-Type: application/json" \
      -d '{"username": "test", "password": "test"}'
    
Step 7: Write tests

Add your tests next to the existing ones in tests/unit/test_metrics_instrumentation.py, or in a new tests/unit/test_upstream_status.py, using the same pesu, upstream and make_response fixtures. Cover:

  • both values start as None;
  • a successful call sets lastSuccessAt only;
  • a 500 response sets lastErrorAt only;
  • a 403 and a 429 each set lastErrorAt only, while another 4xx (for example 404) sets lastSuccessAt;
  • an HTTP 200 error envelope on the profile call sets lastSuccessAt;
  • a timeout or connection error sets lastErrorAt only;
  • a cancelled call changes neither;
  • a wrong-password login sets lastSuccessAt;
  • both operations record;
  • /health returns the values in IST, and null when they're unset;
  • the existing /metrics tests still pass unchanged.

Then run the whole suite with your PESU credentials in .env (see "Tests that need credentials" in CONTRIBUTING.md):

uv run python scripts/run_tests.py

Coverage must stay at 100%, lines and branches, and the live tests must pass. Paste the summary line into your PR. A PR that hasn't run the live tests will not be reviewed.

Step 8: Open your PR
  • Bump the minor version in pyproject.toml, then run uv lock.
  • Run uv run pre-commit run --all-files.
  • Open the PR from your fork, against dev, with a Conventional Commit title such as feat: show when PESU Academy last responded in /health, and write Closes #170 in the description.

Done when

  • /health returns upstream.lastSuccessAt and upstream.lastErrorAt as described
  • They're recorded only in _upstream_call, following the rules in Step 3, for both operations
  • Nothing about a user or an error ends up in them
  • /metrics output and its tests are unchanged
  • Swagger and the README show the new object
  • Every case in Step 7 is tested, coverage is 100%, and the live tests pass
  • Pre-commit passes, the version is bumped, and the PR is from your fork against dev

Want to go deeper?

Run git submodule update --init, then read these in .agents/pesudev-skills/auth/docs/:

  • upstream.md: how pesu-auth talks to PESU Academy.
  • architecture.md: the client lifecycle.
  • security.md: what may and may not be exposed.

They are being updated for 5.0.0; where they still describe the CSRF flow, the code in app/pesu.py wins. The repo's checklist for changing this part of the code is .agents/skills/change-upstream-client/SKILL.md. Reading it before you open your PR is a good way to catch mistakes.

Lingua principale
Python
Stelle
22
Fork
29
Merge medio
22h 1m
PR unite (30g)
20

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di pesu-dev/auth

Tutte le issue di pesu-dev/auth

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.