seekgist 1.0.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.
- seekgist-1.0.0/.gitignore +9 -0
- seekgist-1.0.0/CHANGELOG.md +5 -0
- seekgist-1.0.0/LICENSE +21 -0
- seekgist-1.0.0/PKG-INFO +134 -0
- seekgist-1.0.0/README.md +107 -0
- seekgist-1.0.0/examples/async_suggest.py +24 -0
- seekgist-1.0.0/examples/search.py +33 -0
- seekgist-1.0.0/examples/sync_catalog.py +31 -0
- seekgist-1.0.0/examples/webhook_receiver.py +35 -0
- seekgist-1.0.0/pyproject.toml +65 -0
- seekgist-1.0.0/src/seekgist/__init__.py +48 -0
- seekgist-1.0.0/src/seekgist/_async_client.py +183 -0
- seekgist-1.0.0/src/seekgist/_client.py +186 -0
- seekgist-1.0.0/src/seekgist/_generated/__init__.py +3 -0
- seekgist-1.0.0/src/seekgist/_generated/api/__init__.py +110 -0
- seekgist-1.0.0/src/seekgist/_generated/api/_ops.py +106 -0
- seekgist-1.0.0/src/seekgist/_generated/api/account.py +108 -0
- seekgist-1.0.0/src/seekgist/_generated/api/analytics.py +66 -0
- seekgist-1.0.0/src/seekgist/_generated/api/assistant.py +222 -0
- seekgist-1.0.0/src/seekgist/_generated/api/billing.py +148 -0
- seekgist-1.0.0/src/seekgist/_generated/api/catalog.py +181 -0
- seekgist-1.0.0/src/seekgist/_generated/api/connectors.py +98 -0
- seekgist-1.0.0/src/seekgist/_generated/api/events.py +62 -0
- seekgist-1.0.0/src/seekgist/_generated/api/experiments.py +181 -0
- seekgist-1.0.0/src/seekgist/_generated/api/files.py +47 -0
- seekgist-1.0.0/src/seekgist/_generated/api/health.py +60 -0
- seekgist-1.0.0/src/seekgist/_generated/api/imports.py +120 -0
- seekgist-1.0.0/src/seekgist/_generated/api/jobs.py +44 -0
- seekgist-1.0.0/src/seekgist/_generated/api/rules.py +120 -0
- seekgist-1.0.0/src/seekgist/_generated/api/search.py +130 -0
- seekgist-1.0.0/src/seekgist/_generated/api/synonyms.py +227 -0
- seekgist-1.0.0/src/seekgist/_generated/api/translation.py +274 -0
- seekgist-1.0.0/src/seekgist/_generated/api/webhooks.py +182 -0
- seekgist-1.0.0/src/seekgist/_generated/types/__init__.py +443 -0
- seekgist-1.0.0/src/seekgist/_generated/types/account.py +293 -0
- seekgist-1.0.0/src/seekgist/_generated/types/catalog.py +192 -0
- seekgist-1.0.0/src/seekgist/_generated/types/common.py +174 -0
- seekgist-1.0.0/src/seekgist/_generated/types/content.py +238 -0
- seekgist-1.0.0/src/seekgist/_generated/types/demo.py +48 -0
- seekgist-1.0.0/src/seekgist/_generated/types/merch.py +263 -0
- seekgist-1.0.0/src/seekgist/_generated/types/misc.py +101 -0
- seekgist-1.0.0/src/seekgist/_generated/types/platform.py +411 -0
- seekgist-1.0.0/src/seekgist/_generated/types/platform_2.py +67 -0
- seekgist-1.0.0/src/seekgist/_generated/types/search.py +196 -0
- seekgist-1.0.0/src/seekgist/_generated/types/stats.py +177 -0
- seekgist-1.0.0/src/seekgist/_spec.py +92 -0
- seekgist-1.0.0/src/seekgist/_transport.py +253 -0
- seekgist-1.0.0/src/seekgist/_upsert.py +104 -0
- seekgist-1.0.0/src/seekgist/errors.py +42 -0
- seekgist-1.0.0/src/seekgist/py.typed +0 -0
- seekgist-1.0.0/src/seekgist/suggest.py +95 -0
- seekgist-1.0.0/src/seekgist/webhooks.py +80 -0
seekgist-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SeekGist
|
|
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.
|
seekgist-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seekgist
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: SeekGist API client for Python: Arabic-first e-commerce search, suggest, catalog sync and webhooks.
|
|
5
|
+
Project-URL: Homepage, https://seekgist.com/docs/sdk/python
|
|
6
|
+
Project-URL: Documentation, https://seekgist.com/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/seekgist/seekgist-python
|
|
8
|
+
Project-URL: Issues, https://github.com/seekgist/seekgist-python/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/seekgist/seekgist-python/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: SeekGist <support@seekgist.com>
|
|
11
|
+
Maintainer-email: SeekGist <support@seekgist.com>
|
|
12
|
+
License: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: arabic,autocomplete,ecommerce,sdk,search,seekgist
|
|
15
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
16
|
+
Classifier: Framework :: AsyncIO
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Requires-Dist: httpx<1,>=0.27
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# seekgist: SeekGist for Python
|
|
29
|
+
|
|
30
|
+
The official Python client for the [SeekGist](https://seekgist.com) API: Arabic-first e-commerce
|
|
31
|
+
search, suggest, catalog sync and webhooks. Python 3.12+, sync (`SeekGist`) and asyncio
|
|
32
|
+
(`AsyncSeekGist`) clients on httpx, fully typed (`py.typed`, TypedDicts for every request and
|
|
33
|
+
response).
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install seekgist
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Documentation: https://seekgist.com/docs. Support: support@seekgist.com.
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from seekgist import SeekGist
|
|
45
|
+
|
|
46
|
+
with SeekGist(api_key="sg_sec_...") as client: # or set SEEKGIST_API_KEY
|
|
47
|
+
products = [{"id": "42", "name": {"en": "Desk lamp", "ar": "مصباح مكتب"}, "price": "12.500", "currency": "JOD"}]
|
|
48
|
+
summary = client.upsert_products(products) # batches, retries, one Idempotency-Key per batch
|
|
49
|
+
print(summary.upserted, summary.errors)
|
|
50
|
+
|
|
51
|
+
res = client.find("مصباح", lang="ar", per_page=12)
|
|
52
|
+
print(res.data["ids"]) # product ids in rank order; res.data["hits"] has the product cards
|
|
53
|
+
print(res.usage, res.request_id) # searches used this period; quote the request id to support
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from seekgist import AsyncSeekGist
|
|
58
|
+
|
|
59
|
+
async with AsyncSeekGist() as client:
|
|
60
|
+
res = await client.find("white sneakers", lang="en")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Keys: a **public** key (`sg_pub_...`) can search, suggest, recommend and send events.
|
|
64
|
+
A **secret** key (`sg_sec_...`) can do everything an API key may; keep it on your server.
|
|
65
|
+
Options: `base_url` (default `https://api.seekgist.com`, or `SEEKGIST_BASE_URL`), `timeout`
|
|
66
|
+
(10 s per attempt), `max_retries` (2), `headers`, `http_client`.
|
|
67
|
+
|
|
68
|
+
## All API methods
|
|
69
|
+
|
|
70
|
+
One namespace per API area, one method per operation, keyword arguments only:
|
|
71
|
+
`client.search`, `events`, `catalog`, `imports`, `connectors`, `jobs`, `synonyms`, `rules`,
|
|
72
|
+
`experiments`, `translation`, `analytics`, `webhooks`, `account`, `billing`, `health`, `files`.
|
|
73
|
+
|
|
74
|
+
Catalog translation works: `translation.backfill`, `status`, `review_*`, `glossary_*`, `export`,
|
|
75
|
+
`approved` and `ack`. Not available on this platform: the chat methods. They answer
|
|
76
|
+
`404 feature_unavailable`.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
client.synonyms.create(body={"kind": "equivalent", "terms": ["sneakers", "trainers", "سنيكرز"]})
|
|
80
|
+
client.catalog.products_get(product_id="42")
|
|
81
|
+
client.analytics.stats(report="top_queries", from_="2026-10-01") # `from` is a keyword: from_
|
|
82
|
+
client.search.search_get(q="sneakers", options={"query": {"filter[brand]": ["Nike", "Adidas"]}})
|
|
83
|
+
r = client.request("search_post", body={"q": "dress"}) # any operation, with headers
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Every method takes `options={"timeout", "max_retries", "idempotency_key", "headers", "query"}`.
|
|
87
|
+
Dashboard sign-in, admin and store-app callback endpoints are not in the SDK on purpose.
|
|
88
|
+
|
|
89
|
+
## Helpers
|
|
90
|
+
|
|
91
|
+
| Helper | What it does |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `client.find(q, **fields)` | POST /v1/search; returns `ApiResponse` with `.usage` |
|
|
94
|
+
| `client.upsert_products(products, batch_size=500, delete_ids=..., replace_all=False, key_prefix=None, on_batch=None)` | Batches of up to 1,000 with one `Idempotency-Key` per batch (`<key_prefix>-<n>`) and 5 retries; the same `key_prefix` again = safe resume; `replace_all=True` = full sync. Returns `UpsertSummary` |
|
|
95
|
+
| `client.wait_for_job(job_id)` | Polls `GET /v1/jobs/{id}` until done |
|
|
96
|
+
| `client.pages(lambda c: client.synonyms.list(cursor=c))` | Every item of a cursor list |
|
|
97
|
+
| `AsyncSeekGist.suggester(debounce=0.12, lang=..., products=...)` | `await s.input(text)`: debounced, drops out-of-date answers, caches; `None` for short or stale text. Suggest is never billed |
|
|
98
|
+
| `verify_webhook(secret, headers, raw_body)` | Standard Webhooks signature + 5-minute time check; returns the event |
|
|
99
|
+
| `parse_usage(header)` | Reads `X-SeekGist-Usage` |
|
|
100
|
+
|
|
101
|
+
Debounce matters where people type: in a browser use the JS SDK (`@seekgist/sdk`) or the
|
|
102
|
+
widget. In Python the suggester is for asyncio code such as a chat bot or a server-side proxy.
|
|
103
|
+
|
|
104
|
+
## Errors, retries and idempotency
|
|
105
|
+
|
|
106
|
+
- `ApiError` (`status`, `code`, `request_id`, `field_errors`, `retry_after_s`): use `code` in
|
|
107
|
+
your program. `TimeoutError`, `ConnectionError`: no answer. `WebhookVerificationError`: bad webhook.
|
|
108
|
+
All are `SeekGistError`.
|
|
109
|
+
- Retries (default 2, backoff 0.5 s, 1 s, 2 s ... with jitter; `Retry-After` wins): GET calls,
|
|
110
|
+
search, and writes that take an `Idempotency-Key` (the SDK adds one and keeps it across
|
|
111
|
+
retries). Other writes (click, event) are never retried after a network error
|
|
112
|
+
or a 5xx; a 429 is retried for every call.
|
|
113
|
+
- Store search pages: `SeekGist(timeout=0.3, max_retries=0)` and fall back to the store's own
|
|
114
|
+
search on any `SeekGistError`.
|
|
115
|
+
|
|
116
|
+
## Examples
|
|
117
|
+
|
|
118
|
+
`examples/search.py`, `examples/sync_catalog.py`, `examples/webhook_receiver.py` (standard
|
|
119
|
+
library server), `examples/async_suggest.py`.
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
|
|
123
|
+
From a clone of https://github.com/seekgist/seekgist-python:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pip install -e . pytest pytest-asyncio
|
|
127
|
+
pytest
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Bugs and questions: https://github.com/seekgist/seekgist-python/issues or support@seekgist.com.
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT. See `LICENSE`.
|
seekgist-1.0.0/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# seekgist: SeekGist for Python
|
|
2
|
+
|
|
3
|
+
The official Python client for the [SeekGist](https://seekgist.com) API: Arabic-first e-commerce
|
|
4
|
+
search, suggest, catalog sync and webhooks. Python 3.12+, sync (`SeekGist`) and asyncio
|
|
5
|
+
(`AsyncSeekGist`) clients on httpx, fully typed (`py.typed`, TypedDicts for every request and
|
|
6
|
+
response).
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install seekgist
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Documentation: https://seekgist.com/docs. Support: support@seekgist.com.
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from seekgist import SeekGist
|
|
18
|
+
|
|
19
|
+
with SeekGist(api_key="sg_sec_...") as client: # or set SEEKGIST_API_KEY
|
|
20
|
+
products = [{"id": "42", "name": {"en": "Desk lamp", "ar": "مصباح مكتب"}, "price": "12.500", "currency": "JOD"}]
|
|
21
|
+
summary = client.upsert_products(products) # batches, retries, one Idempotency-Key per batch
|
|
22
|
+
print(summary.upserted, summary.errors)
|
|
23
|
+
|
|
24
|
+
res = client.find("مصباح", lang="ar", per_page=12)
|
|
25
|
+
print(res.data["ids"]) # product ids in rank order; res.data["hits"] has the product cards
|
|
26
|
+
print(res.usage, res.request_id) # searches used this period; quote the request id to support
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from seekgist import AsyncSeekGist
|
|
31
|
+
|
|
32
|
+
async with AsyncSeekGist() as client:
|
|
33
|
+
res = await client.find("white sneakers", lang="en")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Keys: a **public** key (`sg_pub_...`) can search, suggest, recommend and send events.
|
|
37
|
+
A **secret** key (`sg_sec_...`) can do everything an API key may; keep it on your server.
|
|
38
|
+
Options: `base_url` (default `https://api.seekgist.com`, or `SEEKGIST_BASE_URL`), `timeout`
|
|
39
|
+
(10 s per attempt), `max_retries` (2), `headers`, `http_client`.
|
|
40
|
+
|
|
41
|
+
## All API methods
|
|
42
|
+
|
|
43
|
+
One namespace per API area, one method per operation, keyword arguments only:
|
|
44
|
+
`client.search`, `events`, `catalog`, `imports`, `connectors`, `jobs`, `synonyms`, `rules`,
|
|
45
|
+
`experiments`, `translation`, `analytics`, `webhooks`, `account`, `billing`, `health`, `files`.
|
|
46
|
+
|
|
47
|
+
Catalog translation works: `translation.backfill`, `status`, `review_*`, `glossary_*`, `export`,
|
|
48
|
+
`approved` and `ack`. Not available on this platform: the chat methods. They answer
|
|
49
|
+
`404 feature_unavailable`.
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
client.synonyms.create(body={"kind": "equivalent", "terms": ["sneakers", "trainers", "سنيكرز"]})
|
|
53
|
+
client.catalog.products_get(product_id="42")
|
|
54
|
+
client.analytics.stats(report="top_queries", from_="2026-10-01") # `from` is a keyword: from_
|
|
55
|
+
client.search.search_get(q="sneakers", options={"query": {"filter[brand]": ["Nike", "Adidas"]}})
|
|
56
|
+
r = client.request("search_post", body={"q": "dress"}) # any operation, with headers
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Every method takes `options={"timeout", "max_retries", "idempotency_key", "headers", "query"}`.
|
|
60
|
+
Dashboard sign-in, admin and store-app callback endpoints are not in the SDK on purpose.
|
|
61
|
+
|
|
62
|
+
## Helpers
|
|
63
|
+
|
|
64
|
+
| Helper | What it does |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `client.find(q, **fields)` | POST /v1/search; returns `ApiResponse` with `.usage` |
|
|
67
|
+
| `client.upsert_products(products, batch_size=500, delete_ids=..., replace_all=False, key_prefix=None, on_batch=None)` | Batches of up to 1,000 with one `Idempotency-Key` per batch (`<key_prefix>-<n>`) and 5 retries; the same `key_prefix` again = safe resume; `replace_all=True` = full sync. Returns `UpsertSummary` |
|
|
68
|
+
| `client.wait_for_job(job_id)` | Polls `GET /v1/jobs/{id}` until done |
|
|
69
|
+
| `client.pages(lambda c: client.synonyms.list(cursor=c))` | Every item of a cursor list |
|
|
70
|
+
| `AsyncSeekGist.suggester(debounce=0.12, lang=..., products=...)` | `await s.input(text)`: debounced, drops out-of-date answers, caches; `None` for short or stale text. Suggest is never billed |
|
|
71
|
+
| `verify_webhook(secret, headers, raw_body)` | Standard Webhooks signature + 5-minute time check; returns the event |
|
|
72
|
+
| `parse_usage(header)` | Reads `X-SeekGist-Usage` |
|
|
73
|
+
|
|
74
|
+
Debounce matters where people type: in a browser use the JS SDK (`@seekgist/sdk`) or the
|
|
75
|
+
widget. In Python the suggester is for asyncio code such as a chat bot or a server-side proxy.
|
|
76
|
+
|
|
77
|
+
## Errors, retries and idempotency
|
|
78
|
+
|
|
79
|
+
- `ApiError` (`status`, `code`, `request_id`, `field_errors`, `retry_after_s`): use `code` in
|
|
80
|
+
your program. `TimeoutError`, `ConnectionError`: no answer. `WebhookVerificationError`: bad webhook.
|
|
81
|
+
All are `SeekGistError`.
|
|
82
|
+
- Retries (default 2, backoff 0.5 s, 1 s, 2 s ... with jitter; `Retry-After` wins): GET calls,
|
|
83
|
+
search, and writes that take an `Idempotency-Key` (the SDK adds one and keeps it across
|
|
84
|
+
retries). Other writes (click, event) are never retried after a network error
|
|
85
|
+
or a 5xx; a 429 is retried for every call.
|
|
86
|
+
- Store search pages: `SeekGist(timeout=0.3, max_retries=0)` and fall back to the store's own
|
|
87
|
+
search on any `SeekGistError`.
|
|
88
|
+
|
|
89
|
+
## Examples
|
|
90
|
+
|
|
91
|
+
`examples/search.py`, `examples/sync_catalog.py`, `examples/webhook_receiver.py` (standard
|
|
92
|
+
library server), `examples/async_suggest.py`.
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
From a clone of https://github.com/seekgist/seekgist-python:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pip install -e . pytest pytest-asyncio
|
|
100
|
+
pytest
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Bugs and questions: https://github.com/seekgist/seekgist-python/issues or support@seekgist.com.
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Debounced suggestions in asyncio code (a chat bot, a terminal UI, a server-side proxy).
|
|
2
|
+
|
|
3
|
+
SEEKGIST_API_KEY=sg_pub_... python examples/async_suggest.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
|
|
10
|
+
from seekgist import AsyncSeekGist
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def main() -> None:
|
|
14
|
+
async with AsyncSeekGist() as client:
|
|
15
|
+
suggester = client.suggester(debounce=0.12, lang="auto", products=3)
|
|
16
|
+
typed = ["d", "dr", "dre", "dres", "dress"]
|
|
17
|
+
tasks = [asyncio.ensure_future(suggester.input(text)) for text in typed] # fast typing
|
|
18
|
+
results = await asyncio.gather(*tasks)
|
|
19
|
+
for text, res in zip(typed, results, strict=True):
|
|
20
|
+
print(f"{text!r}: {'(skipped)' if res is None else [s['text'] for s in res['suggestions']]}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Search from Python with a PUBLIC or SECRET key.
|
|
2
|
+
|
|
3
|
+
SEEKGIST_API_KEY=sg_pub_... SEEKGIST_BASE_URL=http://localhost:58000 python examples/search.py "فستان"
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from seekgist import ApiError, SeekGist
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> int:
|
|
14
|
+
query = sys.argv[1] if len(sys.argv) > 1 else "dress"
|
|
15
|
+
with SeekGist() as client: # reads SEEKGIST_API_KEY and SEEKGIST_BASE_URL
|
|
16
|
+
try:
|
|
17
|
+
res = client.find(query, per_page=5)
|
|
18
|
+
except ApiError as e:
|
|
19
|
+
print(f"API error {e.status} {e.code} (request {e.request_id})", file=sys.stderr)
|
|
20
|
+
return 1
|
|
21
|
+
print(f"{res.data['total']} results for {query!r} ({res.data['took_ms']} ms)")
|
|
22
|
+
for hit in res.data["hits"]:
|
|
23
|
+
p = hit["product"]
|
|
24
|
+
print(f"- {p['name'].get('ar') or p['name'].get('en')} | {p['price']} {p['currency']}")
|
|
25
|
+
if res.usage:
|
|
26
|
+
print(f"usage: {res.usage.searches} searches in {res.usage.period} (limit {res.usage.limit})")
|
|
27
|
+
sug = client.search.suggest(q=query[:3])
|
|
28
|
+
print("suggestions:", ", ".join(s["text"] for s in sug["suggestions"]))
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Push products with a SECRET key: batches of 500, one Idempotency-Key per batch, retries.
|
|
2
|
+
Run it again with the same KEY_PREFIX to resume a stopped run safely.
|
|
3
|
+
|
|
4
|
+
SEEKGIST_API_KEY=sg_sec_... KEY_PREFIX=nightly-2026-10-13 python examples/sync_catalog.py
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from seekgist import SeekGist
|
|
12
|
+
|
|
13
|
+
products = [
|
|
14
|
+
{
|
|
15
|
+
"id": f"demo-{i}",
|
|
16
|
+
"name": {"en": f"Demo product {i}", "ar": f"منتج تجريبي {i}"},
|
|
17
|
+
"price": f"{5 + i % 50:.3f}",
|
|
18
|
+
"currency": "USD",
|
|
19
|
+
"in_stock": i % 7 != 0,
|
|
20
|
+
}
|
|
21
|
+
for i in range(1200)
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
with SeekGist() as client:
|
|
25
|
+
summary = client.upsert_products(
|
|
26
|
+
products,
|
|
27
|
+
batch_size=500,
|
|
28
|
+
key_prefix=os.environ.get("KEY_PREFIX"),
|
|
29
|
+
on_batch=lambda b: print(f"batch {b.index + 1}: {b.count} items{' (already done)' if b.replayed else ''}"),
|
|
30
|
+
)
|
|
31
|
+
print(summary)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Receive SeekGist webhooks with the standard library and check the signature.
|
|
2
|
+
|
|
3
|
+
SEEKGIST_WEBHOOK_SECRET=whsec_... python examples/webhook_receiver.py (listens on :3001)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
10
|
+
|
|
11
|
+
from seekgist import WebhookVerificationError, verify_webhook
|
|
12
|
+
|
|
13
|
+
SECRET = os.environ.get("SEEKGIST_WEBHOOK_SECRET", "")
|
|
14
|
+
seen: set[str] = set() # drop repeated deliveries (use your database in production)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Handler(BaseHTTPRequestHandler):
|
|
18
|
+
def do_POST(self) -> None:
|
|
19
|
+
body = self.rfile.read(int(self.headers.get("Content-Length", "0"))) # the RAW body
|
|
20
|
+
try:
|
|
21
|
+
event = verify_webhook(SECRET, dict(self.headers), body)
|
|
22
|
+
except WebhookVerificationError as e:
|
|
23
|
+
self.send_response(400)
|
|
24
|
+
self.end_headers()
|
|
25
|
+
self.wfile.write(str(e).encode())
|
|
26
|
+
return
|
|
27
|
+
if event["id"] not in seen:
|
|
28
|
+
seen.add(event["id"])
|
|
29
|
+
print(event["type"], event["data"])
|
|
30
|
+
self.send_response(204)
|
|
31
|
+
self.end_headers()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
HTTPServer(("127.0.0.1", 3001), Handler).serve_forever()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling==1.27.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "seekgist"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "SeekGist API client for Python: Arabic-first e-commerce search, suggest, catalog sync and webhooks."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "SeekGist", email = "support@seekgist.com" }]
|
|
13
|
+
maintainers = [{ name = "SeekGist", email = "support@seekgist.com" }]
|
|
14
|
+
keywords = ["seekgist", "search", "arabic", "ecommerce", "autocomplete", "sdk"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 5 - Production/Stable",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Framework :: AsyncIO",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"httpx>=0.27,<1",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://seekgist.com/docs/sdk/python"
|
|
32
|
+
Documentation = "https://seekgist.com/docs"
|
|
33
|
+
Repository = "https://github.com/seekgist/seekgist-python"
|
|
34
|
+
Issues = "https://github.com/seekgist/seekgist-python/issues"
|
|
35
|
+
Changelog = "https://github.com/seekgist/seekgist-python/blob/main/CHANGELOG.md"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/seekgist"]
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.sdist]
|
|
41
|
+
include = ["src/seekgist", "README.md", "LICENSE", "CHANGELOG.md", "examples"]
|
|
42
|
+
|
|
43
|
+
# Ruff: the same rules as the SeekGist API code, plus room for generated code (long docstrings
|
|
44
|
+
# and operation lines). Kept here in full so the package also lints on its own.
|
|
45
|
+
[tool.ruff]
|
|
46
|
+
line-length = 120
|
|
47
|
+
target-version = "py312"
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = ["E", "F", "W", "I", "B", "UP", "S", "ASYNC", "RUF", "SIM", "PL"]
|
|
51
|
+
ignore = ["PLR0913", "PLR2004", "PLR0911", "PLR0912", "PLR0915", "PLR0917", "S101", "RUF001", "RUF002", "RUF003", "B008", "PLC0415"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint.isort]
|
|
54
|
+
# seekgist_api: the API app, used only by the live tests that run next to the API code.
|
|
55
|
+
known-first-party = ["seekgist", "seekgist_api"]
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint.per-file-ignores]
|
|
58
|
+
"tests/**" = ["S", "PLR", "B017", "E501", "ASYNC"]
|
|
59
|
+
"src/seekgist/_generated/**" = ["E501", "I001", "RUF022"]
|
|
60
|
+
|
|
61
|
+
[tool.ruff.format]
|
|
62
|
+
exclude = ["src/seekgist/_generated/**"]
|
|
63
|
+
|
|
64
|
+
[tool.pytest.ini_options]
|
|
65
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""SeekGist Python SDK: Arabic-first e-commerce search (search, suggest, catalog sync, webhooks).
|
|
2
|
+
|
|
3
|
+
from seekgist import SeekGist
|
|
4
|
+
client = SeekGist(api_key="sg_sec_...") # or set SEEKGIST_API_KEY
|
|
5
|
+
res = client.find("فستان سهرة تحت 100")
|
|
6
|
+
print(res.data["ids"], res.usage)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ._async_client import AsyncSeekGist
|
|
10
|
+
from ._client import DEFAULT_BASE_URL, SeekGist, __version__
|
|
11
|
+
from ._generated.api import GENERATOR_VERSION, OPS, SPEC_SHA256
|
|
12
|
+
from ._spec import ApiResponse, FileInput, OperationSpec, RateLimit, RequestOptions, Usage
|
|
13
|
+
from ._transport import new_idempotency_key, parse_usage
|
|
14
|
+
from ._upsert import MAX_BATCH, BatchInfo, UpsertSummary
|
|
15
|
+
from .errors import ApiError, ConnectionError, SeekGistError, TimeoutError, WebhookVerificationError
|
|
16
|
+
from .suggest import AsyncSuggester
|
|
17
|
+
from .webhooks import TOLERANCE_S, WebhookEvent, sign_webhook, verify_webhook
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"DEFAULT_BASE_URL",
|
|
21
|
+
"GENERATOR_VERSION",
|
|
22
|
+
"MAX_BATCH",
|
|
23
|
+
"OPS",
|
|
24
|
+
"SPEC_SHA256",
|
|
25
|
+
"TOLERANCE_S",
|
|
26
|
+
"ApiError",
|
|
27
|
+
"ApiResponse",
|
|
28
|
+
"AsyncSeekGist",
|
|
29
|
+
"AsyncSuggester",
|
|
30
|
+
"BatchInfo",
|
|
31
|
+
"ConnectionError",
|
|
32
|
+
"FileInput",
|
|
33
|
+
"OperationSpec",
|
|
34
|
+
"RateLimit",
|
|
35
|
+
"RequestOptions",
|
|
36
|
+
"SeekGist",
|
|
37
|
+
"SeekGistError",
|
|
38
|
+
"TimeoutError",
|
|
39
|
+
"UpsertSummary",
|
|
40
|
+
"Usage",
|
|
41
|
+
"WebhookEvent",
|
|
42
|
+
"WebhookVerificationError",
|
|
43
|
+
"__version__",
|
|
44
|
+
"new_idempotency_key",
|
|
45
|
+
"parse_usage",
|
|
46
|
+
"sign_webhook",
|
|
47
|
+
"verify_webhook",
|
|
48
|
+
]
|