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

How to create an alias for a field created via annotation #1272

Open
tobi-or-not opened this issue Aug 21, 2024 · 2 comments
Open

How to create an alias for a field created via annotation #1272

tobi-or-not opened this issue Aug 21, 2024 · 2 comments

Comments

@tobi-or-not
Copy link

tobi-or-not commented Aug 21, 2024

I have an ORM query that creates annotations. While hour makes sense within the context of the query, as a property in the API response, I'd like to call the field time. In the WaterLevelSchema, I can specify hour: datetime, which works but if I only specify time: datetime = Field(..., alias='hour') I get an error:

  Field required [type=missing, input_value=<DjangoGetter: WaterLevel...o.ZoneInfo(key='UTC')))>, input_type=DjangoGetter]
    For further information visit https://errors.pydantic.dev/2.8/v/missing

How can I create an alias for a field that was created via annotation in a Django ORM query? When I specify both hour and time in the WaterLevelSchema, there is no error, but then I have both properties in the API response, which I do not want. Do I have my thinking backwards? How can this be done?

class WaterLevelSchema(Schema):
    hour: datetime  # --> works
    time: datetime = Field(..., alias='hour')   # --> raises an error unless hour is specified as above

class DashboardSchema(Schema):
    ...
    historic_water_levels_m3: list[WaterLevelSchema]

@router.get('/dashboard/{location_id}', response=DashboardSchema)
def dashboard(request, location_id):
   latest_water_levels = (
      ExecutionLog.objects
      .filter(forecast_id__in=latest_forecasts_ids)
      .annotate(day=TruncDay('created_at'), hour=TruncHour('created_at'))  # Truncate timestamp to day and hour
      .annotate(avg_water_level_m3=Avg('water_level_m3'))  # Calculate average water level per hour per day
      .values('day', 'hour')  # Group by day and hour
   )

   return DashboardSchema(
            ...
            historic_water_levels_m3=latest_water_levels
   )
@lapinvert
Copy link

lapinvert commented Aug 22, 2024

It would seem to me to be pretty normal but I may be mistaken.

You're sending hour attribute to your WaterLevelSchema when it's expecting time.

The syntax for alias is good, but in this case you don't want an alias, you just want a different schema.

So you remove hour from WaterLevelSchema, you define time as time: datetime (not an alias), and you annotate your object to output time instead of hour.

Btw I don't think you need to return DashboardSchema, as it's defined in response=DashboardSchema, that's the whole point, you can return the object as-is, as long as it's in the format you defined.

class WaterLevelSchema(Schema):
    time: datetime

# (...)

@router.get('/dashboard/{location_id}', response=DashboardSchema)
def dashboard(request, location_id):
   latest_water_levels = (
      # (...)
      .annotate(day=TruncDay('created_at'), time=TruncHour('created_at'))  # Truncate timestamp to day and hour
      # (...)
   )

   return {
        # (...)
        historic_water_levels_m3=latest_water_levels
   }

@tobi-or-not
Copy link
Author

Thanks @lapinvert!

Upon review, the name change works. I am still confused, though, why I hour is accessible as a 'regular' schema field but not via an alias.

The reason I am returning the DashboardSchema is that the data for it comes from a number of different queries. I guess I could

  • Remove response=DashboardSchemafrom the decorator to avoid duplication or
  • Keep response=DashboardSchemaand return a dict instead.

Thanks for your thoughts!

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

No branches or pull requests

2 participants