clavis-sinica 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Clavis Sinica
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,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: clavis-sinica
3
+ Version: 0.1.0
4
+ Summary: Official Python client for the Clavis Sinica API — Chinese idiom, sentence, passage, classical Chinese and lesson decomposition.
5
+ Author: Clavis Sinica
6
+ License: MIT
7
+ Project-URL: Homepage, https://clavissinica.org
8
+ Project-URL: Documentation, https://clavissinica.org/docs
9
+ Project-URL: API Reference, https://clavissinica.org/openapi.json
10
+ Keywords: chinese,nlp,idiom,classical-chinese,hsk,api-client
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: requests>=2.25
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=7.0; extra == "dev"
17
+ Dynamic: license-file
18
+
19
+ # clavis-sinica
20
+
21
+ Official Python client for the [Clavis Sinica](https://clavissinica.org) API —
22
+ Chinese idiom, sentence, passage, classical Chinese and lesson decomposition.
23
+
24
+ ```bash
25
+ pip install clavis-sinica
26
+ ```
27
+
28
+ ```python
29
+ from clavis_sinica import Clavis
30
+
31
+ c = Clavis("ck-live_...")
32
+
33
+ c.run("decompose.idiom", {"idiom": "守株待兔"})
34
+ c.run("convert.script", {"text": "汉字", "direction": "s2t"})
35
+
36
+ # Async services poll automatically until done
37
+ c.run("decompose.lesson", {"text": "...", "title": "第一课"})
38
+ ```
39
+
40
+ ## What it handles for you
41
+
42
+ - `Authorization: Bearer` auth — the only accepted scheme
43
+ - The `payload` request shape, which works for every service code
44
+ - Fetches the field spec from `GET /v1/chinese/services` at startup instead of
45
+ hardcoding a local copy
46
+ - Retries `429` honouring `Retry-After`, and `502`/`503` with exponential
47
+ backoff; never retries `4xx` that won't succeed on retry
48
+ - Polls async services (`decompose.lesson`, `decompose.wenyan_lesson`) to
49
+ completion
50
+ - Logs the `warnings` array — unknown top-level request fields are dropped
51
+ silently, and that array is the only signal
52
+
53
+ ## Errors
54
+
55
+ `ClavisAuthError` (401) · `ClavisParamError` (400, has `.field` / `.expected`) ·
56
+ `ClavisNotFound` (404) · `ClavisRateLimited` (429, has `.retry_after` /
57
+ `.scope`) · `ClavisUpstreamError` (502/503, worth retrying) · `ClavisTimeout`
58
+
59
+ ## Not this API
60
+
61
+ Single-character and vocabulary decomposition belong to the sister product
62
+ chinesekey.org — separate pricing, `pk_live_` keys, not interchangeable.
63
+
64
+ MIT
@@ -0,0 +1,46 @@
1
+ # clavis-sinica
2
+
3
+ Official Python client for the [Clavis Sinica](https://clavissinica.org) API —
4
+ Chinese idiom, sentence, passage, classical Chinese and lesson decomposition.
5
+
6
+ ```bash
7
+ pip install clavis-sinica
8
+ ```
9
+
10
+ ```python
11
+ from clavis_sinica import Clavis
12
+
13
+ c = Clavis("ck-live_...")
14
+
15
+ c.run("decompose.idiom", {"idiom": "守株待兔"})
16
+ c.run("convert.script", {"text": "汉字", "direction": "s2t"})
17
+
18
+ # Async services poll automatically until done
19
+ c.run("decompose.lesson", {"text": "...", "title": "第一课"})
20
+ ```
21
+
22
+ ## What it handles for you
23
+
24
+ - `Authorization: Bearer` auth — the only accepted scheme
25
+ - The `payload` request shape, which works for every service code
26
+ - Fetches the field spec from `GET /v1/chinese/services` at startup instead of
27
+ hardcoding a local copy
28
+ - Retries `429` honouring `Retry-After`, and `502`/`503` with exponential
29
+ backoff; never retries `4xx` that won't succeed on retry
30
+ - Polls async services (`decompose.lesson`, `decompose.wenyan_lesson`) to
31
+ completion
32
+ - Logs the `warnings` array — unknown top-level request fields are dropped
33
+ silently, and that array is the only signal
34
+
35
+ ## Errors
36
+
37
+ `ClavisAuthError` (401) · `ClavisParamError` (400, has `.field` / `.expected`) ·
38
+ `ClavisNotFound` (404) · `ClavisRateLimited` (429, has `.retry_after` /
39
+ `.scope`) · `ClavisUpstreamError` (502/503, worth retrying) · `ClavisTimeout`
40
+
41
+ ## Not this API
42
+
43
+ Single-character and vocabulary decomposition belong to the sister product
44
+ chinesekey.org — separate pricing, `pk_live_` keys, not interchangeable.
45
+
46
+ MIT
@@ -0,0 +1,39 @@
1
+ """Clavis Sinica —— 中文语义分解 API 的官方 Python 客户端。
2
+
3
+ 设计取舍(每一条都来自 2026-09-10 的真实实测, 不是照文档抄的):
4
+
5
+ * 认证只有 `Authorization: Bearer ck-live_...` 一种。实测过 X-API-Key /
6
+ Authorization 裸值 / 小写变体 / 查询参数, 全部 401。
7
+ * 请求一律用 `payload` 形态。`{"input": ...}` 那种快捷写法只能设置单字段
8
+ 服务的主字段, 存在的唯一理由是历史兼容 —— 统一走 payload 就不必判断
9
+ "这个服务能不能用 input"。
10
+ * 字段规格不硬编码, 启动时从 `/v1/chinese/services` 拉。那个接口零费用,
11
+ 且与引擎校验用的是同一份定义。此前有接入方维护本地手抄规格表, 累计
12
+ 出过五处错误, 根因都是"规格只存在于服务端的代码里"。
13
+ * 429 按 `Retry-After` 退避, 5xx 指数退避, 4xx 立即抛。被限流的请求不
14
+ 扣费, 所以重试是安全的。
15
+ * `upstream_error` / `upstream_unavailable` 是服务端侧故障, 与调用方的
16
+ 请求无关, 值得重试; `invalid_param` 这类改了参数再发才有意义。
17
+ * 异步服务(decompose.lesson / decompose.wenyan_lesson)自动轮询到终态,
18
+ 调用方不需要自己写轮询循环。
19
+ * 响应里的 `warnings` 默认打到 logging.WARNING。顶层未知字段会被静默
20
+ 丢弃, 这个数组是唯一提示 —— 曾有接入方在顶层发了几个月的无效参数,
21
+ 一切正常、毫无信号。
22
+ """
23
+ from .client import (
24
+ Clavis,
25
+ ClavisError,
26
+ ClavisAuthError,
27
+ ClavisParamError,
28
+ ClavisRateLimited,
29
+ ClavisUpstreamError,
30
+ ClavisNotFound,
31
+ ClavisTimeout,
32
+ )
33
+
34
+ __version__ = "0.1.0"
35
+ __all__ = [
36
+ "Clavis", "ClavisError", "ClavisAuthError", "ClavisParamError",
37
+ "ClavisRateLimited", "ClavisUpstreamError", "ClavisNotFound",
38
+ "ClavisTimeout",
39
+ ]
@@ -0,0 +1,263 @@
1
+ # -*- coding: utf-8 -*-
2
+ import logging
3
+ import time
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ import requests
7
+
8
+ log = logging.getLogger("clavis_sinica")
9
+
10
+ DEFAULT_BASE_URL = "https://clavissinica.org"
11
+
12
+
13
+ class ClavisError(Exception):
14
+ """所有本客户端抛出的错误的基类。"""
15
+
16
+ def __init__(self, message, *, status_code=None, error=None, payload=None):
17
+ super().__init__(message)
18
+ self.status_code = status_code
19
+ self.error = error
20
+ self.payload = payload or {}
21
+
22
+ def __str__(self):
23
+ base = super().__str__()
24
+ return "%s (HTTP %s, error=%s)" % (base, self.status_code, self.error)
25
+
26
+
27
+ class ClavisAuthError(ClavisError):
28
+ """401 —— Key 缺失/格式错/前缀不对/已吊销。重试没有意义。"""
29
+
30
+
31
+ class ClavisParamError(ClavisError):
32
+ """400 invalid_param —— 参数有问题。`field` 指出是哪一个。"""
33
+
34
+ @property
35
+ def field(self):
36
+ return self.payload.get("field")
37
+
38
+ @property
39
+ def expected(self):
40
+ return self.payload.get("expected")
41
+
42
+
43
+ class ClavisNotFound(ClavisError):
44
+ """404 —— 接口正常, 内容不在词典里。重试没有意义。"""
45
+
46
+
47
+ class ClavisRateLimited(ClavisError):
48
+ """429 —— 触发限流。被拒的请求不扣费。"""
49
+
50
+ @property
51
+ def retry_after(self):
52
+ return self.payload.get("retry_after")
53
+
54
+ @property
55
+ def scope(self):
56
+ return self.payload.get("scope")
57
+
58
+
59
+ class ClavisUpstreamError(ClavisError):
60
+ """502/503 —— 服务端侧故障, 与你的请求无关。退避后重试是合理的。"""
61
+
62
+
63
+ class ClavisTimeout(ClavisError):
64
+ """异步任务在给定时间内没有到达终态。任务本身可能仍在跑。"""
65
+
66
+
67
+ _RETRYABLE_STATUS = (429, 502, 503)
68
+ _TERMINAL_TASK_STATES = ("done", "error", "success", "completed", "failed")
69
+
70
+
71
+ class Clavis:
72
+ """Clavis Sinica API 客户端。
73
+
74
+ c = Clavis("ck-live_...")
75
+ c.run("decompose.idiom", {"idiom": "守株待兔"})
76
+ c.run("decompose.lesson", {"text": "...", "title": "..."}) # 自动轮询
77
+
78
+ 参数
79
+ api_key 形如 ck-live_ / ck-test_ 的 Key
80
+ base_url 默认 https://clavissinica.org
81
+ max_retries 429/5xx 的最大重试次数, 默认 4
82
+ load_catalog 构造时是否拉取服务目录, 默认 True。设 False 可以
83
+ 省掉一次网络往返, 但异步服务将无法自动识别,
84
+ 需要显式传 poll=True
85
+ """
86
+
87
+ def __init__(self, api_key: str, base_url: str = DEFAULT_BASE_URL,
88
+ timeout: float = 180.0, max_retries: int = 4,
89
+ session: Optional[requests.Session] = None,
90
+ load_catalog: bool = True):
91
+ if not api_key or not api_key.startswith("ck-"):
92
+ # 提前拦住最常见的两种错: 传了 chinesekey.org 的 pk_live_ Key,
93
+ # 或者把 Stripe 的 cs_live_ 当成了 API Key。服务端也会拒,
94
+ # 但在这里拒能省一次往返, 而且错误信息更直白。
95
+ raise ValueError(
96
+ "api_key 必须以 ck- 开头(ck-live_ 或 ck-test_)。"
97
+ "chinesekey.org 的 pk_live_ Key 不能用在这里; "
98
+ "cs_live_ 开头的是 Stripe checkout 会话 id, 不是 API Key。")
99
+ self.api_key = api_key
100
+ self.base_url = base_url.rstrip("/")
101
+ self.timeout = timeout
102
+ self.max_retries = max_retries
103
+ self.http = session or requests.Session()
104
+ self.http.headers.update({
105
+ "Authorization": "Bearer " + api_key,
106
+ "Content-Type": "application/json",
107
+ "User-Agent": "clavis-sinica-python/0.1.0",
108
+ })
109
+ self._catalog: Optional[Dict[str, Any]] = None
110
+ self._modes: Dict[str, str] = {}
111
+ if load_catalog:
112
+ try:
113
+ self.refresh_catalog()
114
+ except ClavisError:
115
+ # 目录拉不到不该让构造失败 —— 调用时显式传 poll 仍然可用
116
+ log.warning("服务目录拉取失败, 异步服务需显式传 poll=True")
117
+
118
+ # ── 服务目录 ────────────────────────────────────────
119
+ def refresh_catalog(self) -> Dict[str, Any]:
120
+ """拉取 /v1/chinese/services。零费用, 任意有效 Key 可读。"""
121
+ self._catalog = self._request("GET", "/v1/chinese/services")
122
+ self._modes = {
123
+ s["service_code"]: (s.get("execution") or {}).get("mode", "sync")
124
+ for s in self._catalog.get("services", [])
125
+ }
126
+ return self._catalog
127
+
128
+ @property
129
+ def catalog(self) -> Dict[str, Any]:
130
+ if self._catalog is None:
131
+ self.refresh_catalog()
132
+ return self._catalog
133
+
134
+ def service_codes(self) -> List[str]:
135
+ return list(self._modes) or [
136
+ s["service_code"] for s in self.catalog.get("services", [])]
137
+
138
+ def spec(self, service_code: str) -> Dict[str, Any]:
139
+ """某个服务的完整字段规格 —— 权威来源, 不要在本地抄一份。"""
140
+ for s in self.catalog.get("services", []):
141
+ if s["service_code"] == service_code:
142
+ return s
143
+ raise KeyError(service_code)
144
+
145
+ # ── 调用 ────────────────────────────────────────────
146
+ def run(self, service_code: str, payload: Dict[str, Any],
147
+ idempotency_key: Optional[str] = None,
148
+ poll: Optional[bool] = None,
149
+ poll_interval: float = 5.0,
150
+ poll_timeout: float = 600.0) -> Dict[str, Any]:
151
+ """调用一个服务。
152
+
153
+ 同步服务直接返回完整响应体。异步服务(submit_poll)默认自动轮询
154
+ 到终态再返回; 传 poll=False 可以拿到 {"task_id": ...} 自己轮询。
155
+ """
156
+ body = {"service_code": service_code, "payload": payload}
157
+ if idempotency_key:
158
+ body["idempotency_key"] = idempotency_key
159
+ out = self._request("POST", "/v1/chinese/run", json=body)
160
+
161
+ if poll is None:
162
+ poll = self._modes.get(service_code) == "submit_poll"
163
+ task_id = out.get("task_id")
164
+ if task_id and poll:
165
+ return self.wait_task(task_id, interval=poll_interval,
166
+ timeout=poll_timeout)
167
+ return out
168
+
169
+ def get_task(self, task_id: str) -> Dict[str, Any]:
170
+ return self._request("GET", "/v1/chinese/tasks/" + task_id)
171
+
172
+ def wait_task(self, task_id: str, interval: float = 5.0,
173
+ timeout: float = 600.0) -> Dict[str, Any]:
174
+ deadline = time.time() + timeout
175
+ while True:
176
+ t = self.get_task(task_id)
177
+ if (t.get("status") or "") in _TERMINAL_TASK_STATES:
178
+ return t
179
+ if time.time() >= deadline:
180
+ raise ClavisTimeout(
181
+ "任务 %s 在 %.0f 秒内没有到达终态(它可能仍在执行, "
182
+ "可以稍后用 get_task 继续查)" % (task_id, timeout),
183
+ payload={"task_id": task_id, "last_status": t.get("status")})
184
+ time.sleep(interval)
185
+
186
+ # ── 账户 ────────────────────────────────────────────
187
+ def balance(self) -> Dict[str, Any]:
188
+ return self._request("GET", "/v1/account/balance")
189
+
190
+ def usage(self) -> Dict[str, Any]:
191
+ return self._request("GET", "/v1/account/usage")
192
+
193
+ def ledger(self) -> Dict[str, Any]:
194
+ return self._request("GET", "/v1/account/ledger")
195
+
196
+ # ── 内部 ────────────────────────────────────────────
197
+ def _request(self, method: str, path: str, **kw) -> Dict[str, Any]:
198
+ url = self.base_url + path
199
+ last_exc = None
200
+ for attempt in range(self.max_retries + 1):
201
+ try:
202
+ r = self.http.request(method, url, timeout=self.timeout, **kw)
203
+ except requests.RequestException as e:
204
+ last_exc = ClavisUpstreamError("网络请求失败: %s" % e)
205
+ if attempt < self.max_retries:
206
+ time.sleep(min(2 ** attempt, 30))
207
+ continue
208
+ raise last_exc
209
+
210
+ if r.status_code < 300:
211
+ data = r.json()
212
+ for w in (data.get("warnings") or []):
213
+ # 顶层未知字段会被静默丢弃, 这个数组是唯一提示。
214
+ log.warning("clavis warning: %s", w)
215
+ return data
216
+
217
+ detail, err, msg = self._parse_error(r)
218
+ if r.status_code in _RETRYABLE_STATUS and attempt < self.max_retries:
219
+ wait = self._retry_wait(r, detail, attempt)
220
+ log.warning("HTTP %s (%s), %.1f 秒后重试 (%d/%d)",
221
+ r.status_code, err, wait, attempt + 1, self.max_retries)
222
+ time.sleep(wait)
223
+ continue
224
+ raise self._to_exception(r.status_code, err, msg, detail)
225
+ raise last_exc or ClavisError("请求失败")
226
+
227
+ @staticmethod
228
+ def _parse_error(r):
229
+ try:
230
+ body = r.json()
231
+ except Exception:
232
+ # nginx 直接返回的错误是 HTML(比如请求体超过 8MB 被挡在应用之外)
233
+ return ({}, "http_%d" % r.status_code,
234
+ (r.text or "")[:200].strip())
235
+ detail = body.get("detail") if isinstance(body.get("detail"), dict) else body
236
+ return detail, detail.get("error"), detail.get("msg") or str(body)[:200]
237
+
238
+ @staticmethod
239
+ def _retry_wait(r, detail, attempt):
240
+ hdr = r.headers.get("Retry-After")
241
+ if hdr:
242
+ try:
243
+ return float(hdr)
244
+ except ValueError:
245
+ pass
246
+ if isinstance(detail, dict) and detail.get("retry_after"):
247
+ return float(detail["retry_after"])
248
+ return min(2 ** attempt, 30)
249
+
250
+ @staticmethod
251
+ def _to_exception(status, err, msg, detail):
252
+ kw = dict(status_code=status, error=err, payload=detail or {})
253
+ if status == 401:
254
+ return ClavisAuthError(msg, **kw)
255
+ if status == 404 or err == "not_found":
256
+ return ClavisNotFound(msg, **kw)
257
+ if status == 429 or err == "rate_limited":
258
+ return ClavisRateLimited(msg, **kw)
259
+ if status in (502, 503) or (err or "").startswith("upstream_"):
260
+ return ClavisUpstreamError(msg, **kw)
261
+ if status == 400 or err == "invalid_param":
262
+ return ClavisParamError(msg, **kw)
263
+ return ClavisError(msg, **kw)
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: clavis-sinica
3
+ Version: 0.1.0
4
+ Summary: Official Python client for the Clavis Sinica API — Chinese idiom, sentence, passage, classical Chinese and lesson decomposition.
5
+ Author: Clavis Sinica
6
+ License: MIT
7
+ Project-URL: Homepage, https://clavissinica.org
8
+ Project-URL: Documentation, https://clavissinica.org/docs
9
+ Project-URL: API Reference, https://clavissinica.org/openapi.json
10
+ Keywords: chinese,nlp,idiom,classical-chinese,hsk,api-client
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: requests>=2.25
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=7.0; extra == "dev"
17
+ Dynamic: license-file
18
+
19
+ # clavis-sinica
20
+
21
+ Official Python client for the [Clavis Sinica](https://clavissinica.org) API —
22
+ Chinese idiom, sentence, passage, classical Chinese and lesson decomposition.
23
+
24
+ ```bash
25
+ pip install clavis-sinica
26
+ ```
27
+
28
+ ```python
29
+ from clavis_sinica import Clavis
30
+
31
+ c = Clavis("ck-live_...")
32
+
33
+ c.run("decompose.idiom", {"idiom": "守株待兔"})
34
+ c.run("convert.script", {"text": "汉字", "direction": "s2t"})
35
+
36
+ # Async services poll automatically until done
37
+ c.run("decompose.lesson", {"text": "...", "title": "第一课"})
38
+ ```
39
+
40
+ ## What it handles for you
41
+
42
+ - `Authorization: Bearer` auth — the only accepted scheme
43
+ - The `payload` request shape, which works for every service code
44
+ - Fetches the field spec from `GET /v1/chinese/services` at startup instead of
45
+ hardcoding a local copy
46
+ - Retries `429` honouring `Retry-After`, and `502`/`503` with exponential
47
+ backoff; never retries `4xx` that won't succeed on retry
48
+ - Polls async services (`decompose.lesson`, `decompose.wenyan_lesson`) to
49
+ completion
50
+ - Logs the `warnings` array — unknown top-level request fields are dropped
51
+ silently, and that array is the only signal
52
+
53
+ ## Errors
54
+
55
+ `ClavisAuthError` (401) · `ClavisParamError` (400, has `.field` / `.expected`) ·
56
+ `ClavisNotFound` (404) · `ClavisRateLimited` (429, has `.retry_after` /
57
+ `.scope`) · `ClavisUpstreamError` (502/503, worth retrying) · `ClavisTimeout`
58
+
59
+ ## Not this API
60
+
61
+ Single-character and vocabulary decomposition belong to the sister product
62
+ chinesekey.org — separate pricing, `pk_live_` keys, not interchangeable.
63
+
64
+ MIT
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ clavis_sinica/__init__.py
5
+ clavis_sinica/client.py
6
+ clavis_sinica.egg-info/PKG-INFO
7
+ clavis_sinica.egg-info/SOURCES.txt
8
+ clavis_sinica.egg-info/dependency_links.txt
9
+ clavis_sinica.egg-info/requires.txt
10
+ clavis_sinica.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ requests>=2.25
2
+
3
+ [dev]
4
+ pytest>=7.0
@@ -0,0 +1 @@
1
+ clavis_sinica
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "clavis-sinica"
7
+ version = "0.1.0"
8
+ description = "Official Python client for the Clavis Sinica API — Chinese idiom, sentence, passage, classical Chinese and lesson decomposition."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = {text = "MIT"}
12
+ authors = [{name = "Clavis Sinica"}]
13
+ keywords = ["chinese", "nlp", "idiom", "classical-chinese", "hsk", "api-client"]
14
+ dependencies = ["requests>=2.25"]
15
+
16
+ [project.urls]
17
+ Homepage = "https://clavissinica.org"
18
+ Documentation = "https://clavissinica.org/docs"
19
+ "API Reference" = "https://clavissinica.org/openapi.json"
20
+
21
+ [project.optional-dependencies]
22
+ dev = ["pytest>=7.0"]
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["."]
26
+ include = ["clavis_sinica*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+