-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from lsst-sqre/tickets/DM-43967
DM-43967: Add a realistic demo app with Astroplan domain
- Loading branch information
Showing
32 changed files
with
2,726 additions
and
295 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ starlette | |
uvicorn[standard] | ||
|
||
# Other dependencies. | ||
astroplan | ||
pydantic | ||
pydantic-settings | ||
safir>=5 | ||
python-slugify |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# -*- conf -*- | ||
# | ||
# Editable tox dependencies | ||
# Add tox and its plugins here. These will be installed in the user's venv for | ||
# local development and by CI when running tox actions. | ||
# | ||
# After editing, update requirements/dev.txt by running: | ||
# make update-deps | ||
|
||
-c main.txt | ||
-c dev.txt | ||
|
||
tox | ||
tox-uv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# This file was autogenerated by uv via the following command: | ||
# uv pip compile --generate-hashes --output-file requirements/tox.txt requirements/tox.in | ||
cachetools==5.3.3 \ | ||
--hash=sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945 \ | ||
--hash=sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105 | ||
# via tox | ||
chardet==5.2.0 \ | ||
--hash=sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7 \ | ||
--hash=sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970 | ||
# via tox | ||
colorama==0.4.6 \ | ||
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ | ||
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 | ||
# via tox | ||
distlib==0.3.8 \ | ||
--hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ | ||
--hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 | ||
# via virtualenv | ||
filelock==3.14.0 \ | ||
--hash=sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f \ | ||
--hash=sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a | ||
# via | ||
# tox | ||
# virtualenv | ||
packaging==24.0 \ | ||
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ | ||
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 | ||
# via | ||
# pyproject-api | ||
# tox | ||
# tox-uv | ||
platformdirs==4.2.1 \ | ||
--hash=sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf \ | ||
--hash=sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1 | ||
# via | ||
# tox | ||
# virtualenv | ||
pluggy==1.5.0 \ | ||
--hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ | ||
--hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 | ||
# via tox | ||
pyproject-api==1.6.1 \ | ||
--hash=sha256:1817dc018adc0d1ff9ca1ed8c60e1623d5aaca40814b953af14a9cf9a5cae538 \ | ||
--hash=sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675 | ||
# via tox | ||
tox==4.15.0 \ | ||
--hash=sha256:300055f335d855b2ab1b12c5802de7f62a36d4fd53f30bd2835f6a201dda46ea \ | ||
--hash=sha256:7a0beeef166fbe566f54f795b4906c31b428eddafc0102ac00d20998dd1933f6 | ||
# via tox-uv | ||
tox-uv==1.8.0 \ | ||
--hash=sha256:35347683c52793c2827ba30f6254fbea774bd1beddc9136fb1203ff093bfdddf \ | ||
--hash=sha256:e36149f8721fe11668cad14b78d61d8fdedf6f6d2c56d2e00447b96a475b671b | ||
uv==0.1.39 \ | ||
--hash=sha256:2333dd52e6734e0da6722bdd7b7257d0f8beeac89623c5cfc3888b4c56bc812e \ | ||
--hash=sha256:2ae930189742536f8178617c4ec05cb10271cb3886f6039abd36ee6ab511b160 \ | ||
--hash=sha256:2bda6686a9bb1370d7f53436d34f8ede0fa1b9877b5e152aedd9b22fc3cb33a9 \ | ||
--hash=sha256:3330bd7ab8a6160d815fdc36f48479edf6db8b58d39d20959555095ea7eb63c5 \ | ||
--hash=sha256:3365e0631a738a482d2379e565a230b135f7c5665394313829ccabf7c76c1362 \ | ||
--hash=sha256:388018659e5d73fdeb8ce13c1d812391ec981bf446ab86fb9c0e3d227f727da2 \ | ||
--hash=sha256:4c6ee1148f23aa5d6edf1a1106cc33c4aa57bdbfe8d4c5068c672105415d3b99 \ | ||
--hash=sha256:6b2acc907f7a1735dd9ffeb20d8c7aeeb86b1e5ba0a999e09433ad7f2789dc78 \ | ||
--hash=sha256:7848d703201e6867ae2c70d611e6ffd53d5e5adfc2c9abe89b6d021975e43e81 \ | ||
--hash=sha256:7ee426e0c5fa048cc44f3ac78e476121ef4365bb8bc9199d3cbffc372a80e55d \ | ||
--hash=sha256:88f5601ee957f9be2efc7a24d186f9d2641053806e107e0e42c5e522882c89e0 \ | ||
--hash=sha256:93217578e68a431df235173e390ad7df090499367cd7f5c811520fd4ea3d5047 \ | ||
--hash=sha256:c131dba5fe5079d9c5f06846649e35662901a9afd9b31de17714c63e042d91d2 \ | ||
--hash=sha256:c20b9023dac12ee518de79c91df313be7abb052440cb78f8ffb20dea81d3289e \ | ||
--hash=sha256:cd6d9629ab0e22ab2336b8d6363573ea5a7060ef82ff5d3e6da4b1b30522ef13 \ | ||
--hash=sha256:ce911087f56edc97a5792c17f682ed7611fedead0ea117f56bb6f3942eb3e7b3 \ | ||
--hash=sha256:fba96b3049aea5c1394cd360e5900e4af39829df48ed6fc55eba115c00c8195a | ||
# via tox-uv | ||
virtualenv==20.26.1 \ | ||
--hash=sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b \ | ||
--hash=sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75 | ||
# via tox |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
"""Request context FastAPI dependency.""" | ||
|
||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass | ||
from typing import Annotated, Any | ||
|
||
from fastapi import Depends, Request, Response | ||
from httpx import AsyncClient | ||
from safir.dependencies.http_client import http_client_dependency | ||
from safir.dependencies.logger import logger_dependency | ||
from structlog.stdlib import BoundLogger | ||
|
||
from ..factory import Factory | ||
|
||
# This RequestContext dataclass is what will be provided by the | ||
# context_dependency FastAPI dependency. This is a useful pattern that wraps | ||
# up multiple FastAPI dependencies into one container, and is a useful | ||
# place to put methods that work on those dependencies (e.g., like | ||
# binding context to the logger. | ||
# | ||
# The RequestContext would hold the sqlalchemy Session for apps that use | ||
# databases, or a Redis client, etc. | ||
|
||
|
||
@dataclass | ||
class RequestContext: | ||
"""Holds the incoming request and its surrounding context. | ||
The primary reason for the existence of this class is to allow the | ||
functions involved in request processing to repeatedly rebind the request | ||
logger to include more information, without having to pass both the | ||
request and the logger separately to every function. | ||
""" | ||
|
||
request: Request | ||
"""The incoming request.""" | ||
|
||
response: Response | ||
"""The response to be returned. | ||
The response can be modified to include additional headers or status codes. | ||
""" | ||
|
||
logger: BoundLogger | ||
"""The request logger, rebound with discovered context.""" | ||
|
||
http_client: AsyncClient | ||
"""The HTTPX async HTTP client.""" | ||
|
||
factory: Factory | ||
"""The component factory.""" | ||
|
||
def rebind_logger(self, **values: Any) -> None: | ||
"""Add the given values to the logging context. | ||
Also updates the logging context stored in the request object in case | ||
the request context later needs to be recreated from the request. | ||
Parameters | ||
---------- | ||
**values | ||
Additional values that should be added to the logging context. | ||
""" | ||
self.logger = self.logger.bind(**values) | ||
|
||
|
||
# This is the FastAPI dependency that provides the RequestContext. Notice | ||
# how dependency function signatures look like FastAPI path operations. | ||
# This context dependency takes the request and response arguemnts available | ||
# to path operations, and also depends on other dependencies. | ||
|
||
|
||
async def context_dependency( | ||
request: Request, | ||
response: Response, | ||
logger: Annotated[BoundLogger, Depends(logger_dependency)], | ||
http_client: Annotated[AsyncClient, Depends(http_client_dependency)], | ||
) -> RequestContext: | ||
"""Provide a RequestContext as a dependency.""" | ||
return RequestContext( | ||
request=request, | ||
response=response, | ||
logger=logger, | ||
http_client=http_client, | ||
factory=Factory(logger=logger, http_client=http_client), | ||
) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
"""Models for the Astroplan-related app domain.""" | ||
|
||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass | ||
from datetime import datetime | ||
from typing import Any | ||
|
||
from astroplan import Observer as AstroplanObserver | ||
from astropy.coordinates import AltAz, Angle, SkyCoord | ||
from astropy.time import Time | ||
|
||
__all__ = ["Observer", "TargetObservability"] | ||
|
||
# The domain layer is where your application's core business logic resides. | ||
# In this demo, the domain is built around the Astroplan library and its | ||
# Observer class. In fact, we subclass Astroplan's Observer to add some | ||
# additional attributes that are useful for our application. | ||
# | ||
# Note that the domain layer doesn't have dependendencies on other layers like | ||
# the storage or web API layers (unlike the service layer, which knows about | ||
# the storage layer in abstract detail). The domain doesn't care that its | ||
# in the middle of a FastAPI app. Every other layer of the app "cares" about | ||
# the domain, though. | ||
|
||
|
||
class Observer(AstroplanObserver): | ||
"""The observer domain model.""" | ||
|
||
def __init__( | ||
self, | ||
observer_id: str, | ||
local_timezone: str, | ||
aliases: list[str] | None = None, | ||
*args: Any, | ||
**kwargs: Any, | ||
) -> None: | ||
super().__init__(*args, **kwargs) | ||
|
||
self.observer_id = observer_id | ||
self.aliases = list(aliases) if aliases else [] | ||
self.local_timezone = local_timezone | ||
|
||
|
||
@dataclass(kw_only=True) | ||
class TargetObservability: | ||
"""The observability of a target for an observer.""" | ||
|
||
observer: Observer | ||
target: SkyCoord | ||
time: datetime | ||
airmass: float | None | ||
altaz: AltAz | ||
is_above_horizon: bool | ||
is_night: bool | ||
moon_up: bool | ||
moon_separation: Angle | ||
moon_altaz: AltAz | ||
moon_illumination: float | ||
|
||
@classmethod | ||
def compute( | ||
cls, | ||
observer: Observer, | ||
target: SkyCoord, | ||
time: datetime, | ||
) -> TargetObservability: | ||
"""Compute the observability of a target for an observer.""" | ||
astropy_time = Time(time) | ||
is_up = observer.target_is_up(astropy_time, target) | ||
|
||
altaz = target.transform_to( | ||
AltAz(obstime=astropy_time, location=observer.location) | ||
) | ||
airmass = altaz.secz if is_up else None | ||
|
||
is_night = observer.is_night(astropy_time) | ||
|
||
moon_altaz = observer.moon_altaz(astropy_time) | ||
moon_up = moon_altaz.alt > 0 | ||
moon_separation = altaz.separation(moon_altaz) | ||
moon_illumination = observer.moon_illumination(astropy_time) | ||
|
||
return cls( | ||
observer=observer, | ||
target=target, | ||
time=time, | ||
is_above_horizon=is_up, | ||
airmass=airmass, | ||
altaz=altaz, | ||
is_night=is_night, | ||
moon_up=moon_up, | ||
moon_separation=moon_separation, | ||
moon_altaz=moon_altaz, | ||
moon_illumination=moon_illumination, | ||
) |
Oops, something went wrong.