parseapi 0.1.0__py3-none-any.whl

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.
parseapi/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Official parseAPI client for Python."""
2
+
3
+ from ._client import AsyncParseAPI, ParseAPI, ParseAPIError
4
+
5
+ __version__ = "0.1.0"
6
+ __all__ = ["ParseAPI", "AsyncParseAPI", "ParseAPIError", "__version__"]
parseapi/_client.py ADDED
@@ -0,0 +1,520 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import random
5
+ import time
6
+ from typing import Any, Dict, Optional
7
+ from urllib.parse import quote
8
+
9
+ import httpx
10
+
11
+ VERSION = "0.1.0"
12
+ DEFAULT_BASE_URL = "https://api.parseapi.com"
13
+ DEFAULT_TIMEOUT = 10.0
14
+ DEFAULT_RETRIES = 2
15
+ RETRY_STATUS = {429, 500, 502, 503, 504}
16
+ RETRY_AFTER_CAP = 5.0
17
+
18
+ Json = Dict[str, Any]
19
+
20
+
21
+ class ParseAPIError(Exception):
22
+ """Every non-2xx response from the API. Branch on `code`, never on the message."""
23
+
24
+ def __init__(self, status: int, code: str, message: str, docs: Optional[str], request_id: Optional[str]):
25
+ super().__init__(message)
26
+ self.status = status
27
+ self.code = code
28
+ self.docs = docs
29
+ self.request_id = request_id
30
+
31
+
32
+ def _seg(value: Any) -> str:
33
+ return quote(str(value), safe="")
34
+
35
+
36
+ def _retry_delay(attempt: int, retry_after: Optional[str]) -> float:
37
+ if retry_after:
38
+ try:
39
+ seconds = float(retry_after)
40
+ if seconds >= 0:
41
+ return min(seconds, RETRY_AFTER_CAP)
42
+ except ValueError:
43
+ pass
44
+ return random.random() * 0.25 * (2**attempt)
45
+
46
+
47
+ def _error_from(response: httpx.Response) -> ParseAPIError:
48
+ try:
49
+ body = response.json()
50
+ except Exception:
51
+ body = {}
52
+ if not isinstance(body, dict):
53
+ body = {}
54
+ return ParseAPIError(
55
+ status=response.status_code,
56
+ code=body.get("code") if isinstance(body.get("code"), str) else "unknown_error",
57
+ message=body.get("message")
58
+ if isinstance(body.get("message"), str)
59
+ else f"Request failed with status {response.status_code}",
60
+ docs=body.get("docs") if isinstance(body.get("docs"), str) else None,
61
+ request_id=body.get("request_id") if isinstance(body.get("request_id"), str) else None,
62
+ )
63
+
64
+
65
+ def _clean(params: Dict[str, Any]) -> Dict[str, Any]:
66
+ return {name: value for name, value in params.items() if value is not None and value is not False}
67
+
68
+
69
+ class _Config:
70
+ def __init__(
71
+ self,
72
+ api_key: Optional[str],
73
+ base_url: Optional[str],
74
+ timeout: Optional[float],
75
+ retries: Optional[int],
76
+ ):
77
+ key = api_key or os.environ.get("PARSEAPI_KEY")
78
+ if not key:
79
+ raise ValueError("parseapi: missing API key. Pass one or set PARSEAPI_KEY.")
80
+ self.api_key = key
81
+ self.base_url = (base_url or os.environ.get("PARSEAPI_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
82
+ self.timeout = DEFAULT_TIMEOUT if timeout is None else timeout
83
+ self.retries = DEFAULT_RETRIES if retries is None else retries
84
+
85
+ def headers(self) -> Dict[str, str]:
86
+ return {"X-API-Key": self.api_key, "User-Agent": f"parseapi-python/{VERSION}"}
87
+
88
+
89
+ class ParseAPI:
90
+ """Synchronous client. `parse = ParseAPI()` reads PARSEAPI_KEY from the env."""
91
+
92
+ def __init__(
93
+ self,
94
+ api_key: Optional[str] = None,
95
+ *,
96
+ base_url: Optional[str] = None,
97
+ timeout: Optional[float] = None,
98
+ retries: Optional[int] = None,
99
+ transport: Optional[httpx.BaseTransport] = None,
100
+ ):
101
+ self._config = _Config(api_key, base_url, timeout, retries)
102
+ self._http = httpx.Client(
103
+ base_url=self._config.base_url,
104
+ timeout=self._config.timeout,
105
+ headers=self._config.headers(),
106
+ transport=transport,
107
+ )
108
+ self.ip = _IpSync(self)
109
+ self.continent = _ContinentSync(self)
110
+ self.country = _CountrySync(self)
111
+ self.state = _StateSync(self)
112
+ self.city = _CitySync(self)
113
+ self.postal = _PostalSync(self)
114
+ self.currency = _CurrencySync(self)
115
+ self.holiday = _HolidaySync(self)
116
+ self.emoji = _EmojiSync(self)
117
+
118
+ def close(self) -> None:
119
+ self._http.close()
120
+
121
+ def __enter__(self) -> "ParseAPI":
122
+ return self
123
+
124
+ def __exit__(self, *exc: Any) -> None:
125
+ self.close()
126
+
127
+ def _get(self, path: str, params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None) -> Json:
128
+ attempt = 0
129
+ while True:
130
+ try:
131
+ response = self._http.get(path, params=_clean(params or {}), headers=headers)
132
+ except httpx.HTTPError:
133
+ if attempt < self._config.retries:
134
+ time.sleep(_retry_delay(attempt, None))
135
+ attempt += 1
136
+ continue
137
+ raise
138
+ if response.is_success:
139
+ return response.json()
140
+ if response.status_code in RETRY_STATUS and attempt < self._config.retries:
141
+ time.sleep(_retry_delay(attempt, response.headers.get("Retry-After")))
142
+ attempt += 1
143
+ continue
144
+ raise _error_from(response)
145
+
146
+ # Plain methods (no subresources)
147
+
148
+ def district(self, code: str, *, country: Optional[str] = None) -> Json:
149
+ return self._get(f"/district/{_seg(code)}", {"country": country})
150
+
151
+ def email(self, email: str, *, deep: bool = False) -> Json:
152
+ return self._get(f"/email/{_seg(email)}", {"deep": deep})
153
+
154
+ def phone(self, number: str, *, country: Optional[str] = None, deep: bool = False) -> Json:
155
+ return self._get(f"/phone/{_seg(number)}", {"country": country, "deep": deep})
156
+
157
+ def domain(self, domain: str, *, deep: bool = False) -> Json:
158
+ return self._get(f"/domain/{_seg(domain)}", {"deep": deep})
159
+
160
+ def mx(self, domain: str) -> Json:
161
+ return self._get(f"/mx/{_seg(domain)}")
162
+
163
+ def useragent(self, ua: str, *, deep: bool = False) -> Json:
164
+ return self._get("/useragent", {"deep": deep}, headers={"User-Agent": ua})
165
+
166
+ def timezone(self, id: str, *, at: Optional[str] = None) -> Json:
167
+ return self._get(f"/timezone/{_seg(id)}", {"at": at})
168
+
169
+ def language(self, code: str) -> Json:
170
+ return self._get(f"/language/{_seg(code)}")
171
+
172
+ def elevation(self, lat: float, lon: float) -> Json:
173
+ return self._get("/elevation", {"lat": lat, "lon": lon})
174
+
175
+ def point(self, lat: float, lon: float, *, deep: bool = False) -> Json:
176
+ return self._get("/point", {"lat": lat, "lon": lon, "deep": deep})
177
+
178
+ def weather(self, lat: float, lon: float, *, deep: bool = False) -> Json:
179
+ return self._get("/weather", {"lat": lat, "lon": lon, "deep": deep})
180
+
181
+
182
+ class _IpSync:
183
+ def __init__(self, client: ParseAPI):
184
+ self._client = client
185
+
186
+ def __call__(self, ip: str, *, deep: bool = False) -> Json:
187
+ return self._client._get(f"/ip/{_seg(ip)}", {"deep": deep})
188
+
189
+ def self(self, *, deep: bool = False) -> Json:
190
+ return self._client._get("/ip", {"deep": deep})
191
+
192
+
193
+ class _ContinentSync:
194
+ def __init__(self, client: ParseAPI):
195
+ self._client = client
196
+
197
+ def __call__(self, code: str) -> Json:
198
+ return self._client._get(f"/continent/{_seg(code)}")
199
+
200
+ def countries(self, code: str) -> Json:
201
+ return self._client._get(f"/continent/{_seg(code)}/countries")
202
+
203
+
204
+ class _CountrySync:
205
+ def __init__(self, client: ParseAPI):
206
+ self._client = client
207
+
208
+ def __call__(self, code: str) -> Json:
209
+ return self._client._get(f"/country/{_seg(code)}")
210
+
211
+ def states(self, code: str) -> Json:
212
+ return self._client._get(f"/country/{_seg(code)}/states")
213
+
214
+
215
+ class _StateSync:
216
+ def __init__(self, client: ParseAPI):
217
+ self._client = client
218
+
219
+ def __call__(self, code: str, *, country: str) -> Json:
220
+ return self._client._get(f"/state/{_seg(code)}", {"country": country})
221
+
222
+ def districts(self, code: str, *, country: str) -> Json:
223
+ return self._client._get(f"/state/{_seg(code)}/districts", {"country": country})
224
+
225
+
226
+ class _CitySync:
227
+ def __init__(self, client: ParseAPI):
228
+ self._client = client
229
+
230
+ def __call__(self, name: str, *, country: Optional[str] = None, state: Optional[str] = None) -> Json:
231
+ return self._client._get(f"/city/{_seg(name)}", {"country": country, "state": state})
232
+
233
+ def id(self, id: str) -> Json:
234
+ return self._client._get(f"/city/id/{_seg(id)}")
235
+
236
+ def search(
237
+ self,
238
+ q: str,
239
+ *,
240
+ country: Optional[str] = None,
241
+ state: Optional[str] = None,
242
+ limit: Optional[int] = None,
243
+ ) -> Json:
244
+ return self._client._get("/city", {"q": q, "country": country, "state": state, "limit": limit})
245
+
246
+ def nearest(self, lat: float, lon: float) -> Json:
247
+ return self._client._get("/city", {"lat": lat, "lon": lon})
248
+
249
+
250
+ class _PostalSync:
251
+ def __init__(self, client: ParseAPI):
252
+ self._client = client
253
+
254
+ def __call__(self, code: str, *, country: str) -> Json:
255
+ return self._client._get(f"/postal/{_seg(code)}", {"country": country})
256
+
257
+ def nearby(
258
+ self,
259
+ code: str,
260
+ *,
261
+ country: str,
262
+ radius: Optional[float] = None,
263
+ unit: Optional[str] = None,
264
+ ) -> Json:
265
+ return self._client._get(f"/postal/{_seg(code)}/nearby", {"country": country, "radius": radius, "unit": unit})
266
+
267
+ def distance(self, from_postal: str, to_postal: str, *, country: str) -> Json:
268
+ return self._client._get(f"/postal/{_seg(from_postal)}/distance/{_seg(to_postal)}", {"country": country})
269
+
270
+
271
+ class _CurrencySync:
272
+ def __init__(self, client: ParseAPI):
273
+ self._client = client
274
+
275
+ def __call__(self, code: str) -> Json:
276
+ return self._client._get(f"/currency/{_seg(code)}")
277
+
278
+ def rate(self, base: str, quote_currency: str) -> Json:
279
+ return self._client._get(f"/currency/{_seg(base)}/{_seg(quote_currency)}")
280
+
281
+
282
+ class _HolidaySync:
283
+ def __init__(self, client: ParseAPI):
284
+ self._client = client
285
+
286
+ def __call__(self, country: str, *, year: Optional[int] = None) -> Json:
287
+ return self._client._get(f"/holiday/{_seg(country)}", {"year": year})
288
+
289
+ def date(self, country: str, date: str) -> Json:
290
+ return self._client._get(f"/holiday/{_seg(country)}/{_seg(date)}")
291
+
292
+
293
+ class _EmojiSync:
294
+ def __init__(self, client: ParseAPI):
295
+ self._client = client
296
+
297
+ def __call__(self, emoji: str) -> Json:
298
+ return self._client._get(f"/emoji/{_seg(emoji)}")
299
+
300
+ def search(self, q: str, *, limit: Optional[int] = None) -> Json:
301
+ return self._client._get("/emoji", {"q": q, "limit": limit})
302
+
303
+
304
+ class AsyncParseAPI:
305
+ """Async client. `parse = AsyncParseAPI()` reads PARSEAPI_KEY from the env."""
306
+
307
+ def __init__(
308
+ self,
309
+ api_key: Optional[str] = None,
310
+ *,
311
+ base_url: Optional[str] = None,
312
+ timeout: Optional[float] = None,
313
+ retries: Optional[int] = None,
314
+ transport: Optional[httpx.AsyncBaseTransport] = None,
315
+ ):
316
+ self._config = _Config(api_key, base_url, timeout, retries)
317
+ self._http = httpx.AsyncClient(
318
+ base_url=self._config.base_url,
319
+ timeout=self._config.timeout,
320
+ headers=self._config.headers(),
321
+ transport=transport,
322
+ )
323
+ self.ip = _IpAsync(self)
324
+ self.continent = _ContinentAsync(self)
325
+ self.country = _CountryAsync(self)
326
+ self.state = _StateAsync(self)
327
+ self.city = _CityAsync(self)
328
+ self.postal = _PostalAsync(self)
329
+ self.currency = _CurrencyAsync(self)
330
+ self.holiday = _HolidayAsync(self)
331
+ self.emoji = _EmojiAsync(self)
332
+
333
+ async def close(self) -> None:
334
+ await self._http.aclose()
335
+
336
+ async def __aenter__(self) -> "AsyncParseAPI":
337
+ return self
338
+
339
+ async def __aexit__(self, *exc: Any) -> None:
340
+ await self.close()
341
+
342
+ async def _get(
343
+ self, path: str, params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None
344
+ ) -> Json:
345
+ import asyncio
346
+
347
+ attempt = 0
348
+ while True:
349
+ try:
350
+ response = await self._http.get(path, params=_clean(params or {}), headers=headers)
351
+ except httpx.HTTPError:
352
+ if attempt < self._config.retries:
353
+ await asyncio.sleep(_retry_delay(attempt, None))
354
+ attempt += 1
355
+ continue
356
+ raise
357
+ if response.is_success:
358
+ return response.json()
359
+ if response.status_code in RETRY_STATUS and attempt < self._config.retries:
360
+ await asyncio.sleep(_retry_delay(attempt, response.headers.get("Retry-After")))
361
+ attempt += 1
362
+ continue
363
+ raise _error_from(response)
364
+
365
+ async def district(self, code: str, *, country: Optional[str] = None) -> Json:
366
+ return await self._get(f"/district/{_seg(code)}", {"country": country})
367
+
368
+ async def email(self, email: str, *, deep: bool = False) -> Json:
369
+ return await self._get(f"/email/{_seg(email)}", {"deep": deep})
370
+
371
+ async def phone(self, number: str, *, country: Optional[str] = None, deep: bool = False) -> Json:
372
+ return await self._get(f"/phone/{_seg(number)}", {"country": country, "deep": deep})
373
+
374
+ async def domain(self, domain: str, *, deep: bool = False) -> Json:
375
+ return await self._get(f"/domain/{_seg(domain)}", {"deep": deep})
376
+
377
+ async def mx(self, domain: str) -> Json:
378
+ return await self._get(f"/mx/{_seg(domain)}")
379
+
380
+ async def useragent(self, ua: str, *, deep: bool = False) -> Json:
381
+ return await self._get("/useragent", {"deep": deep}, headers={"User-Agent": ua})
382
+
383
+ async def timezone(self, id: str, *, at: Optional[str] = None) -> Json:
384
+ return await self._get(f"/timezone/{_seg(id)}", {"at": at})
385
+
386
+ async def language(self, code: str) -> Json:
387
+ return await self._get(f"/language/{_seg(code)}")
388
+
389
+ async def elevation(self, lat: float, lon: float) -> Json:
390
+ return await self._get("/elevation", {"lat": lat, "lon": lon})
391
+
392
+ async def point(self, lat: float, lon: float, *, deep: bool = False) -> Json:
393
+ return await self._get("/point", {"lat": lat, "lon": lon, "deep": deep})
394
+
395
+ async def weather(self, lat: float, lon: float, *, deep: bool = False) -> Json:
396
+ return await self._get("/weather", {"lat": lat, "lon": lon, "deep": deep})
397
+
398
+
399
+ class _IpAsync:
400
+ def __init__(self, client: AsyncParseAPI):
401
+ self._client = client
402
+
403
+ async def __call__(self, ip: str, *, deep: bool = False) -> Json:
404
+ return await self._client._get(f"/ip/{_seg(ip)}", {"deep": deep})
405
+
406
+ async def self(self, *, deep: bool = False) -> Json:
407
+ return await self._client._get("/ip", {"deep": deep})
408
+
409
+
410
+ class _ContinentAsync:
411
+ def __init__(self, client: AsyncParseAPI):
412
+ self._client = client
413
+
414
+ async def __call__(self, code: str) -> Json:
415
+ return await self._client._get(f"/continent/{_seg(code)}")
416
+
417
+ async def countries(self, code: str) -> Json:
418
+ return await self._client._get(f"/continent/{_seg(code)}/countries")
419
+
420
+
421
+ class _CountryAsync:
422
+ def __init__(self, client: AsyncParseAPI):
423
+ self._client = client
424
+
425
+ async def __call__(self, code: str) -> Json:
426
+ return await self._client._get(f"/country/{_seg(code)}")
427
+
428
+ async def states(self, code: str) -> Json:
429
+ return await self._client._get(f"/country/{_seg(code)}/states")
430
+
431
+
432
+ class _StateAsync:
433
+ def __init__(self, client: AsyncParseAPI):
434
+ self._client = client
435
+
436
+ async def __call__(self, code: str, *, country: str) -> Json:
437
+ return await self._client._get(f"/state/{_seg(code)}", {"country": country})
438
+
439
+ async def districts(self, code: str, *, country: str) -> Json:
440
+ return await self._client._get(f"/state/{_seg(code)}/districts", {"country": country})
441
+
442
+
443
+ class _CityAsync:
444
+ def __init__(self, client: AsyncParseAPI):
445
+ self._client = client
446
+
447
+ async def __call__(self, name: str, *, country: Optional[str] = None, state: Optional[str] = None) -> Json:
448
+ return await self._client._get(f"/city/{_seg(name)}", {"country": country, "state": state})
449
+
450
+ async def id(self, id: str) -> Json:
451
+ return await self._client._get(f"/city/id/{_seg(id)}")
452
+
453
+ async def search(
454
+ self,
455
+ q: str,
456
+ *,
457
+ country: Optional[str] = None,
458
+ state: Optional[str] = None,
459
+ limit: Optional[int] = None,
460
+ ) -> Json:
461
+ return await self._client._get("/city", {"q": q, "country": country, "state": state, "limit": limit})
462
+
463
+ async def nearest(self, lat: float, lon: float) -> Json:
464
+ return await self._client._get("/city", {"lat": lat, "lon": lon})
465
+
466
+
467
+ class _PostalAsync:
468
+ def __init__(self, client: AsyncParseAPI):
469
+ self._client = client
470
+
471
+ async def __call__(self, code: str, *, country: str) -> Json:
472
+ return await self._client._get(f"/postal/{_seg(code)}", {"country": country})
473
+
474
+ async def nearby(
475
+ self,
476
+ code: str,
477
+ *,
478
+ country: str,
479
+ radius: Optional[float] = None,
480
+ unit: Optional[str] = None,
481
+ ) -> Json:
482
+ return await self._client._get(
483
+ f"/postal/{_seg(code)}/nearby", {"country": country, "radius": radius, "unit": unit}
484
+ )
485
+
486
+ async def distance(self, from_postal: str, to_postal: str, *, country: str) -> Json:
487
+ return await self._client._get(f"/postal/{_seg(from_postal)}/distance/{_seg(to_postal)}", {"country": country})
488
+
489
+
490
+ class _CurrencyAsync:
491
+ def __init__(self, client: AsyncParseAPI):
492
+ self._client = client
493
+
494
+ async def __call__(self, code: str) -> Json:
495
+ return await self._client._get(f"/currency/{_seg(code)}")
496
+
497
+ async def rate(self, base: str, quote_currency: str) -> Json:
498
+ return await self._client._get(f"/currency/{_seg(base)}/{_seg(quote_currency)}")
499
+
500
+
501
+ class _HolidayAsync:
502
+ def __init__(self, client: AsyncParseAPI):
503
+ self._client = client
504
+
505
+ async def __call__(self, country: str, *, year: Optional[int] = None) -> Json:
506
+ return await self._client._get(f"/holiday/{_seg(country)}", {"year": year})
507
+
508
+ async def date(self, country: str, date: str) -> Json:
509
+ return await self._client._get(f"/holiday/{_seg(country)}/{_seg(date)}")
510
+
511
+
512
+ class _EmojiAsync:
513
+ def __init__(self, client: AsyncParseAPI):
514
+ self._client = client
515
+
516
+ async def __call__(self, emoji: str) -> Json:
517
+ return await self._client._get(f"/emoji/{_seg(emoji)}")
518
+
519
+ async def search(self, q: str, *, limit: Optional[int] = None) -> Json:
520
+ return await self._client._get("/emoji", {"q": q, "limit": limit})
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: parseapi
3
+ Version: 0.1.0
4
+ Summary: Official parseAPI client for Python. One key, minimal JSON, fast.
5
+ Project-URL: Homepage, https://parseapi.com
6
+ Project-URL: Documentation, https://parseapi.com/docs
7
+ Project-URL: Repository, https://github.com/parseapi/python
8
+ Author-email: parseAPI <hello@parseapi.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: currency,email validation,geolocation,ip,parseapi,phone validation,postal,timezone,weather
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.9
17
+ Requires-Dist: httpx>=0.24
18
+ Description-Content-Type: text/markdown
19
+
20
+ # parseapi
21
+
22
+ Official parseAPI client for Python.
23
+
24
+ ```bash
25
+ pip install parseapi
26
+ ```
27
+
28
+ ```python
29
+ from parseapi import ParseAPI
30
+
31
+ parse = ParseAPI("your-api-key")
32
+ country = parse.country("US")
33
+ ```
34
+
35
+ Get a key at [parseapi.com](https://parseapi.com). The client also reads `PARSEAPI_KEY` from the environment.
36
+
37
+ ## Calls
38
+
39
+ One method per endpoint, named after the route.
40
+
41
+ ```python
42
+ parse.ip("8.8.8.8")
43
+ parse.ip.self()
44
+ parse.email("hello@gmail.com")
45
+ parse.phone("+14155552671")
46
+ parse.postal("28202", country="US")
47
+ parse.postal.nearby("28202", country="US", radius=40)
48
+ parse.postal.distance("28202", "10001", country="US")
49
+ parse.city("charlotte", country="US")
50
+ parse.city.id("city_mb8mbqrkz8zb")
51
+ parse.city.search("char", country="US", limit=10)
52
+ parse.city.nearest(35.2271, -80.8431)
53
+ parse.country("US")
54
+ parse.country.states("US")
55
+ parse.state("NC", country="US")
56
+ parse.state.districts("NC", country="US")
57
+ parse.district("37081")
58
+ parse.continent("NA")
59
+ parse.continent.countries("NA")
60
+ parse.currency("USD")
61
+ parse.currency.rate("USD", "EUR")
62
+ parse.language("en")
63
+ parse.timezone("America/New_York")
64
+ parse.holiday("US", year=2026)
65
+ parse.holiday.date("US", "2026-12-25")
66
+ parse.elevation(35.2271, -80.8431)
67
+ parse.point(36.0726, -79.792)
68
+ parse.weather(40.7128, -74.006)
69
+ parse.domain("example.com")
70
+ parse.mx("example.com")
71
+ parse.useragent(ua_string)
72
+ parse.emoji("rocket")
73
+ parse.emoji.search("fire")
74
+ ```
75
+
76
+ Responses are plain dicts, exactly the JSON the API returns.
77
+
78
+ ## Async
79
+
80
+ Same surface, `await` everything.
81
+
82
+ ```python
83
+ from parseapi import AsyncParseAPI
84
+
85
+ parse = AsyncParseAPI("your-api-key")
86
+ country = await parse.country("US")
87
+ ```
88
+
89
+ ## Deep
90
+
91
+ Pass `deep=True` to include the nested `deep` object with richer fields.
92
+
93
+ ```python
94
+ ip = parse.ip("52.94.76.10", deep=True)
95
+ ip["deep"]["datacenter"] # True
96
+ ```
97
+
98
+ ## Errors
99
+
100
+ Every non-2xx response raises `ParseAPIError` with `status`, `code`, `docs`, and `request_id`. Branch on `code`.
101
+
102
+ ```python
103
+ from parseapi import ParseAPIError
104
+
105
+ try:
106
+ parse.city("atlantis")
107
+ except ParseAPIError as err:
108
+ if err.code == "not_found":
109
+ ... # no such city
110
+ ```
111
+
112
+ ## Options
113
+
114
+ ```python
115
+ parse = ParseAPI(
116
+ "your-api-key",
117
+ timeout=10.0, # per-attempt timeout in seconds
118
+ retries=2, # automatic retries on network errors, 429, and 5xx
119
+ )
120
+ ```
121
+
122
+ Requires Python 3.9 or later. One dependency (httpx).
123
+
124
+ ## Docs
125
+
126
+ Full field reference for every endpoint: [parseapi.com/docs](https://parseapi.com/docs)
@@ -0,0 +1,6 @@
1
+ parseapi/__init__.py,sha256=axNmGAESTPPh1WlmkULnS3oPONXhCclt1LtokECJwHA,199
2
+ parseapi/_client.py,sha256=zgQ5ZPq50BY3I5hjv31V4GZ72LEN0ME1NfY1Y0z5uaU,18240
3
+ parseapi-0.1.0.dist-info/METADATA,sha256=TBJNDHZYqmVeh3nzu8GeJSckFIV45lVQ6XpFFp0aeVQ,3183
4
+ parseapi-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ parseapi-0.1.0.dist-info/licenses/LICENSE,sha256=tdXjNJFfw9PIftBGjZIHyLHbRN6-Y7COFUVuyobBqWY,1065
6
+ parseapi-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 parseAPI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.