|
1 | 1 | # Codex Python API library |
2 | 2 |
|
3 | | -[](https://pypi.org/project/codex/) |
| 3 | +# Installation |
4 | 4 |
|
5 | | -The Codex Python library provides convenient access to the Codex REST API from any Python 3.8+ |
6 | | -application. The library includes type definitions for all request params and response fields, |
7 | | -and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). |
8 | | - |
9 | | -It is generated with [Stainless](https://www.stainlessapi.com/). |
10 | | - |
11 | | -## Documentation |
12 | | - |
13 | | -The REST API documentation can be found on [help.cleanlab.ai](https://help.cleanlab.ai). The full API of this library can be found in [api.md](api.md). |
14 | | - |
15 | | -## Installation |
16 | | - |
17 | | -```sh |
18 | | -# install from this staging repo |
19 | | -pip install git+ssh:// [email protected]/stainless-sdks/codex-python.git |
20 | | -``` |
21 | | - |
22 | | -> [!NOTE] |
23 | | -> Once this package is [published to PyPI](https://app.stainlessapi.com/docs/guides/publish), this will become: `pip install --pre codex` |
24 | | -
|
25 | | -## Usage |
26 | | - |
27 | | -The full API of this library can be found in [api.md](api.md). |
28 | | - |
29 | | -```python |
30 | | -from codex import Codex |
31 | | - |
32 | | -client = Codex( |
33 | | - # or 'production' | 'local'; defaults to "production". |
34 | | - environment="staging", |
35 | | -) |
36 | | - |
37 | | -project_return_schema = client.projects.create( |
38 | | - config={}, |
39 | | - name="name", |
40 | | - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", |
41 | | -) |
42 | | -print(project_return_schema.id) |
43 | | -``` |
44 | | - |
45 | | -While you can provide a `bearer_token` keyword argument, |
46 | | -we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) |
47 | | -to add `BEARER_TOKEN="My Bearer Token"` to your `.env` file |
48 | | -so that your Bearer Token is not stored in source control. |
49 | | - |
50 | | -## Async usage |
51 | | - |
52 | | -Simply import `AsyncCodex` instead of `Codex` and use `await` with each API call: |
53 | | - |
54 | | -```python |
55 | | -import asyncio |
56 | | -from codex import AsyncCodex |
57 | | - |
58 | | -client = AsyncCodex( |
59 | | - # or 'production' | 'local'; defaults to "production". |
60 | | - environment="staging", |
61 | | -) |
62 | | - |
63 | | - |
64 | | -async def main() -> None: |
65 | | - project_return_schema = await client.projects.create( |
66 | | - config={}, |
67 | | - name="name", |
68 | | - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", |
69 | | - ) |
70 | | - print(project_return_schema.id) |
71 | | - |
72 | | - |
73 | | -asyncio.run(main()) |
74 | | -``` |
75 | | - |
76 | | -Functionality between the synchronous and asynchronous clients is otherwise identical. |
77 | | - |
78 | | -## Using types |
79 | | - |
80 | | -Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like: |
81 | | - |
82 | | -- Serializing back into JSON, `model.to_json()` |
83 | | -- Converting to a dictionary, `model.to_dict()` |
84 | | - |
85 | | -Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. |
86 | | - |
87 | | -## Handling errors |
88 | | - |
89 | | -When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `codex.APIConnectionError` is raised. |
90 | | - |
91 | | -When the API returns a non-success status code (that is, 4xx or 5xx |
92 | | -response), a subclass of `codex.APIStatusError` is raised, containing `status_code` and `response` properties. |
93 | | - |
94 | | -All errors inherit from `codex.APIError`. |
95 | | - |
96 | | -```python |
97 | | -import codex |
98 | | -from codex import Codex |
99 | | - |
100 | | -client = Codex() |
101 | | - |
102 | | -try: |
103 | | - client.projects.create( |
104 | | - config={}, |
105 | | - name="name", |
106 | | - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", |
107 | | - ) |
108 | | -except codex.APIConnectionError as e: |
109 | | - print("The server could not be reached") |
110 | | - print(e.__cause__) # an underlying Exception, likely raised within httpx. |
111 | | -except codex.RateLimitError as e: |
112 | | - print("A 429 status code was received; we should back off a bit.") |
113 | | -except codex.APIStatusError as e: |
114 | | - print("Another non-200-range status code was received") |
115 | | - print(e.status_code) |
116 | | - print(e.response) |
117 | | -``` |
118 | | - |
119 | | -Error codes are as follows: |
120 | | - |
121 | | -| Status Code | Error Type | |
122 | | -| ----------- | -------------------------- | |
123 | | -| 400 | `BadRequestError` | |
124 | | -| 401 | `AuthenticationError` | |
125 | | -| 403 | `PermissionDeniedError` | |
126 | | -| 404 | `NotFoundError` | |
127 | | -| 422 | `UnprocessableEntityError` | |
128 | | -| 429 | `RateLimitError` | |
129 | | -| >=500 | `InternalServerError` | |
130 | | -| N/A | `APIConnectionError` | |
131 | | - |
132 | | -### Retries |
133 | | - |
134 | | -Certain errors are automatically retried 2 times by default, with a short exponential backoff. |
135 | | -Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, |
136 | | -429 Rate Limit, and >=500 Internal errors are all retried by default. |
137 | | - |
138 | | -You can use the `max_retries` option to configure or disable retry settings: |
139 | | - |
140 | | -```python |
141 | | -from codex import Codex |
142 | | - |
143 | | -# Configure the default for all requests: |
144 | | -client = Codex( |
145 | | - # default is 2 |
146 | | - max_retries=0, |
147 | | -) |
148 | | - |
149 | | -# Or, configure per-request: |
150 | | -client.with_options(max_retries=5).projects.create( |
151 | | - config={}, |
152 | | - name="name", |
153 | | - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", |
154 | | -) |
155 | | -``` |
156 | | - |
157 | | -### Timeouts |
158 | | - |
159 | | -By default requests time out after 1 minute. You can configure this with a `timeout` option, |
160 | | -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: |
| 5 | +You can install the Codex python client from [our internal repo](https://github.com/cleanlab/codex-python) with: |
161 | 6 |
|
162 | 7 | ```python |
163 | | -from codex import Codex |
164 | | - |
165 | | -# Configure the default for all requests: |
166 | | -client = Codex( |
167 | | - # 20 seconds (default is 1 minute) |
168 | | - timeout=20.0, |
169 | | -) |
170 | | - |
171 | | -# More granular control: |
172 | | -client = Codex( |
173 | | - timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), |
174 | | -) |
175 | | - |
176 | | -# Override per-request: |
177 | | -client.with_options(timeout=5.0).projects.create( |
178 | | - config={}, |
179 | | - name="name", |
180 | | - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", |
181 | | -) |
182 | | -``` |
183 | | - |
184 | | -On timeout, an `APITimeoutError` is thrown. |
185 | | - |
186 | | -Note that requests that time out are [retried twice by default](#retries). |
187 | | - |
188 | | -## Advanced |
189 | | - |
190 | | -### Logging |
191 | | - |
192 | | -We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module. |
193 | | - |
194 | | -You can enable logging by setting the environment variable `CODEX_LOG` to `info`. |
195 | | - |
196 | | -```shell |
197 | | -$ export CODEX_LOG=info |
| 8 | +pip install git+ssh://git@github.com/cleanlab/codex-python.git |
198 | 9 | ``` |
199 | | - |
200 | | -Or to `debug` for more verbose logging. |
201 | | - |
202 | | -### How to tell whether `None` means `null` or missing |
203 | | - |
204 | | -In an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`: |
205 | | - |
206 | | -```py |
207 | | -if response.my_field is None: |
208 | | - if 'my_field' not in response.model_fields_set: |
209 | | - print('Got json like {}, without a "my_field" key present at all.') |
210 | | - else: |
211 | | - print('Got json like {"my_field": null}.') |
212 | | -``` |
213 | | - |
214 | | -### Accessing raw response data (e.g. headers) |
215 | | - |
216 | | -The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g., |
217 | | - |
218 | | -```py |
219 | | -from codex import Codex |
220 | | - |
221 | | -client = Codex() |
222 | | -response = client.projects.with_raw_response.create( |
223 | | - config={}, |
224 | | - name="name", |
225 | | - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", |
226 | | -) |
227 | | -print(response.headers.get('X-My-Header')) |
228 | | - |
229 | | -project = response.parse() # get the object that `projects.create()` would have returned |
230 | | -print(project.id) |
231 | | -``` |
232 | | - |
233 | | -These methods return an [`APIResponse`](https://github.com/stainless-sdks/codex-python/tree/main/src/codex/_response.py) object. |
234 | | - |
235 | | -The async client returns an [`AsyncAPIResponse`](https://github.com/stainless-sdks/codex-python/tree/main/src/codex/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. |
236 | | - |
237 | | -#### `.with_streaming_response` |
238 | | - |
239 | | -The above interface eagerly reads the full response body when you make the request, which may not always be what you want. |
240 | | - |
241 | | -To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. |
242 | | - |
243 | | -```python |
244 | | -with client.projects.with_streaming_response.create( |
245 | | - config={}, |
246 | | - name="name", |
247 | | - organization_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", |
248 | | -) as response: |
249 | | - print(response.headers.get("X-My-Header")) |
250 | | - |
251 | | - for line in response.iter_lines(): |
252 | | - print(line) |
253 | | -``` |
254 | | - |
255 | | -The context manager is required so that the response will reliably be closed. |
256 | | - |
257 | | -### Making custom/undocumented requests |
258 | | - |
259 | | -This library is typed for convenient access to the documented API. |
260 | | - |
261 | | -If you need to access undocumented endpoints, params, or response properties, the library can still be used. |
262 | | - |
263 | | -#### Undocumented endpoints |
264 | | - |
265 | | -To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other |
266 | | -http verbs. Options on the client will be respected (such as retries) when making this request. |
267 | | - |
268 | | -```py |
269 | | -import httpx |
270 | | - |
271 | | -response = client.post( |
272 | | - "/foo", |
273 | | - cast_to=httpx.Response, |
274 | | - body={"my_param": True}, |
275 | | -) |
276 | | - |
277 | | -print(response.headers.get("x-foo")) |
278 | | -``` |
279 | | - |
280 | | -#### Undocumented request params |
281 | | - |
282 | | -If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request |
283 | | -options. |
284 | | - |
285 | | -#### Undocumented response properties |
286 | | - |
287 | | -To access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You |
288 | | -can also get all the extra fields on the Pydantic model as a dict with |
289 | | -[`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra). |
290 | | - |
291 | | -### Configuring the HTTP client |
292 | | - |
293 | | -You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including: |
294 | | - |
295 | | -- Support for [proxies](https://www.python-httpx.org/advanced/proxies/) |
296 | | -- Custom [transports](https://www.python-httpx.org/advanced/transports/) |
297 | | -- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality |
298 | | - |
299 | | -```python |
300 | | -import httpx |
301 | | -from codex import Codex, DefaultHttpxClient |
302 | | - |
303 | | -client = Codex( |
304 | | - # Or use the `CODEX_BASE_URL` env var |
305 | | - base_url="http://my.test.server.example.com:8083", |
306 | | - http_client=DefaultHttpxClient( |
307 | | - proxy="http://my.test.proxy.example.com", |
308 | | - transport=httpx.HTTPTransport(local_address="0.0.0.0"), |
309 | | - ), |
310 | | -) |
311 | | -``` |
312 | | - |
313 | | -You can also customize the client on a per-request basis by using `with_options()`: |
314 | | - |
315 | | -```python |
316 | | -client.with_options(http_client=DefaultHttpxClient(...)) |
317 | | -``` |
318 | | - |
319 | | -### Managing HTTP resources |
320 | | - |
321 | | -By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting. |
322 | | - |
323 | | -```py |
324 | | -from codex import Codex |
325 | | - |
326 | | -with Codex() as client: |
327 | | - # make requests here |
328 | | - ... |
329 | | - |
330 | | -# HTTP client is now closed |
331 | | -``` |
332 | | - |
333 | | -## Versioning |
334 | | - |
335 | | -This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: |
336 | | - |
337 | | -1. Changes that only affect static types, without breaking runtime behavior. |
338 | | -2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ |
339 | | -3. Changes that we do not expect to impact the vast majority of users in practice. |
340 | | - |
341 | | -We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. |
342 | | - |
343 | | -We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/codex-python/issues) with questions, bugs, or suggestions. |
344 | | - |
345 | | -### Determining the installed version |
346 | | - |
347 | | -If you've upgraded to the latest version but aren't seeing any new features you were expecting then your python environment is likely still using an older version. |
348 | | - |
349 | | -You can determine the version that is being used at runtime with: |
350 | | - |
351 | | -```py |
352 | | -import codex |
353 | | -print(codex.__version__) |
354 | | -``` |
355 | | - |
356 | | -## Requirements |
357 | | - |
358 | | -Python 3.8 or higher. |
359 | | - |
360 | | -## Contributing |
361 | | - |
362 | | -See [the contributing documentation](./CONTRIBUTING.md). |
0 commit comments