Skip to content

Commit 19f1db5

Browse files
authored
Merge pull request #3 from Geocodio/fix/refactor-class-names-extras
Fix/refactor class names extras
2 parents a6f95ee + acc84cb commit 19f1db5

File tree

3 files changed

+53
-36
lines changed

3 files changed

+53
-36
lines changed

src/geocodio/models.py

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,25 @@
1010

1111
import 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)
4648
class 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)
324342
class 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

tests/e2e/test_lists_api.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,6 @@ def test_delete_list(client, list_response):
113113

114114
if list_id in all_list_ids:
115115
raise AssertionError(f"List with ID {list_id} was not deleted successfully. It still exists in the list of lists.")
116-
else:
117-
print(f"List with ID {list_id} was deleted successfully and is no longer in the list of lists.")
118116

119117

120118
def test_download_csv_to_file(client, tmp_path):

tests/unit/test_geocode.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ def sample_payload() -> dict:
5353
def test_geocode_single(client, httpx_mock):
5454
# Arrange: stub the API call with a callback to inspect the request
5555
def response_callback(request):
56-
print("\nActual request:", request.url)
57-
print("Actual request headers:", dict(request.headers))
5856
return httpx.Response(200, json=sample_payload())
5957

6058
httpx_mock.add_callback(

0 commit comments

Comments
 (0)