htag-sdk 1.9.2__tar.gz → 1.9.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/PKG-INFO +1 -1
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/pyproject.toml +1 -1
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/__init__.py +6 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/markets/models.py +1 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/property/__init__.py +6 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/property/async_client.py +21 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/property/client.py +29 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/property/models.py +40 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/reference/models.py +1 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/tests/test_domains.py +34 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/.gitignore +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/README.md +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/_async_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/_base.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/_exceptions.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/_types.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/address/__init__.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/address/async_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/address/client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/address/models.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/agents/__init__.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/agents/async_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/agents/client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/agents/models.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/demographics/__init__.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/demographics/async_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/demographics/client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/demographics/models.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/economics/__init__.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/economics/async_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/economics/client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/economics/models.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/intent_hub/__init__.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/intent_hub/async_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/intent_hub/client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/intent_hub/models.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/internal/__init__.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/internal/async_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/internal/client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/markets/__init__.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/markets/async_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/markets/client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/py.typed +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/reference/__init__.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/reference/async_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/reference/client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/spatial/__init__.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/spatial/async_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/spatial/client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/src/htag_sdk/spatial/models.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/tests/__init__.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/tests/test_base.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/tests/test_client.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/tests/test_exceptions.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/tests/test_models.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/tests/test_spatial_viewport.py +0 -0
- {htag_sdk-1.9.2 → htag_sdk-1.9.3}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: htag-sdk
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.3
|
|
4
4
|
Summary: Official Python SDK for the HtAG Location Intelligence API — address search, property data, and market analytics for Australia
|
|
5
5
|
Project-URL: Homepage, https://developer.htagai.com
|
|
6
6
|
Project-URL: Documentation, https://developer.htagai.com
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "htag-sdk"
|
|
7
|
-
version = "1.9.
|
|
7
|
+
version = "1.9.3"
|
|
8
8
|
description = "Official Python SDK for the HtAG Location Intelligence API — address search, property data, and market analytics for Australia"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -62,8 +62,11 @@ from htag_sdk.property.models import (
|
|
|
62
62
|
PropertyAvmResponse,
|
|
63
63
|
PropertyEstimatesRecord,
|
|
64
64
|
PropertyEstimatesResponse,
|
|
65
|
+
PropertyHistoryResponse,
|
|
65
66
|
PropertyMarketRecord,
|
|
66
67
|
PropertyMarketResponse,
|
|
68
|
+
PropertyRentedHistoryRecord,
|
|
69
|
+
PropertySoldHistoryRecord,
|
|
67
70
|
PropertySummaryRecord,
|
|
68
71
|
PropertySummaryResponse,
|
|
69
72
|
RentedPropertiesResponse,
|
|
@@ -233,8 +236,11 @@ __all__ = [
|
|
|
233
236
|
"PropertyAvmResponse",
|
|
234
237
|
"PropertyEstimatesRecord",
|
|
235
238
|
"PropertyEstimatesResponse",
|
|
239
|
+
"PropertyHistoryResponse",
|
|
236
240
|
"PropertyMarketRecord",
|
|
237
241
|
"PropertyMarketResponse",
|
|
242
|
+
"PropertyRentedHistoryRecord",
|
|
243
|
+
"PropertySoldHistoryRecord",
|
|
238
244
|
"PropertySummaryRecord",
|
|
239
245
|
"PropertySummaryResponse",
|
|
240
246
|
"RentedPropertiesResponse",
|
|
@@ -722,6 +722,7 @@ class MarketFundamentalsRecord(BaseModel):
|
|
|
722
722
|
uh_ratio: Optional[float] = None
|
|
723
723
|
uhv_ratio: Optional[float] = None
|
|
724
724
|
years_to_own: Optional[float] = None
|
|
725
|
+
non_res_ba_per_capita_value: Optional[float] = None
|
|
725
726
|
|
|
726
727
|
|
|
727
728
|
class MarketCycleRecord(BaseModel):
|
|
@@ -7,8 +7,11 @@ from htag_sdk.property.models import (
|
|
|
7
7
|
PropertyAvmResponse,
|
|
8
8
|
PropertyEstimatesRecord,
|
|
9
9
|
PropertyEstimatesResponse,
|
|
10
|
+
PropertyHistoryResponse,
|
|
10
11
|
PropertyMarketRecord,
|
|
11
12
|
PropertyMarketResponse,
|
|
13
|
+
PropertyRentedHistoryRecord,
|
|
14
|
+
PropertySoldHistoryRecord,
|
|
12
15
|
PropertySummaryRecord,
|
|
13
16
|
PropertySummaryResponse,
|
|
14
17
|
SoldPropertiesResponse,
|
|
@@ -22,8 +25,11 @@ __all__ = [
|
|
|
22
25
|
"PropertyAvmResponse",
|
|
23
26
|
"PropertyEstimatesRecord",
|
|
24
27
|
"PropertyEstimatesResponse",
|
|
28
|
+
"PropertyHistoryResponse",
|
|
25
29
|
"PropertyMarketRecord",
|
|
26
30
|
"PropertyMarketResponse",
|
|
31
|
+
"PropertyRentedHistoryRecord",
|
|
32
|
+
"PropertySoldHistoryRecord",
|
|
27
33
|
"PropertySummaryRecord",
|
|
28
34
|
"PropertySummaryResponse",
|
|
29
35
|
"SoldPropertiesResponse",
|
|
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, List, Optional, Union
|
|
|
6
6
|
|
|
7
7
|
from htag_sdk.property.models import (
|
|
8
8
|
PropertyEstimatesResponse,
|
|
9
|
+
PropertyHistoryResponse,
|
|
9
10
|
PropertyMarketResponse,
|
|
10
11
|
PropertySummaryResponse,
|
|
11
12
|
RentedPropertiesResponse,
|
|
@@ -230,6 +231,26 @@ class AsyncPropertyClient:
|
|
|
230
231
|
data = await self._client._request("GET", "/property/market", params=params)
|
|
231
232
|
return PropertyMarketResponse.model_validate(data)
|
|
232
233
|
|
|
234
|
+
async def history(
|
|
235
|
+
self,
|
|
236
|
+
*,
|
|
237
|
+
address_key: str,
|
|
238
|
+
sold: bool = True,
|
|
239
|
+
rented: bool = True,
|
|
240
|
+
) -> PropertyHistoryResponse:
|
|
241
|
+
"""Async: retrieve the full sale and/or rental history for an address.
|
|
242
|
+
|
|
243
|
+
See :meth:`htag_sdk.property.client.PropertyClient.history` for full
|
|
244
|
+
documentation.
|
|
245
|
+
"""
|
|
246
|
+
params = {
|
|
247
|
+
"address_key": address_key,
|
|
248
|
+
"sold": sold,
|
|
249
|
+
"rented": rented,
|
|
250
|
+
}
|
|
251
|
+
data = await self._client._request("GET", "/property/history", params=params)
|
|
252
|
+
return PropertyHistoryResponse.model_validate(data)
|
|
253
|
+
|
|
233
254
|
async def structural_adjustment(
|
|
234
255
|
self,
|
|
235
256
|
*,
|
|
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, List, Optional, Union
|
|
|
6
6
|
|
|
7
7
|
from htag_sdk.property.models import (
|
|
8
8
|
PropertyEstimatesResponse,
|
|
9
|
+
PropertyHistoryResponse,
|
|
9
10
|
PropertyMarketResponse,
|
|
10
11
|
PropertySummaryResponse,
|
|
11
12
|
RentedPropertiesResponse,
|
|
@@ -257,6 +258,34 @@ class PropertyClient:
|
|
|
257
258
|
data = self._client._request("GET", "/property/market", params=params)
|
|
258
259
|
return PropertyMarketResponse.model_validate(data)
|
|
259
260
|
|
|
261
|
+
def history(
|
|
262
|
+
self,
|
|
263
|
+
*,
|
|
264
|
+
address_key: str,
|
|
265
|
+
sold: bool = True,
|
|
266
|
+
rented: bool = True,
|
|
267
|
+
) -> PropertyHistoryResponse:
|
|
268
|
+
"""Retrieve the full sale and/or rental history for an address.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
address_key: Canonical HTAG address key.
|
|
272
|
+
sold: Include sale history (default ``True``).
|
|
273
|
+
rented: Include rental history (default ``True``). At least one of
|
|
274
|
+
``sold`` or ``rented`` must be ``True``.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
A :class:`PropertyHistoryResponse` with sale and rental timelines,
|
|
278
|
+
the canonical top-level ``property_type``, and a billable ``total``
|
|
279
|
+
row count.
|
|
280
|
+
"""
|
|
281
|
+
params = {
|
|
282
|
+
"address_key": address_key,
|
|
283
|
+
"sold": sold,
|
|
284
|
+
"rented": rented,
|
|
285
|
+
}
|
|
286
|
+
data = self._client._request("GET", "/property/history", params=params)
|
|
287
|
+
return PropertyHistoryResponse.model_validate(data)
|
|
288
|
+
|
|
260
289
|
def structural_adjustment(
|
|
261
290
|
self,
|
|
262
291
|
*,
|
|
@@ -137,6 +137,46 @@ class PropertyMarketResponse(BaseModel):
|
|
|
137
137
|
total: int
|
|
138
138
|
|
|
139
139
|
|
|
140
|
+
class PropertySoldHistoryRecord(BaseModel):
|
|
141
|
+
"""A single sale entry in the property history timeline."""
|
|
142
|
+
|
|
143
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
144
|
+
|
|
145
|
+
sale_date: Optional[str] = None
|
|
146
|
+
sale_price: Optional[int] = None
|
|
147
|
+
bedrooms: Optional[int] = None
|
|
148
|
+
bathrooms: Optional[int] = None
|
|
149
|
+
car_spaces: Optional[int] = None
|
|
150
|
+
land_area: Optional[float] = None
|
|
151
|
+
floor_area: Optional[float] = None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class PropertyRentedHistoryRecord(BaseModel):
|
|
155
|
+
"""A single rental entry in the property history timeline."""
|
|
156
|
+
|
|
157
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
158
|
+
|
|
159
|
+
list_date: Optional[str] = None
|
|
160
|
+
rent: Optional[float] = None
|
|
161
|
+
bedrooms: Optional[int] = None
|
|
162
|
+
bathrooms: Optional[int] = None
|
|
163
|
+
car_spaces: Optional[int] = None
|
|
164
|
+
land_area: Optional[float] = None
|
|
165
|
+
floor_area: Optional[float] = None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class PropertyHistoryResponse(BaseModel):
|
|
169
|
+
"""Response from the property history endpoint."""
|
|
170
|
+
|
|
171
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
172
|
+
|
|
173
|
+
address_key: str
|
|
174
|
+
property_type: Optional[str] = None
|
|
175
|
+
total: int = 0
|
|
176
|
+
sales: Optional[List[PropertySoldHistoryRecord]] = None
|
|
177
|
+
rentals: Optional[List[PropertyRentedHistoryRecord]] = None
|
|
178
|
+
|
|
179
|
+
|
|
140
180
|
class PropertyAvmRecord(BaseModel):
|
|
141
181
|
"""A single AVM valuation result."""
|
|
142
182
|
|
|
@@ -215,6 +215,40 @@ class TestPropertyClient:
|
|
|
215
215
|
assert result.results[0].address_key == "100102HICKOXSTREETTRARALGONVIC3844"
|
|
216
216
|
client.close()
|
|
217
217
|
|
|
218
|
+
def test_property_history_sends_correct_params(self):
|
|
219
|
+
client = HtAgApi(api_key="sk-test", environment="prod")
|
|
220
|
+
mock_resp = _mock_response(
|
|
221
|
+
200,
|
|
222
|
+
{
|
|
223
|
+
"address_key": "AK_HIST_1",
|
|
224
|
+
"property_type": "unit",
|
|
225
|
+
"total": 2,
|
|
226
|
+
"sales": [{"sale_date": "2025-10-13", "sale_price": 750000}],
|
|
227
|
+
"rentals": [{"list_date": "2024-05-04", "rent": 550.0}],
|
|
228
|
+
},
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
with patch.object(client._http, "request", return_value=mock_resp) as mock_req:
|
|
232
|
+
result = client.property.history(
|
|
233
|
+
address_key="AK_HIST_1",
|
|
234
|
+
sold=True,
|
|
235
|
+
rented=False,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
call_kwargs = mock_req.call_args
|
|
239
|
+
assert call_kwargs[0][0] == "GET"
|
|
240
|
+
assert "/property/history" in call_kwargs[0][1]
|
|
241
|
+
params = call_kwargs[1]["params"]
|
|
242
|
+
assert params["address_key"] == "AK_HIST_1"
|
|
243
|
+
assert params["sold"] == "true"
|
|
244
|
+
assert params["rented"] == "false"
|
|
245
|
+
assert result.address_key == "AK_HIST_1"
|
|
246
|
+
assert result.property_type == "unit"
|
|
247
|
+
assert result.total == 2
|
|
248
|
+
assert result.sales[0].sale_price == 750000
|
|
249
|
+
assert result.rentals[0].rent == 550.0
|
|
250
|
+
client.close()
|
|
251
|
+
|
|
218
252
|
def test_rented_search_property_type_list_param(self):
|
|
219
253
|
client = HtAgApi(api_key="sk-test", environment="prod")
|
|
220
254
|
mock_resp = _mock_response(200, {"results": [], "total": 0})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|