1010
1111import httpx
1212
13- T = TypeVar ('T' , bound = '_HasExtras' )
13+ T = TypeVar ("T" , bound = "ExtrasMixin" )
1414
1515
16- class ApiModelMixin :
16+ class ExtrasMixin :
17+ """Mixin to provide additional functionality for API response models."""
18+
19+ extras : Dict [str , Any ]
20+
21+ def get_extra (self , key : str , default = None ):
22+ return self .extras .get (key , default )
23+
24+ def __getattr__ (self , item ):
25+ try :
26+ return self .extras [item ]
27+ except KeyError as exc :
28+ raise AttributeError (item ) from exc
29+
30+
31+ class ApiModelMixin (ExtrasMixin ):
1732 """Mixin to provide additional functionality for API response models."""
1833
1934 @classmethod
@@ -29,27 +44,14 @@ def from_api(cls: Type[T], data: Dict[str, Any]) -> T:
2944 return cls (** core , extras = extra )
3045
3146
32- class _HasExtras :
33- extras : Dict [str , Any ]
34-
35- def get_extra (self , key : str , default = None ):
36- return self .extras .get (key , default )
37-
38- def __getattr__ (self , item ):
39- try :
40- return self .extras [item ]
41- except KeyError as exc :
42- raise AttributeError (item ) from exc
43-
44-
4547@dataclass (slots = True , frozen = True )
4648class Location :
4749 lat : float
4850 lng : float
4951
5052
5153@dataclass (frozen = True )
52- class AddressComponents (_HasExtras , ApiModelMixin ):
54+ class AddressComponents (ApiModelMixin ):
5355 # core / always-present
5456 number : Optional [str ] = None
5557 predirectional : Optional [str ] = None # e.g. "N"
@@ -70,15 +72,15 @@ class AddressComponents(_HasExtras, ApiModelMixin):
7072
7173
7274@dataclass (frozen = True )
73- class Timezone (_HasExtras , ApiModelMixin ):
75+ class Timezone (ApiModelMixin ):
7476 name : str
7577 utc_offset : int
7678 observes_dst : Optional [bool ] = None # new key documented by Geocodio
7779 extras : Dict [str , Any ] = field (default_factory = dict , repr = False )
7880
7981
8082@dataclass (slots = True , frozen = True )
81- class CongressionalDistrict (_HasExtras , ApiModelMixin ):
83+ class CongressionalDistrict (ApiModelMixin ):
8284 name : str
8385 district_number : int
8486 congress_number : str
@@ -87,10 +89,11 @@ class CongressionalDistrict(_HasExtras, ApiModelMixin):
8789
8890
8991@dataclass (slots = True , frozen = True )
90- class StateLegislativeDistrict (_HasExtras , ApiModelMixin ):
92+ class StateLegislativeDistrict (ApiModelMixin ):
9193 """
9294 State legislative district information.
9395 """
96+
9497 name : str
9598 district_number : int
9699 chamber : str # 'house' or 'senate'
@@ -100,10 +103,11 @@ class StateLegislativeDistrict(_HasExtras, ApiModelMixin):
100103
101104
102105@dataclass (slots = True , frozen = True )
103- class CensusData (_HasExtras , ApiModelMixin ):
106+ class CensusData (ApiModelMixin ):
104107 """
105108 Census data for a location.
106109 """
110+
107111 block : Optional [str ] = None
108112 blockgroup : Optional [str ] = None
109113 tract : Optional [str ] = None
@@ -115,10 +119,11 @@ class CensusData(_HasExtras, ApiModelMixin):
115119
116120
117121@dataclass (slots = True , frozen = True )
118- class ACSSurveyData (_HasExtras , ApiModelMixin ):
122+ class ACSSurveyData (ApiModelMixin ):
119123 """
120124 American Community Survey data for a location.
121125 """
126+
122127 population : Optional [int ] = None
123128 households : Optional [int ] = None
124129 median_income : Optional [int ] = None
@@ -127,10 +132,11 @@ class ACSSurveyData(_HasExtras, ApiModelMixin):
127132
128133
129134@dataclass (slots = True , frozen = True )
130- class SchoolDistrict (_HasExtras , ApiModelMixin ):
135+ class SchoolDistrict (ApiModelMixin ):
131136 """
132137 School district information.
133138 """
139+
134140 name : str
135141 district_number : Optional [str ] = None
136142 lea_id : Optional [str ] = None # Local Education Agency ID
@@ -139,10 +145,11 @@ class SchoolDistrict(_HasExtras, ApiModelMixin):
139145
140146
141147@dataclass (slots = True , frozen = True )
142- class Demographics (_HasExtras , ApiModelMixin ):
148+ class Demographics (ApiModelMixin ):
143149 """
144150 American Community Survey demographics data.
145151 """
152+
146153 total_population : Optional [int ] = None
147154 male_population : Optional [int ] = None
148155 female_population : Optional [int ] = None
@@ -155,10 +162,11 @@ class Demographics(_HasExtras, ApiModelMixin):
155162
156163
157164@dataclass (slots = True , frozen = True )
158- class Economics (_HasExtras , ApiModelMixin ):
165+ class Economics (ApiModelMixin ):
159166 """
160167 American Community Survey economics data.
161168 """
169+
162170 median_household_income : Optional [int ] = None
163171 mean_household_income : Optional [int ] = None
164172 per_capita_income : Optional [int ] = None
@@ -168,10 +176,11 @@ class Economics(_HasExtras, ApiModelMixin):
168176
169177
170178@dataclass (slots = True , frozen = True )
171- class Families (_HasExtras , ApiModelMixin ):
179+ class Families (ApiModelMixin ):
172180 """
173181 American Community Survey families data.
174182 """
183+
175184 total_households : Optional [int ] = None
176185 family_households : Optional [int ] = None
177186 nonfamily_households : Optional [int ] = None
@@ -183,10 +192,11 @@ class Families(_HasExtras, ApiModelMixin):
183192
184193
185194@dataclass (slots = True , frozen = True )
186- class Housing (_HasExtras , ApiModelMixin ):
195+ class Housing (ApiModelMixin ):
187196 """
188197 American Community Survey housing data.
189198 """
199+
190200 total_housing_units : Optional [int ] = None
191201 occupied_housing_units : Optional [int ] = None
192202 vacant_housing_units : Optional [int ] = None
@@ -198,10 +208,11 @@ class Housing(_HasExtras, ApiModelMixin):
198208
199209
200210@dataclass (slots = True , frozen = True )
201- class Social (_HasExtras , ApiModelMixin ):
211+ class Social (ApiModelMixin ):
202212 """
203213 American Community Survey social data.
204214 """
215+
205216 high_school_graduate_or_higher : Optional [int ] = None
206217 bachelors_degree_or_higher : Optional [int ] = None
207218 graduate_degree_or_higher : Optional [int ] = None
@@ -211,17 +222,19 @@ class Social(_HasExtras, ApiModelMixin):
211222
212223
213224@dataclass (slots = True , frozen = True )
214- class ZIP4Data (_HasExtras , ApiModelMixin ):
225+ class ZIP4Data (ApiModelMixin ):
215226 """USPS ZIP+4 code and delivery information."""
227+
216228 zip4 : str
217229 delivery_point : str
218230 carrier_route : str
219231 extras : Dict [str , Any ] = field (default_factory = dict , repr = False )
220232
221233
222234@dataclass (slots = True , frozen = True )
223- class FederalRiding (_HasExtras , ApiModelMixin ):
235+ class FederalRiding (ApiModelMixin ):
224236 """Canadian federal electoral district information."""
237+
225238 code : str
226239 name_english : str
227240 name_french : str
@@ -232,8 +245,9 @@ class FederalRiding(_HasExtras, ApiModelMixin):
232245
233246
234247@dataclass (slots = True , frozen = True )
235- class ProvincialRiding (_HasExtras , ApiModelMixin ):
248+ class ProvincialRiding (ApiModelMixin ):
236249 """Canadian provincial electoral district information."""
250+
237251 name_english : str
238252 name_french : str
239253 ocd_id : str
@@ -243,8 +257,9 @@ class ProvincialRiding(_HasExtras, ApiModelMixin):
243257
244258
245259@dataclass (slots = True , frozen = True )
246- class StatisticsCanadaData (_HasExtras , ApiModelMixin ):
260+ class StatisticsCanadaData (ApiModelMixin ):
247261 """Canadian statistical boundaries from Statistics Canada."""
262+
248263 division : Dict [str , Any ]
249264 consolidated_subdivision : Dict [str , Any ]
250265 subdivision : Dict [str , Any ]
@@ -261,8 +276,9 @@ class StatisticsCanadaData(_HasExtras, ApiModelMixin):
261276
262277
263278@dataclass (slots = True , frozen = True )
264- class FFIECData (_HasExtras , ApiModelMixin ):
279+ class FFIECData (ApiModelMixin ):
265280 """FFIEC CRA/HMDA Data (Beta)."""
281+
266282 # Add FFIEC specific fields as they become available
267283 extras : Dict [str , Any ] = field (default_factory = dict , repr = False )
268284
@@ -273,6 +289,7 @@ class GeocodioFields:
273289 Container for optional 'fields' returned by the Geocodio API.
274290 Add new attributes as additional data‑append endpoints become useful.
275291 """
292+
276293 timezone : Optional [Timezone ] = None
277294 congressional_districts : Optional [List [CongressionalDistrict ]] = None
278295 state_legislative_districts : Optional [List [StateLegislativeDistrict ]] = None
@@ -320,6 +337,7 @@ class GeocodioFields:
320337# Main result objects
321338# ──────────────────────────────────────────────────────────────────────────────
322339
340+
323341@dataclass (slots = True , frozen = True )
324342class GeocodingResult :
325343 address_components : AddressComponents
@@ -336,6 +354,7 @@ class GeocodingResponse:
336354 """
337355 Top‑level structure returned by client.geocode() / client.reverse().
338356 """
357+
339358 input : Dict [str , Optional [str ]]
340359 results : List [GeocodingResult ] = field (default_factory = list )
341360
@@ -355,6 +374,7 @@ class ListResponse:
355374 """
356375 status, download_url, expires_at are not always present.
357376 """
377+
358378 id : str
359379 file : Dict [str , Any ]
360380 status : Optional [Dict [str , Any ]] = None
@@ -368,6 +388,7 @@ class PaginatedResponse():
368388 """
369389 Base class for paginated responses.
370390 """
391+
371392 current_page : int
372393 data : List [ListResponse ]
373394 from_ : int
0 commit comments