Skip to content

Commit

Permalink
Bump up to pydantic v2
Browse files Browse the repository at this point in the history
  • Loading branch information
ugyballoons committed Jul 17, 2023
1 parent 417f809 commit 74867ba
Show file tree
Hide file tree
Showing 17 changed files with 906 additions and 355 deletions.
1 change: 0 additions & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
v19.6.0
v19.6.0
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include src/rubintv/models/models_data.yaml
2 changes: 2 additions & 0 deletions requirements/dev.in
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ pytest-asyncio
pytest-cov
types-PyYAML
beautifulsoup4
types-python-dateutil
moto
590 changes: 497 additions & 93 deletions requirements/dev.txt

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion requirements/main.in
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
fastapi
starlette
uvicorn[standard]

pydantic_settings
# Other dependencies.
safir>=3.4.0
google-cloud-storage
Expand Down
531 changes: 306 additions & 225 deletions requirements/main.txt

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions src/rubintv/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from __future__ import annotations

from pydantic import BaseSettings, Field
from pydantic import Field
from pydantic_settings import BaseSettings
from safir.logging import LogLevel, Profile

__all__ = ["Configuration", "config"]
Expand All @@ -14,25 +15,25 @@ class Configuration(BaseSettings):
name: str = Field(
"rubintv",
title="Name of application",
env="SAFIR_NAME",
validation_alias="SAFIR_NAME",
)

path_prefix: str = Field(
"/rubintv",
title="URL prefix for application",
env="SAFIR_PATH_PREFIX",
validation_alias="SAFIR_PATH_PREFIX",
)

profile: Profile = Field(
Profile.development,
title="Application logging profile",
env="SAFIR_PROFILE",
validation_alias="SAFIR_PROFILE",
)

log_level: LogLevel = Field(
LogLevel.INFO,
title="Log level of the application's logger",
env="SAFIR_LOG_LEVEL",
validation_alias="SAFIR_LOG_LEVEL",
)


Expand Down
4 changes: 3 additions & 1 deletion src/rubintv/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from fastapi.templating import Jinja2Templates

from rubintv import __version__
from rubintv.models_init import dict_from_list_of_named_objects as list_to_dict
from rubintv.models.models_init import (
dict_from_list_of_named_objects as list_to_dict,
)

__all__ = ["get_templates"]

Expand Down
38 changes: 33 additions & 5 deletions src/rubintv/handlers/external.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Handlers for the app's external root, ``/rubintv/``."""
from datetime import date
from itertools import chain
from typing import Tuple

Expand All @@ -9,7 +10,13 @@

from rubintv.dependencies import get_templates
from rubintv.handlers.helpers import find_first
from rubintv.models import Camera, Location
from rubintv.models.buckethandler import BucketHandlerInterface
from rubintv.models.models import (
Camera,
Location,
build_prefix_with_date,
get_current_day_obs,
)

__all__ = ["get_home", "external_router", "templates"]

