ventimath 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.
- ventimath-0.1.0/PKG-INFO +214 -0
- ventimath-0.1.0/README.md +206 -0
- ventimath-0.1.0/pyproject.toml +20 -0
- ventimath-0.1.0/setup.cfg +4 -0
- ventimath-0.1.0/src/ventimath/__init__.py +25 -0
- ventimath-0.1.0/src/ventimath/client.py +441 -0
- ventimath-0.1.0/src/ventimath.egg-info/PKG-INFO +214 -0
- ventimath-0.1.0/src/ventimath.egg-info/SOURCES.txt +9 -0
- ventimath-0.1.0/src/ventimath.egg-info/dependency_links.txt +1 -0
- ventimath-0.1.0/src/ventimath.egg-info/top_level.txt +1 -0
- ventimath-0.1.0/tests/test_client.py +423 -0
ventimath-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ventimath
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Small standard-library client for VentiMath answer keys and profile skill tags
|
|
5
|
+
Project-URL: Repository, https://github.com/VentiMath/venti-sdk
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# VentiMath Python SDK
|
|
10
|
+
|
|
11
|
+
Download answer keys, import your problem classifications, and find problems by skill.
|
|
12
|
+
Requires Python 3.10 or newer, with no runtime dependencies.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
pip install ventimath
|
|
18
|
+
# Or add it to a uv project:
|
|
19
|
+
uv add ventimath
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The default API base URL is
|
|
23
|
+
`https://us-central1-ventimath.cloudfunctions.net`; use `base_url=` for a deployed
|
|
24
|
+
test environment or functions emulator.
|
|
25
|
+
|
|
26
|
+
## Download answer keys for the existing classifier
|
|
27
|
+
|
|
28
|
+
Answer-key reads are public and always omit authentication, even when the client
|
|
29
|
+
has a token. Download a file; the classifier's existing `--answer-key` reads a
|
|
30
|
+
local JSON path, not a URL.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from ventimath import VentiMathClient
|
|
34
|
+
|
|
35
|
+
public = VentiMathClient()
|
|
36
|
+
result = public.download_answer_key("answer-key.json", exam="EXAM_ID")
|
|
37
|
+
print("Missing problems:", result["missing"])
|
|
38
|
+
print("No usable key:", result["unavailable"])
|
|
39
|
+
|
|
40
|
+
# Or select up to 100 canonical problem IDs:
|
|
41
|
+
keys = public.answer_keys(ids=["PROBLEM_ID_1", "PROBLEM_ID_2"])
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
python classify_skills.py PROBLEM_ID --answer-key answer-key.json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The response and saved file have the same shape:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"problems": {
|
|
53
|
+
"PROBLEM_ID": {"correctChoice": "B", "answerText": "2"}
|
|
54
|
+
},
|
|
55
|
+
"missing": [],
|
|
56
|
+
"unavailable": []
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Text-answer records carry `answerText`; accepted alternatives may also appear in
|
|
61
|
+
`allowedAnswers`. Exam requests are bounded to 500 placements. Unknown canonical
|
|
62
|
+
IDs appear in `missing`; proofs, absent keys, and placeholders appear in
|
|
63
|
+
`unavailable`.
|
|
64
|
+
|
|
65
|
+
## Import classifications on your own profile
|
|
66
|
+
|
|
67
|
+
Create your VentiMath account and mint **your own** personal access token in
|
|
68
|
+
Settings → Developer. Select `tags.read` and `tags.write`. Keep the token in your
|
|
69
|
+
environment rather than in source files. The authenticated account owns the set;
|
|
70
|
+
there is no option to assign your upload to someone else's account.
|
|
71
|
+
|
|
72
|
+
`VentiMathClient(token=...)` uses an explicit token. `VentiMathClient.from_env()`
|
|
73
|
+
reads `VENTI_API_KEY` and optionally `VENTI_API_URL`. Neither stores credentials.
|
|
74
|
+
The ordinary `VentiMathClient()` constructor remains anonymous even if a token
|
|
75
|
+
environment variable exists.
|
|
76
|
+
|
|
77
|
+
Sets are **public by default**. Pass `public=False` at creation to keep a set
|
|
78
|
+
private to your account. Publishing a private set is permanent; public sets cannot
|
|
79
|
+
be made private. This import example creates a private set:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from pathlib import Path
|
|
83
|
+
from ventimath import (
|
|
84
|
+
VentiMathClient,
|
|
85
|
+
load_classification_files,
|
|
86
|
+
load_vocabulary_csv,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
client = VentiMathClient.from_env()
|
|
90
|
+
vocabulary = load_vocabulary_csv("skill-lists/skills-merged.csv")
|
|
91
|
+
created = client.create_tag_set(
|
|
92
|
+
"amc-skills",
|
|
93
|
+
"My AMC skill classifications",
|
|
94
|
+
public=False,
|
|
95
|
+
vocabulary=vocabulary,
|
|
96
|
+
)
|
|
97
|
+
set_id = created["set"]["id"]
|
|
98
|
+
|
|
99
|
+
# Use saved per-problem output from a single classifier run folder.
|
|
100
|
+
records = load_classification_files(sorted(Path("output/skills/MODEL_DIRECTORY").glob("*.json")))
|
|
101
|
+
|
|
102
|
+
# Or use the classifier's combined output instead:
|
|
103
|
+
# records = load_classification_files(["combined.json"])
|
|
104
|
+
|
|
105
|
+
preview = client.import_classifications(set_id, records, dry_run=True)
|
|
106
|
+
print(preview)
|
|
107
|
+
|
|
108
|
+
# After inspecting the preview, upload the same records.
|
|
109
|
+
report = client.import_classifications(set_id, records)
|
|
110
|
+
print("Imported:", report["imported"], "unchanged:", report["unchanged"])
|
|
111
|
+
for rejected in report["rejected"]:
|
|
112
|
+
print(rejected)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Individual records use `{problemId, result, model, reasoningEffort}`. Combined
|
|
116
|
+
files use `{problemId, llmResult}` and usually omit model provenance; supply
|
|
117
|
+
`model="MODEL_USED"` and `reasoning_effort="EFFORT_USED"` to `import_classifications`
|
|
118
|
+
when those values are known. Per-record values take precedence. Both shapes pass
|
|
119
|
+
through to the API unchanged. The server validates each classification and
|
|
120
|
+
discards copied prompts, raw replies, statements, and answer fields.
|
|
121
|
+
|
|
122
|
+
Vocabulary CSV headers are `category`, `skill`, and `description`, in any order.
|
|
123
|
+
Quoted commas/newlines, escaped quotes, and a UTF-8 BOM are supported. On import,
|
|
124
|
+
`vocabulary=` merges by skill name; supplied entries update matching descriptions
|
|
125
|
+
and categories. Accepted records add unseen skill names. To explicitly replace
|
|
126
|
+
the vocabulary, use `update_tag_set(set_id, vocabulary=...)`.
|
|
127
|
+
|
|
128
|
+
## Retrieve problems by skill
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
# Your own sets, including private sets; needs tags.read.
|
|
132
|
+
mine = client.list_tag_sets()
|
|
133
|
+
|
|
134
|
+
# Public discovery always omits the token, even on an authenticated client.
|
|
135
|
+
shared = client.list_public_tag_sets(owner="OWNER_NUMERIC_ID", limit=20)
|
|
136
|
+
# Pass shared["nextCursor"] as cursor= to request the next page.
|
|
137
|
+
|
|
138
|
+
# Set listing returns metadata. Fetch a set to get its vocabulary.
|
|
139
|
+
tag_set = client.get_tag_set(set_id)
|
|
140
|
+
print(tag_set["set"]["vocabulary"])
|
|
141
|
+
|
|
142
|
+
# One page, preserving the response envelope and pagination cursor:
|
|
143
|
+
page = client.tagged_problems(
|
|
144
|
+
set_id, skill="Casework", importance="essential", limit=25
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Or iterate across every page:
|
|
148
|
+
for problem in client.iter_tagged_problems(set_id, skill="Casework"):
|
|
149
|
+
print(problem["id"], problem["details"]["statement"])
|
|
150
|
+
print(problem["classification"])
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Skill names match exactly. Importance is `essential`, `time-saving`, or `helpful`
|
|
154
|
+
and requires a skill filter.
|
|
155
|
+
Use `public=True` on `get_tag_set`, `tagged_problems`, or `iter_tagged_problems`
|
|
156
|
+
to read a public set without sending the client's token. Public problem records
|
|
157
|
+
omit catalog answer fields; their attached classifications remain attributed to
|
|
158
|
+
the set owner.
|
|
159
|
+
|
|
160
|
+
## Reports, errors, and retries
|
|
161
|
+
|
|
162
|
+
Imports are split into requests of at most 200 records and 2 MiB of UTF-8 JSON.
|
|
163
|
+
All input is checked for JSON encodability and individual request size before
|
|
164
|
+
any request is sent. The aggregate response contains `imported`, `unchanged`,
|
|
165
|
+
`rejected`, `dryRun`, `total`, `processed`, and `remaining`. Rejected entries retain
|
|
166
|
+
the server's error code and problem ID; their zero-based `index` refers to your
|
|
167
|
+
complete input sequence. Row rejections do not raise an exception; inspect them.
|
|
168
|
+
|
|
169
|
+
Dry runs validate each batch against the currently stored set without writing.
|
|
170
|
+
For imports spanning batches, repeated problem IDs or the cumulative vocabulary
|
|
171
|
+
limit can make actual counts differ from the preview. Inspect the final report.
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from ventimath import VentiMathAPIError, VentiMathImportError
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
report = client.import_classifications(set_id, records)
|
|
178
|
+
except VentiMathImportError as error:
|
|
179
|
+
print("Completed requests:", error.report)
|
|
180
|
+
print("HTTP status, if available:", error.status)
|
|
181
|
+
print("Server payload, if available:", error.payload)
|
|
182
|
+
# A failed response does not prove the last request was uncommitted.
|
|
183
|
+
# Retrying the identical input is safe; unchanged records are not rewritten.
|
|
184
|
+
except VentiMathAPIError as error:
|
|
185
|
+
print(error.status, error.payload)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Other methods raise `VentiMathAPIError` with the original HTTP status and server
|
|
189
|
+
payload for HTTP failures. `VentiMathConnectionError` covers transport failures,
|
|
190
|
+
including interrupted response bodies;
|
|
191
|
+
`VentiMathProtocolError` covers unexpected responses. Invalid local arguments
|
|
192
|
+
raise `ValueError`. Imports wrap transport/protocol/HTTP failures in
|
|
193
|
+
`VentiMathImportError` to preserve the counts from completed requests.
|
|
194
|
+
|
|
195
|
+
These methods call the actual REST routes: `GET /answerKeys`,
|
|
196
|
+
`GET|POST /problemTags/sets`, `GET|PATCH /problemTags/sets/{id}`,
|
|
197
|
+
`POST /problemTags/sets/{id}/import`, and
|
|
198
|
+
`GET /problemTags/sets/{id}/problems`. You can call those endpoints directly from
|
|
199
|
+
your existing Python HTTP code; the SDK is optional.
|
|
200
|
+
|
|
201
|
+
## Development
|
|
202
|
+
|
|
203
|
+
Install from a source checkout with `python -m pip install .`.
|
|
204
|
+
|
|
205
|
+
From this package directory, without installing or contacting a server:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
PYTHONPATH=src python -B -m unittest discover -s tests -v
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
All transport calls in these tests are mocked. They exercise exact URLs and
|
|
212
|
+
headers, anonymous discovery, private ownership request shaping, both classifier
|
|
213
|
+
record formats, CSV parsing, Unicode batch limits, pagination, rejected redirects,
|
|
214
|
+
and partial-failure reports.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# VentiMath Python SDK
|
|
2
|
+
|
|
3
|
+
Download answer keys, import your problem classifications, and find problems by skill.
|
|
4
|
+
Requires Python 3.10 or newer, with no runtime dependencies.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pip install ventimath
|
|
10
|
+
# Or add it to a uv project:
|
|
11
|
+
uv add ventimath
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The default API base URL is
|
|
15
|
+
`https://us-central1-ventimath.cloudfunctions.net`; use `base_url=` for a deployed
|
|
16
|
+
test environment or functions emulator.
|
|
17
|
+
|
|
18
|
+
## Download answer keys for the existing classifier
|
|
19
|
+
|
|
20
|
+
Answer-key reads are public and always omit authentication, even when the client
|
|
21
|
+
has a token. Download a file; the classifier's existing `--answer-key` reads a
|
|
22
|
+
local JSON path, not a URL.
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from ventimath import VentiMathClient
|
|
26
|
+
|
|
27
|
+
public = VentiMathClient()
|
|
28
|
+
result = public.download_answer_key("answer-key.json", exam="EXAM_ID")
|
|
29
|
+
print("Missing problems:", result["missing"])
|
|
30
|
+
print("No usable key:", result["unavailable"])
|
|
31
|
+
|
|
32
|
+
# Or select up to 100 canonical problem IDs:
|
|
33
|
+
keys = public.answer_keys(ids=["PROBLEM_ID_1", "PROBLEM_ID_2"])
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
python classify_skills.py PROBLEM_ID --answer-key answer-key.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The response and saved file have the same shape:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"problems": {
|
|
45
|
+
"PROBLEM_ID": {"correctChoice": "B", "answerText": "2"}
|
|
46
|
+
},
|
|
47
|
+
"missing": [],
|
|
48
|
+
"unavailable": []
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Text-answer records carry `answerText`; accepted alternatives may also appear in
|
|
53
|
+
`allowedAnswers`. Exam requests are bounded to 500 placements. Unknown canonical
|
|
54
|
+
IDs appear in `missing`; proofs, absent keys, and placeholders appear in
|
|
55
|
+
`unavailable`.
|
|
56
|
+
|
|
57
|
+
## Import classifications on your own profile
|
|
58
|
+
|
|
59
|
+
Create your VentiMath account and mint **your own** personal access token in
|
|
60
|
+
Settings → Developer. Select `tags.read` and `tags.write`. Keep the token in your
|
|
61
|
+
environment rather than in source files. The authenticated account owns the set;
|
|
62
|
+
there is no option to assign your upload to someone else's account.
|
|
63
|
+
|
|
64
|
+
`VentiMathClient(token=...)` uses an explicit token. `VentiMathClient.from_env()`
|
|
65
|
+
reads `VENTI_API_KEY` and optionally `VENTI_API_URL`. Neither stores credentials.
|
|
66
|
+
The ordinary `VentiMathClient()` constructor remains anonymous even if a token
|
|
67
|
+
environment variable exists.
|
|
68
|
+
|
|
69
|
+
Sets are **public by default**. Pass `public=False` at creation to keep a set
|
|
70
|
+
private to your account. Publishing a private set is permanent; public sets cannot
|
|
71
|
+
be made private. This import example creates a private set:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from pathlib import Path
|
|
75
|
+
from ventimath import (
|
|
76
|
+
VentiMathClient,
|
|
77
|
+
load_classification_files,
|
|
78
|
+
load_vocabulary_csv,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
client = VentiMathClient.from_env()
|
|
82
|
+
vocabulary = load_vocabulary_csv("skill-lists/skills-merged.csv")
|
|
83
|
+
created = client.create_tag_set(
|
|
84
|
+
"amc-skills",
|
|
85
|
+
"My AMC skill classifications",
|
|
86
|
+
public=False,
|
|
87
|
+
vocabulary=vocabulary,
|
|
88
|
+
)
|
|
89
|
+
set_id = created["set"]["id"]
|
|
90
|
+
|
|
91
|
+
# Use saved per-problem output from a single classifier run folder.
|
|
92
|
+
records = load_classification_files(sorted(Path("output/skills/MODEL_DIRECTORY").glob("*.json")))
|
|
93
|
+
|
|
94
|
+
# Or use the classifier's combined output instead:
|
|
95
|
+
# records = load_classification_files(["combined.json"])
|
|
96
|
+
|
|
97
|
+
preview = client.import_classifications(set_id, records, dry_run=True)
|
|
98
|
+
print(preview)
|
|
99
|
+
|
|
100
|
+
# After inspecting the preview, upload the same records.
|
|
101
|
+
report = client.import_classifications(set_id, records)
|
|
102
|
+
print("Imported:", report["imported"], "unchanged:", report["unchanged"])
|
|
103
|
+
for rejected in report["rejected"]:
|
|
104
|
+
print(rejected)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Individual records use `{problemId, result, model, reasoningEffort}`. Combined
|
|
108
|
+
files use `{problemId, llmResult}` and usually omit model provenance; supply
|
|
109
|
+
`model="MODEL_USED"` and `reasoning_effort="EFFORT_USED"` to `import_classifications`
|
|
110
|
+
when those values are known. Per-record values take precedence. Both shapes pass
|
|
111
|
+
through to the API unchanged. The server validates each classification and
|
|
112
|
+
discards copied prompts, raw replies, statements, and answer fields.
|
|
113
|
+
|
|
114
|
+
Vocabulary CSV headers are `category`, `skill`, and `description`, in any order.
|
|
115
|
+
Quoted commas/newlines, escaped quotes, and a UTF-8 BOM are supported. On import,
|
|
116
|
+
`vocabulary=` merges by skill name; supplied entries update matching descriptions
|
|
117
|
+
and categories. Accepted records add unseen skill names. To explicitly replace
|
|
118
|
+
the vocabulary, use `update_tag_set(set_id, vocabulary=...)`.
|
|
119
|
+
|
|
120
|
+
## Retrieve problems by skill
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# Your own sets, including private sets; needs tags.read.
|
|
124
|
+
mine = client.list_tag_sets()
|
|
125
|
+
|
|
126
|
+
# Public discovery always omits the token, even on an authenticated client.
|
|
127
|
+
shared = client.list_public_tag_sets(owner="OWNER_NUMERIC_ID", limit=20)
|
|
128
|
+
# Pass shared["nextCursor"] as cursor= to request the next page.
|
|
129
|
+
|
|
130
|
+
# Set listing returns metadata. Fetch a set to get its vocabulary.
|
|
131
|
+
tag_set = client.get_tag_set(set_id)
|
|
132
|
+
print(tag_set["set"]["vocabulary"])
|
|
133
|
+
|
|
134
|
+
# One page, preserving the response envelope and pagination cursor:
|
|
135
|
+
page = client.tagged_problems(
|
|
136
|
+
set_id, skill="Casework", importance="essential", limit=25
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Or iterate across every page:
|
|
140
|
+
for problem in client.iter_tagged_problems(set_id, skill="Casework"):
|
|
141
|
+
print(problem["id"], problem["details"]["statement"])
|
|
142
|
+
print(problem["classification"])
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Skill names match exactly. Importance is `essential`, `time-saving`, or `helpful`
|
|
146
|
+
and requires a skill filter.
|
|
147
|
+
Use `public=True` on `get_tag_set`, `tagged_problems`, or `iter_tagged_problems`
|
|
148
|
+
to read a public set without sending the client's token. Public problem records
|
|
149
|
+
omit catalog answer fields; their attached classifications remain attributed to
|
|
150
|
+
the set owner.
|
|
151
|
+
|
|
152
|
+
## Reports, errors, and retries
|
|
153
|
+
|
|
154
|
+
Imports are split into requests of at most 200 records and 2 MiB of UTF-8 JSON.
|
|
155
|
+
All input is checked for JSON encodability and individual request size before
|
|
156
|
+
any request is sent. The aggregate response contains `imported`, `unchanged`,
|
|
157
|
+
`rejected`, `dryRun`, `total`, `processed`, and `remaining`. Rejected entries retain
|
|
158
|
+
the server's error code and problem ID; their zero-based `index` refers to your
|
|
159
|
+
complete input sequence. Row rejections do not raise an exception; inspect them.
|
|
160
|
+
|
|
161
|
+
Dry runs validate each batch against the currently stored set without writing.
|
|
162
|
+
For imports spanning batches, repeated problem IDs or the cumulative vocabulary
|
|
163
|
+
limit can make actual counts differ from the preview. Inspect the final report.
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from ventimath import VentiMathAPIError, VentiMathImportError
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
report = client.import_classifications(set_id, records)
|
|
170
|
+
except VentiMathImportError as error:
|
|
171
|
+
print("Completed requests:", error.report)
|
|
172
|
+
print("HTTP status, if available:", error.status)
|
|
173
|
+
print("Server payload, if available:", error.payload)
|
|
174
|
+
# A failed response does not prove the last request was uncommitted.
|
|
175
|
+
# Retrying the identical input is safe; unchanged records are not rewritten.
|
|
176
|
+
except VentiMathAPIError as error:
|
|
177
|
+
print(error.status, error.payload)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Other methods raise `VentiMathAPIError` with the original HTTP status and server
|
|
181
|
+
payload for HTTP failures. `VentiMathConnectionError` covers transport failures,
|
|
182
|
+
including interrupted response bodies;
|
|
183
|
+
`VentiMathProtocolError` covers unexpected responses. Invalid local arguments
|
|
184
|
+
raise `ValueError`. Imports wrap transport/protocol/HTTP failures in
|
|
185
|
+
`VentiMathImportError` to preserve the counts from completed requests.
|
|
186
|
+
|
|
187
|
+
These methods call the actual REST routes: `GET /answerKeys`,
|
|
188
|
+
`GET|POST /problemTags/sets`, `GET|PATCH /problemTags/sets/{id}`,
|
|
189
|
+
`POST /problemTags/sets/{id}/import`, and
|
|
190
|
+
`GET /problemTags/sets/{id}/problems`. You can call those endpoints directly from
|
|
191
|
+
your existing Python HTTP code; the SDK is optional.
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
Install from a source checkout with `python -m pip install .`.
|
|
196
|
+
|
|
197
|
+
From this package directory, without installing or contacting a server:
|
|
198
|
+
|
|
199
|
+
```sh
|
|
200
|
+
PYTHONPATH=src python -B -m unittest discover -s tests -v
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
All transport calls in these tests are mocked. They exercise exact URLs and
|
|
204
|
+
headers, anonymous discovery, private ownership request shaping, both classifier
|
|
205
|
+
record formats, CSV parsing, Unicode batch limits, pagination, rejected redirects,
|
|
206
|
+
and partial-failure reports.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ventimath"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Small standard-library client for VentiMath answer keys and profile skill tags"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = []
|
|
12
|
+
|
|
13
|
+
[project.urls]
|
|
14
|
+
Repository = "https://github.com/VentiMath/venti-sdk"
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.packages.find]
|
|
17
|
+
where = ["src"]
|
|
18
|
+
|
|
19
|
+
[tool.ruff]
|
|
20
|
+
line-length = 100
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""VentiMath REST client. No inference providers or runtime dependencies."""
|
|
2
|
+
|
|
3
|
+
from .client import (
|
|
4
|
+
DEFAULT_BASE_URL,
|
|
5
|
+
VentiMathAPIError,
|
|
6
|
+
VentiMathClient,
|
|
7
|
+
VentiMathConnectionError,
|
|
8
|
+
VentiMathError,
|
|
9
|
+
VentiMathImportError,
|
|
10
|
+
VentiMathProtocolError,
|
|
11
|
+
load_classification_files,
|
|
12
|
+
load_vocabulary_csv,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"DEFAULT_BASE_URL",
|
|
17
|
+
"VentiMathAPIError",
|
|
18
|
+
"VentiMathClient",
|
|
19
|
+
"VentiMathConnectionError",
|
|
20
|
+
"VentiMathError",
|
|
21
|
+
"VentiMathImportError",
|
|
22
|
+
"VentiMathProtocolError",
|
|
23
|
+
"load_classification_files",
|
|
24
|
+
"load_vocabulary_csv",
|
|
25
|
+
]
|