permisapi-client 0.2.0__tar.gz → 0.3.0__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.
- {permisapi_client-0.2.0 → permisapi_client-0.3.0}/PKG-INFO +1 -1
- {permisapi_client-0.2.0 → permisapi_client-0.3.0}/permisapi_client/client.py +81 -0
- {permisapi_client-0.2.0 → permisapi_client-0.3.0}/pyproject.toml +1 -1
- {permisapi_client-0.2.0 → permisapi_client-0.3.0}/tests/test_client.py +70 -0
- {permisapi_client-0.2.0 → permisapi_client-0.3.0}/.gitignore +0 -0
- {permisapi_client-0.2.0 → permisapi_client-0.3.0}/README.md +0 -0
- {permisapi_client-0.2.0 → permisapi_client-0.3.0}/permisapi_client/__init__.py +0 -0
- {permisapi_client-0.2.0 → permisapi_client-0.3.0}/permisapi_client/exceptions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: permisapi-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Client Python officiel pour PermisAPI : permis de construire France en API REST.
|
|
5
5
|
Project-URL: Homepage, https://permisapi.fr
|
|
6
6
|
Project-URL: Documentation, https://permisapi.fr/docs
|
|
@@ -130,6 +130,54 @@ class _PermitsResource:
|
|
|
130
130
|
params["max_distance"] = max_distance
|
|
131
131
|
return self._c._get(f"/v1/permits/{num_pa}/dvf", params=params or None)
|
|
132
132
|
|
|
133
|
+
def export(
|
|
134
|
+
self,
|
|
135
|
+
*,
|
|
136
|
+
dep_code: str | None = None,
|
|
137
|
+
comm_code: str | None = None,
|
|
138
|
+
permit_type: str | None = None,
|
|
139
|
+
date_from: str | None = None,
|
|
140
|
+
date_to: str | None = None,
|
|
141
|
+
etat_pa: int | None = None,
|
|
142
|
+
max_rows: int | None = None,
|
|
143
|
+
) -> bytes:
|
|
144
|
+
"""GET /v1/permits/export : export streaming CSV bulk (Business+).
|
|
145
|
+
|
|
146
|
+
Plans Business et Enterprise uniquement (403 si plan inferieur).
|
|
147
|
+
Cap par defaut : 100k lignes Business, 1M Enterprise. Le user
|
|
148
|
+
peut reduire via `max_rows` mais pas augmenter.
|
|
149
|
+
|
|
150
|
+
Retourne le contenu CSV brut en bytes UTF-8 (header + rows). Pour
|
|
151
|
+
sauver dans un fichier :
|
|
152
|
+
|
|
153
|
+
csv_bytes = client.permits.export(dep_code="75")
|
|
154
|
+
with open("paris.csv", "wb") as f:
|
|
155
|
+
f.write(csv_bytes)
|
|
156
|
+
|
|
157
|
+
Pour les gros exports (>100 MB), prefere le streaming via
|
|
158
|
+
`client._http.stream("GET", "/v1/permits/export", params=...)`.
|
|
159
|
+
"""
|
|
160
|
+
params: dict[str, Any] = {}
|
|
161
|
+
if dep_code is not None:
|
|
162
|
+
params["dep_code"] = dep_code
|
|
163
|
+
if comm_code is not None:
|
|
164
|
+
params["comm_code"] = comm_code
|
|
165
|
+
if permit_type is not None:
|
|
166
|
+
params["permit_type"] = permit_type
|
|
167
|
+
if date_from is not None:
|
|
168
|
+
params["date_from"] = date_from
|
|
169
|
+
if date_to is not None:
|
|
170
|
+
params["date_to"] = date_to
|
|
171
|
+
if etat_pa is not None:
|
|
172
|
+
params["etat_pa"] = etat_pa
|
|
173
|
+
if max_rows is not None:
|
|
174
|
+
params["max_rows"] = max_rows
|
|
175
|
+
# Sortie binaire (CSV) -> on contourne _get qui parse en JSON.
|
|
176
|
+
url = f"{self._c.base_url}/v1/permits/export"
|
|
177
|
+
resp = self._c._http.get(url, params=params or None)
|
|
178
|
+
_raise_for_status(resp)
|
|
179
|
+
return resp.content
|
|
180
|
+
|
|
133
181
|
|
|
134
182
|
class _StatsResource:
|
|
135
183
|
def __init__(self, client: PermisAPI) -> None:
|
|
@@ -357,6 +405,39 @@ class _AsyncPermitsResource:
|
|
|
357
405
|
params["max_distance"] = max_distance
|
|
358
406
|
return await self._c._get(f"/v1/permits/{num_pa}/dvf", params=params or None)
|
|
359
407
|
|
|
408
|
+
async def export(
|
|
409
|
+
self,
|
|
410
|
+
*,
|
|
411
|
+
dep_code: str | None = None,
|
|
412
|
+
comm_code: str | None = None,
|
|
413
|
+
permit_type: str | None = None,
|
|
414
|
+
date_from: str | None = None,
|
|
415
|
+
date_to: str | None = None,
|
|
416
|
+
etat_pa: int | None = None,
|
|
417
|
+
max_rows: int | None = None,
|
|
418
|
+
) -> bytes:
|
|
419
|
+
"""GET /v1/permits/export : version async. Plans Business+ only.
|
|
420
|
+
Voir _PermitsResource.export pour la doc complete des params."""
|
|
421
|
+
params: dict[str, Any] = {}
|
|
422
|
+
if dep_code is not None:
|
|
423
|
+
params["dep_code"] = dep_code
|
|
424
|
+
if comm_code is not None:
|
|
425
|
+
params["comm_code"] = comm_code
|
|
426
|
+
if permit_type is not None:
|
|
427
|
+
params["permit_type"] = permit_type
|
|
428
|
+
if date_from is not None:
|
|
429
|
+
params["date_from"] = date_from
|
|
430
|
+
if date_to is not None:
|
|
431
|
+
params["date_to"] = date_to
|
|
432
|
+
if etat_pa is not None:
|
|
433
|
+
params["etat_pa"] = etat_pa
|
|
434
|
+
if max_rows is not None:
|
|
435
|
+
params["max_rows"] = max_rows
|
|
436
|
+
url = f"{self._c.base_url}/v1/permits/export"
|
|
437
|
+
resp = await self._c._http.get(url, params=params or None)
|
|
438
|
+
_raise_for_status(resp)
|
|
439
|
+
return resp.content
|
|
440
|
+
|
|
360
441
|
|
|
361
442
|
class _AsyncStatsResource:
|
|
362
443
|
def __init__(self, client: AsyncPermisAPI) -> None:
|
|
@@ -245,6 +245,76 @@ async def test_async_permits_dvf() -> None:
|
|
|
245
245
|
assert result["num_pa"] == "PC-DVF-A"
|
|
246
246
|
|
|
247
247
|
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
# Export bulk CSV (Business+)
|
|
250
|
+
# ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@respx.mock
|
|
254
|
+
def test_permits_export_returns_csv_bytes() -> None:
|
|
255
|
+
"""client.permits.export() retourne le CSV brut en bytes."""
|
|
256
|
+
csv_body = (
|
|
257
|
+
b"num_pa,dep_code,comm_code,permit_type\n"
|
|
258
|
+
b"PC-1,75,75118,PC_LOGEMENT\n"
|
|
259
|
+
b"PC-2,75,75118,PC_LOCAUX\n"
|
|
260
|
+
)
|
|
261
|
+
respx.get(f"{BASE}/v1/permits/export").respond(
|
|
262
|
+
content=csv_body,
|
|
263
|
+
headers={"content-type": "text/csv"},
|
|
264
|
+
)
|
|
265
|
+
with PermisAPI(api_key="pk_test_abc") as client:
|
|
266
|
+
result = client.permits.export(dep_code="75")
|
|
267
|
+
assert isinstance(result, bytes)
|
|
268
|
+
assert b"PC-1,75" in result
|
|
269
|
+
assert result.startswith(b"num_pa,")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@respx.mock
|
|
273
|
+
def test_permits_export_passes_filters_as_query() -> None:
|
|
274
|
+
route = respx.get(f"{BASE}/v1/permits/export").respond(content=b"num_pa\n")
|
|
275
|
+
with PermisAPI(api_key="pk_test_abc") as client:
|
|
276
|
+
client.permits.export(
|
|
277
|
+
dep_code="75",
|
|
278
|
+
comm_code="75118",
|
|
279
|
+
permit_type="PC_LOGEMENT",
|
|
280
|
+
date_from="2024-01-01",
|
|
281
|
+
date_to="2024-12-31",
|
|
282
|
+
etat_pa=1,
|
|
283
|
+
max_rows=5000,
|
|
284
|
+
)
|
|
285
|
+
qs = dict(route.calls.last.request.url.params)
|
|
286
|
+
assert qs["dep_code"] == "75"
|
|
287
|
+
assert qs["comm_code"] == "75118"
|
|
288
|
+
assert qs["permit_type"] == "PC_LOGEMENT"
|
|
289
|
+
assert qs["date_from"] == "2024-01-01"
|
|
290
|
+
assert qs["max_rows"] == "5000"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@respx.mock
|
|
294
|
+
def test_permits_export_403_for_explorer_plan() -> None:
|
|
295
|
+
"""Plan inferieur a Business -> 403 Forbidden remonte tel quel."""
|
|
296
|
+
respx.get(f"{BASE}/v1/permits/export").respond(
|
|
297
|
+
status_code=403,
|
|
298
|
+
json={"error": "forbidden", "detail": "Plan Business+ requis"},
|
|
299
|
+
)
|
|
300
|
+
with PermisAPI(api_key="pk_test_abc") as client:
|
|
301
|
+
with pytest.raises(Exception):
|
|
302
|
+
client.permits.export()
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@pytest.mark.asyncio
|
|
306
|
+
@respx.mock
|
|
307
|
+
async def test_async_permits_export() -> None:
|
|
308
|
+
csv_body = b"num_pa,dep_code\nPC-A,75\n"
|
|
309
|
+
respx.get(f"{BASE}/v1/permits/export").respond(
|
|
310
|
+
content=csv_body, headers={"content-type": "text/csv"},
|
|
311
|
+
)
|
|
312
|
+
async with AsyncPermisAPI(api_key="pk_test_abc") as client:
|
|
313
|
+
result = await client.permits.export(dep_code="75")
|
|
314
|
+
assert isinstance(result, bytes)
|
|
315
|
+
assert b"PC-A,75" in result
|
|
316
|
+
|
|
317
|
+
|
|
248
318
|
def test_custom_base_url() -> None:
|
|
249
319
|
client = PermisAPI(api_key="pk_test_abc", base_url="http://localhost:8000/")
|
|
250
320
|
assert client.base_url == "http://localhost:8000"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|