Expand Down Expand Up @@ -67,10 +74,31 @@ async def get_location_camera(
return (location, camera)


# @external_router.get(
# "/api/location/{location_name}/camera/{camera_name}",
# response_model=Tuple[Location, Camera],
# )
@external_router.get(
"/api/location/{location_name}/camera/{camera_name}/latest"
)
async def get_camera_latest_data(
location_name: str,
camera_name: str,
request: Request,
logger: BoundLogger = Depends(logger_dependency),
) -> dict[str, date | list]:
location, camera = await get_location_camera(
location_name, camera_name, request
)
day_obs = get_current_day_obs()
prefix = build_prefix_with_date(camera, day_obs)
logger.info(f"Looking for data for :{prefix}")
channel_data = scrape_data_for_prefix(location.bucket, prefix, request)
return {"date": day_obs, "channels": channel_data}


def scrape_data_for_prefix(
bucket_name: str, prefix: str, request: Request
) -> list:
bucket_handler: BucketHandlerInterface = request.app.state.bucket_handler
objects = bucket_handler.list_objects(prefix)
return objects


@external_router.get(
Expand Down
2 changes: 1 addition & 1 deletion src/rubintv/handlers/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async def _find_by_key_and_value(
if not a_list:
return None
try:
result = (o for o in a_list if o.dict()[key] == to_match)
result = (o for o in a_list if o.model_dump()[key] == to_match)
except IndexError:
pass
return result
17 changes: 6 additions & 11 deletions src/rubintv/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
from safir.logging import configure_logging, configure_uvicorn_logging
from safir.middleware.x_forwarded import XForwardedMiddleware

from .buckethandler import BucketHandlerInterface, BucketHandlerMaker
from .config import config
from .handlers.external import external_router
from .handlers.internal import internal_router
from .models_init import ModelsInitiator
from .models.buckethandler import BucketHandlerInterface, BucketHandlerMaker
from .models.models_init import ModelsInitiator

__all__ = ["app", "config"]

Expand All @@ -47,15 +47,10 @@
m = ModelsInitiator()
app.state.fixtures = m

# Fetch some test data
bucket_maker = BucketHandlerMaker("gcs")
bucket: BucketHandlerInterface = bucket_maker.get_bucket_handler(
"rubintv_data"
)
test_blobs = bucket.list_objects(
"auxtel_monitor/auxtel-monitor_dayObs_2023-05-31"
)
app.state.test_blobs = test_blobs
# Attach a bucket
bhm = BucketHandlerMaker("gcs")
bucket_handler: BucketHandlerInterface = bhm.get_bucket_handler("rubintv_data")
app.state.bucket_handler = bucket_handler

# Intwine jinja2 templating
app.mount(
Expand Down
Empty file added src/rubintv/models/__init__.py
Empty file.
File renamed without changes.
56 changes: 47 additions & 9 deletions src/rubintv/models.py → src/rubintv/models/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Models for rubintv."""

import re
from datetime import date
from datetime import date, datetime, timedelta
from typing import Any, Type

from pydantic import BaseModel, validator
from dateutil.tz import gettz
from pydantic import BaseModel, field_validator
from pydantic.dataclasses import dataclass

__all__ = [
Expand All @@ -14,6 +15,8 @@
"Heartbeat",
"Event",
"EventInitialisationError",
"get_current_day_obs",
"build_prefix_with_date",
]


Expand Down Expand Up @@ -41,15 +44,15 @@ class Camera(BaseModel):
metadata_slug: str = ""
logo: str = ""
image_viewer_link: str = ""
channels: list[Channel] | None
per_day_channels: list[Channel] | None
channels: list[Channel] | None = None
per_day_channels: list[Channel] | None = None
night_report_prefix: str = ""
night_report_label: str = "Night Report"
metadata_cols: dict[str, dict[str, str]] | dict[str, str] | None
metadata_cols: dict[str, dict[str, str]] | dict[str, str] | None = None
js_entry: str = ""

# If metadata_slug/js_entry not set, use name as default
@validator("metadata_slug", "js_entry", pre=True, always=True)
@field_validator("metadata_slug", "js_entry")
def default_as_name(cls: Type, v: str, values: Any) -> str:
return v or values.get("name")

Expand All @@ -74,15 +77,26 @@ class Event:
name: str = ""
day_obs: date = date.today()
seq_num: int | str = 0
bucket_name: str = ""
camera_name: str = ""
channel_name: str = ""
#
location: Location | None = None
camera: Camera | None = None
channel: Channel | None = None

def __post_init__(self) -> None:
self.name, self.day_obs, self.seq_num = self.parse_url()

def parse_url(self) -> tuple:
"""Parses the object URL.
Returns
-------
url_parts: `tuple`
Raises
------
EventInitialisationError
Thrown if any part of the parsing process breaks.
"""
url = self.url
name_re = re.compile(r"^http[s]:\/\/[\w*\.]+\/[\w*\.]+\/")
if match := name_re.match(url):
Expand All @@ -108,3 +122,27 @@ def parse_url(self) -> tuple:
else:
raise EventInitialisationError(url)
return (name, day_obs, seq_num)


def build_prefix_with_date(camera: Camera, day_obs: date) -> str:
camera_name = camera.name
# eventually the prefix will be formed something like:
# return f"{location_name}/{camera_name}/{day_obs}/{channel_name}"
return f"{camera_name}_monitor/{camera_name}-monitor_dayObs_{day_obs}"


def get_current_day_obs() -> date:
"""Get the current day_obs.
The observatory rolls the date over at UTC minus 12 hours.
Returns
-------
dayObs : `date`
The current observation day.
"""
utc = gettz("UTC")
nowUtc = datetime.now().astimezone(utc)
offset = timedelta(hours=-12)
dayObs = (nowUtc + offset).date()
return dayObs
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import yaml
from pydantic import BaseModel

from rubintv.models import Camera, Heartbeat, Location
from .models import Camera, Heartbeat, Location

__all__ = ["ModelsInitiator", "dict_from_list_of_named_objects"]

Expand Down
4 changes: 2 additions & 2 deletions tests/handlers/external_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from httpx import AsyncClient

from rubintv.handlers.helpers import find_first
from rubintv.models import Location
from rubintv.models_init import ModelsInitiator
from rubintv.models.models import Location
from rubintv.models.models_init import ModelsInitiator

m = ModelsInitiator()

Expand Down

0 comments on commit 74867ba

Please sign in to comment.