Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-43967: Add a realistic demo app with Astroplan domain #3

Merged
merged 15 commits into from
Apr 30, 2024

Conversation

jonathansick
Copy link
Member

@jonathansick jonathansick commented Apr 19, 2024

This sets up a more realistic application within the bootcamp app that's built around Astroplan and its Observer as the domain model (along with a JSON file of observatories as the data storage). This implements the full set of architectural layers we use in our real API apps:

  • Storage layer
  • Service layer
  • HTTP path operations and API models
  • A Factory pattern for creating services and storage clients
  • The RequestContext pattern for creating a factory and providing other dependencies to endpoints.

The endpoints we implement are:

  • GET /fastapi-bootcamp/astroplan/observers(?name={name}) to list observatories
  • GET /fastapi-bootcamp/astroplan/observers/{id} to get a specific observatory
  • POST /fastapi-bootcamp/astroplan/observers/{id}/observability to compute the observability of a target at a specific time.

Merge after #2, which provides the initial commits.

@jonathansick jonathansick force-pushed the tickets/DM-43967 branch 3 times, most recently from 11b89bf to cb95484 Compare April 23, 2024 21:52
@jonathansick jonathansick changed the base branch from tickets/DM-43939 to main April 23, 2024 22:00
@jonathansick jonathansick marked this pull request as ready for review April 23, 2024 22:00
@jonathansick jonathansick requested a review from rra April 26, 2024 20:43
Copy link
Member

@rra rra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks great. I love having a real astronomy example to show people! This was a ton of work; thank you so much. I only had a few minor comments and questions, none blocking.

It's pretty minor, but it might be worth importing the changes to the template to use requirements/tox.in so that people at the bootcamp will get the same layout as this app when they create a new one.

Comment on lines 54 to 59
def get_request_username(self) -> str | None:
"""Get the username who made the request.

Uses the X-Auth-Request-Username header passed by Gafaelfawr.
"""
return self.request.headers.get("X-Auth-Request-User")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually use auth_dependency provided by Safir instead, since it fails if there is no user. Maybe the problem there is that this makes it annoying to use the app locally where there's no Gafaelfawr? (It also requires passing in a user when running tests, but that's pretty easy to handle in a fixture.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must have copied this from somewhere. The auth_dependency would be a good idea for a real app. I do worry it'd make it harder to demo the API interactively locally if it fails if there are no Gafaelfawr headers.

src/fastapibootcamp/dependencies/requestcontext.py Outdated Show resolved Hide resolved
src/fastapibootcamp/domain/models.py Outdated Show resolved Hide resolved
src/fastapibootcamp/handlers/astroplan/endpoints.py Outdated Show resolved Hide resolved
Comment on lines 73 to 74
# Make any modifications to the response headers or status code.
context.response.headers["Location"] = response_data.self_url
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought Location was only defined for 201 and 3xx responses, but this seems to be returning 200. I think I'm missing something? Maybe it's just an example of something that you could do if you were returning a redirect?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to include a clever example of setting headers and other things on the response but I'll find a better place for that. Good point!

local_timezone: str = Field(
...,
description="Local timezone of the observation site.",
examples=["Chile/Continental"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor nit: Chile/Continental is a deprecated time zone identifier and not available on many systems (such as the laptop where I'm writing this). It predates the switch to Olson time zone identifiers, which are always of the form Continent/City where the city is the most populous city in the time zone. The current corresponding time zone identifier is America/Santiago.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I'm going by the Astropy data here. I'll see if I can update it and the examples as well.

Comment on lines +17 to +18
current IERS data. Requests that trigger an IERS download will take longer
and will block the app since Astropy downloads IERS data synchronously.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, another approach would be to write all the handlers and underlying methods that might trigger an IERS download as non-async functions and methods, which will tell FastAPI to run them in a thread pool and avoid blocking the app. I'm not sure if you want to get into that in this comment, though.

src/fastapibootcamp/storage/observerstore.py Outdated Show resolved Hide resolved
We'll use astroplan as the domain in this app example.

https://astroplan.readthedocs.io/en/stable/
This sets up a more realistic application within the bootcamp app that's
built around Astroplan and its Observer as the domain model. This
implements the full set of architectural layers we use in our real API
apps:

- Storage layer
- Service layer
- HTTP path operations and API models
- A Factory pattern for creating services and storage clients
- The RequestContext pattern for creating a factory and providing other
  dependencies to endpoints.
- Error handling for user request arguments

Note on vendoring the data for the sites store:

Because the Astropy astropy.coordinates.EarthLocation includes
duplicates of sites under aliases, we're including the original
site.json file in the package itself so we can use our own constructor
for the observer object. Potentially we could to an httpx request to the
Astropy data server at
http://www.astropy.org/astropy-data/coordinates/sites.json but vendoring
makes things easier for this demo.
This demonstrates an endpoint for listing all resources, including
querying/filtering with query parameters.
This endpoint exposes a bit more complexity. It shows how the "work" of
the application resides in the domain layer, and how the service and
HTTP layers can cleanly use that functionality.
This storage class provides control over the Astropy IERS data cache so
that we can pre-download the data on start up.
Ideally we'd use the auth_dependency from Safir here instead, but that
*requires* that the app run behind gafaelfawr, and can interfere with
running the app locally since every request needs to send the
X-Auth-Request-User header to avoid errors.

Since we're not using this feature in this app, we'll drop it but
perhaps mention this dependency elsewhere in the demo.
This allows us to pin the version of tox we use locally and in CI.
@jonathansick jonathansick merged commit c7a07a8 into main Apr 30, 2024
3 checks passed
@jonathansick jonathansick deleted the tickets/DM-43967 branch April 30, 2024 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants