onlinecompiler 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,3 @@
1
+ dist/
2
+ *.egg-info/
3
+ __pycache__/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RunCode Inc.
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,285 @@
1
+ Metadata-Version: 2.5
2
+ Name: onlinecompiler
3
+ Version: 0.1.0
4
+ Summary: Python client for the OnlineCompiler.io code execution API: run and judge code in 46 languages in a sandbox
5
+ Project-URL: Homepage, https://onlinecompiler.io
6
+ Project-URL: Documentation, https://onlinecompiler.io/docs
7
+ Project-URL: Pricing, https://onlinecompiler.io/pricing
8
+ Project-URL: Changelog, https://onlinecompiler.io/changelog
9
+ Author: RunCode Inc.
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: code execution,code judge,compiler api,judge0,online compiler,online judge,onlinecompiler,sandbox,signed receipts,webhooks
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Software Development :: Compilers
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Provides-Extra: receipts
21
+ Requires-Dist: cryptography>=42; extra == 'receipts'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # OnlineCompiler.io Python client
25
+
26
+ Run code in 46 languages from Python, or judge it against test cases, through the
27
+ [OnlineCompiler.io](https://onlinecompiler.io/?utm_source=pypi) code execution API. Every run gets a
28
+ fresh gVisor sandbox with no network, 512 MB of memory and a 30-second limit. Free up to 100,000 runs
29
+ a month, commercial use included.
30
+
31
+ No dependencies beyond the standard library. Python 3.10 or newer. Verifying signed receipts needs
32
+ one optional extra, `pip install "onlinecompiler[receipts]"` (it adds `cryptography`).
33
+
34
+ ```bash
35
+ pip install onlinecompiler
36
+ ```
37
+
38
+ Get a free API key at https://api.onlinecompiler.io/compilers/api-keys/ and set it as
39
+ `ONLINECOMPILER_API_KEY`, or pass it to the client.
40
+
41
+ ## Run a program
42
+
43
+ ```python
44
+ from onlinecompiler import OnlineCompiler
45
+
46
+ client = OnlineCompiler() # reads ONLINECOMPILER_API_KEY
47
+
48
+ result = client.run("python-3.14", "name = input()\nprint(f'Hello, {name}!')", input="Ada")
49
+ print(result["output"]) # Hello, Ada!
50
+ print(result["exit_code"], result["time"], result["memory"])
51
+ ```
52
+
53
+ The result has `output` (stdout), `error` (stderr or compiler errors), `exit_code`, `signal`, `time`
54
+ and `total` (seconds), `memory` (KB), `truncated`, `time_limit` and `files`. `time`, `total` and
55
+ `memory` are decimal strings, as the API sends them. Compiler ids such as `gcc-15`, `openjdk-25` and
56
+ `nodejs-24` come from `client.compilers()`. Pass `time_limit` (seconds, up to 30) to stop a run sooner.
57
+
58
+ ## Judge a solution against test cases
59
+
60
+ ```python
61
+ result = client.run_tests(
62
+ "python-3.14",
63
+ "a, b = map(int, input().split())\nprint(a + b)",
64
+ tests=[
65
+ {"input": "1 2", "expected_output": "3"},
66
+ {"input": "10 20", "expected_output": "30", "hidden": True},
67
+ ],
68
+ )
69
+ print(result["verdict"], result["score"], f"{result['passed']}/{result['total']}")
70
+ for case in result["tests"]:
71
+ print(case["verdict"])
72
+ ```
73
+
74
+ Each test counts as one run. Hidden tests return only their verdict. `checker` picks how output is
75
+ compared (`trim` by default, `exact`, `tokens`, `float` or `rows`), `scoring` is `all_or_nothing`
76
+ (default), `percent` or `weighted` (with a `weight` per test), and `time_limit` is seconds per test
77
+ (default 2). See the [run-tests docs](https://onlinecompiler.io/docs/run-tests?utm_source=pypi).
78
+
79
+ Every run-tests result also has `summary` (the result in plain words), `submission` (its id) and a
80
+ signed `receipt`.
81
+
82
+ ## Judge in the background
83
+
84
+ ```python
85
+ queued = client.run_tests(
86
+ "python-3.14",
87
+ "a, b = map(int, input().split())\nprint(a + b)",
88
+ tests=[{"input": "1 2", "expected_output": "3"}, {"input": "10 20", "expected_output": "30", "hidden": True}],
89
+ async_=True,
90
+ external_ref="attempt-4821",
91
+ idempotency_key="attempt-4821",
92
+ )
93
+ done = client.wait_for_submission(queued["submission"]) # looks it up every 2 seconds
94
+ result = done["result"] # None when it failed, which means it was not judged
95
+ if result is not None:
96
+ print(result["verdict"], result["score"])
97
+ else:
98
+ print("Not judged:", done["error"]) # send it again
99
+ client.delete_submission(done["submission"]) # optional: drop the stored code and output
100
+ ```
101
+
102
+ `async_=True` (`async` is a Python keyword) answers at once with the submission id, and the submission
103
+ waits in your account's queue instead of getting 429 in a burst. Add `callback_url` to have the result
104
+ POSTed to your server as a signed webhook (see below); it needs `async_=True`, and the client refuses it
105
+ without. `external_ref` is your own id, signed into the receipt; `metadata` is any JSON object, returned in
106
+ lookups and webhooks. `idempotency_key` (1 to 100 printable ASCII characters, checked before sending) is
107
+ sent as the `Idempotency-Key` header, so a retry after a timeout answers with the original result instead
108
+ of running again. `client.submission(id)` looks a submission up once.
109
+
110
+ ## Judge against a stored problem
111
+
112
+ A stored problem keeps its tests, checker and limits on the server, so you send only the candidate's
113
+ code and the hidden tests never leave it.
114
+
115
+ ```python
116
+ client.create_problem({
117
+ "title": "Sum of two numbers",
118
+ "slug": "sum",
119
+ "languages": ["python-3.14"],
120
+ "tests": [
121
+ {"name": "sample", "input": "1 2\n", "expected_output": "3\n", "hidden": False},
122
+ {"input": "5 7\n", "expected_output": "12\n"}, # tests are hidden unless you say otherwise
123
+ ],
124
+ })
125
+ client.publish_problem("sum") # no reference solution, so it is published at once
126
+
127
+ result = client.judge("sum", "python-3.14", "a, b = map(int, input().split())\nprint(a + b)")
128
+ print(result["verdict"], result["score"], result["problem"], result["version"])
129
+ ```
130
+
131
+ `judge` takes the problem's id (`prob_...`) or your slug for it. `mode="samples"` judges only the
132
+ visible tests, and `async_`, `callback_url`, `external_ref`, `metadata` and `idempotency_key` work as
133
+ above. Problems are plain dicts, as the
134
+ [stored problems docs](https://onlinecompiler.io/docs/problems?utm_source=pypi) describe them:
135
+ `problems()`, `problem()`, `create_problem()`, `update_problem()`, `replace_draft()`,
136
+ `publish_problem()`, `archive_problem()`, `restore_problem()` and `public_problem()`.
137
+
138
+ ## Sessions for the widget
139
+
140
+ A session lets one candidate's [embedded editor](https://onlinecompiler.io/docs/widget?utm_source=pypi)
141
+ Submit to a stored problem without your API key ever reaching the browser. Create it on your server and
142
+ put only its token in the page:
143
+
144
+ ```python
145
+ problem_id = client.problem("sum")["problem"] # the widget takes the id, never the slug
146
+ session = client.create_session(problem_id, "attempt-4821", max_submits=2, expires_in=3600)
147
+
148
+ html = (f'<div class="runcode" data-key="YOUR_WIDGET_KEY" data-problem="{problem_id}" '
149
+ f'data-session="{session["token"]}"></div>\n'
150
+ '<script src="https://onlinecompiler.io/widget.js"></script>')
151
+ ```
152
+
153
+ The token is shown only this once and the client never logs it or puts it in an error, so keep it out
154
+ of your own logs too. `external_ref` should be an opaque id: the candidate's page sees it. Record the
155
+ score from the webhook or `client.submission(id)`, never from the browser.
156
+ `client.session(problem_id, session["session"])` shows the Submits used and the submission ids, and
157
+ `client.revoke_session(problem_id, session["session"])` ends it at once.
158
+
159
+ ## Verify webhooks
160
+
161
+ An async submission with a `callback_url` (or a callback URL on the API key) gets one signed
162
+ `submission.finished` event. Check it before you trust it.
163
+
164
+ Verify the raw request body exactly as received: a body that was parsed and serialized again never
165
+ matches. In Flask that is `request.get_data()`, in Django `request.body`, in FastAPI
166
+ `await request.body()`.
167
+
168
+ ```python
169
+ import os
170
+
171
+ from flask import Flask, abort, request
172
+ from onlinecompiler import verify_webhook
173
+
174
+ app = Flask(__name__)
175
+ SECRET = os.environ["ONLINECOMPILER_WEBHOOK_SECRET"] # whsec_..., shown with the API key in your dashboard
176
+
177
+ @app.post("/hooks/oc")
178
+ def webhook():
179
+ if not verify_webhook(SECRET, request.get_data(), request.headers):
180
+ abort(400)
181
+ event = request.get_json()
182
+ print(event["submission"], event["status"], event["result"] and event["result"]["verdict"])
183
+ return "", 204 # answer quickly; deduplicate on event["delivery"]
184
+ ```
185
+
186
+ `verify_webhook(secret, raw_body, headers, tolerance=300)` needs no client and no network. It takes any
187
+ mapping of headers (Django's, Flask's, FastAPI's or a dict), compares in constant time, accepts either
188
+ signature while a secret is being rotated, takes a list of secrets while you rotate yours, and rejects a
189
+ timestamp more than `tolerance` seconds (default 300) from now. Anything missing or malformed returns
190
+ `False`. See the [webhooks docs](https://onlinecompiler.io/docs/webhooks?utm_source=pypi).
191
+
192
+ ## Verify receipts
193
+
194
+ Every judged submission comes with a receipt signed with Ed25519: proof of what ran, against which
195
+ tests, and what came out. Anyone can check it with the published keys.
196
+
197
+ ```bash
198
+ pip install "onlinecompiler[receipts]"
199
+ ```
200
+
201
+ ```python
202
+ from onlinecompiler import verify_receipt
203
+
204
+ receipt = client.submission("sub_8f2Kq7LmT3xR9vWb1cYd4hNe")["receipt"]
205
+ print(client.verify_receipt(receipt)) # fetches the published keys once, no API key needed
206
+
207
+ keys = client.receipt_keys() # or save them and verify offline later
208
+ print(verify_receipt(receipt, keys)) # False once any field has been changed
209
+ ```
210
+
211
+ Both return `False` for `None`, which a submission that was not judged has as its receipt. Without the
212
+ extra, verifying raises an error that says what to install. Store and deduplicate receipts by their
213
+ `submission` id (or the whole receipt), not by the signature string. `canonical_json(value)` returns
214
+ the canonical bytes receipts are signed over, for reproducing their hashes as the
215
+ [receipts docs](https://onlinecompiler.io/docs/receipts?utm_source=pypi) describe.
216
+
217
+ ## Files in and out
218
+
219
+ ```python
220
+ import base64
221
+
222
+ code = """
223
+ import pandas as pd
224
+ import matplotlib.pyplot as plt
225
+ df = pd.read_csv("sales.csv")
226
+ df.plot(x="month", y="units")
227
+ plt.show() # saved as figure-1.png in $OUTPUT_DIR
228
+ """
229
+ result = client.run("python-3.14", code, files=[{"name": "sales.csv", "content": open("sales.csv").read()}])
230
+ for f in result["files"]:
231
+ with open(f["name"], "wb") as out:
232
+ out.write(base64.b64decode(f["content_base64"]))
233
+ ```
234
+
235
+ Input files sit in the program's working directory; send binary ones with `"encoding": "base64"`.
236
+ Files the program writes to `$OUTPUT_DIR` come back in `files`.
237
+
238
+ ## Queue runs and get results by callback
239
+
240
+ ```python
241
+ queued = client.submit("g++-15", source, input="3\n", extra_params={"submission_id": 981})
242
+ print(queued["id"])
243
+ ```
244
+
245
+ `submit` returns at once. When the run finishes, its result is POSTed to the callback URL set on your
246
+ API key in the dashboard, with your `extra_params` echoed back.
247
+
248
+ ## Other calls
249
+
250
+ - `client.compilers()`: every compiler id and language name (no key needed).
251
+ - `client.usage()`: this month's runs and what's left, for the account and for the key.
252
+ - `client.limits()`: per-run limits and default account limits (no key needed).
253
+
254
+ ## Errors
255
+
256
+ ```python
257
+ from onlinecompiler import OnlineCompilerError, RateLimitError
258
+
259
+ try:
260
+ client.run("python-3.14", "print(1)")
261
+ except RateLimitError as e: # HTTP 429
262
+ print(e.message) # which limit was hit, and how to raise it
263
+ except OnlineCompilerError as e: # anything else, including AuthenticationError
264
+ print(e.status, e.message) # status is 0 when the API could not be reached
265
+ ```
266
+
267
+ `AuthenticationError` covers a missing, unknown or unauthorized key; `NotFoundError` a submission,
268
+ problem or session that does not exist or is not yours. A conflict, such as an archived problem or an
269
+ `Idempotency-Key` reused with another body, is an `OnlineCompilerError` with status 409 and the API's
270
+ message.
271
+
272
+ The client never retries a request by itself, because every run the API accepts counts toward your
273
+ allowance. `OnlineCompiler(api_key=None, *, base_url="https://api.onlinecompiler.io", timeout=90.0)`
274
+ takes the key, the API address and a timeout in seconds.
275
+
276
+ ## Links
277
+
278
+ [Docs](https://onlinecompiler.io/docs?utm_source=pypi) ·
279
+ [Languages](https://onlinecompiler.io/languages?utm_source=pypi) ·
280
+ [Limits](https://onlinecompiler.io/docs/limits?utm_source=pypi) ·
281
+ [Pricing](https://onlinecompiler.io/pricing?utm_source=pypi) ·
282
+ [Security](https://onlinecompiler.io/security?utm_source=pypi) ·
283
+ [Changelog](https://onlinecompiler.io/changelog?utm_source=pypi)
284
+
285
+ MIT licensed. OnlineCompiler.io is a product of RunCode Inc.
@@ -0,0 +1,262 @@
1
+ # OnlineCompiler.io Python client
2
+
3
+ Run code in 46 languages from Python, or judge it against test cases, through the
4
+ [OnlineCompiler.io](https://onlinecompiler.io/?utm_source=pypi) code execution API. Every run gets a
5
+ fresh gVisor sandbox with no network, 512 MB of memory and a 30-second limit. Free up to 100,000 runs
6
+ a month, commercial use included.
7
+
8
+ No dependencies beyond the standard library. Python 3.10 or newer. Verifying signed receipts needs
9
+ one optional extra, `pip install "onlinecompiler[receipts]"` (it adds `cryptography`).
10
+
11
+ ```bash
12
+ pip install onlinecompiler
13
+ ```
14
+
15
+ Get a free API key at https://api.onlinecompiler.io/compilers/api-keys/ and set it as
16
+ `ONLINECOMPILER_API_KEY`, or pass it to the client.
17
+
18
+ ## Run a program
19
+
20
+ ```python
21
+ from onlinecompiler import OnlineCompiler
22
+
23
+ client = OnlineCompiler() # reads ONLINECOMPILER_API_KEY
24
+
25
+ result = client.run("python-3.14", "name = input()\nprint(f'Hello, {name}!')", input="Ada")
26
+ print(result["output"]) # Hello, Ada!
27
+ print(result["exit_code"], result["time"], result["memory"])
28
+ ```
29
+
30
+ The result has `output` (stdout), `error` (stderr or compiler errors), `exit_code`, `signal`, `time`
31
+ and `total` (seconds), `memory` (KB), `truncated`, `time_limit` and `files`. `time`, `total` and
32
+ `memory` are decimal strings, as the API sends them. Compiler ids such as `gcc-15`, `openjdk-25` and
33
+ `nodejs-24` come from `client.compilers()`. Pass `time_limit` (seconds, up to 30) to stop a run sooner.
34
+
35
+ ## Judge a solution against test cases
36
+
37
+ ```python
38
+ result = client.run_tests(
39
+ "python-3.14",
40
+ "a, b = map(int, input().split())\nprint(a + b)",
41
+ tests=[
42
+ {"input": "1 2", "expected_output": "3"},
43
+ {"input": "10 20", "expected_output": "30", "hidden": True},
44
+ ],
45
+ )
46
+ print(result["verdict"], result["score"], f"{result['passed']}/{result['total']}")
47
+ for case in result["tests"]:
48
+ print(case["verdict"])
49
+ ```
50
+
51
+ Each test counts as one run. Hidden tests return only their verdict. `checker` picks how output is
52
+ compared (`trim` by default, `exact`, `tokens`, `float` or `rows`), `scoring` is `all_or_nothing`
53
+ (default), `percent` or `weighted` (with a `weight` per test), and `time_limit` is seconds per test
54
+ (default 2). See the [run-tests docs](https://onlinecompiler.io/docs/run-tests?utm_source=pypi).
55
+
56
+ Every run-tests result also has `summary` (the result in plain words), `submission` (its id) and a
57
+ signed `receipt`.
58
+
59
+ ## Judge in the background
60
+
61
+ ```python
62
+ queued = client.run_tests(
63
+ "python-3.14",
64
+ "a, b = map(int, input().split())\nprint(a + b)",
65
+ tests=[{"input": "1 2", "expected_output": "3"}, {"input": "10 20", "expected_output": "30", "hidden": True}],
66
+ async_=True,
67
+ external_ref="attempt-4821",
68
+ idempotency_key="attempt-4821",
69
+ )
70
+ done = client.wait_for_submission(queued["submission"]) # looks it up every 2 seconds
71
+ result = done["result"] # None when it failed, which means it was not judged
72
+ if result is not None:
73
+ print(result["verdict"], result["score"])
74
+ else:
75
+ print("Not judged:", done["error"]) # send it again
76
+ client.delete_submission(done["submission"]) # optional: drop the stored code and output
77
+ ```
78
+
79
+ `async_=True` (`async` is a Python keyword) answers at once with the submission id, and the submission
80
+ waits in your account's queue instead of getting 429 in a burst. Add `callback_url` to have the result
81
+ POSTed to your server as a signed webhook (see below); it needs `async_=True`, and the client refuses it
82
+ without. `external_ref` is your own id, signed into the receipt; `metadata` is any JSON object, returned in
83
+ lookups and webhooks. `idempotency_key` (1 to 100 printable ASCII characters, checked before sending) is
84
+ sent as the `Idempotency-Key` header, so a retry after a timeout answers with the original result instead
85
+ of running again. `client.submission(id)` looks a submission up once.
86
+
87
+ ## Judge against a stored problem
88
+
89
+ A stored problem keeps its tests, checker and limits on the server, so you send only the candidate's
90
+ code and the hidden tests never leave it.
91
+
92
+ ```python
93
+ client.create_problem({
94
+ "title": "Sum of two numbers",
95
+ "slug": "sum",
96
+ "languages": ["python-3.14"],
97
+ "tests": [
98
+ {"name": "sample", "input": "1 2\n", "expected_output": "3\n", "hidden": False},
99
+ {"input": "5 7\n", "expected_output": "12\n"}, # tests are hidden unless you say otherwise
100
+ ],
101
+ })
102
+ client.publish_problem("sum") # no reference solution, so it is published at once
103
+
104
+ result = client.judge("sum", "python-3.14", "a, b = map(int, input().split())\nprint(a + b)")
105
+ print(result["verdict"], result["score"], result["problem"], result["version"])
106
+ ```
107
+
108
+ `judge` takes the problem's id (`prob_...`) or your slug for it. `mode="samples"` judges only the
109
+ visible tests, and `async_`, `callback_url`, `external_ref`, `metadata` and `idempotency_key` work as
110
+ above. Problems are plain dicts, as the
111
+ [stored problems docs](https://onlinecompiler.io/docs/problems?utm_source=pypi) describe them:
112
+ `problems()`, `problem()`, `create_problem()`, `update_problem()`, `replace_draft()`,
113
+ `publish_problem()`, `archive_problem()`, `restore_problem()` and `public_problem()`.
114
+
115
+ ## Sessions for the widget
116
+
117
+ A session lets one candidate's [embedded editor](https://onlinecompiler.io/docs/widget?utm_source=pypi)
118
+ Submit to a stored problem without your API key ever reaching the browser. Create it on your server and
119
+ put only its token in the page:
120
+
121
+ ```python
122
+ problem_id = client.problem("sum")["problem"] # the widget takes the id, never the slug
123
+ session = client.create_session(problem_id, "attempt-4821", max_submits=2, expires_in=3600)
124
+
125
+ html = (f'<div class="runcode" data-key="YOUR_WIDGET_KEY" data-problem="{problem_id}" '
126
+ f'data-session="{session["token"]}"></div>\n'
127
+ '<script src="https://onlinecompiler.io/widget.js"></script>')
128
+ ```
129
+
130
+ The token is shown only this once and the client never logs it or puts it in an error, so keep it out
131
+ of your own logs too. `external_ref` should be an opaque id: the candidate's page sees it. Record the
132
+ score from the webhook or `client.submission(id)`, never from the browser.
133
+ `client.session(problem_id, session["session"])` shows the Submits used and the submission ids, and
134
+ `client.revoke_session(problem_id, session["session"])` ends it at once.
135
+
136
+ ## Verify webhooks
137
+
138
+ An async submission with a `callback_url` (or a callback URL on the API key) gets one signed
139
+ `submission.finished` event. Check it before you trust it.
140
+
141
+ Verify the raw request body exactly as received: a body that was parsed and serialized again never
142
+ matches. In Flask that is `request.get_data()`, in Django `request.body`, in FastAPI
143
+ `await request.body()`.
144
+
145
+ ```python
146
+ import os
147
+
148
+ from flask import Flask, abort, request
149
+ from onlinecompiler import verify_webhook
150
+
151
+ app = Flask(__name__)
152
+ SECRET = os.environ["ONLINECOMPILER_WEBHOOK_SECRET"] # whsec_..., shown with the API key in your dashboard
153
+
154
+ @app.post("/hooks/oc")
155
+ def webhook():
156
+ if not verify_webhook(SECRET, request.get_data(), request.headers):
157
+ abort(400)
158
+ event = request.get_json()
159
+ print(event["submission"], event["status"], event["result"] and event["result"]["verdict"])
160
+ return "", 204 # answer quickly; deduplicate on event["delivery"]
161
+ ```
162
+
163
+ `verify_webhook(secret, raw_body, headers, tolerance=300)` needs no client and no network. It takes any
164
+ mapping of headers (Django's, Flask's, FastAPI's or a dict), compares in constant time, accepts either
165
+ signature while a secret is being rotated, takes a list of secrets while you rotate yours, and rejects a
166
+ timestamp more than `tolerance` seconds (default 300) from now. Anything missing or malformed returns
167
+ `False`. See the [webhooks docs](https://onlinecompiler.io/docs/webhooks?utm_source=pypi).
168
+
169
+ ## Verify receipts
170
+
171
+ Every judged submission comes with a receipt signed with Ed25519: proof of what ran, against which
172
+ tests, and what came out. Anyone can check it with the published keys.
173
+
174
+ ```bash
175
+ pip install "onlinecompiler[receipts]"
176
+ ```
177
+
178
+ ```python
179
+ from onlinecompiler import verify_receipt
180
+
181
+ receipt = client.submission("sub_8f2Kq7LmT3xR9vWb1cYd4hNe")["receipt"]
182
+ print(client.verify_receipt(receipt)) # fetches the published keys once, no API key needed
183
+
184
+ keys = client.receipt_keys() # or save them and verify offline later
185
+ print(verify_receipt(receipt, keys)) # False once any field has been changed
186
+ ```
187
+
188
+ Both return `False` for `None`, which a submission that was not judged has as its receipt. Without the
189
+ extra, verifying raises an error that says what to install. Store and deduplicate receipts by their
190
+ `submission` id (or the whole receipt), not by the signature string. `canonical_json(value)` returns
191
+ the canonical bytes receipts are signed over, for reproducing their hashes as the
192
+ [receipts docs](https://onlinecompiler.io/docs/receipts?utm_source=pypi) describe.
193
+
194
+ ## Files in and out
195
+
196
+ ```python
197
+ import base64
198
+
199
+ code = """
200
+ import pandas as pd
201
+ import matplotlib.pyplot as plt
202
+ df = pd.read_csv("sales.csv")
203
+ df.plot(x="month", y="units")
204
+ plt.show() # saved as figure-1.png in $OUTPUT_DIR
205
+ """
206
+ result = client.run("python-3.14", code, files=[{"name": "sales.csv", "content": open("sales.csv").read()}])
207
+ for f in result["files"]:
208
+ with open(f["name"], "wb") as out:
209
+ out.write(base64.b64decode(f["content_base64"]))
210
+ ```
211
+
212
+ Input files sit in the program's working directory; send binary ones with `"encoding": "base64"`.
213
+ Files the program writes to `$OUTPUT_DIR` come back in `files`.
214
+
215
+ ## Queue runs and get results by callback
216
+
217
+ ```python
218
+ queued = client.submit("g++-15", source, input="3\n", extra_params={"submission_id": 981})
219
+ print(queued["id"])
220
+ ```
221
+
222
+ `submit` returns at once. When the run finishes, its result is POSTed to the callback URL set on your
223
+ API key in the dashboard, with your `extra_params` echoed back.
224
+
225
+ ## Other calls
226
+
227
+ - `client.compilers()`: every compiler id and language name (no key needed).
228
+ - `client.usage()`: this month's runs and what's left, for the account and for the key.
229
+ - `client.limits()`: per-run limits and default account limits (no key needed).
230
+
231
+ ## Errors
232
+
233
+ ```python
234
+ from onlinecompiler import OnlineCompilerError, RateLimitError
235
+
236
+ try:
237
+ client.run("python-3.14", "print(1)")
238
+ except RateLimitError as e: # HTTP 429
239
+ print(e.message) # which limit was hit, and how to raise it
240
+ except OnlineCompilerError as e: # anything else, including AuthenticationError
241
+ print(e.status, e.message) # status is 0 when the API could not be reached
242
+ ```
243
+
244
+ `AuthenticationError` covers a missing, unknown or unauthorized key; `NotFoundError` a submission,
245
+ problem or session that does not exist or is not yours. A conflict, such as an archived problem or an
246
+ `Idempotency-Key` reused with another body, is an `OnlineCompilerError` with status 409 and the API's
247
+ message.
248
+
249
+ The client never retries a request by itself, because every run the API accepts counts toward your
250
+ allowance. `OnlineCompiler(api_key=None, *, base_url="https://api.onlinecompiler.io", timeout=90.0)`
251
+ takes the key, the API address and a timeout in seconds.
252
+
253
+ ## Links
254
+
255
+ [Docs](https://onlinecompiler.io/docs?utm_source=pypi) ·
256
+ [Languages](https://onlinecompiler.io/languages?utm_source=pypi) ·
257
+ [Limits](https://onlinecompiler.io/docs/limits?utm_source=pypi) ·
258
+ [Pricing](https://onlinecompiler.io/pricing?utm_source=pypi) ·
259
+ [Security](https://onlinecompiler.io/security?utm_source=pypi) ·
260
+ [Changelog](https://onlinecompiler.io/changelog?utm_source=pypi)
261
+
262
+ MIT licensed. OnlineCompiler.io is a product of RunCode Inc.
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "onlinecompiler"
7
+ dynamic = ["version"]
8
+ description = "Python client for the OnlineCompiler.io code execution API: run and judge code in 46 languages in a sandbox"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "RunCode Inc." }]
14
+ keywords = ["code execution", "online compiler", "compiler api", "judge0", "online judge", "sandbox", "onlinecompiler", "code judge", "webhooks", "signed receipts"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Topic :: Software Development :: Compilers",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = []
24
+
25
+ [project.optional-dependencies]
26
+ # Ed25519 for verify_receipt; everything else uses the standard library.
27
+ receipts = ["cryptography>=42"]
28
+
29
+ [project.urls]
30
+ Homepage = "https://onlinecompiler.io"
31
+ Documentation = "https://onlinecompiler.io/docs"
32
+ Pricing = "https://onlinecompiler.io/pricing"
33
+ Changelog = "https://onlinecompiler.io/changelog"
34
+
35
+ [tool.hatch.version]
36
+ path = "src/onlinecompiler/_client.py"
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["src/onlinecompiler"]
@@ -0,0 +1,67 @@
1
+ """Python client for the OnlineCompiler.io code execution API: run and judge code in 46 languages."""
2
+
3
+ from ._client import (
4
+ AuthenticationError,
5
+ Case,
6
+ CaseResult,
7
+ Checker,
8
+ Compiler,
9
+ InputFile,
10
+ JudgeResult,
11
+ Mode,
12
+ NotFoundError,
13
+ OnlineCompiler,
14
+ OnlineCompilerError,
15
+ OutputFile,
16
+ ProblemSession,
17
+ ProblemSessionCreated,
18
+ PublishResult,
19
+ Queued,
20
+ RateLimitError,
21
+ Receipt,
22
+ ReceiptKey,
23
+ RunResult,
24
+ RunTestsResult,
25
+ Scoring,
26
+ Submission,
27
+ SubmissionDeleted,
28
+ SubmissionProblem,
29
+ SubmissionQueued,
30
+ SubmissionResult,
31
+ __version__,
32
+ )
33
+ from ._verify import canonical_json, verify_receipt, verify_webhook
34
+
35
+ __all__ = [
36
+ "AuthenticationError",
37
+ "Case",
38
+ "CaseResult",
39
+ "Checker",
40
+ "Compiler",
41
+ "InputFile",
42
+ "JudgeResult",
43
+ "Mode",
44
+ "NotFoundError",
45
+ "OnlineCompiler",
46
+ "OnlineCompilerError",
47
+ "OutputFile",
48
+ "ProblemSession",
49
+ "ProblemSessionCreated",
50
+ "PublishResult",
51
+ "Queued",
52
+ "RateLimitError",
53
+ "Receipt",
54
+ "ReceiptKey",
55
+ "RunResult",
56
+ "RunTestsResult",
57
+ "Scoring",
58
+ "Submission",
59
+ "SubmissionDeleted",
60
+ "SubmissionProblem",
61
+ "SubmissionQueued",
62
+ "SubmissionResult",
63
+ "__version__",
64
+ "canonical_json",
65
+ "verify_receipt",
66
+ "verify_webhook",
67
+ ]