parseapi 1.1.0__tar.gz → 1.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.
- {parseapi-1.1.0 → parseapi-1.3.0}/CHANGELOG.md +4 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/PKG-INFO +17 -2
- {parseapi-1.1.0 → parseapi-1.3.0}/README.md +16 -1
- {parseapi-1.1.0 → parseapi-1.3.0}/api/public.json +46 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/pyproject.toml +1 -1
- {parseapi-1.1.0 → parseapi-1.3.0}/src/parseapi/__init__.py +1 -1
- {parseapi-1.1.0 → parseapi-1.3.0}/src/parseapi/_client.py +25 -3
- parseapi-1.3.0/tests/test_stack.py +251 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/.github/workflows/publish.yml +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/.github/workflows/test.yml +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/.gitignore +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/LICENSE +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/scripts/public_api.py +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/smoke/smoke.py +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/src/parseapi/py.typed +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/tests/test_client.py +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/tests/test_email_enrichment.py +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/tests/test_language.py +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/tests/test_name_formatting.py +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/tests/test_public_api.py +0 -0
- {parseapi-1.1.0 → parseapi-1.3.0}/tests/test_version.py +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.0 - Unreleased
|
|
4
|
+
|
|
5
|
+
Adds Stack site inventory lookup with eight technology category arrays, nullable versions, and explicit page coverage. Stack uses a 35-second default attempt timeout while explicit caller settings remain honored. Existing calls and API contract `2.0.0` remain unchanged.
|
|
6
|
+
|
|
3
7
|
## 1.1.0 - 2026-09-20
|
|
4
8
|
|
|
5
9
|
Email deep results now include nullable suggested first name, no-reply flag, plus-address tag, mail provider, verification status and reason. Existing lookup calls, retry defaults and API contract `2.0.0` remain unchanged. Missing details remain unknown, and suggested names do not verify identity.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: parseapi
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Official ParseAPI client for Python. One key, minimal JSON, fast.
|
|
5
5
|
Project-URL: Homepage, https://parseapi.com
|
|
6
6
|
Project-URL: Documentation, https://parseapi.com/docs
|
|
@@ -36,7 +36,7 @@ Get a key at [parseapi.com](https://parseapi.com). The client also reads `PARSEA
|
|
|
36
36
|
|
|
37
37
|
## API versions
|
|
38
38
|
|
|
39
|
-
Version 1.
|
|
39
|
+
Version 1.3.0 explicitly selects the API contract supported by this SDK. It sends `Parse-Version: 2.0.0` on every lookup so responses match the API contract supported by the package. Your key and the team's saved default stay the same.
|
|
40
40
|
|
|
41
41
|
Upgrade the dependency in staging, review the [release notes](https://parseapi.com/docs/releases), and test the application before deploying the same code and dependency version to production. Commit your dependency lockfile so the tested package travels with your deployment. Future major SDK upgrades can select a newer API contract.
|
|
42
42
|
|
|
@@ -293,3 +293,18 @@ basic = parse.time("America/New_York")
|
|
|
293
293
|
detail = parse.time("America/New_York", deep=True)
|
|
294
294
|
print(basic["at"], detail.get("deep", {}).get("next_dst"))
|
|
295
295
|
```
|
|
296
|
+
|
|
297
|
+
## Stack API
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
result = parse.stack("example.com")
|
|
301
|
+
# AsyncParseAPI exposes the same calls with await.
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Pass a public hostname without a scheme, path, port or IP address. Stack returns the checked URL and `checked_at` time, followed by `scope`, `pages` and `partial`. `scope` is `homepage` or `site`; `pages` counts successfully checked HTML pages. `partial` is true for homepage-only or incomplete bounded site checks. False means the known in-scope candidates were completed, not that every page on a website was visited. A homepage result has `scope: "homepage"`, `pages: 1` and `partial: true`.
|
|
305
|
+
|
|
306
|
+
`cms`, `servers`, `frameworks`, `ecommerce`, `analytics`, `chat`, `payments` and `hosting` are arrays because a site can use several technologies in each category. Each entry contains `technology`, `name` and nullable `version`. Technology codes are open strings. A successful check uses empty arrays for categories with no matches. When no HTML page could be checked, `checked_at` and all categories are null, `pages` is 0 and `partial` is null. Unknown or conflicting versions are null. Missing detections do not prove absence.
|
|
307
|
+
|
|
308
|
+
Successful checks may be reused for up to 24 hours. `pretty` optionally formats the wire JSON. Stack uses your plan's request allowance and API version 2.0.0 selected by this client.
|
|
309
|
+
|
|
310
|
+
Stack defaults to a 35-second transport timeout so a first scan has time to finish. Other lookups retain their 10-second default. An explicit client timeout takes precedence.
|
|
@@ -17,7 +17,7 @@ Get a key at [parseapi.com](https://parseapi.com). The client also reads `PARSEA
|
|
|
17
17
|
|
|
18
18
|
## API versions
|
|
19
19
|
|
|
20
|
-
Version 1.
|
|
20
|
+
Version 1.3.0 explicitly selects the API contract supported by this SDK. It sends `Parse-Version: 2.0.0` on every lookup so responses match the API contract supported by the package. Your key and the team's saved default stay the same.
|
|
21
21
|
|
|
22
22
|
Upgrade the dependency in staging, review the [release notes](https://parseapi.com/docs/releases), and test the application before deploying the same code and dependency version to production. Commit your dependency lockfile so the tested package travels with your deployment. Future major SDK upgrades can select a newer API contract.
|
|
23
23
|
|
|
@@ -274,3 +274,18 @@ basic = parse.time("America/New_York")
|
|
|
274
274
|
detail = parse.time("America/New_York", deep=True)
|
|
275
275
|
print(basic["at"], detail.get("deep", {}).get("next_dst"))
|
|
276
276
|
```
|
|
277
|
+
|
|
278
|
+
## Stack API
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
result = parse.stack("example.com")
|
|
282
|
+
# AsyncParseAPI exposes the same calls with await.
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Pass a public hostname without a scheme, path, port or IP address. Stack returns the checked URL and `checked_at` time, followed by `scope`, `pages` and `partial`. `scope` is `homepage` or `site`; `pages` counts successfully checked HTML pages. `partial` is true for homepage-only or incomplete bounded site checks. False means the known in-scope candidates were completed, not that every page on a website was visited. A homepage result has `scope: "homepage"`, `pages: 1` and `partial: true`.
|
|
286
|
+
|
|
287
|
+
`cms`, `servers`, `frameworks`, `ecommerce`, `analytics`, `chat`, `payments` and `hosting` are arrays because a site can use several technologies in each category. Each entry contains `technology`, `name` and nullable `version`. Technology codes are open strings. A successful check uses empty arrays for categories with no matches. When no HTML page could be checked, `checked_at` and all categories are null, `pages` is 0 and `partial` is null. Unknown or conflicting versions are null. Missing detections do not prove absence.
|
|
288
|
+
|
|
289
|
+
Successful checks may be reused for up to 24 hours. `pretty` optionally formats the wire JSON. Stack uses your plan's request allowance and API version 2.0.0 selected by this client.
|
|
290
|
+
|
|
291
|
+
Stack defaults to a 35-second transport timeout so a first scan has time to finish. Other lookups retain their 10-second default. An explicit client timeout takes precedence.
|
|
@@ -1224,6 +1224,29 @@
|
|
|
1224
1224
|
],
|
|
1225
1225
|
"returns": "Json"
|
|
1226
1226
|
},
|
|
1227
|
+
"stack": {
|
|
1228
|
+
"async": true,
|
|
1229
|
+
"parameters": [
|
|
1230
|
+
{
|
|
1231
|
+
"kind": "POSITIONAL_OR_KEYWORD",
|
|
1232
|
+
"name": "domain",
|
|
1233
|
+
"type": "str"
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
"default": false,
|
|
1237
|
+
"kind": "KEYWORD_ONLY",
|
|
1238
|
+
"name": "deep",
|
|
1239
|
+
"type": "bool"
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
"default": false,
|
|
1243
|
+
"kind": "KEYWORD_ONLY",
|
|
1244
|
+
"name": "pretty",
|
|
1245
|
+
"type": "bool"
|
|
1246
|
+
}
|
|
1247
|
+
],
|
|
1248
|
+
"returns": "Json"
|
|
1249
|
+
},
|
|
1227
1250
|
"state": {
|
|
1228
1251
|
"async": true,
|
|
1229
1252
|
"parameters": [
|
|
@@ -2778,6 +2801,29 @@
|
|
|
2778
2801
|
],
|
|
2779
2802
|
"returns": "Json"
|
|
2780
2803
|
},
|
|
2804
|
+
"stack": {
|
|
2805
|
+
"async": false,
|
|
2806
|
+
"parameters": [
|
|
2807
|
+
{
|
|
2808
|
+
"kind": "POSITIONAL_OR_KEYWORD",
|
|
2809
|
+
"name": "domain",
|
|
2810
|
+
"type": "str"
|
|
2811
|
+
},
|
|
2812
|
+
{
|
|
2813
|
+
"default": false,
|
|
2814
|
+
"kind": "KEYWORD_ONLY",
|
|
2815
|
+
"name": "deep",
|
|
2816
|
+
"type": "bool"
|
|
2817
|
+
},
|
|
2818
|
+
{
|
|
2819
|
+
"default": false,
|
|
2820
|
+
"kind": "KEYWORD_ONLY",
|
|
2821
|
+
"name": "pretty",
|
|
2822
|
+
"type": "bool"
|
|
2823
|
+
}
|
|
2824
|
+
],
|
|
2825
|
+
"returns": "Json"
|
|
2826
|
+
},
|
|
2781
2827
|
"state": {
|
|
2782
2828
|
"async": false,
|
|
2783
2829
|
"parameters": [
|
|
@@ -10,7 +10,7 @@ from urllib.parse import quote
|
|
|
10
10
|
|
|
11
11
|
import httpx
|
|
12
12
|
|
|
13
|
-
VERSION = "1.
|
|
13
|
+
VERSION = "1.3.0"
|
|
14
14
|
_API_VERSION = "2.0.0"
|
|
15
15
|
DEFAULT_BASE_URL = "https://api.parseapi.com"
|
|
16
16
|
DEFAULT_TIMEOUT = 10.0
|
|
@@ -96,12 +96,16 @@ class _Config:
|
|
|
96
96
|
self.api_key = key
|
|
97
97
|
self.base_url = (base_url or os.environ.get("PARSEAPI_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
98
98
|
self.timeout = DEFAULT_TIMEOUT if timeout is None else timeout
|
|
99
|
+
self.timeout_explicit = timeout is not None
|
|
99
100
|
self.retries = retries
|
|
100
101
|
if type(self.timeout) not in (int, float) or not math.isfinite(self.timeout) or self.timeout <= 0:
|
|
101
102
|
raise ValueError("parseapi: timeout must be a finite positive number.")
|
|
102
103
|
if self.retries is not None and (type(self.retries) is not int or self.retries < 0):
|
|
103
104
|
raise ValueError("parseapi: retries must be a non-negative integer.")
|
|
104
105
|
|
|
106
|
+
def timeout_for(self, path: str) -> float:
|
|
107
|
+
return 35.0 if not self.timeout_explicit and path.startswith("/stack/") else self.timeout
|
|
108
|
+
|
|
105
109
|
def headers(self) -> Dict[str, str]:
|
|
106
110
|
return {"X-API-Key": self.api_key, "User-Agent": f"parseapi-python/{VERSION}", "Parse-Version": _API_VERSION}
|
|
107
111
|
|
|
@@ -157,7 +161,7 @@ class ParseAPI:
|
|
|
157
161
|
attempt = 0
|
|
158
162
|
while True:
|
|
159
163
|
try:
|
|
160
|
-
response = self._http.get(path, params=_clean(params or {}), headers=headers)
|
|
164
|
+
response = self._http.get(path, params=_clean(params or {}), headers=headers, timeout=self._config.timeout_for(path))
|
|
161
165
|
except httpx.HTTPError:
|
|
162
166
|
if attempt < retries:
|
|
163
167
|
time.sleep(_retry_delay(attempt, None))
|
|
@@ -230,6 +234,15 @@ class ParseAPI:
|
|
|
230
234
|
diagnostics within the same metered lookup. No automatic retries by default."""
|
|
231
235
|
return self._get(f"/hlr/{_seg(number)}", {"country": country, "deep": deep})
|
|
232
236
|
|
|
237
|
+
def stack(self, domain: str, *, deep: bool = False, pretty: bool = False) -> Json:
|
|
238
|
+
"""Identify website technologies and versions by category.
|
|
239
|
+
|
|
240
|
+
All categories are lists. Scope, pages and partial describe bounded coverage.
|
|
241
|
+
Lists are null when no page could be checked and empty for no matches.
|
|
242
|
+
Pass a hostname without a URL scheme or path.
|
|
243
|
+
"""
|
|
244
|
+
return self._get(f"/stack/{_seg(domain)}", {"deep": deep, "pretty": pretty})
|
|
245
|
+
|
|
233
246
|
def domain(self, domain: str, *, deep: bool = False) -> Json:
|
|
234
247
|
"""Check whether a domain is registered. Deep adds registration dates, registrar, status and DNSSEC on paid plans."""
|
|
235
248
|
return self._get(f"/domain/{_seg(domain)}", {"deep": deep})
|
|
@@ -531,7 +544,7 @@ class AsyncParseAPI:
|
|
|
531
544
|
attempt = 0
|
|
532
545
|
while True:
|
|
533
546
|
try:
|
|
534
|
-
response = await self._http.get(path, params=_clean(params or {}), headers=headers)
|
|
547
|
+
response = await self._http.get(path, params=_clean(params or {}), headers=headers, timeout=self._config.timeout_for(path))
|
|
535
548
|
except httpx.HTTPError:
|
|
536
549
|
if attempt < retries:
|
|
537
550
|
await asyncio.sleep(_retry_delay(attempt, None))
|
|
@@ -602,6 +615,15 @@ class AsyncParseAPI:
|
|
|
602
615
|
diagnostics within the same metered lookup. No automatic retries by default."""
|
|
603
616
|
return await self._get(f"/hlr/{_seg(number)}", {"country": country, "deep": deep})
|
|
604
617
|
|
|
618
|
+
async def stack(self, domain: str, *, deep: bool = False, pretty: bool = False) -> Json:
|
|
619
|
+
"""Identify website technologies and versions by category.
|
|
620
|
+
|
|
621
|
+
All categories are lists. Scope, pages and partial describe bounded coverage.
|
|
622
|
+
Lists are null when no page could be checked and empty for no matches.
|
|
623
|
+
Pass a hostname without a URL scheme or path.
|
|
624
|
+
"""
|
|
625
|
+
return await self._get(f"/stack/{_seg(domain)}", {"deep": deep, "pretty": pretty})
|
|
626
|
+
|
|
605
627
|
async def domain(self, domain: str, *, deep: bool = False) -> Json:
|
|
606
628
|
"""Check whether a domain is registered. Deep adds registration dates, registrar, status and DNSSEC on paid plans."""
|
|
607
629
|
return await self._get(f"/domain/{_seg(domain)}", {"deep": deep})
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import httpx
|
|
4
|
+
from parseapi import ParseAPI, AsyncParseAPI
|
|
5
|
+
|
|
6
|
+
RECORDS = json.loads(r'''
|
|
7
|
+
[
|
|
8
|
+
{
|
|
9
|
+
"domain": "xn--bcher-kva.example",
|
|
10
|
+
"url": "https://xn--bcher-kva.example/",
|
|
11
|
+
"checked_at": null,
|
|
12
|
+
"scope": "homepage",
|
|
13
|
+
"pages": 0,
|
|
14
|
+
"partial": null,
|
|
15
|
+
"cms": null,
|
|
16
|
+
"servers": null,
|
|
17
|
+
"frameworks": null,
|
|
18
|
+
"ecommerce": null,
|
|
19
|
+
"analytics": null,
|
|
20
|
+
"chat": null,
|
|
21
|
+
"payments": null,
|
|
22
|
+
"hosting": null,
|
|
23
|
+
"future": true
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"domain": "xn--bcher-kva.example",
|
|
27
|
+
"url": "https://xn--bcher-kva.example/",
|
|
28
|
+
"checked_at": "2026-09-21T12:00:00Z",
|
|
29
|
+
"scope": "homepage",
|
|
30
|
+
"pages": 1,
|
|
31
|
+
"partial": true,
|
|
32
|
+
"cms": [],
|
|
33
|
+
"servers": [],
|
|
34
|
+
"frameworks": [],
|
|
35
|
+
"ecommerce": [],
|
|
36
|
+
"analytics": [],
|
|
37
|
+
"chat": [],
|
|
38
|
+
"payments": [],
|
|
39
|
+
"hosting": [],
|
|
40
|
+
"deep": {},
|
|
41
|
+
"future": true
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"domain": "xn--bcher-kva.example",
|
|
45
|
+
"url": "https://xn--bcher-kva.example/",
|
|
46
|
+
"checked_at": "2026-09-21T12:00:00Z",
|
|
47
|
+
"scope": "homepage",
|
|
48
|
+
"pages": 1,
|
|
49
|
+
"partial": true,
|
|
50
|
+
"cms": [
|
|
51
|
+
{
|
|
52
|
+
"technology": "wordpress",
|
|
53
|
+
"name": "WordPress",
|
|
54
|
+
"version": "6.8.2"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"servers": [
|
|
58
|
+
{
|
|
59
|
+
"technology": "nginx",
|
|
60
|
+
"name": "nginx",
|
|
61
|
+
"version": null
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"frameworks": [
|
|
65
|
+
{
|
|
66
|
+
"technology": "react",
|
|
67
|
+
"name": "React",
|
|
68
|
+
"version": null
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"ecommerce": [],
|
|
72
|
+
"analytics": [],
|
|
73
|
+
"chat": [],
|
|
74
|
+
"payments": [],
|
|
75
|
+
"hosting": [],
|
|
76
|
+
"future": true
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"domain": "xn--bcher-kva.example",
|
|
80
|
+
"url": "https://xn--bcher-kva.example/",
|
|
81
|
+
"checked_at": "2026-09-21T12:00:00Z",
|
|
82
|
+
"scope": "site",
|
|
83
|
+
"pages": 6,
|
|
84
|
+
"partial": false,
|
|
85
|
+
"cms": [
|
|
86
|
+
{
|
|
87
|
+
"technology": "wordpress",
|
|
88
|
+
"name": "WordPress",
|
|
89
|
+
"version": "6.8.2"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"technology": "ghost",
|
|
93
|
+
"name": "Ghost",
|
|
94
|
+
"version": null
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"servers": [
|
|
98
|
+
{
|
|
99
|
+
"technology": "nginx",
|
|
100
|
+
"name": "nginx",
|
|
101
|
+
"version": null
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"technology": "apache",
|
|
105
|
+
"name": "Apache",
|
|
106
|
+
"version": null
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"frameworks": [
|
|
110
|
+
{
|
|
111
|
+
"technology": "nextjs",
|
|
112
|
+
"name": "Next.js",
|
|
113
|
+
"version": "15.0.0",
|
|
114
|
+
"future": true
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"technology": "react",
|
|
118
|
+
"name": "React",
|
|
119
|
+
"version": null
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
"ecommerce": [
|
|
123
|
+
{
|
|
124
|
+
"technology": "woocommerce",
|
|
125
|
+
"name": "WooCommerce",
|
|
126
|
+
"version": null
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"analytics": [
|
|
130
|
+
{
|
|
131
|
+
"technology": "google-analytics",
|
|
132
|
+
"name": "Google Analytics",
|
|
133
|
+
"version": null
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
"chat": [
|
|
137
|
+
{
|
|
138
|
+
"technology": "intercom",
|
|
139
|
+
"name": "Intercom",
|
|
140
|
+
"version": null
|
|
141
|
+
}
|
|
142
|
+
],
|
|
143
|
+
"payments": [
|
|
144
|
+
{
|
|
145
|
+
"technology": "stripe",
|
|
146
|
+
"name": "Stripe",
|
|
147
|
+
"version": null
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
"hosting": [
|
|
151
|
+
{
|
|
152
|
+
"technology": "vercel",
|
|
153
|
+
"name": "Vercel",
|
|
154
|
+
"version": null
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
"future": true
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"domain": "xn--bcher-kva.example",
|
|
161
|
+
"url": "https://xn--bcher-kva.example/",
|
|
162
|
+
"checked_at": "2026-09-21T12:00:00Z",
|
|
163
|
+
"scope": "site",
|
|
164
|
+
"pages": 3,
|
|
165
|
+
"partial": true,
|
|
166
|
+
"cms": [],
|
|
167
|
+
"servers": [],
|
|
168
|
+
"frameworks": [
|
|
169
|
+
{
|
|
170
|
+
"technology": "nextjs",
|
|
171
|
+
"name": "Next.js",
|
|
172
|
+
"version": null,
|
|
173
|
+
"future": true
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
"ecommerce": [],
|
|
177
|
+
"analytics": [],
|
|
178
|
+
"chat": [],
|
|
179
|
+
"payments": [],
|
|
180
|
+
"hosting": [],
|
|
181
|
+
"deep": {},
|
|
182
|
+
"future": true
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"domain": "xn--bcher-kva.example",
|
|
186
|
+
"url": "https://xn--bcher-kva.example/",
|
|
187
|
+
"checked_at": null,
|
|
188
|
+
"scope": "site",
|
|
189
|
+
"pages": 0,
|
|
190
|
+
"partial": null,
|
|
191
|
+
"cms": null,
|
|
192
|
+
"servers": null,
|
|
193
|
+
"frameworks": null,
|
|
194
|
+
"ecommerce": null,
|
|
195
|
+
"analytics": null,
|
|
196
|
+
"chat": null,
|
|
197
|
+
"payments": null,
|
|
198
|
+
"hosting": null,
|
|
199
|
+
"deep": {},
|
|
200
|
+
"future": true
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
''')
|
|
204
|
+
|
|
205
|
+
def test_stack_sync_and_async_preserve_site_inventory_shapes_and_encoding():
|
|
206
|
+
async def run_async(record):
|
|
207
|
+
calls = []
|
|
208
|
+
async def handler(request):
|
|
209
|
+
calls.append(request)
|
|
210
|
+
return httpx.Response(200, json=record)
|
|
211
|
+
async with AsyncParseAPI("test_key", transport=httpx.MockTransport(handler)) as client:
|
|
212
|
+
assert await client.stack("bücher.example", deep=True, pretty=True) == record
|
|
213
|
+
assert await client.stack("example.com") == record
|
|
214
|
+
check(calls)
|
|
215
|
+
for record in RECORDS:
|
|
216
|
+
calls = []
|
|
217
|
+
def handler(request):
|
|
218
|
+
calls.append(request)
|
|
219
|
+
return httpx.Response(200, json=record)
|
|
220
|
+
with ParseAPI("test_key", transport=httpx.MockTransport(handler)) as client:
|
|
221
|
+
assert client.stack("bücher.example", deep=True, pretty=True) == record
|
|
222
|
+
assert client.stack("example.com") == record
|
|
223
|
+
check(calls)
|
|
224
|
+
asyncio.run(run_async(record))
|
|
225
|
+
|
|
226
|
+
def check(calls):
|
|
227
|
+
assert str(calls[0].url) == "https://api.parseapi.com/stack/b%C3%BCcher.example?deep=true&pretty=true"
|
|
228
|
+
assert str(calls[1].url) == "https://api.parseapi.com/stack/example.com"
|
|
229
|
+
assert all(request.headers["Parse-Version"] == "2.0.0" for request in calls)
|
|
230
|
+
|
|
231
|
+
def test_stack_deadline_defaults_and_explicit_sync_async_settings():
|
|
232
|
+
for explicit in [None, 10.0, 1.2, 45.0]:
|
|
233
|
+
expected = 35.0 if explicit is None else explicit
|
|
234
|
+
calls = []
|
|
235
|
+
def handler(request):
|
|
236
|
+
calls.append(request.extensions["timeout"])
|
|
237
|
+
return httpx.Response(200, json={**RECORDS[0], "deep": {}})
|
|
238
|
+
with ParseAPI("test_key", timeout=explicit, transport=httpx.MockTransport(handler)) as client:
|
|
239
|
+
assert client.stack("example.com")["deep"] == {}
|
|
240
|
+
client.domain("example.com")
|
|
241
|
+
client.stack("example.com", deep=True)
|
|
242
|
+
assert [call["read"] for call in calls] == [expected, 10.0 if explicit is None else explicit, expected]
|
|
243
|
+
assert all(len(set(call.values())) == 1 for call in calls)
|
|
244
|
+
async def run():
|
|
245
|
+
calls.clear()
|
|
246
|
+
async with AsyncParseAPI("test_key", timeout=explicit, transport=httpx.MockTransport(handler)) as client:
|
|
247
|
+
assert (await client.stack("example.com"))["deep"] == {}
|
|
248
|
+
await client.domain("example.com")
|
|
249
|
+
await client.stack("example.com", deep=True)
|
|
250
|
+
assert [call["read"] for call in calls] == [expected, 10.0 if explicit is None else explicit, expected]
|
|
251
|
+
asyncio.run(run())
|
|
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
|