From b2b74c4a53b07711447769d95d1430a802d42fd2 Mon Sep 17 00:00:00 2001 From: Medhat Gayed Date: Tue, 16 Jul 2024 14:36:49 +1200 Subject: [PATCH 1/6] SE-1356 Modify features endpoint to dynamically set requested features. --- predicthq/endpoints/v1/features/endpoint.py | 4 +- predicthq/endpoints/v1/features/schemas.py | 139 +------------------- predicthq/version.py | 2 +- 3 files changed, 9 insertions(+), 136 deletions(-) diff --git a/predicthq/endpoints/v1/features/endpoint.py b/predicthq/endpoints/v1/features/endpoint.py index f9cbfd2..fdb1f6c 100644 --- a/predicthq/endpoints/v1/features/endpoint.py +++ b/predicthq/endpoints/v1/features/endpoint.py @@ -10,8 +10,8 @@ class FeaturesEndpoint(UserBaseEndpoint): BASE_FEATURE_CRITERIA = {"stats": [["sum", "count"]], "phq_rank": [None]} FIELDS_TO_MUTATE = frozenset([ "phq_attendance_", - "phq_viewership_sports", - "phq_impact_severe_weather_", + "phq_viewership_", + "phq_impact_", "phq_spend_" ]) diff --git a/predicthq/endpoints/v1/features/schemas.py b/predicthq/endpoints/v1/features/schemas.py index ad636c9..1451ded 100644 --- a/predicthq/endpoints/v1/features/schemas.py +++ b/predicthq/endpoints/v1/features/schemas.py @@ -1,9 +1,7 @@ from datetime import date -from typing import List +from typing import Any, List, Optional, Union -from typing import Optional - -from pydantic import BaseModel +from pydantic import BaseModel, RootModel from predicthq.endpoints.schemas import ResultSet @@ -26,136 +24,11 @@ class FeatureStat(BaseModel): stats: FeatureStats -class Feature(BaseModel): - class Options: - serialize_when_none = False +class Feature(RootModel): + root: dict[str, Union[date, Optional[FeatureStat], Optional[FeatureRankLevel]]] - date: date - # Attendance based features - phq_attendance_academic_graduation: Optional[FeatureStat] = None - phq_attendance_academic_social: Optional[FeatureStat] = None - phq_attendance_community: Optional[FeatureStat] = None - phq_attendance_concerts: Optional[FeatureStat] = None - phq_attendance_conferences: Optional[FeatureStat] = None - phq_attendance_expos: Optional[FeatureStat] = None - phq_attendance_festivals: Optional[FeatureStat] = None - phq_attendance_performing_arts: Optional[FeatureStat] = None - phq_attendance_school_holidays: Optional[FeatureStat] = None - phq_attendance_sports: Optional[FeatureStat] = None - # Attendance based feature for accommodation vertical - phq_attendance_community_accommodation: Optional[FeatureStat] = None - phq_attendance_concerts_accommodation: Optional[FeatureStat] = None - phq_attendance_conferences_accommodation: Optional[FeatureStat] = None - phq_attendance_expos_accommodation: Optional[FeatureStat] = None - phq_attendance_festivals_accommodation: Optional[FeatureStat] = None - phq_attendance_performing_arts_accommodation: Optional[FeatureStat] = None - phq_attendance_sports_accommodation: Optional[FeatureStat] = None - # Attendance based feature for hospitality vertical - phq_attendance_community_hospitality: Optional[FeatureStat] = None - phq_attendance_concerts_hospitality: Optional[FeatureStat] = None - phq_attendance_conferences_hospitality: Optional[FeatureStat] = None - phq_attendance_expos_hospitality: Optional[FeatureStat] = None - phq_attendance_festivals_hospitality: Optional[FeatureStat] = None - phq_attendance_performing_arts_hospitality: Optional[FeatureStat] = None - phq_attendance_sports_hospitality: Optional[FeatureStat] = None - # Rank based features - phq_rank_daylight_savings: Optional[FeatureRankLevel] = None - phq_rank_health_warnings: Optional[FeatureRankLevel] = None - phq_rank_observances: Optional[FeatureRankLevel] = None - phq_rank_public_holidays: Optional[FeatureRankLevel] = None - phq_rank_school_holidays: Optional[FeatureRankLevel] = None - phq_rank_politics: Optional[FeatureRankLevel] = None - phq_rank_academic_session: Optional[FeatureRankLevel] = None - phq_rank_academic_exam: Optional[FeatureRankLevel] = None - phq_rank_academic_holiday: Optional[FeatureRankLevel] = None - # Impact based feature criteria - phq_impact_severe_weather_air_quality_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_blizzard_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_cold_wave_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_cold_wave_snow_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_cold_wave_storm_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_dust_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_dust_storm_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_flood_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_heat_wave_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_hurricane_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_thunderstorm_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_tornado_retail: Optional[FeatureStat] = None - phq_impact_severe_weather_tropical_storm_retail: Optional[FeatureStat] = None - # Viewership based features - phq_viewership_sports: Optional[FeatureStat] = None - phq_viewership_sports_american_football: Optional[FeatureStat] = None - phq_viewership_sports_american_football_ncaa_men: Optional[FeatureStat] = None - phq_viewership_sports_american_football_nfl: Optional[FeatureStat] = None - phq_viewership_sports_auto_racing: Optional[FeatureStat] = None - phq_viewership_sports_auto_racing_indy_car: Optional[FeatureStat] = None - phq_viewership_sports_auto_racing_nascar: Optional[FeatureStat] = None - phq_viewership_sports_baseball: Optional[FeatureStat] = None - phq_viewership_sports_baseball_mlb: Optional[FeatureStat] = None - phq_viewership_sports_baseball_ncaa_men: Optional[FeatureStat] = None - phq_viewership_sports_basketball: Optional[FeatureStat] = None - phq_viewership_sports_basketball_ncaa_women: Optional[FeatureStat] = None - phq_viewership_sports_basketball_ncaa_men: Optional[FeatureStat] = None - phq_viewership_sports_basketball_nba: Optional[FeatureStat] = None - phq_viewership_sports_boxing: Optional[FeatureStat] = None - phq_viewership_sports_golf: Optional[FeatureStat] = None - phq_viewership_sports_golf_masters: Optional[FeatureStat] = None - phq_viewership_sports_golf_pga_championship: Optional[FeatureStat] = None - phq_viewership_sports_golf_pga_tour: Optional[FeatureStat] = None - phq_viewership_sports_golf_us_open: Optional[FeatureStat] = None - phq_viewership_sports_horse_racing: Optional[FeatureStat] = None - phq_viewership_sports_horse_racing_belmont_stakes: Optional[FeatureStat] = None - phq_viewership_sports_horse_racing_kentucky_derby: Optional[FeatureStat] = None - phq_viewership_sports_horse_racing_preakness_stakes: Optional[FeatureStat] = None - phq_viewership_sports_ice_hockey: Optional[FeatureStat] = None - phq_viewership_sports_ice_hockey_nhl: Optional[FeatureStat] = None - phq_viewership_sports_mma: Optional[FeatureStat] = None - phq_viewership_sports_mma_ufc: Optional[FeatureStat] = None - phq_viewership_sports_soccer: Optional[FeatureStat] = None - phq_viewership_sports_soccer_concacaf_champions_league: Optional[FeatureStat] = None - phq_viewership_sports_soccer_concacaf_gold_cup: Optional[FeatureStat] = None - phq_viewership_sports_soccer_copa_america_men: Optional[FeatureStat] = None - phq_viewership_sports_soccer_fifa_world_cup_women: Optional[FeatureStat] = None - phq_viewership_sports_soccer_fifa_world_cup_men: Optional[FeatureStat] = None - phq_viewership_sports_soccer_mls: Optional[FeatureStat] = None - phq_viewership_sports_soccer_uefa_champions_league_men: Optional[FeatureStat] = None - phq_viewership_sports_softball: Optional[FeatureStat] = None - phq_viewership_sports_softball_ncaa_women: Optional[FeatureStat] = None - phq_viewership_sports_tennis: Optional[FeatureStat] = None - phq_viewership_sports_tennis_us_open: Optional[FeatureStat] = None - phq_viewership_sports_tennis_wimbledon: Optional[FeatureStat] = None - # PHQ spend based feature - phq_spend_conferences: Optional[FeatureStat] = None - phq_spend_expos: Optional[FeatureStat] = None - phq_spend_sports: Optional[FeatureStat] = None - phq_spend_community: Optional[FeatureStat] = None - phq_spend_concerts: Optional[FeatureStat] = None - phq_spend_festivals: Optional[FeatureStat] = None - phq_spend_performing_arts: Optional[FeatureStat] = None - # PHQ spend accommodation based feature - phq_spend_conferences_accommodation: Optional[FeatureStat] = None - phq_spend_expos_accommodation: Optional[FeatureStat] = None - phq_spend_sports_accommodation: Optional[FeatureStat] = None - phq_spend_community_accommodation: Optional[FeatureStat] = None - phq_spend_concerts_accommodation: Optional[FeatureStat] = None - phq_spend_festivals_accommodation: Optional[FeatureStat] = None - phq_spend_performing_arts_accommodation: Optional[FeatureStat] = None - # PHQ spend hospitality based feature - phq_spend_conferences_hospitality: Optional[FeatureStat] = None - phq_spend_expos_hospitality: Optional[FeatureStat] = None - phq_spend_sports_hospitality: Optional[FeatureStat] = None - phq_spend_community_hospitality: Optional[FeatureStat] = None - phq_spend_concerts_hospitality: Optional[FeatureStat] = None - phq_spend_festivals_hospitality: Optional[FeatureStat] = None - phq_spend_performing_arts_hospitality: Optional[FeatureStat] = None - # PHQ spend transportation based feature - phq_spend_conferences_transportation: Optional[FeatureStat] = None - phq_spend_expos_transportation: Optional[FeatureStat] = None - phq_spend_sports_transportation: Optional[FeatureStat] = None - phq_spend_community_transportation: Optional[FeatureStat] = None - phq_spend_concerts_transportation: Optional[FeatureStat] = None - phq_spend_festivals_transportation: Optional[FeatureStat] = None - phq_spend_performing_arts_transportation: Optional[FeatureStat] = None + def __getattr__(self, name: str) -> Any: + return self.root[name] class FeatureResultSet(ResultSet): diff --git a/predicthq/version.py b/predicthq/version.py index 903a158..dcbfb52 100644 --- a/predicthq/version.py +++ b/predicthq/version.py @@ -1 +1 @@ -__version__ = "3.4.0" +__version__ = "3.5.0" From 4a97ebd72e756a7186b5c23dcb4dcdbdab92da37 Mon Sep 17 00:00:00 2001 From: Medhat Gayed Date: Tue, 16 Jul 2024 15:19:10 +1200 Subject: [PATCH 2/6] SE-1356 Fix TypeError: 'type' object is not subscriptable error for Python < 3.9 --- predicthq/endpoints/v1/features/schemas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/predicthq/endpoints/v1/features/schemas.py b/predicthq/endpoints/v1/features/schemas.py index 1451ded..482bf1e 100644 --- a/predicthq/endpoints/v1/features/schemas.py +++ b/predicthq/endpoints/v1/features/schemas.py @@ -1,5 +1,5 @@ from datetime import date -from typing import Any, List, Optional, Union +from typing import Any, Dict, List, Optional, Union from pydantic import BaseModel, RootModel @@ -25,7 +25,7 @@ class FeatureStat(BaseModel): class Feature(RootModel): - root: dict[str, Union[date, Optional[FeatureStat], Optional[FeatureRankLevel]]] + root: Dict[str, Union[date, Optional[FeatureStat], Optional[FeatureRankLevel]]] def __getattr__(self, name: str) -> Any: return self.root[name] From c0f5a441e19eb8cf8ea995b3846032c0907462d6 Mon Sep 17 00:00:00 2001 From: Medhat Gayed Date: Tue, 16 Jul 2024 16:49:03 +1200 Subject: [PATCH 3/6] SE-1356 Simplify and improve type hints. --- predicthq/endpoints/v1/features/schemas.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/predicthq/endpoints/v1/features/schemas.py b/predicthq/endpoints/v1/features/schemas.py index 482bf1e..6bbe42b 100644 --- a/predicthq/endpoints/v1/features/schemas.py +++ b/predicthq/endpoints/v1/features/schemas.py @@ -1,5 +1,5 @@ from datetime import date -from typing import Any, Dict, List, Optional, Union +from typing import Dict, List, Optional, Union from pydantic import BaseModel, RootModel @@ -25,9 +25,9 @@ class FeatureStat(BaseModel): class Feature(RootModel): - root: Dict[str, Union[date, Optional[FeatureStat], Optional[FeatureRankLevel]]] + root: Dict[str, Union[date, FeatureStat, FeatureRankLevel]] - def __getattr__(self, name: str) -> Any: + def __getattr__(self, name: str) -> Union[date, FeatureStat, FeatureRankLevel]: return self.root[name] From 521d5027b683ba6e6f954171d2ca79c0027c1862 Mon Sep 17 00:00:00 2001 From: Medhat Gayed Date: Tue, 16 Jul 2024 17:07:00 +1200 Subject: [PATCH 4/6] SE-1356 Black formatting. --- predicthq/endpoints/v1/features/endpoint.py | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/predicthq/endpoints/v1/features/endpoint.py b/predicthq/endpoints/v1/features/endpoint.py index fdb1f6c..bae59ec 100644 --- a/predicthq/endpoints/v1/features/endpoint.py +++ b/predicthq/endpoints/v1/features/endpoint.py @@ -1,25 +1,27 @@ -import inspect - from predicthq.endpoints.base import UserBaseEndpoint from predicthq.endpoints.decorators import accepts, returns + from .schemas import FeatureResultSet class FeaturesEndpoint(UserBaseEndpoint): - BASE_FEATURE_CRITERIA = {"stats": [["sum", "count"]], "phq_rank": [None]} - FIELDS_TO_MUTATE = frozenset([ - "phq_attendance_", - "phq_viewership_", - "phq_impact_", - "phq_spend_" - ]) + FIELDS_TO_MUTATE = frozenset( + [ + "phq_attendance_", + "phq_viewership_", + "phq_impact_", + "phq_spend_", + ] + ) @classmethod def mutate_bool_to_default_for_type(cls, user_request_spec): for key, val in user_request_spec.items(): if any(key.startswith(x) for x in cls.FIELDS_TO_MUTATE): - user_request_spec[key] = [cls.BASE_FEATURE_CRITERIA if isinstance(v, bool) else v for v in val] + user_request_spec[key] = [ + cls.BASE_FEATURE_CRITERIA if isinstance(v, bool) else v for v in val + ] @accepts(query_string=False) @returns(FeatureResultSet) From 5a7284e6522f6ad1806385d0436e64aa88fcdbbb Mon Sep 17 00:00:00 2001 From: Medhat Gayed Date: Wed, 17 Jul 2024 12:10:20 +1200 Subject: [PATCH 5/6] SE-1356 Add notes on how to serialize search results and usage of .model_dump(exclude_none=True) --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 882dfb0..587deb1 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ Please refer to our [Places endpoint documentation](https://docs.predicthq.com/r ### Features endpoint -The following example obtain features of events which are active between 2017-12-31 and 2018-01-02, with place_id 4671654. +The following example obtain features of events which are active between 2017-12-31 and 2018-01-02, with place_id 4671654. Requested features: * rank_levels for public_holidays @@ -209,6 +209,43 @@ suggested_radius = phq.radius.search(location__origin="45.5051,-122.6750") print(suggested_radius.radius, suggested_radius.radius_unit, suggested_radius.location.to_dict()) ``` +### Serializing search results into a dictionary + +All search results can be serialized to a dictionary using the `.model_dump(exclude_none=True)` method call. + +Examples: + +```Python +from predicthq import Client + +phq = Client(access_token="abc123") + + +for event in phq.events.search(q="Katy Perry", rank_level=[4, 5], category="concerts"): + # Serialize event data into a dictionary and remove None values + print(event.model_dump(exclude_none=True)) +``` + +```Python +from predicthq import Client + +phq = Client(access_token="abc123") + + +for feature in phq.features.obtain_features( + active__gte="2017-12-31", + active__lte="2018-01-02", + location__place_id=["4671654"], + phq_rank_public_holidays=True, + phq_attendance_sports__stats=["count", "median"], + phq_attendance_sports__phq_rank={ + "gt": 50 + } +): + # Serialize feature data into a dictionary and remove None values + print(feature.model_dump(exclude_none=True)) +``` + ### Config parameters We support some `config` parameters for additional flexibility. From f7795148ac86cf52e788b0aa77615b5be86d4ff0 Mon Sep 17 00:00:00 2001 From: Medhat Gayed Date: Wed, 17 Jul 2024 13:35:09 +1200 Subject: [PATCH 6/6] SE-1356 README update --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 587deb1..c06253f 100644 --- a/README.md +++ b/README.md @@ -206,12 +206,16 @@ phq = Client(access_token="abc123") suggested_radius = phq.radius.search(location__origin="45.5051,-122.6750") -print(suggested_radius.radius, suggested_radius.radius_unit, suggested_radius.location.to_dict()) +print(suggested_radius.radius, suggested_radius.radius_unit, suggested_radius.location.model_dump(exclude_none=True)) ``` ### Serializing search results into a dictionary -All search results can be serialized to a dictionary using the `.model_dump(exclude_none=True)` method call. +All search results can be serialized into a dictionary using the `.model_dump()` method call. + +To keep `None` values use `.model_dump()` + +To remove `None` values use `.model_dump(exclude_none=True)` Examples: