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.
Files changed (52) hide show
  1. seekgist-1.0.0/.gitignore +9 -0
  2. seekgist-1.0.0/CHANGELOG.md +5 -0
  3. seekgist-1.0.0/LICENSE +21 -0
  4. seekgist-1.0.0/PKG-INFO +134 -0
  5. seekgist-1.0.0/README.md +107 -0
  6. seekgist-1.0.0/examples/async_suggest.py +24 -0
  7. seekgist-1.0.0/examples/search.py +33 -0
  8. seekgist-1.0.0/examples/sync_catalog.py +31 -0
  9. seekgist-1.0.0/examples/webhook_receiver.py +35 -0
  10. seekgist-1.0.0/pyproject.toml +65 -0
  11. seekgist-1.0.0/src/seekgist/__init__.py +48 -0
  12. seekgist-1.0.0/src/seekgist/_async_client.py +183 -0
  13. seekgist-1.0.0/src/seekgist/_client.py +186 -0
  14. seekgist-1.0.0/src/seekgist/_generated/__init__.py +3 -0
  15. seekgist-1.0.0/src/seekgist/_generated/api/__init__.py +110 -0
  16. seekgist-1.0.0/src/seekgist/_generated/api/_ops.py +106 -0
  17. seekgist-1.0.0/src/seekgist/_generated/api/account.py +108 -0
  18. seekgist-1.0.0/src/seekgist/_generated/api/analytics.py +66 -0
  19. seekgist-1.0.0/src/seekgist/_generated/api/assistant.py +222 -0
  20. seekgist-1.0.0/src/seekgist/_generated/api/billing.py +148 -0
  21. seekgist-1.0.0/src/seekgist/_generated/api/catalog.py +181 -0
  22. seekgist-1.0.0/src/seekgist/_generated/api/connectors.py +98 -0
  23. seekgist-1.0.0/src/seekgist/_generated/api/events.py +62 -0
  24. seekgist-1.0.0/src/seekgist/_generated/api/experiments.py +181 -0
  25. seekgist-1.0.0/src/seekgist/_generated/api/files.py +47 -0
  26. seekgist-1.0.0/src/seekgist/_generated/api/health.py +60 -0
  27. seekgist-1.0.0/src/seekgist/_generated/api/imports.py +120 -0
  28. seekgist-1.0.0/src/seekgist/_generated/api/jobs.py +44 -0
  29. seekgist-1.0.0/src/seekgist/_generated/api/rules.py +120 -0
  30. seekgist-1.0.0/src/seekgist/_generated/api/search.py +130 -0
  31. seekgist-1.0.0/src/seekgist/_generated/api/synonyms.py +227 -0
  32. seekgist-1.0.0/src/seekgist/_generated/api/translation.py +274 -0
  33. seekgist-1.0.0/src/seekgist/_generated/api/webhooks.py +182 -0
  34. seekgist-1.0.0/src/seekgist/_generated/types/__init__.py +443 -0
  35. seekgist-1.0.0/src/seekgist/_generated/types/account.py +293 -0
  36. seekgist-1.0.0/src/seekgist/_generated/types/catalog.py +192 -0
  37. seekgist-1.0.0/src/seekgist/_generated/types/common.py +174 -0
  38. seekgist-1.0.0/src/seekgist/_generated/types/content.py +238 -0
  39. seekgist-1.0.0/src/seekgist/_generated/types/demo.py +48 -0
  40. seekgist-1.0.0/src/seekgist/_generated/types/merch.py +263 -0
  41. seekgist-1.0.0/src/seekgist/_generated/types/misc.py +101 -0
  42. seekgist-1.0.0/src/seekgist/_generated/types/platform.py +411 -0
  43. seekgist-1.0.0/src/seekgist/_generated/types/platform_2.py +67 -0
  44. seekgist-1.0.0/src/seekgist/_generated/types/search.py +196 -0
  45. seekgist-1.0.0/src/seekgist/_generated/types/stats.py +177 -0
  46. seekgist-1.0.0/src/seekgist/_spec.py +92 -0
  47. seekgist-1.0.0/src/seekgist/_transport.py +253 -0
  48. seekgist-1.0.0/src/seekgist/_upsert.py +104 -0
  49. seekgist-1.0.0/src/seekgist/errors.py +42 -0
  50. seekgist-1.0.0/src/seekgist/py.typed +0 -0
  51. seekgist-1.0.0/src/seekgist/suggest.py +95 -0
  52. seekgist-1.0.0/src/seekgist/webhooks.py +80 -0
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 — 2026-10-19
4
+
5
+ First public release.
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.
@@ -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`.
@@ -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
+ ]