diff --git a/submission/.gitattributes b/submission/.gitattributes
new file mode 100644
index 000000000..4d75d5900
--- /dev/null
+++ b/submission/.gitattributes
@@ -0,0 +1,2 @@
+# This allows generated code to be indexed correctly
+*.py linguist-generated=false
\ No newline at end of file
diff --git a/submission/.gitignore b/submission/.gitignore
new file mode 100644
index 000000000..5a82b069a
--- /dev/null
+++ b/submission/.gitignore
@@ -0,0 +1,10 @@
+.speakeasy/reports
+README-PYPI.md
+.venv/
+venv/
+src/*.egg-info/
+__pycache__/
+.pytest_cache/
+.python-version
+.DS_Store
+pyrightconfig.json
diff --git a/submission/.python-version b/submission/.python-version
new file mode 100644
index 000000000..bd28b9c5c
--- /dev/null
+++ b/submission/.python-version
@@ -0,0 +1 @@
+3.9
diff --git a/submission/.speakeasy/gen.lock b/submission/.speakeasy/gen.lock
new file mode 100644
index 000000000..cee1ca30a
--- /dev/null
+++ b/submission/.speakeasy/gen.lock
@@ -0,0 +1,92 @@
+lockVersion: 2.0.0
+id: 33caae79-0482-46e7-9dea-4e7a337d182e
+management:
+ docChecksum: d4af78e6c6d718d7121f5ce9edb789ef
+ docVersion: 1.0.0
+ speakeasyVersion: 1.477.2
+ generationVersion: 2.497.8
+ releaseVersion: 1.13.8
+ configChecksum: 2af7ad3cb0b3991d2ff7d01b3d9b94cb
+ repoURL: https://github.com/epilot-dev/sdk-python.git
+ repoSubDirectory: submission
+ installationURL: https://github.com/epilot-dev/sdk-python.git#subdirectory=submission
+features:
+ python:
+ additionalDependencies: 1.0.0
+ additionalProperties: 1.0.1
+ core: 5.10.6
+ defaultEnabledRetries: 0.2.0
+ deprecations: 3.0.2
+ enumUnions: 0.1.0
+ envVarSecurityUsage: 0.3.2
+ globalSecurity: 3.0.2
+ globalSecurityCallbacks: 1.0.0
+ globalSecurityFlattening: 1.0.0
+ globalServerURLs: 3.1.0
+ methodArguments: 1.0.2
+ responseFormat: 1.0.1
+ retries: 3.0.2
+ sdkHooks: 1.0.0
+generatedFiles:
+ - .gitattributes
+ - .python-version
+ - .vscode/settings.json
+ - USAGE.md
+ - docs/models/files.md
+ - docs/models/optin.md
+ - docs/models/s3reference.md
+ - docs/models/schema.md
+ - docs/models/security.md
+ - docs/models/submissionentity.md
+ - docs/models/submissionpayload.md
+ - docs/models/utils/retryconfig.md
+ - docs/sdks/epilot/README.md
+ - docs/sdks/submissions/README.md
+ - poetry.toml
+ - py.typed
+ - pylintrc
+ - pyproject.toml
+ - scripts/prepare-readme.py
+ - scripts/publish.sh
+ - src/epilot_submission/__init__.py
+ - src/epilot_submission/_hooks/__init__.py
+ - src/epilot_submission/_hooks/sdkhooks.py
+ - src/epilot_submission/_hooks/types.py
+ - src/epilot_submission/_version.py
+ - src/epilot_submission/basesdk.py
+ - src/epilot_submission/httpclient.py
+ - src/epilot_submission/models/__init__.py
+ - src/epilot_submission/models/optin.py
+ - src/epilot_submission/models/s3reference.py
+ - src/epilot_submission/models/sdkerror.py
+ - src/epilot_submission/models/security.py
+ - src/epilot_submission/models/submissionentity.py
+ - src/epilot_submission/models/submissionpayload.py
+ - src/epilot_submission/py.typed
+ - src/epilot_submission/sdk.py
+ - src/epilot_submission/sdkconfiguration.py
+ - src/epilot_submission/submissions.py
+ - src/epilot_submission/types/__init__.py
+ - src/epilot_submission/types/basemodel.py
+ - src/epilot_submission/utils/__init__.py
+ - src/epilot_submission/utils/annotations.py
+ - src/epilot_submission/utils/enums.py
+ - src/epilot_submission/utils/eventstreaming.py
+ - src/epilot_submission/utils/forms.py
+ - src/epilot_submission/utils/headers.py
+ - src/epilot_submission/utils/logger.py
+ - src/epilot_submission/utils/metadata.py
+ - src/epilot_submission/utils/queryparams.py
+ - src/epilot_submission/utils/requestbodies.py
+ - src/epilot_submission/utils/retries.py
+ - src/epilot_submission/utils/security.py
+ - src/epilot_submission/utils/serializers.py
+ - src/epilot_submission/utils/url.py
+ - src/epilot_submission/utils/values.py
+examples:
+ createSubmission:
+ speakeasy-default-create-submission:
+ requestBody:
+ application/json: {"entities": [{"_schema": "submission", "description": "Submission created via API", "files": [{"filename": "document.pdf", "s3ref": {"bucket": "epilot-user-content", "key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf"}}], "contact_first_name": "First", "contact_last_name": "Last", "contact_email": "example@submission.com", "request": "I would like to know more about electric vehicles"}], "journey_submit_id": "123", "opt_ins": [{"identifier": "example@email.com", "topic": "EMAIL_MARKETING"}], "organization_id": "123", "source_id": "ce99875f-fba9-4fe2-a8f9-afaf52059051", "source_type": "journey"}
+examplesVersion: 1.0.0
+generatedTests: {}
diff --git a/submission/.vscode/settings.json b/submission/.vscode/settings.json
new file mode 100644
index 000000000..8d79f0abb
--- /dev/null
+++ b/submission/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "python.testing.pytestArgs": ["tests", "-vv"],
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestEnabled": true,
+ "pylint.args": ["--rcfile=pylintrc"]
+}
diff --git a/submission/CONTRIBUTING.md b/submission/CONTRIBUTING.md
new file mode 100644
index 000000000..d585717fc
--- /dev/null
+++ b/submission/CONTRIBUTING.md
@@ -0,0 +1,26 @@
+# Contributing to This Repository
+
+Thank you for your interest in contributing to this repository. Please note that this repository contains generated code. As such, we do not accept direct changes or pull requests. Instead, we encourage you to follow the guidelines below to report issues and suggest improvements.
+
+## How to Report Issues
+
+If you encounter any bugs or have suggestions for improvements, please open an issue on GitHub. When reporting an issue, please provide as much detail as possible to help us reproduce the problem. This includes:
+
+- A clear and descriptive title
+- Steps to reproduce the issue
+- Expected and actual behavior
+- Any relevant logs, screenshots, or error messages
+- Information about your environment (e.g., operating system, software versions)
+ - For example can be collected using the `npx envinfo` command from your terminal if you have Node.js installed
+
+## Issue Triage and Upstream Fixes
+
+We will review and triage issues as quickly as possible. Our goal is to address bugs and incorporate improvements in the upstream source code. Fixes will be included in the next generation of the generated code.
+
+## Contact
+
+If you have any questions or need further assistance, please feel free to reach out by opening an issue.
+
+Thank you for your understanding and cooperation!
+
+The Maintainers
diff --git a/submission/README.md b/submission/README.md
index 815eaf538..1c50d6d53 100755
--- a/submission/README.md
+++ b/submission/README.md
@@ -1,76 +1,589 @@
# epilot-submission
-
+
## SDK Installation
+> [!TIP]
+> To finish publishing your SDK to PyPI you must [run your first generation action](https://www.speakeasy.com/docs/github-setup#step-by-step-guide).
+
+
+> [!NOTE]
+> **Python version upgrade policy**
+>
+> Once a Python version reaches its [official end of life date](https://devguide.python.org/versions/), a 3-month grace period is provided for users to upgrade. Following this grace period, the minimum python version supported in the SDK will be updated.
+
+The SDK can be installed with either *pip* or *poetry* package managers.
+
+### PIP
+
+*PIP* is the default package installer for Python, enabling easy installation and management of packages from PyPI via the command line.
+
```bash
pip install git+https://github.com/epilot-dev/sdk-python.git#subdirectory=submission
```
-
+### Poetry
+
+*Poetry* is a modern tool that simplifies dependency management and package publishing by using a single `pyproject.toml` file to handle project metadata and dependencies.
+
+```bash
+poetry add git+https://github.com/epilot-dev/sdk-python.git#subdirectory=submission
+```
+
+
+
## SDK Example Usage
-
+
+### Example
+
```python
-import epilot
-from epilot.models import operations, shared
-
-s = epilot.Epilot()
-
-
-req = shared.SubmissionPayload(
- ivy_opportunity_ids=[
- "deserunt",
- "porro",
- "nulla",
- ],
- entities=[
- {
- "perspiciatis": "nulla",
- "nihil": "fuga",
- "facilis": "eum",
- "iusto": "ullam",
- },
- {
- "inventore": "sapiente",
- "enim": "eum",
- "voluptatum": "autem",
- "vel": "non",
- },
- {
- "similique": "reprehenderit",
- "molestiae": "quo",
- },
- ],
- journey_submit_id="123",
- opt_ins=[
- shared.OptIn(
- identifier="example@email.com",
- meta={
- "dicta": "est",
- "voluptatem": "consequatur",
+# Synchronous Example
+import epilot_submission
+from epilot_submission import Epilot
+
+with Epilot() as epilot:
+
+ epilot.submissions.create_submission(request={
+ "entities": [
+ epilot_submission.SubmissionEntity(
+ schema_=epilot_submission.Schema.SUBMISSION,
+ description="Submission created via API",
+ files=[
+ epilot_submission.Files(
+ s3ref={
+ "bucket": "epilot-user-content",
+ "key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf",
+ },
+ filename="document.pdf",
+ ),
+ ],
+ **{
+ "contact_first_name": "First",
+ "contact_last_name": "Last",
+ "contact_email": "example@submission.com",
+ "request": "I would like to know more about electric vehicles",
+ },
+ ),
+ ],
+ "organization_id": "123",
+ "source_id": "ce99875f-fba9-4fe2-a8f9-afaf52059051",
+ "source_type": "journey",
+ "journey_submit_id": "123",
+ "opt_ins": [
+ {
+ "identifier": "example@email.com",
+ "topic": "EMAIL_MARKETING",
},
- topic="EMAIL_MARKETING",
- ),
- ],
- organization_id="123",
- source_id="ce99875f-fba9-4fe2-a8f9-afaf52059051",
- source_type="journey",
-)
-
-res = s.submissions.create_submission(req)
-
-if res.status_code == 200:
- # handle response
+ ],
+ })
+
+ # Use the SDK ...
```
-
-
-## SDK Available Operations
+
+
+The same SDK client can also be used to make asychronous requests by importing asyncio.
+```python
+# Asynchronous Example
+import asyncio
+import epilot_submission
+from epilot_submission import Epilot
+
+async def main():
+ async with Epilot() as epilot:
+
+ await epilot.submissions.create_submission_async(request={
+ "entities": [
+ epilot_submission.SubmissionEntity(
+ schema_=epilot_submission.Schema.SUBMISSION,
+ description="Submission created via API",
+ files=[
+ epilot_submission.Files(
+ s3ref={
+ "bucket": "epilot-user-content",
+ "key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf",
+ },
+ filename="document.pdf",
+ ),
+ ],
+ **{
+ "contact_first_name": "First",
+ "contact_last_name": "Last",
+ "contact_email": "example@submission.com",
+ "request": "I would like to know more about electric vehicles",
+ },
+ ),
+ ],
+ "organization_id": "123",
+ "source_id": "ce99875f-fba9-4fe2-a8f9-afaf52059051",
+ "source_type": "journey",
+ "journey_submit_id": "123",
+ "opt_ins": [
+ {
+ "identifier": "example@email.com",
+ "topic": "EMAIL_MARKETING",
+ },
+ ],
+ })
+
+ # Use the SDK ...
+
+asyncio.run(main())
+```
+
+
+
+## Available Resources and Operations
+
+
+Available methods
+
+
+### [submissions](docs/sdks/submissions/README.md)
+
+* [create_submission](docs/sdks/submissions/README.md#create_submission) - createSubmission
+
+
+
+
+
+## Retries
+
+Some of the endpoints in this SDK support retries. If you use the SDK without any configuration, it will fall back to the default retry strategy provided by the API. However, the default retry strategy can be overridden on a per-operation basis, or across the entire SDK.
+
+To change the default retry strategy for a single API call, simply provide a `RetryConfig` object to the call:
+```python
+import epilot_submission
+from epilot_submission import Epilot
+from epilot_submission.utils import BackoffStrategy, RetryConfig
+
+with Epilot() as epilot:
+
+ epilot.submissions.create_submission(request={
+ "entities": [
+ epilot_submission.SubmissionEntity(
+ schema_=epilot_submission.Schema.SUBMISSION,
+ description="Submission created via API",
+ files=[
+ epilot_submission.Files(
+ s3ref={
+ "bucket": "epilot-user-content",
+ "key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf",
+ },
+ filename="document.pdf",
+ ),
+ ],
+ **{
+ "contact_first_name": "First",
+ "contact_last_name": "Last",
+ "contact_email": "example@submission.com",
+ "request": "I would like to know more about electric vehicles",
+ },
+ ),
+ ],
+ "organization_id": "123",
+ "source_id": "ce99875f-fba9-4fe2-a8f9-afaf52059051",
+ "source_type": "journey",
+ "journey_submit_id": "123",
+ "opt_ins": [
+ {
+ "identifier": "example@email.com",
+ "topic": "EMAIL_MARKETING",
+ },
+ ],
+ },
+ RetryConfig("backoff", BackoffStrategy(1, 50, 1.1, 100), False))
+
+ # Use the SDK ...
+
+```
+
+If you'd like to override the default retry strategy for all operations that support retries, you can use the `retry_config` optional parameter when initializing the SDK:
+```python
+import epilot_submission
+from epilot_submission import Epilot
+from epilot_submission.utils import BackoffStrategy, RetryConfig
+
+with Epilot(
+ retry_config=RetryConfig("backoff", BackoffStrategy(1, 50, 1.1, 100), False),
+) as epilot:
+
+ epilot.submissions.create_submission(request={
+ "entities": [
+ epilot_submission.SubmissionEntity(
+ schema_=epilot_submission.Schema.SUBMISSION,
+ description="Submission created via API",
+ files=[
+ epilot_submission.Files(
+ s3ref={
+ "bucket": "epilot-user-content",
+ "key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf",
+ },
+ filename="document.pdf",
+ ),
+ ],
+ **{
+ "contact_first_name": "First",
+ "contact_last_name": "Last",
+ "contact_email": "example@submission.com",
+ "request": "I would like to know more about electric vehicles",
+ },
+ ),
+ ],
+ "organization_id": "123",
+ "source_id": "ce99875f-fba9-4fe2-a8f9-afaf52059051",
+ "source_type": "journey",
+ "journey_submit_id": "123",
+ "opt_ins": [
+ {
+ "identifier": "example@email.com",
+ "topic": "EMAIL_MARKETING",
+ },
+ ],
+ })
+
+ # Use the SDK ...
+
+```
+
+
+
+## Error Handling
+
+Handling errors in this SDK should largely match your expectations. All operations return a response object or raise an exception.
+
+By default, an API error will raise a models.SDKError exception, which has the following properties:
+
+| Property | Type | Description |
+|-----------------|------------------|-----------------------|
+| `.status_code` | *int* | The HTTP status code |
+| `.message` | *str* | The error message |
+| `.raw_response` | *httpx.Response* | The raw HTTP response |
+| `.body` | *str* | The response content |
+
+When custom error responses are specified for an operation, the SDK may also raise their associated exceptions. You can refer to respective *Errors* tables in SDK docs for more details on possible exception types for each operation. For example, the `create_submission_async` method may raise the following exceptions:
+
+| Error Type | Status Code | Content Type |
+| --------------- | ----------- | ------------ |
+| models.SDKError | 4XX, 5XX | \*/\* |
+
+### Example
+
+```python
+import epilot_submission
+from epilot_submission import Epilot, models
+
+with Epilot() as epilot:
+
+ try:
+
+ epilot.submissions.create_submission(request={
+ "entities": [
+ epilot_submission.SubmissionEntity(
+ schema_=epilot_submission.Schema.SUBMISSION,
+ description="Submission created via API",
+ files=[
+ epilot_submission.Files(
+ s3ref={
+ "bucket": "epilot-user-content",
+ "key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf",
+ },
+ filename="document.pdf",
+ ),
+ ],
+ **{
+ "contact_first_name": "First",
+ "contact_last_name": "Last",
+ "contact_email": "example@submission.com",
+ "request": "I would like to know more about electric vehicles",
+ },
+ ),
+ ],
+ "organization_id": "123",
+ "source_id": "ce99875f-fba9-4fe2-a8f9-afaf52059051",
+ "source_type": "journey",
+ "journey_submit_id": "123",
+ "opt_ins": [
+ {
+ "identifier": "example@email.com",
+ "topic": "EMAIL_MARKETING",
+ },
+ ],
+ })
+
+ # Use the SDK ...
+
+ except models.SDKError as e:
+ # handle exception
+ raise(e)
+```
+
+
+
+## Server Selection
+
+### Override Server URL Per-Client
+
+The default server can also be overridden globally by passing a URL to the `server_url: str` optional parameter when initializing the SDK client instance. For example:
+```python
+import epilot_submission
+from epilot_submission import Epilot
+
+with Epilot(
+ server_url="https://submission.sls.epilot.io",
+) as epilot:
+
+ epilot.submissions.create_submission(request={
+ "entities": [
+ epilot_submission.SubmissionEntity(
+ schema_=epilot_submission.Schema.SUBMISSION,
+ description="Submission created via API",
+ files=[
+ epilot_submission.Files(
+ s3ref={
+ "bucket": "epilot-user-content",
+ "key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf",
+ },
+ filename="document.pdf",
+ ),
+ ],
+ **{
+ "contact_first_name": "First",
+ "contact_last_name": "Last",
+ "contact_email": "example@submission.com",
+ "request": "I would like to know more about electric vehicles",
+ },
+ ),
+ ],
+ "organization_id": "123",
+ "source_id": "ce99875f-fba9-4fe2-a8f9-afaf52059051",
+ "source_type": "journey",
+ "journey_submit_id": "123",
+ "opt_ins": [
+ {
+ "identifier": "example@email.com",
+ "topic": "EMAIL_MARKETING",
+ },
+ ],
+ })
+
+ # Use the SDK ...
+
+```
+
+
+
+## Custom HTTP Client
+
+The Python SDK makes API calls using the [httpx](https://www.python-httpx.org/) HTTP library. In order to provide a convenient way to configure timeouts, cookies, proxies, custom headers, and other low-level configuration, you can initialize the SDK client with your own HTTP client instance.
+Depending on whether you are using the sync or async version of the SDK, you can pass an instance of `HttpClient` or `AsyncHttpClient` respectively, which are Protocol's ensuring that the client has the necessary methods to make API calls.
+This allows you to wrap the client with your own custom logic, such as adding custom headers, logging, or error handling, or you can just pass an instance of `httpx.Client` or `httpx.AsyncClient` directly.
+
+For example, you could specify a header for every request that this sdk makes as follows:
+```python
+from epilot_submission import Epilot
+import httpx
+
+http_client = httpx.Client(headers={"x-custom-header": "someValue"})
+s = Epilot(client=http_client)
+```
+
+or you could wrap the client with your own custom logic:
+```python
+from epilot_submission import Epilot
+from epilot_submission.httpclient import AsyncHttpClient
+import httpx
+
+class CustomClient(AsyncHttpClient):
+ client: AsyncHttpClient
+
+ def __init__(self, client: AsyncHttpClient):
+ self.client = client
+
+ async def send(
+ self,
+ request: httpx.Request,
+ *,
+ stream: bool = False,
+ auth: Union[
+ httpx._types.AuthTypes, httpx._client.UseClientDefault, None
+ ] = httpx.USE_CLIENT_DEFAULT,
+ follow_redirects: Union[
+ bool, httpx._client.UseClientDefault
+ ] = httpx.USE_CLIENT_DEFAULT,
+ ) -> httpx.Response:
+ request.headers["Client-Level-Header"] = "added by client"
+
+ return await self.client.send(
+ request, stream=stream, auth=auth, follow_redirects=follow_redirects
+ )
+
+ def build_request(
+ self,
+ method: str,
+ url: httpx._types.URLTypes,
+ *,
+ content: Optional[httpx._types.RequestContent] = None,
+ data: Optional[httpx._types.RequestData] = None,
+ files: Optional[httpx._types.RequestFiles] = None,
+ json: Optional[Any] = None,
+ params: Optional[httpx._types.QueryParamTypes] = None,
+ headers: Optional[httpx._types.HeaderTypes] = None,
+ cookies: Optional[httpx._types.CookieTypes] = None,
+ timeout: Union[
+ httpx._types.TimeoutTypes, httpx._client.UseClientDefault
+ ] = httpx.USE_CLIENT_DEFAULT,
+ extensions: Optional[httpx._types.RequestExtensions] = None,
+ ) -> httpx.Request:
+ return self.client.build_request(
+ method,
+ url,
+ content=content,
+ data=data,
+ files=files,
+ json=json,
+ params=params,
+ headers=headers,
+ cookies=cookies,
+ timeout=timeout,
+ extensions=extensions,
+ )
+
+s = Epilot(async_client=CustomClient(httpx.AsyncClient()))
+```
+
+
+
+## Authentication
+
+### Per-Client Security Schemes
+
+This SDK supports the following security scheme globally:
+
+| Name | Type | Scheme |
+| ------------- | ---- | ----------- |
+| `epilot_auth` | http | HTTP Bearer |
+
+To authenticate with the API the `epilot_auth` parameter must be set when initializing the SDK client instance. For example:
+```python
+import epilot_submission
+from epilot_submission import Epilot
+
+with Epilot(
+ epilot_auth="",
+) as epilot:
+
+ epilot.submissions.create_submission(request={
+ "entities": [
+ epilot_submission.SubmissionEntity(
+ schema_=epilot_submission.Schema.SUBMISSION,
+ description="Submission created via API",
+ files=[
+ epilot_submission.Files(
+ s3ref={
+ "bucket": "epilot-user-content",
+ "key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf",
+ },
+ filename="document.pdf",
+ ),
+ ],
+ **{
+ "contact_first_name": "First",
+ "contact_last_name": "Last",
+ "contact_email": "example@submission.com",
+ "request": "I would like to know more about electric vehicles",
+ },
+ ),
+ ],
+ "organization_id": "123",
+ "source_id": "ce99875f-fba9-4fe2-a8f9-afaf52059051",
+ "source_type": "journey",
+ "journey_submit_id": "123",
+ "opt_ins": [
+ {
+ "identifier": "example@email.com",
+ "topic": "EMAIL_MARKETING",
+ },
+ ],
+ })
+
+ # Use the SDK ...
+
+```
+
+
+
+## Resource Management
+
+The `Epilot` class implements the context manager protocol and registers a finalizer function to close the underlying sync and async HTTPX clients it uses under the hood. This will close HTTP connections, release memory and free up other resources held by the SDK. In short-lived Python programs and notebooks that make a few SDK method calls, resource management may not be a concern. However, in longer-lived programs, it is beneficial to create a single SDK instance via a [context manager][context-manager] and reuse it across the application.
+
+[context-manager]: https://docs.python.org/3/reference/datamodel.html#context-managers
+
+```python
+from epilot_submission import Epilot
+def main():
+ with Epilot() as epilot:
+ # Rest of application here...
+
+
+# Or when using async:
+async def amain():
+ async with Epilot() as epilot:
+ # Rest of application here...
+```
+
+
+
+## Debugging
+
+You can setup your SDK to emit debug logs for SDK requests and responses.
+
+You can pass your own logger class directly into your SDK.
+```python
+from epilot_submission import Epilot
+import logging
+
+logging.basicConfig(level=logging.DEBUG)
+s = Epilot(debug_logger=logging.getLogger("epilot_submission"))
+```
+
+
+
+## IDE Support
+
+### PyCharm
+
+Generally, the SDK will work well with most IDEs out of the box. However, when using PyCharm, you can enjoy much better integration with Pydantic by installing an additional plugin.
+
+- [PyCharm Pydantic Plugin](https://docs.pydantic.dev/latest/integrations/pycharm/)
+
+
+
+## Summary
+
+Submission API: Use this API to handle submissions entities from external sources e.g. journeys and frontends
+
+
+
+## Table of Contents
+
+* [epilot-submission](#epilot-submission)
+ * [SDK Installation](#sdk-installation)
+ * [SDK Example Usage](#sdk-example-usage)
+ * [Available Resources and Operations](#available-resources-and-operations)
+ * [Retries](#retries)
+ * [Error Handling](#error-handling)
+ * [Server Selection](#server-selection)
+ * [Custom HTTP Client](#custom-http-client)
+ * [Authentication](#authentication)
+ * [Resource Management](#resource-management)
+ * [Debugging](#debugging)
+ * [IDE Support](#ide-support)
+
+
+
-### submissions
-* `create_submission` - createSubmission
-
### SDK Generated by [Speakeasy](https://docs.speakeasyapi.dev/docs/using-speakeasy/client-sdks)
diff --git a/submission/RELEASES.md b/submission/RELEASES.md
index 2ba9053d4..681442eef 100644
--- a/submission/RELEASES.md
+++ b/submission/RELEASES.md
@@ -34,4 +34,292 @@ Based on:
### Changes
Based on:
- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
-- Speakeasy CLI 1.19.2 (2.16.5) https://github.com/speakeasy-api/speakeasy
\ No newline at end of file
+- Speakeasy CLI 1.19.2 (2.16.5) https://github.com/speakeasy-api/speakeasy
+
+## 2024-08-08 00:23:02
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.357.3 (2.390.0) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.3.0] submission
+
+## 2024-08-09 00:23:10
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.358.0 (2.390.6) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.3.1] submission
+
+## 2024-08-15 00:22:38
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.368.0 (2.399.0) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.3.2] submission
+
+## 2024-08-20 00:23:04
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.376.0 (2.402.5) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.4.0] submission
+
+## 2024-08-30 00:24:14
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.383.2 (2.407.0) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.4.1] submission
+
+## 2024-09-07 00:23:51
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.392.1 (2.411.9) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.4.2] submission
+
+## 2024-09-10 00:25:04
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.394.0 (2.413.0) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.5.0] submission
+
+## 2024-09-13 00:24:36
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.396.6 (2.415.6) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.6.0] submission
+
+## 2024-09-21 00:24:30
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.401.2 (2.421.3) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.7.0] submission
+
+## 2024-10-01 00:29:00
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.405.6 (2.428.1) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.7.1] submission
+
+## 2024-10-03 00:25:45
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.406.0 (2.429.0) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.7.2] submission
+
+## 2024-10-08 00:25:48
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.411.1 (2.436.1) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.8.0] submission
+
+## 2024-10-24 00:26:00
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.421.1 (2.438.15) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.8.1] submission
+
+## 2024-10-31 00:26:30
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.423.5 (2.445.1) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.9.0] submission
+
+## 2024-11-05 00:25:56
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.426.0 (2.446.5) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.9.1] submission
+
+## 2024-11-09 00:25:22
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.436.0 (2.455.2) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.9.2] submission
+
+## 2024-11-22 00:28:05
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.443.0 (2.461.4) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.9.3] submission
+
+## 2024-11-26 00:27:48
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.446.0 (2.462.0) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.10.0] submission
+
+## 2024-11-28 00:28:05
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.448.2 (2.467.4) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.10.1] submission
+
+## 2024-12-05 00:28:35
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.451.5 (2.471.2) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.10.2] submission
+
+## 2024-12-10 00:28:55
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.453.8 (2.474.15) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.10.3] submission
+
+## 2024-12-14 00:27:43
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.455.3 (2.479.3) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.11.0] submission
+
+## 2024-12-17 00:28:30
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.455.7 (2.480.1) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.12.0] submission
+
+## 2024-12-21 00:26:01
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.459.2 (2.483.1) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.12.1] submission
+
+## 2024-12-24 00:26:27
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.460.2 (2.484.0) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.12.2] submission
+
+## 2025-01-04 00:25:54
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.462.2 (2.486.1) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.12.3] submission
+
+## 2025-01-09 00:26:30
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.465.1 (2.488.4) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.12.4] submission
+
+## 2025-01-10 00:27:09
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.467.0 (2.492.3) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.13.0] submission
+
+## 2025-01-12 00:29:30
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.468.2 (2.493.4) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.13.1] submission
+
+## 2025-01-15 00:26:09
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.468.6 (2.493.13) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.13.2] submission
+
+## 2025-01-16 00:25:53
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.468.8 (2.493.19) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.13.3] submission
+
+## 2025-01-20 00:26:48
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.469.2 (2.493.23) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.13.4] submission
+
+## 2025-01-21 00:25:35
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.469.11 (2.493.32) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.13.5] submission
+
+## 2025-01-24 00:25:57
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.474.0 (2.495.0) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.13.6] submission
+
+## 2025-01-25 00:25:21
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.476.0 (2.496.0) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.13.7] submission
+
+## 2025-01-30 00:25:23
+### Changes
+Based on:
+- OpenAPI Doc 1.0.0 https://docs.api.epilot.io/submission-api.yaml
+- Speakeasy CLI 1.477.2 (2.497.8) https://github.com/speakeasy-api/speakeasy
+### Generated
+- [python v1.13.8] submission
\ No newline at end of file
diff --git a/submission/USAGE.md b/submission/USAGE.md
old mode 100755
new mode 100644
index f99c11640..78dd32724
--- a/submission/USAGE.md
+++ b/submission/USAGE.md
@@ -1,54 +1,96 @@
-
+
```python
-import epilot
-from epilot.models import operations, shared
-
-s = epilot.Epilot()
-
-
-req = shared.SubmissionPayload(
- ivy_opportunity_ids=[
- "deserunt",
- "porro",
- "nulla",
- ],
- entities=[
- {
- "perspiciatis": "nulla",
- "nihil": "fuga",
- "facilis": "eum",
- "iusto": "ullam",
- },
- {
- "inventore": "sapiente",
- "enim": "eum",
- "voluptatum": "autem",
- "vel": "non",
- },
- {
- "similique": "reprehenderit",
- "molestiae": "quo",
- },
- ],
- journey_submit_id="123",
- opt_ins=[
- shared.OptIn(
- identifier="example@email.com",
- meta={
- "dicta": "est",
- "voluptatem": "consequatur",
+# Synchronous Example
+import epilot_submission
+from epilot_submission import Epilot
+
+with Epilot() as epilot:
+
+ epilot.submissions.create_submission(request={
+ "entities": [
+ epilot_submission.SubmissionEntity(
+ schema_=epilot_submission.Schema.SUBMISSION,
+ description="Submission created via API",
+ files=[
+ epilot_submission.Files(
+ s3ref={
+ "bucket": "epilot-user-content",
+ "key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf",
+ },
+ filename="document.pdf",
+ ),
+ ],
+ **{
+ "contact_first_name": "First",
+ "contact_last_name": "Last",
+ "contact_email": "example@submission.com",
+ "request": "I would like to know more about electric vehicles",
+ },
+ ),
+ ],
+ "organization_id": "123",
+ "source_id": "ce99875f-fba9-4fe2-a8f9-afaf52059051",
+ "source_type": "journey",
+ "journey_submit_id": "123",
+ "opt_ins": [
+ {
+ "identifier": "example@email.com",
+ "topic": "EMAIL_MARKETING",
},
- topic="EMAIL_MARKETING",
- ),
- ],
- organization_id="123",
- source_id="ce99875f-fba9-4fe2-a8f9-afaf52059051",
- source_type="journey",
-)
-
-res = s.submissions.create_submission(req)
-
-if res.status_code == 200:
- # handle response
+ ],
+ })
+
+ # Use the SDK ...
+```
+
+
+
+The same SDK client can also be used to make asychronous requests by importing asyncio.
+```python
+# Asynchronous Example
+import asyncio
+import epilot_submission
+from epilot_submission import Epilot
+
+async def main():
+ async with Epilot() as epilot:
+
+ await epilot.submissions.create_submission_async(request={
+ "entities": [
+ epilot_submission.SubmissionEntity(
+ schema_=epilot_submission.Schema.SUBMISSION,
+ description="Submission created via API",
+ files=[
+ epilot_submission.Files(
+ s3ref={
+ "bucket": "epilot-user-content",
+ "key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf",
+ },
+ filename="document.pdf",
+ ),
+ ],
+ **{
+ "contact_first_name": "First",
+ "contact_last_name": "Last",
+ "contact_email": "example@submission.com",
+ "request": "I would like to know more about electric vehicles",
+ },
+ ),
+ ],
+ "organization_id": "123",
+ "source_id": "ce99875f-fba9-4fe2-a8f9-afaf52059051",
+ "source_type": "journey",
+ "journey_submit_id": "123",
+ "opt_ins": [
+ {
+ "identifier": "example@email.com",
+ "topic": "EMAIL_MARKETING",
+ },
+ ],
+ })
+
+ # Use the SDK ...
+
+asyncio.run(main())
```
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/submission/docs/models/files.md b/submission/docs/models/files.md
new file mode 100644
index 000000000..27426f413
--- /dev/null
+++ b/submission/docs/models/files.md
@@ -0,0 +1,12 @@
+# Files
+
+
+## Fields
+
+| Field | Type | Required | Description |
+| ---------------------------------------------- | ---------------------------------------------- | ---------------------------------------------- | ---------------------------------------------- |
+| `s3ref` | [models.S3Reference](../models/s3reference.md) | :heavy_check_mark: | S3 Reference from File API |
+| `__pydantic_extra__` | Dict[str, *Any*] | :heavy_minus_sign: | N/A |
+| `tags` | List[*str*] | :heavy_minus_sign: | List of tags for File entities |
+| `filename` | *Optional[str]* | :heavy_minus_sign: | Override the file name |
+| `relation_tags` | List[*str*] | :heavy_minus_sign: | List of relation labels for File attachments |
\ No newline at end of file
diff --git a/submission/docs/models/optin.md b/submission/docs/models/optin.md
new file mode 100644
index 000000000..14c95bda5
--- /dev/null
+++ b/submission/docs/models/optin.md
@@ -0,0 +1,10 @@
+# OptIn
+
+
+## Fields
+
+| Field | Type | Required | Description | Example |
+| ------------------ | ------------------ | ------------------ | ------------------ | ------------------ |
+| `identifier` | *Optional[str]* | :heavy_minus_sign: | Consent identifier | example@email.com |
+| `meta` | Dict[str, *Any*] | :heavy_minus_sign: | N/A | |
+| `topic` | *Optional[str]* | :heavy_minus_sign: | N/A | EMAIL_MARKETING |
\ No newline at end of file
diff --git a/submission/docs/models/s3reference.md b/submission/docs/models/s3reference.md
new file mode 100644
index 000000000..75994bcaf
--- /dev/null
+++ b/submission/docs/models/s3reference.md
@@ -0,0 +1,11 @@
+# S3Reference
+
+S3 Reference from File API
+
+
+## Fields
+
+| Field | Type | Required | Description | Example |
+| ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- |
+| `bucket` | *str* | :heavy_check_mark: | N/A | epilot-user-content |
+| `key` | *str* | :heavy_check_mark: | N/A | temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf |
\ No newline at end of file
diff --git a/submission/docs/models/schema.md b/submission/docs/models/schema.md
new file mode 100644
index 000000000..2dcb7523b
--- /dev/null
+++ b/submission/docs/models/schema.md
@@ -0,0 +1,8 @@
+# Schema
+
+
+## Values
+
+| Name | Value |
+| ------------ | ------------ |
+| `SUBMISSION` | submission |
\ No newline at end of file
diff --git a/submission/docs/models/security.md b/submission/docs/models/security.md
new file mode 100644
index 000000000..3f1d33548
--- /dev/null
+++ b/submission/docs/models/security.md
@@ -0,0 +1,8 @@
+# Security
+
+
+## Fields
+
+| Field | Type | Required | Description |
+| ------------------ | ------------------ | ------------------ | ------------------ |
+| `epilot_auth` | *Optional[str]* | :heavy_minus_sign: | N/A |
\ No newline at end of file
diff --git a/submission/docs/models/submissionentity.md b/submission/docs/models/submissionentity.md
new file mode 100644
index 000000000..2c3de1985
--- /dev/null
+++ b/submission/docs/models/submissionentity.md
@@ -0,0 +1,13 @@
+# SubmissionEntity
+
+The submission entity to create
+
+
+## Fields
+
+| Field | Type | Required | Description | Example |
+| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `schema_` | [models.Schema](../models/schema.md) | :heavy_check_mark: | N/A | |
+| `__pydantic_extra__` | Dict[str, *Any*] | :heavy_minus_sign: | N/A | {
"_schema": "submission",
"description": "Submission created via API",
"contact_first_name": "First",
"contact_last_name": "Last",
"contact_email": "example@submission.com",
"request": "I would like to know more about electric vehicles",
"files": [
{
"s3ref": {
"bucket": "epilot-user-content",
"key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf"
},
"filename": "document.pdf"
}
]
} |
+| `description` | *Optional[str]* | :heavy_minus_sign: | Readable description of the submission. Will be used as the title if passed | |
+| `files` | List[[models.Files](../models/files.md)] | :heavy_minus_sign: | Files to attach to Submission Entity as a relation (s3refs from File API)
| |
\ No newline at end of file
diff --git a/submission/docs/models/submissionpayload.md b/submission/docs/models/submissionpayload.md
new file mode 100644
index 000000000..fef4ab869
--- /dev/null
+++ b/submission/docs/models/submissionpayload.md
@@ -0,0 +1,16 @@
+# SubmissionPayload
+
+Holds content and meta information
+
+
+## Fields
+
+| Field | Type | Required | Description | Example |
+| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `entities` | List[[models.SubmissionEntity](../models/submissionentity.md)] | :heavy_check_mark: | Entities to create from submission
| |
+| `organization_id` | *str* | :heavy_check_mark: | organization id | 123 |
+| `source_id` | *str* | :heavy_check_mark: | identifier for source e.g. journey ID or frontend ID | ce99875f-fba9-4fe2-a8f9-afaf52059051 |
+| `source_type` | *str* | :heavy_check_mark: | type of source, e.g. journey or frontend | journey |
+| ~~`ivy_opportunity_ids`~~ | List[*str*] | :heavy_minus_sign: | : warning: ** DEPRECATED **: This will be removed in a future release, please migrate away from it as soon as possible.
Related Ivy Opportunity Ids | |
+| `journey_submit_id` | *Optional[str]* | :heavy_minus_sign: | journey submit uid | 123 |
+| `opt_ins` | List[[models.OptIn](../models/optin.md)] | :heavy_minus_sign: | Opt-ins to create from submission
| |
\ No newline at end of file
diff --git a/submission/docs/models/utils/retryconfig.md b/submission/docs/models/utils/retryconfig.md
new file mode 100644
index 000000000..69dd549ec
--- /dev/null
+++ b/submission/docs/models/utils/retryconfig.md
@@ -0,0 +1,24 @@
+# RetryConfig
+
+Allows customizing the default retry configuration. Only usable with methods that mention they support retries.
+
+## Fields
+
+| Name | Type | Description | Example |
+| ------------------------- | ----------------------------------- | --------------------------------------- | --------- |
+| `strategy` | `*str*` | The retry strategy to use. | `backoff` |
+| `backoff` | [BackoffStrategy](#backoffstrategy) | Configuration for the backoff strategy. | |
+| `retry_connection_errors` | `*bool*` | Whether to retry on connection errors. | `true` |
+
+## BackoffStrategy
+
+The backoff strategy allows retrying a request with an exponential backoff between each retry.
+
+### Fields
+
+| Name | Type | Description | Example |
+| ------------------ | --------- | ----------------------------------------- | -------- |
+| `initial_interval` | `*int*` | The initial interval in milliseconds. | `500` |
+| `max_interval` | `*int*` | The maximum interval in milliseconds. | `60000` |
+| `exponent` | `*float*` | The exponent to use for the backoff. | `1.5` |
+| `max_elapsed_time` | `*int*` | The maximum elapsed time in milliseconds. | `300000` |
\ No newline at end of file
diff --git a/submission/docs/sdks/epilot/README.md b/submission/docs/sdks/epilot/README.md
new file mode 100644
index 000000000..5590ce363
--- /dev/null
+++ b/submission/docs/sdks/epilot/README.md
@@ -0,0 +1,8 @@
+# Epilot SDK
+
+## Overview
+
+Submission API: Use this API to handle submissions entities from external sources e.g. journeys and frontends
+
+
+### Available Operations
diff --git a/submission/docs/sdks/submissions/README.md b/submission/docs/sdks/submissions/README.md
new file mode 100644
index 000000000..0c79e49e7
--- /dev/null
+++ b/submission/docs/sdks/submissions/README.md
@@ -0,0 +1,74 @@
+# Submissions
+(*submissions*)
+
+## Overview
+
+Journey Submission
+
+### Available Operations
+
+* [create_submission](#create_submission) - createSubmission
+
+## create_submission
+
+Creates a submission from a public facing Journey
+
+
+### Example Usage
+
+```python
+import epilot_submission
+from epilot_submission import Epilot
+
+with Epilot() as epilot:
+
+ epilot.submissions.create_submission(request={
+ "entities": [
+ epilot_submission.SubmissionEntity(
+ schema_=epilot_submission.Schema.SUBMISSION,
+ description="Submission created via API",
+ files=[
+ epilot_submission.Files(
+ s3ref={
+ "bucket": "epilot-user-content",
+ "key": "temp/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf",
+ },
+ filename="document.pdf",
+ ),
+ ],
+ **{
+ "contact_first_name": "First",
+ "contact_last_name": "Last",
+ "contact_email": "example@submission.com",
+ "request": "I would like to know more about electric vehicles",
+ },
+ ),
+ ],
+ "organization_id": "123",
+ "source_id": "ce99875f-fba9-4fe2-a8f9-afaf52059051",
+ "source_type": "journey",
+ "journey_submit_id": "123",
+ "opt_ins": [
+ {
+ "identifier": "example@email.com",
+ "topic": "EMAIL_MARKETING",
+ },
+ ],
+ })
+
+ # Use the SDK ...
+
+```
+
+### Parameters
+
+| Parameter | Type | Required | Description |
+| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
+| `request` | [models.SubmissionPayload](../../models/submissionpayload.md) | :heavy_check_mark: | The request object to use for the request. |
+| `retries` | [Optional[utils.RetryConfig]](../../models/utils/retryconfig.md) | :heavy_minus_sign: | Configuration to override the default retry behavior of the client. |
+
+### Errors
+
+| Error Type | Status Code | Content Type |
+| --------------- | --------------- | --------------- |
+| models.SDKError | 4XX, 5XX | \*/\* |
\ No newline at end of file
diff --git a/submission/gen.yaml b/submission/gen.yaml
index 789de5f7c..3cc0b167c 100644
--- a/submission/gen.yaml
+++ b/submission/gen.yaml
@@ -1,16 +1,47 @@
-configVersion: 1.0.0
-management:
- docChecksum: 89cb5b3bf127d6ae2e1c29fa5742affa
- docVersion: 1.0.0
- speakeasyVersion: 1.19.2
- generationVersion: 2.16.5
+configVersion: 2.0.0
generation:
- telemetryEnabled: false
sdkClassName: epilot
+ usageSnippets:
+ optionalPropertyRendering: withExample
+ fixes:
+ nameResolutionDec2023: false
+ parameterOrderingFeb2024: false
+ requestResponseComponentNamesFeb2024: false
+ auth:
+ oAuth2ClientCredentialsEnabled: false
+ oAuth2PasswordEnabled: false
sdkFlattening: true
- singleTagPerOp: false
+ telemetryEnabled: false
python:
- version: 1.2.2
+ version: 1.13.8
+ additionalDependencies:
+ dev: {}
+ main: {}
author: epilot
+ authors:
+ - Speakeasy
+ clientServerStatusCodesAsErrors: true
+ defaultErrorName: SDKError
description: Python Client SDK for Epilot
+ enableCustomCodeRegions: false
+ enumFormat: enum
+ fixFlags:
+ responseRequiredSep2024: false
+ flattenGlobalSecurity: true
+ flattenRequests: false
+ flatteningOrder: parameters-first
+ imports:
+ option: openapi
+ paths:
+ callbacks: ""
+ errors: ""
+ operations: ""
+ shared: ""
+ webhooks: ""
+ inputModelSuffix: input
+ maxMethodParams: 4
+ methodArguments: infer-optional-args
+ outputModelSuffix: output
packageName: epilot-submission
+ responseFormat: flat
+ templateVersion: v2
diff --git a/submission/poetry.toml b/submission/poetry.toml
new file mode 100644
index 000000000..ab1033bd3
--- /dev/null
+++ b/submission/poetry.toml
@@ -0,0 +1,2 @@
+[virtualenvs]
+in-project = true
diff --git a/submission/py.typed b/submission/py.typed
new file mode 100644
index 000000000..3e38f1a92
--- /dev/null
+++ b/submission/py.typed
@@ -0,0 +1 @@
+# Marker file for PEP 561. The package enables type hints.
diff --git a/submission/pylintrc b/submission/pylintrc
old mode 100755
new mode 100644
index 81c63b93f..81de9662e
--- a/submission/pylintrc
+++ b/submission/pylintrc
@@ -59,10 +59,11 @@ ignore-paths=
# Emacs file locks
ignore-patterns=^\.#
-# List of module names for which member attributes should not be checked
-# (useful for modules/projects where namespaces are manipulated during runtime
-# and thus existing member attributes cannot be deduced by static analysis). It
-# supports qualified module names, as well as Unix pattern matching.
+# List of module names for which member attributes should not be checked and
+# will not be imported (useful for modules/projects where namespaces are
+# manipulated during runtime and thus existing member attributes cannot be
+# deduced by static analysis). It supports qualified module names, as well as
+# Unix pattern matching.
ignored-modules=
# Python code to execute, usually for sys.path manipulation such as
@@ -93,6 +94,12 @@ py-version=3.9
# Discover python modules and packages in the file system subtree.
recursive=no
+# Add paths to the list of the source roots. Supports globbing patterns. The
+# source root is an absolute path or a path relative to the current working
+# directory used to determine a package namespace for modules located under the
+# source root.
+source-roots=src
+
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
@@ -116,20 +123,15 @@ argument-naming-style=snake_case
#argument-rgx=
# Naming style matching correct attribute names.
-attr-naming-style=snake_case
+#attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style. If left empty, attribute names will be checked with the set naming
# style.
-#attr-rgx=
+attr-rgx=[^\W\d][^\W]*|__.*__$
# Bad variable names which should always be refused, separated by a comma.
-bad-names=foo,
- bar,
- baz,
- toto,
- tutu,
- tata
+bad-names=
# Bad variable names regexes, separated by a comma. If names match any regex,
# they will always be refused
@@ -184,7 +186,8 @@ good-names=i,
k,
ex,
Run,
- _
+ _,
+ e
# Good variable names regexes, separated by a comma. If names match any regex,
# they will always be accepted
@@ -228,6 +231,10 @@ no-docstring-rgx=^_
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
+# Regular expression matching correct type alias names. If left empty, type
+# alias names will be checked with the set naming style.
+typealias-rgx=.*
+
# Regular expression matching correct type variable names. If left empty, type
# variable names will be checked with the set naming style.
#typevar-rgx=
@@ -250,15 +257,12 @@ check-protected-access-in-special-methods=no
defining-attr-methods=__init__,
__new__,
setUp,
+ asyncSetUp,
__post_init__
# List of member names, which should be excluded from the protected access
# warning.
-exclude-protected=_asdict,
- _fields,
- _replace,
- _source,
- _make
+exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
@@ -421,6 +425,8 @@ disable=raw-checker-failed,
suppressed-message,
useless-suppression,
deprecated-pragma,
+ use-implicit-booleaness-not-comparison-to-string,
+ use-implicit-booleaness-not-comparison-to-zero,
use-symbolic-message-instead,
trailing-whitespace,
line-too-long,
@@ -433,18 +439,27 @@ disable=raw-checker-failed,
broad-exception-raised,
too-few-public-methods,
too-many-branches,
- chained-comparison,
duplicate-code,
trailing-newlines,
too-many-public-methods,
too-many-locals,
- too-many-lines
+ too-many-lines,
+ using-constant-test,
+ too-many-statements,
+ cyclic-import,
+ too-many-nested-blocks,
+ too-many-boolean-expressions,
+ no-else-raise,
+ bare-except,
+ broad-exception-caught,
+ fixme,
+ relative-beyond-top-level
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
-enable=c-extension-no-member
+enable=
[METHOD_ARGS]
@@ -490,8 +505,9 @@ evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor
# used to format the message information. See doc for all details.
msg-template=
-# Set the output format. Available formats are text, parseable, colorized, json
-# and msvs (visual studio). You can also give a reporter class, e.g.
+# Set the output format. Available formats are: text, parseable, colorized,
+# json2 (improved json format), json (old json format) and msvs (visual
+# studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
#output-format=
@@ -525,8 +541,8 @@ min-similarity-lines=4
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
-# Spelling dictionary name. Available dictionaries: none. To make it work,
-# install the 'python-enchant' package.
+# Spelling dictionary name. No available dictionaries : You need to install
+# both the python package and the system dependency for enchant to work.
spelling-dict=
# List of comma separated words that should be considered directives if they
@@ -619,7 +635,7 @@ additional-builtins=
allow-global-unused-variables=yes
# List of names allowed to shadow builtins
-allowed-redefined-builtins=
+allowed-redefined-builtins=id,object
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
diff --git a/submission/pyproject.toml b/submission/pyproject.toml
new file mode 100644
index 000000000..875e7de18
--- /dev/null
+++ b/submission/pyproject.toml
@@ -0,0 +1,57 @@
+[project]
+name = "epilot-submission"
+version = "1.13.8"
+description = "Python Client SDK for Epilot"
+authors = [{ name = "Speakeasy" },]
+readme = "README-PYPI.md"
+requires-python = ">=3.9"
+dependencies = [
+ "eval-type-backport >=0.2.0",
+ "httpx >=0.28.1",
+ "pydantic >=2.10.3",
+ "python-dateutil >=2.8.2",
+ "typing-inspect >=0.9.0",
+]
+
+[tool.poetry]
+repository = "https://github.com/epilot-dev/sdk-python.git"
+packages = [
+ { include = "epilot_submission", from = "src" }
+]
+include = ["py.typed", "src/epilot_submission/py.typed"]
+
+[tool.setuptools.package-data]
+"*" = ["py.typed", "src/epilot_submission/py.typed"]
+
+[virtualenvs]
+in-project = true
+
+[tool.poetry.group.dev.dependencies]
+mypy = "==1.14.1"
+pylint = "==3.2.3"
+types-python-dateutil = "^2.9.0.20240316"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.pytest.ini_options]
+asyncio_default_fixture_loop_scope = "function"
+pythonpath = ["src"]
+
+[tool.mypy]
+disable_error_code = "misc"
+
+[[tool.mypy.overrides]]
+module = "typing_inspect"
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "jsonpath"
+ignore_missing_imports = true
+
+[tool.pyright]
+venvPath = "."
+venv = ".venv"
+
+
diff --git a/submission/scripts/prepare-readme.py b/submission/scripts/prepare-readme.py
new file mode 100644
index 000000000..fc90759b9
--- /dev/null
+++ b/submission/scripts/prepare-readme.py
@@ -0,0 +1,33 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+import re
+import shutil
+
+try:
+ with open("README.md", "r") as rh:
+ readme_contents = rh.read()
+ GITHUB_URL = "https://github.com/epilot-dev/sdk-python.git"
+ GITHUB_URL = (
+ GITHUB_URL[: -len(".git")] if GITHUB_URL.endswith(".git") else GITHUB_URL
+ )
+ # links on PyPI should have absolute URLs
+ readme_contents = re.sub(
+ r"(\[[^\]]+\]\()((?!https?:)[^\)]+)(\))",
+ lambda m: m.group(1)
+ + GITHUB_URL
+ + "/blob/master/"
+ + m.group(2)
+ + m.group(3),
+ readme_contents,
+ )
+
+ with open("README-PYPI.md", "w") as wh:
+ wh.write(readme_contents)
+except Exception as e:
+ try:
+ print("Failed to rewrite README.md to README-PYPI.md, copying original instead")
+ print(e)
+ shutil.copyfile("README.md", "README-PYPI.md")
+ except Exception as e:
+ print("Failed to copy README.md to README-PYPI.md")
+ print(e)
diff --git a/submission/scripts/publish.sh b/submission/scripts/publish.sh
new file mode 100755
index 000000000..ab45b1f94
--- /dev/null
+++ b/submission/scripts/publish.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+export POETRY_PYPI_TOKEN_PYPI=${PYPI_TOKEN}
+
+poetry run python scripts/prepare-readme.py
+
+poetry publish --build --skip-existing
diff --git a/submission/setup.py b/submission/setup.py
deleted file mode 100755
index a6a164c94..000000000
--- a/submission/setup.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-import setuptools
-
-try:
- with open("README.md", "r") as fh:
- long_description = fh.read()
-except FileNotFoundError:
- long_description = ""
-
-setuptools.setup(
- name="epilot-submission",
- version="1.2.2",
- author="epilot",
- description="Python Client SDK for Epilot",
- long_description=long_description,
- long_description_content_type="text/markdown",
- packages=setuptools.find_packages(where="src"),
- install_requires=[
- "certifi==2022.12.07",
- "charset-normalizer==2.1.1",
- "dataclasses-json-speakeasy==0.5.8",
- "idna==3.3",
- "marshmallow==3.17.1",
- "marshmallow-enum==1.5.1",
- "mypy-extensions==0.4.3",
- "packaging==21.3",
- "pyparsing==3.0.9",
- "python-dateutil==2.8.2",
- "requests==2.28.1",
- "six==1.16.0",
- "typing-inspect==0.8.0",
- "typing_extensions==4.3.0",
- "urllib3==1.26.12",
- "pylint==2.16.2",
- ],
- package_dir={'': 'src'},
- python_requires='>=3.9'
-)
diff --git a/submission/src/epilot/__init__.py b/submission/src/epilot/__init__.py
deleted file mode 100755
index b9e232018..000000000
--- a/submission/src/epilot/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-from .sdk import *
diff --git a/submission/src/epilot/models/__init__.py b/submission/src/epilot/models/__init__.py
deleted file mode 100755
index 889f8adcf..000000000
--- a/submission/src/epilot/models/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
diff --git a/submission/src/epilot/models/operations/__init__.py b/submission/src/epilot/models/operations/__init__.py
deleted file mode 100755
index 2f456ee2a..000000000
--- a/submission/src/epilot/models/operations/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-from .createsubmission import *
-
-__all__ = ["CreateSubmissionResponse"]
diff --git a/submission/src/epilot/models/operations/createsubmission.py b/submission/src/epilot/models/operations/createsubmission.py
deleted file mode 100755
index 64112745c..000000000
--- a/submission/src/epilot/models/operations/createsubmission.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-from __future__ import annotations
-import dataclasses
-import requests as requests_http
-from typing import Optional
-
-
-@dataclasses.dataclass
-class CreateSubmissionResponse:
-
- content_type: str = dataclasses.field()
- status_code: int = dataclasses.field()
- raw_response: Optional[requests_http.Response] = dataclasses.field(default=None)
-
\ No newline at end of file
diff --git a/submission/src/epilot/models/shared/__init__.py b/submission/src/epilot/models/shared/__init__.py
deleted file mode 100755
index 36a4fdd7a..000000000
--- a/submission/src/epilot/models/shared/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-from .optin import *
-from .submissionpayload import *
-
-__all__ = ["OptIn","SubmissionPayload"]
diff --git a/submission/src/epilot/models/shared/optin.py b/submission/src/epilot/models/shared/optin.py
deleted file mode 100755
index 4e148ae0f..000000000
--- a/submission/src/epilot/models/shared/optin.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-from __future__ import annotations
-import dataclasses
-from dataclasses_json import Undefined, dataclass_json
-from epilot import utils
-from typing import Any, Optional
-
-
-@dataclass_json(undefined=Undefined.EXCLUDE)
-@dataclasses.dataclass
-class OptIn:
-
- identifier: Optional[str] = dataclasses.field(default=None, metadata={'dataclasses_json': { 'letter_case': utils.get_field_name('identifier'), 'exclude': lambda f: f is None }})
- r"""Consent identifier"""
- meta: Optional[dict[str, Any]] = dataclasses.field(default=None, metadata={'dataclasses_json': { 'letter_case': utils.get_field_name('meta'), 'exclude': lambda f: f is None }})
- topic: Optional[str] = dataclasses.field(default=None, metadata={'dataclasses_json': { 'letter_case': utils.get_field_name('topic'), 'exclude': lambda f: f is None }})
-
\ No newline at end of file
diff --git a/submission/src/epilot/models/shared/submissionpayload.py b/submission/src/epilot/models/shared/submissionpayload.py
deleted file mode 100755
index be943dc72..000000000
--- a/submission/src/epilot/models/shared/submissionpayload.py
+++ /dev/null
@@ -1,34 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-from __future__ import annotations
-import dataclasses
-from ..shared import optin as shared_optin
-from dataclasses_json import Undefined, dataclass_json
-from epilot import utils
-from typing import Any, Optional
-
-
-@dataclass_json(undefined=Undefined.EXCLUDE)
-@dataclasses.dataclass
-class SubmissionPayload:
- r"""Holds content and meta information"""
-
- entities: list[dict[str, Any]] = dataclasses.field(metadata={'dataclasses_json': { 'letter_case': utils.get_field_name('entities') }})
- r"""Entities to create from submission
-
- """
- organization_id: str = dataclasses.field(metadata={'dataclasses_json': { 'letter_case': utils.get_field_name('organization_id') }})
- r"""organization id"""
- source_id: str = dataclasses.field(metadata={'dataclasses_json': { 'letter_case': utils.get_field_name('source_id') }})
- r"""identifier for source e.g. journey ID or frontend ID"""
- source_type: str = dataclasses.field(metadata={'dataclasses_json': { 'letter_case': utils.get_field_name('source_type') }})
- r"""type of source, e.g. journey or frontend"""
- ivy_opportunity_ids: Optional[list[str]] = dataclasses.field(default=None, metadata={'dataclasses_json': { 'letter_case': utils.get_field_name('_ivy_opportunity_ids'), 'exclude': lambda f: f is None }})
- r"""Related Ivy Opportunity Ids"""
- journey_submit_id: Optional[str] = dataclasses.field(default=None, metadata={'dataclasses_json': { 'letter_case': utils.get_field_name('journey_submit_id'), 'exclude': lambda f: f is None }})
- r"""journey submit uid"""
- opt_ins: Optional[list[shared_optin.OptIn]] = dataclasses.field(default=None, metadata={'dataclasses_json': { 'letter_case': utils.get_field_name('opt_ins'), 'exclude': lambda f: f is None }})
- r"""Opt-ins to create from submission
-
- """
-
\ No newline at end of file
diff --git a/submission/src/epilot/sdk.py b/submission/src/epilot/sdk.py
deleted file mode 100755
index 8e8a4c1d5..000000000
--- a/submission/src/epilot/sdk.py
+++ /dev/null
@@ -1,67 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-import requests as requests_http
-from . import utils
-from .submissions import Submissions
-
-SERVERS = [
- "https://submission.sls.epilot.io",
-]
-"""Contains the list of servers available to the SDK"""
-
-class Epilot:
- r"""Use this API to handle submissions entities from external sources e.g. journeys and frontends
-
- """
- submissions: Submissions
- r"""Journey Submission"""
-
- _client: requests_http.Session
- _security_client: requests_http.Session
- _server_url: str = SERVERS[0]
- _language: str = "python"
- _sdk_version: str = "1.2.2"
- _gen_version: str = "2.16.5"
-
- def __init__(self,
- server_url: str = None,
- url_params: dict[str, str] = None,
- client: requests_http.Session = None
- ) -> None:
- """Instantiates the SDK configuring it with the provided parameters.
-
- :param server_url: The server URL to use for all operations
- :type server_url: str
- :param url_params: Parameters to optionally template the server URL with
- :type url_params: dict[str, str]
- :param client: The requests.Session HTTP client to use for all operations
- :type client: requests_http.Session
- """
- self._client = requests_http.Session()
-
-
- if server_url is not None:
- if url_params is not None:
- self._server_url = utils.template_url(server_url, url_params)
- else:
- self._server_url = server_url
-
- if client is not None:
- self._client = client
-
- self._security_client = self._client
-
-
- self._init_sdks()
-
- def _init_sdks(self):
- self.submissions = Submissions(
- self._client,
- self._security_client,
- self._server_url,
- self._language,
- self._sdk_version,
- self._gen_version
- )
-
-
\ No newline at end of file
diff --git a/submission/src/epilot/submissions.py b/submission/src/epilot/submissions.py
deleted file mode 100755
index 7feafdcff..000000000
--- a/submission/src/epilot/submissions.py
+++ /dev/null
@@ -1,50 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-import requests as requests_http
-from . import utils
-from epilot.models import operations, shared
-
-class Submissions:
- r"""Journey Submission"""
- _client: requests_http.Session
- _security_client: requests_http.Session
- _server_url: str
- _language: str
- _sdk_version: str
- _gen_version: str
-
- def __init__(self, client: requests_http.Session, security_client: requests_http.Session, server_url: str, language: str, sdk_version: str, gen_version: str) -> None:
- self._client = client
- self._security_client = security_client
- self._server_url = server_url
- self._language = language
- self._sdk_version = sdk_version
- self._gen_version = gen_version
-
- def create_submission(self, request: shared.SubmissionPayload) -> operations.CreateSubmissionResponse:
- r"""createSubmission
- Creates a submission from a public facing Journey
-
- """
- base_url = self._server_url
-
- url = base_url.removesuffix('/') + '/v1/submission/submissions'
-
- headers = {}
- req_content_type, data, form = utils.serialize_request_body(request, "request", 'json')
- if req_content_type not in ('multipart/form-data', 'multipart/mixed'):
- headers['content-type'] = req_content_type
-
- client = self._client
-
- http_res = client.request('POST', url, data=data, files=form, headers=headers)
- content_type = http_res.headers.get('Content-Type')
-
- res = operations.CreateSubmissionResponse(status_code=http_res.status_code, content_type=content_type, raw_response=http_res)
-
- if http_res.status_code == 201:
- pass
-
- return res
-
-
\ No newline at end of file
diff --git a/submission/src/epilot/utils/__init__.py b/submission/src/epilot/utils/__init__.py
deleted file mode 100755
index 94b739857..000000000
--- a/submission/src/epilot/utils/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-from .retries import *
-from .utils import *
diff --git a/submission/src/epilot/utils/retries.py b/submission/src/epilot/utils/retries.py
deleted file mode 100755
index c6251d948..000000000
--- a/submission/src/epilot/utils/retries.py
+++ /dev/null
@@ -1,118 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-import random
-import time
-
-import requests
-
-
-class BackoffStrategy:
- initial_interval: int
- max_interval: int
- exponent: float
- max_elapsed_time: int
-
- def __init__(self, initial_interval: int, max_interval: int, exponent: float, max_elapsed_time: int):
- self.initial_interval = initial_interval
- self.max_interval = max_interval
- self.exponent = exponent
- self.max_elapsed_time = max_elapsed_time
-
-
-class RetryConfig:
- strategy: str
- backoff: BackoffStrategy
- retry_connection_errors: bool
-
- def __init__(self, strategy: str, retry_connection_errors: bool):
- self.strategy = strategy
- self.retry_connection_errors = retry_connection_errors
-
-
-class Retries:
- config: RetryConfig
- status_codes: list[str]
-
- def __init__(self, config: RetryConfig, status_codes: list[str]):
- self.config = config
- self.status_codes = status_codes
-
-
-class TemporaryError(Exception):
- response: requests.Response
-
- def __init__(self, response: requests.Response):
- self.response = response
-
-
-class PermanentError(Exception):
- inner: Exception
-
- def __init__(self, inner: Exception):
- self.inner = inner
-
-
-def retry(func, retries: Retries):
- if retries.config.strategy == 'backoff':
- def do_request():
- res: requests.Response
- try:
- res = func()
-
- for code in retries.status_codes:
- if "X" in code.upper():
- code_range = int(code[0])
-
- status_major = res.status_code / 100
-
- if status_major >= code_range and status_major < code_range + 1:
- raise TemporaryError(res)
- else:
- parsed_code = int(code)
-
- if res.status_code == parsed_code:
- raise TemporaryError(res)
- except requests.exceptions.ConnectionError as exception:
- if not retries.config.config.retry_connection_errors:
- raise
-
- raise PermanentError(exception) from exception
- except requests.exceptions.Timeout as exception:
- if not retries.config.config.retry_connection_errors:
- raise
-
- raise PermanentError(exception) from exception
- except TemporaryError:
- raise
- except Exception as exception:
- raise PermanentError(exception) from exception
-
- return res
-
- return retry_with_backoff(do_request, retries.config.backoff.initial_interval, retries.config.backoff.max_interval, retries.config.backoff.exponent, retries.config.backoff.max_elapsed_time)
-
- return func()
-
-
-def retry_with_backoff(func, initial_interval=500, max_interval=60000, exponent=1.5, max_elapsed_time=3600000):
- start = round(time.time()*1000)
- retries = 0
-
- while True:
- try:
- return func()
- except PermanentError as exception:
- raise exception.inner
- except Exception as exception: # pylint: disable=broad-exception-caught
- now = round(time.time()*1000)
- if now - start > max_elapsed_time:
- if isinstance(exception, TemporaryError):
- return exception.response
-
- raise
- sleep = ((initial_interval/1000) *
- exponent**retries + random.uniform(0, 1))
- if sleep > max_interval/1000:
- sleep = max_interval/1000
- time.sleep(sleep)
- retries += 1
diff --git a/submission/src/epilot/utils/utils.py b/submission/src/epilot/utils/utils.py
deleted file mode 100755
index 9d4fba324..000000000
--- a/submission/src/epilot/utils/utils.py
+++ /dev/null
@@ -1,735 +0,0 @@
-"""Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT."""
-
-import base64
-import json
-import re
-from dataclasses import Field, dataclass, fields, is_dataclass, make_dataclass
-from datetime import date, datetime
-from email.message import Message
-from enum import Enum
-from typing import Any, Callable, Optional, Tuple, Union, get_args, get_origin
-from xmlrpc.client import boolean
-
-import dateutil.parser
-import requests
-from dataclasses_json import DataClassJsonMixin
-
-
-class SecurityClient:
- client: requests.Session
- query_params: dict[str, str] = {}
-
- def __init__(self, client: requests.Session):
- self.client = client
-
- def request(self, method, url, **kwargs):
- params = kwargs.get('params', {})
- kwargs["params"] = self.query_params | params
-
- return self.client.request(method, url, **kwargs)
-
-
-def configure_security_client(client: requests.Session, security: dataclass):
- client = SecurityClient(client)
-
- if security is None:
- return client
-
- sec_fields: Tuple[Field, ...] = fields(security)
- for sec_field in sec_fields:
- value = getattr(security, sec_field.name)
- if value is None:
- continue
-
- metadata = sec_field.metadata.get('security')
- if metadata is None:
- continue
- if metadata.get('option'):
- _parse_security_option(client, value)
- return client
- if metadata.get('scheme'):
- # Special case for basic auth which could be a flattened struct
- if metadata.get("sub_type") == "basic" and not is_dataclass(value):
- _parse_security_scheme(client, metadata, security)
- else:
- _parse_security_scheme(client, metadata, value)
-
- return client
-
-
-def _parse_security_option(client: SecurityClient, option: dataclass):
- opt_fields: Tuple[Field, ...] = fields(option)
- for opt_field in opt_fields:
- metadata = opt_field.metadata.get('security')
- if metadata is None or metadata.get('scheme') is None:
- continue
- _parse_security_scheme(
- client, metadata, getattr(option, opt_field.name))
-
-
-def _parse_security_scheme(client: SecurityClient, scheme_metadata: dict, scheme: any):
- scheme_type = scheme_metadata.get('type')
- sub_type = scheme_metadata.get('sub_type')
-
- if is_dataclass(scheme):
- if scheme_type == 'http' and sub_type == 'basic':
- _parse_basic_auth_scheme(client, scheme)
- return
-
- scheme_fields: Tuple[Field, ...] = fields(scheme)
- for scheme_field in scheme_fields:
- metadata = scheme_field.metadata.get('security')
- if metadata is None or metadata.get('field_name') is None:
- continue
-
- value = getattr(scheme, scheme_field.name)
-
- _parse_security_scheme_value(
- client, scheme_metadata, metadata, value)
- else:
- _parse_security_scheme_value(
- client, scheme_metadata, scheme_metadata, scheme)
-
-
-def _parse_security_scheme_value(client: SecurityClient, scheme_metadata: dict, security_metadata: dict, value: any):
- scheme_type = scheme_metadata.get('type')
- sub_type = scheme_metadata.get('sub_type')
-
- header_name = security_metadata.get('field_name')
-
- if scheme_type == "apiKey":
- if sub_type == 'header':
- client.client.headers[header_name] = value
- elif sub_type == 'query':
- client.query_params[header_name] = value
- elif sub_type == 'cookie':
- client.client.cookies[header_name] = value
- else:
- raise Exception('not supported')
- elif scheme_type == "openIdConnect":
- client.client.headers[header_name] = value
- elif scheme_type == 'oauth2':
- client.client.headers[header_name] = value
- elif scheme_type == 'http':
- if sub_type == 'bearer':
- client.client.headers[header_name] = value
- else:
- raise Exception('not supported')
- else:
- raise Exception('not supported')
-
-
-def _parse_basic_auth_scheme(client: SecurityClient, scheme: dataclass):
- username = ""
- password = ""
-
- scheme_fields: Tuple[Field, ...] = fields(scheme)
- for scheme_field in scheme_fields:
- metadata = scheme_field.metadata.get('security')
- if metadata is None or metadata.get('field_name') is None:
- continue
-
- field_name = metadata.get('field_name')
- value = getattr(scheme, scheme_field.name)
-
- if field_name == 'username':
- username = value
- if field_name == 'password':
- password = value
-
- data = f'{username}:{password}'.encode()
- client.client.headers['Authorization'] = f'Basic {base64.b64encode(data).decode()}'
-
-
-def generate_url(clazz: type, server_url: str, path: str, path_params: dataclass, gbls: dict[str, dict[str, dict[str, Any]]] = None) -> str:
- path_param_fields: Tuple[Field, ...] = fields(clazz)
- for field in path_param_fields:
- request_metadata = field.metadata.get('request')
- if request_metadata is not None:
- continue
-
- param_metadata = field.metadata.get('path_param')
- if param_metadata is None:
- continue
-
- if param_metadata.get('style', 'simple') == 'simple':
- param = getattr(
- path_params, field.name) if path_params is not None else None
- param = _populate_from_globals(
- field.name, param, 'pathParam', gbls)
-
- if param is None:
- continue
-
- if isinstance(param, list):
- pp_vals: list[str] = []
- for pp_val in param:
- if pp_val is None:
- continue
- pp_vals.append(_val_to_string(pp_val))
- path = path.replace(
- '{' + param_metadata.get('field_name', field.name) + '}', ",".join(pp_vals), 1)
- elif isinstance(param, dict):
- pp_vals: list[str] = []
- for pp_key in param:
- if param[pp_key] is None:
- continue
- if param_metadata.get('explode'):
- pp_vals.append(
- f"{pp_key}={_val_to_string(param[pp_key])}")
- else:
- pp_vals.append(
- f"{pp_key},{_val_to_string(param[pp_key])}")
- path = path.replace(
- '{' + param_metadata.get('field_name', field.name) + '}', ",".join(pp_vals), 1)
- elif not isinstance(param, (str, int, float, complex, bool)):
- pp_vals: list[str] = []
- param_fields: Tuple[Field, ...] = fields(param)
- for param_field in param_fields:
- param_value_metadata = param_field.metadata.get(
- 'path_param')
- if not param_value_metadata:
- continue
-
- parm_name = param_value_metadata.get(
- 'field_name', field.name)
-
- param_field_val = getattr(param, param_field.name)
- if param_field_val is None:
- continue
- if param_metadata.get('explode'):
- pp_vals.append(
- f"{parm_name}={_val_to_string(param_field_val)}")
- else:
- pp_vals.append(
- f"{parm_name},{_val_to_string(param_field_val)}")
- path = path.replace(
- '{' + param_metadata.get('field_name', field.name) + '}', ",".join(pp_vals), 1)
- else:
- path = path.replace(
- '{' + param_metadata.get('field_name', field.name) + '}', _val_to_string(param), 1)
-
- return server_url.removesuffix("/") + path
-
-
-def is_optional(field):
- return get_origin(field) is Union and type(None) in get_args(field)
-
-
-def template_url(url_with_params: str, params: dict[str, str]) -> str:
- for key, value in params.items():
- url_with_params = url_with_params.replace(
- '{' + key + '}', value)
-
- return url_with_params
-
-
-def get_query_params(clazz: type, query_params: dataclass, gbls: dict[str, dict[str, dict[str, Any]]] = None) -> dict[str, list[str]]:
- params: dict[str, list[str]] = {}
-
- param_fields: Tuple[Field, ...] = fields(clazz)
- for field in param_fields:
- request_metadata = field.metadata.get('request')
- if request_metadata is not None:
- continue
-
- metadata = field.metadata.get('query_param')
- if not metadata:
- continue
-
- param_name = field.name
- value = getattr(
- query_params, param_name) if query_params is not None else None
-
- value = _populate_from_globals(param_name, value, 'queryParam', gbls)
-
- f_name = metadata.get("field_name")
- serialization = metadata.get('serialization', '')
- if serialization != '':
- params = params | _get_serialized_query_params(
- metadata, f_name, value)
- else:
- style = metadata.get('style', 'form')
- if style == 'deepObject':
- params = params | _get_deep_object_query_params(
- metadata, f_name, value)
- elif style == 'form':
- params = params | _get_form_query_params(
- metadata, f_name, value)
- else:
- raise Exception('not yet implemented')
- return params
-
-
-def get_headers(headers_params: dataclass) -> dict[str, str]:
- if headers_params is None:
- return {}
-
- headers: dict[str, str] = {}
-
- param_fields: Tuple[Field, ...] = fields(headers_params)
- for field in param_fields:
- metadata = field.metadata.get('header')
- if not metadata:
- continue
-
- value = _serialize_header(metadata.get(
- 'explode', False), getattr(headers_params, field.name))
-
- if value != '':
- headers[metadata.get('field_name', field.name)] = value
-
- return headers
-
-
-def _get_serialized_query_params(metadata: dict, field_name: str, obj: any) -> dict[str, list[str]]:
- params: dict[str, list[str]] = {}
-
- serialization = metadata.get('serialization', '')
- if serialization == 'json':
- params[metadata.get("field_name", field_name)] = marshal_json(obj)
-
- return params
-
-
-def _get_deep_object_query_params(metadata: dict, field_name: str, obj: any) -> dict[str, list[str]]:
- params: dict[str, list[str]] = {}
-
- if obj is None:
- return params
-
- if is_dataclass(obj):
- obj_fields: Tuple[Field, ...] = fields(obj)
- for obj_field in obj_fields:
- obj_param_metadata = obj_field.metadata.get('query_param')
- if not obj_param_metadata:
- continue
-
- obj_val = getattr(obj, obj_field.name)
- if obj_val is None:
- continue
-
- if isinstance(obj_val, list):
- for val in obj_val:
- if val is None:
- continue
-
- if params.get(f'{metadata.get("field_name", field_name)}[{obj_param_metadata.get("field_name", obj_field.name)}]') is None:
- params[f'{metadata.get("field_name", field_name)}[{obj_param_metadata.get("field_name", obj_field.name)}]'] = [
- ]
-
- params[
- f'{metadata.get("field_name", field_name)}[{obj_param_metadata.get("field_name", obj_field.name)}]'].append(_val_to_string(val))
- else:
- params[
- f'{metadata.get("field_name", field_name)}[{obj_param_metadata.get("field_name", obj_field.name)}]'] = [
- _val_to_string(obj_val)]
- elif isinstance(obj, dict):
- for key, value in obj.items():
- if value is None:
- continue
-
- if isinstance(value, list):
- for val in value:
- if val is None:
- continue
-
- if params.get(f'{metadata.get("field_name", field_name)}[{key}]') is None:
- params[f'{metadata.get("field_name", field_name)}[{key}]'] = [
- ]
-
- params[
- f'{metadata.get("field_name", field_name)}[{key}]'].append(_val_to_string(val))
- else:
- params[f'{metadata.get("field_name", field_name)}[{key}]'] = [
- _val_to_string(value)]
- return params
-
-
-def _get_query_param_field_name(obj_field: Field) -> str:
- obj_param_metadata = obj_field.metadata.get('query_param')
-
- if not obj_param_metadata:
- return ""
-
- return obj_param_metadata.get("field_name", obj_field.name)
-
-
-def _get_form_query_params(metadata: dict, field_name: str, obj: any) -> dict[str, list[str]]:
- return _populate_form(field_name, metadata.get("explode", True), obj, _get_query_param_field_name)
-
-
-SERIALIZATION_METHOD_TO_CONTENT_TYPE = {
- 'json': 'application/json',
- 'form': 'application/x-www-form-urlencoded',
- 'multipart': 'multipart/form-data',
- 'raw': 'application/octet-stream',
- 'string': 'text/plain',
-}
-
-
-def serialize_request_body(request: dataclass, request_field_name: str, serialization_method: str) -> Tuple[str, any, any]:
- if request is None:
- return None, None, None, None
-
- if not is_dataclass(request) or not hasattr(request, request_field_name):
- return serialize_content_type(request_field_name, SERIALIZATION_METHOD_TO_CONTENT_TYPE[serialization_method], request)
-
- request_val = getattr(request, request_field_name)
-
- request_fields: Tuple[Field, ...] = fields(request)
- request_metadata = None
-
- for field in request_fields:
- if field.name == request_field_name:
- request_metadata = field.metadata.get('request')
- break
-
- if request_metadata is None:
- raise Exception('invalid request type')
-
- return serialize_content_type(request_field_name, request_metadata.get('media_type', 'application/octet-stream'), request_val)
-
-
-def serialize_content_type(field_name: str, media_type: str, request: dataclass) -> Tuple[str, any, list[list[any]]]:
- if re.match(r'(application|text)\/.*?\+*json.*', media_type) is not None:
- return media_type, marshal_json(request), None
- if re.match(r'multipart\/.*', media_type) is not None:
- return serialize_multipart_form(media_type, request)
- if re.match(r'application\/x-www-form-urlencoded.*', media_type) is not None:
- return media_type, serialize_form_data(field_name, request), None
- if isinstance(request, (bytes, bytearray)):
- return media_type, request, None
- if isinstance(request, str):
- return media_type, request, None
-
- raise Exception(
- f"invalid request body type {type(request)} for mediaType {media_type}")
-
-
-def serialize_multipart_form(media_type: str, request: dataclass) -> Tuple[str, any, list[list[any]]]:
- form: list[list[any]] = []
- request_fields = fields(request)
-
- for field in request_fields:
- val = getattr(request, field.name)
- if val is None:
- continue
-
- field_metadata = field.metadata.get('multipart_form')
- if not field_metadata:
- continue
-
- if field_metadata.get("file") is True:
- file_fields = fields(val)
-
- file_name = ""
- field_name = ""
- content = bytes()
-
- for file_field in file_fields:
- file_metadata = file_field.metadata.get('multipart_form')
- if file_metadata is None:
- continue
-
- if file_metadata.get("content") is True:
- content = getattr(val, file_field.name)
- else:
- field_name = file_metadata.get(
- "field_name", file_field.name)
- file_name = getattr(val, file_field.name)
- if field_name == "" or file_name == "" or content == bytes():
- raise Exception('invalid multipart/form-data file')
-
- form.append([field_name, [file_name, content]])
- elif field_metadata.get("json") is True:
- to_append = [field_metadata.get("field_name", field.name), [
- None, marshal_json(val), "application/json"]]
- form.append(to_append)
- else:
- field_name = field_metadata.get(
- "field_name", field.name)
- if isinstance(val, list):
- for value in val:
- if value is None:
- continue
- form.append(
- [field_name + "[]", [None, _val_to_string(value)]])
- else:
- form.append([field_name, [None, _val_to_string(val)]])
- return media_type, None, form
-
-
-def serialize_dict(original: dict, explode: bool, field_name, existing: Optional[dict[str, list[str]]]) -> dict[
- str, list[str]]:
- if existing is None:
- existing = []
-
- if explode is True:
- for key, val in original.items():
- if key not in existing:
- existing[key] = []
- existing[key].append(val)
- else:
- temp = []
- for key, val in original.items():
- temp.append(str(key))
- temp.append(str(val))
- if field_name not in existing:
- existing[field_name] = []
- existing[field_name].append(",".join(temp))
- return existing
-
-
-def serialize_form_data(field_name: str, data: dataclass) -> dict[str, any]:
- form: dict[str, list[str]] = {}
-
- if is_dataclass(data):
- for field in fields(data):
- val = getattr(data, field.name)
- if val is None:
- continue
-
- metadata = field.metadata.get('form')
- if metadata is None:
- continue
-
- field_name = metadata.get('field_name', field.name)
-
- if metadata.get('json'):
- form[field_name] = [marshal_json(val)]
- else:
- if metadata.get('style', 'form') == 'form':
- form = form | _populate_form(
- field_name, metadata.get('explode', True), val, _get_form_field_name)
- else:
- raise Exception(
- f'Invalid form style for field {field.name}')
- elif isinstance(data, dict):
- for key, value in data.items():
- form[key] = [_val_to_string(value)]
- else:
- raise Exception(f'Invalid request body type for field {field_name}')
-
- return form
-
-
-def _get_form_field_name(obj_field: Field) -> str:
- obj_param_metadata = obj_field.metadata.get('form')
-
- if not obj_param_metadata:
- return ""
-
- return obj_param_metadata.get("field_name", obj_field.name)
-
-
-def _populate_form(field_name: str, explode: boolean, obj: any, get_field_name_func: Callable) -> dict[str, list[str]]:
- params: dict[str, list[str]] = {}
-
- if obj is None:
- return params
-
- if is_dataclass(obj):
- items = []
-
- obj_fields: Tuple[Field, ...] = fields(obj)
- for obj_field in obj_fields:
- obj_field_name = get_field_name_func(obj_field)
- if obj_field_name == '':
- continue
-
- val = getattr(obj, obj_field.name)
- if val is None:
- continue
-
- if explode:
- params[obj_field_name] = [_val_to_string(val)]
- else:
- items.append(
- f'{obj_field_name},{_val_to_string(val)}')
-
- if len(items) > 0:
- params[field_name] = [','.join(items)]
- elif isinstance(obj, dict):
- items = []
- for key, value in obj.items():
- if value is None:
- continue
-
- if explode:
- params[key] = _val_to_string(value)
- else:
- items.append(f'{key},{_val_to_string(value)}')
-
- if len(items) > 0:
- params[field_name] = [','.join(items)]
- elif isinstance(obj, list):
- items = []
-
- for value in obj:
- if value is None:
- continue
-
- if explode:
- if not field_name in params:
- params[field_name] = []
- params[field_name].append(_val_to_string(value))
- else:
- items.append(_val_to_string(value))
-
- if len(items) > 0:
- params[field_name] = [','.join([str(item) for item in items])]
- else:
- params[field_name] = [_val_to_string(obj)]
-
- return params
-
-
-def _serialize_header(explode: bool, obj: any) -> str:
- if obj is None:
- return ''
-
- if is_dataclass(obj):
- items = []
- obj_fields: Tuple[Field, ...] = fields(obj)
- for obj_field in obj_fields:
- obj_param_metadata = obj_field.metadata.get('header')
-
- if not obj_param_metadata:
- continue
-
- obj_field_name = obj_param_metadata.get(
- 'field_name', obj_field.name)
- if obj_field_name == '':
- continue
-
- val = getattr(obj, obj_field.name)
- if val is None:
- continue
-
- if explode:
- items.append(
- f'{obj_field_name}={_val_to_string(val)}')
- else:
- items.append(obj_field_name)
- items.append(_val_to_string(val))
-
- if len(items) > 0:
- return ','.join(items)
- elif isinstance(obj, dict):
- items = []
-
- for key, value in obj.items():
- if value is None:
- continue
-
- if explode:
- items.append(f'{key}={_val_to_string(value)}')
- else:
- items.append(key)
- items.append(_val_to_string(value))
-
- if len(items) > 0:
- return ','.join([str(item) for item in items])
- elif isinstance(obj, list):
- items = []
-
- for value in obj:
- if value is None:
- continue
-
- items.append(_val_to_string(value))
-
- if len(items) > 0:
- return ','.join(items)
- else:
- return f'{_val_to_string(obj)}'
-
- return ''
-
-
-def unmarshal_json(data, typ):
- unmarhsal = make_dataclass('Unmarhsal', [('res', typ)],
- bases=(DataClassJsonMixin,))
- json_dict = json.loads(data)
- out = unmarhsal.from_dict({"res": json_dict})
- return out.res
-
-
-def marshal_json(val):
- marshal = make_dataclass('Marshal', [('res', type(val))],
- bases=(DataClassJsonMixin,))
- marshaller = marshal(res=val)
- json_dict = marshaller.to_dict()
- return json.dumps(json_dict["res"])
-
-
-def match_content_type(content_type: str, pattern: str) -> boolean:
- if pattern in (content_type, "*", "*/*"):
- return True
-
- msg = Message()
- msg['content-type'] = content_type
- media_type = msg.get_content_type()
-
- if media_type == pattern:
- return True
-
- parts = media_type.split("/")
- if len(parts) == 2:
- if pattern in (f'{parts[0]}/*', f'*/{parts[1]}'):
- return True
-
- return False
-
-
-def datetimeisoformat(optional: bool):
- def isoformatoptional(val):
- if optional and val is None:
- return None
- return _val_to_string(val)
-
- return isoformatoptional
-
-
-def dateisoformat(optional: bool):
- def isoformatoptional(val):
- if optional and val is None:
- return None
- return date.isoformat(val)
-
- return isoformatoptional
-
-
-def datefromisoformat(date_str: str):
- return dateutil.parser.parse(date_str).date()
-
-
-def get_field_name(name):
- def override(_, _field_name=name):
- return _field_name
-
- return override
-
-
-def _val_to_string(val):
- if isinstance(val, bool):
- return str(val).lower()
- if isinstance(val, datetime):
- return val.isoformat().replace('+00:00', 'Z')
- if isinstance(val, Enum):
- return val.value
-
- return str(val)
-
-
-def _populate_from_globals(param_name: str, value: any, param_type: str, gbls: dict[str, dict[str, dict[str, Any]]]):
- if value is None and gbls is not None:
- if 'parameters' in gbls:
- if param_type in gbls['parameters']:
- if param_name in gbls['parameters'][param_type]:
- global_value = gbls['parameters'][param_type][param_name]
- if global_value is not None:
- value = global_value
-
- return value
diff --git a/submission/src/epilot_submission/__init__.py b/submission/src/epilot_submission/__init__.py
new file mode 100644
index 000000000..dd02e42e4
--- /dev/null
+++ b/submission/src/epilot_submission/__init__.py
@@ -0,0 +1,18 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from ._version import (
+ __title__,
+ __version__,
+ __openapi_doc_version__,
+ __gen_version__,
+ __user_agent__,
+)
+from .sdk import *
+from .sdkconfiguration import *
+from .models import *
+
+
+VERSION: str = __version__
+OPENAPI_DOC_VERSION = __openapi_doc_version__
+SPEAKEASY_GENERATOR_VERSION = __gen_version__
+USER_AGENT = __user_agent__
diff --git a/submission/src/epilot_submission/_hooks/__init__.py b/submission/src/epilot_submission/_hooks/__init__.py
new file mode 100644
index 000000000..2ee66cdd5
--- /dev/null
+++ b/submission/src/epilot_submission/_hooks/__init__.py
@@ -0,0 +1,5 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from .sdkhooks import *
+from .types import *
+from .registration import *
diff --git a/submission/src/epilot_submission/_hooks/registration.py b/submission/src/epilot_submission/_hooks/registration.py
new file mode 100644
index 000000000..1db6a5293
--- /dev/null
+++ b/submission/src/epilot_submission/_hooks/registration.py
@@ -0,0 +1,13 @@
+from .types import Hooks
+
+
+# This file is only ever generated once on the first generation and then is free to be modified.
+# Any hooks you wish to add should be registered in the init_hooks function. Feel free to define them
+# in this file or in separate files in the hooks folder.
+
+
+def init_hooks(hooks: Hooks):
+ # pylint: disable=unused-argument
+ """Add hooks by calling hooks.register{sdk_init/before_request/after_success/after_error}Hook
+ with an instance of a hook that implements that specific Hook interface
+ Hooks are registered per SDK instance, and are valid for the lifetime of the SDK instance"""
diff --git a/submission/src/epilot_submission/_hooks/sdkhooks.py b/submission/src/epilot_submission/_hooks/sdkhooks.py
new file mode 100644
index 000000000..ca34f300f
--- /dev/null
+++ b/submission/src/epilot_submission/_hooks/sdkhooks.py
@@ -0,0 +1,76 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+import httpx
+from .types import (
+ SDKInitHook,
+ BeforeRequestContext,
+ BeforeRequestHook,
+ AfterSuccessContext,
+ AfterSuccessHook,
+ AfterErrorContext,
+ AfterErrorHook,
+ Hooks,
+)
+from .registration import init_hooks
+from typing import List, Optional, Tuple
+from epilot_submission.httpclient import HttpClient
+
+
+class SDKHooks(Hooks):
+ def __init__(self) -> None:
+ self.sdk_init_hooks: List[SDKInitHook] = []
+ self.before_request_hooks: List[BeforeRequestHook] = []
+ self.after_success_hooks: List[AfterSuccessHook] = []
+ self.after_error_hooks: List[AfterErrorHook] = []
+ init_hooks(self)
+
+ def register_sdk_init_hook(self, hook: SDKInitHook) -> None:
+ self.sdk_init_hooks.append(hook)
+
+ def register_before_request_hook(self, hook: BeforeRequestHook) -> None:
+ self.before_request_hooks.append(hook)
+
+ def register_after_success_hook(self, hook: AfterSuccessHook) -> None:
+ self.after_success_hooks.append(hook)
+
+ def register_after_error_hook(self, hook: AfterErrorHook) -> None:
+ self.after_error_hooks.append(hook)
+
+ def sdk_init(self, base_url: str, client: HttpClient) -> Tuple[str, HttpClient]:
+ for hook in self.sdk_init_hooks:
+ base_url, client = hook.sdk_init(base_url, client)
+ return base_url, client
+
+ def before_request(
+ self, hook_ctx: BeforeRequestContext, request: httpx.Request
+ ) -> httpx.Request:
+ for hook in self.before_request_hooks:
+ out = hook.before_request(hook_ctx, request)
+ if isinstance(out, Exception):
+ raise out
+ request = out
+
+ return request
+
+ def after_success(
+ self, hook_ctx: AfterSuccessContext, response: httpx.Response
+ ) -> httpx.Response:
+ for hook in self.after_success_hooks:
+ out = hook.after_success(hook_ctx, response)
+ if isinstance(out, Exception):
+ raise out
+ response = out
+ return response
+
+ def after_error(
+ self,
+ hook_ctx: AfterErrorContext,
+ response: Optional[httpx.Response],
+ error: Optional[Exception],
+ ) -> Tuple[Optional[httpx.Response], Optional[Exception]]:
+ for hook in self.after_error_hooks:
+ result = hook.after_error(hook_ctx, response, error)
+ if isinstance(result, Exception):
+ raise result
+ response, error = result
+ return response, error
diff --git a/submission/src/epilot_submission/_hooks/types.py b/submission/src/epilot_submission/_hooks/types.py
new file mode 100644
index 000000000..412771b3d
--- /dev/null
+++ b/submission/src/epilot_submission/_hooks/types.py
@@ -0,0 +1,94 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from abc import ABC, abstractmethod
+from epilot_submission.httpclient import HttpClient
+import httpx
+from typing import Any, Callable, List, Optional, Tuple, Union
+
+
+class HookContext:
+ operation_id: str
+ oauth2_scopes: Optional[List[str]] = None
+ security_source: Optional[Union[Any, Callable[[], Any]]] = None
+
+ def __init__(
+ self,
+ operation_id: str,
+ oauth2_scopes: Optional[List[str]],
+ security_source: Optional[Union[Any, Callable[[], Any]]],
+ ):
+ self.operation_id = operation_id
+ self.oauth2_scopes = oauth2_scopes
+ self.security_source = security_source
+
+
+class BeforeRequestContext(HookContext):
+ def __init__(self, hook_ctx: HookContext):
+ super().__init__(
+ hook_ctx.operation_id, hook_ctx.oauth2_scopes, hook_ctx.security_source
+ )
+
+
+class AfterSuccessContext(HookContext):
+ def __init__(self, hook_ctx: HookContext):
+ super().__init__(
+ hook_ctx.operation_id, hook_ctx.oauth2_scopes, hook_ctx.security_source
+ )
+
+
+class AfterErrorContext(HookContext):
+ def __init__(self, hook_ctx: HookContext):
+ super().__init__(
+ hook_ctx.operation_id, hook_ctx.oauth2_scopes, hook_ctx.security_source
+ )
+
+
+class SDKInitHook(ABC):
+ @abstractmethod
+ def sdk_init(self, base_url: str, client: HttpClient) -> Tuple[str, HttpClient]:
+ pass
+
+
+class BeforeRequestHook(ABC):
+ @abstractmethod
+ def before_request(
+ self, hook_ctx: BeforeRequestContext, request: httpx.Request
+ ) -> Union[httpx.Request, Exception]:
+ pass
+
+
+class AfterSuccessHook(ABC):
+ @abstractmethod
+ def after_success(
+ self, hook_ctx: AfterSuccessContext, response: httpx.Response
+ ) -> Union[httpx.Response, Exception]:
+ pass
+
+
+class AfterErrorHook(ABC):
+ @abstractmethod
+ def after_error(
+ self,
+ hook_ctx: AfterErrorContext,
+ response: Optional[httpx.Response],
+ error: Optional[Exception],
+ ) -> Union[Tuple[Optional[httpx.Response], Optional[Exception]], Exception]:
+ pass
+
+
+class Hooks(ABC):
+ @abstractmethod
+ def register_sdk_init_hook(self, hook: SDKInitHook):
+ pass
+
+ @abstractmethod
+ def register_before_request_hook(self, hook: BeforeRequestHook):
+ pass
+
+ @abstractmethod
+ def register_after_success_hook(self, hook: AfterSuccessHook):
+ pass
+
+ @abstractmethod
+ def register_after_error_hook(self, hook: AfterErrorHook):
+ pass
diff --git a/submission/src/epilot_submission/_version.py b/submission/src/epilot_submission/_version.py
new file mode 100644
index 000000000..94def56c6
--- /dev/null
+++ b/submission/src/epilot_submission/_version.py
@@ -0,0 +1,15 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+import importlib.metadata
+
+__title__: str = "epilot-submission"
+__version__: str = "1.13.8"
+__openapi_doc_version__: str = "1.0.0"
+__gen_version__: str = "2.497.8"
+__user_agent__: str = "speakeasy-sdk/python 1.13.8 2.497.8 1.0.0 epilot-submission"
+
+try:
+ if __package__ is not None:
+ __version__ = importlib.metadata.version(__package__)
+except importlib.metadata.PackageNotFoundError:
+ pass
diff --git a/submission/src/epilot_submission/basesdk.py b/submission/src/epilot_submission/basesdk.py
new file mode 100644
index 000000000..11606499f
--- /dev/null
+++ b/submission/src/epilot_submission/basesdk.py
@@ -0,0 +1,354 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from .sdkconfiguration import SDKConfiguration
+from epilot_submission import models, utils
+from epilot_submission._hooks import (
+ AfterErrorContext,
+ AfterSuccessContext,
+ BeforeRequestContext,
+)
+from epilot_submission.utils import RetryConfig, SerializedRequestBody, get_body_content
+import httpx
+from typing import Callable, List, Mapping, Optional, Tuple
+from urllib.parse import parse_qs, urlparse
+
+
+class BaseSDK:
+ sdk_configuration: SDKConfiguration
+
+ def __init__(self, sdk_config: SDKConfiguration) -> None:
+ self.sdk_configuration = sdk_config
+
+ def _get_url(self, base_url, url_variables):
+ sdk_url, sdk_variables = self.sdk_configuration.get_server_details()
+
+ if base_url is None:
+ base_url = sdk_url
+
+ if url_variables is None:
+ url_variables = sdk_variables
+
+ return utils.template_url(base_url, url_variables)
+
+ def _build_request_async(
+ self,
+ method,
+ path,
+ base_url,
+ url_variables,
+ request,
+ request_body_required,
+ request_has_path_params,
+ request_has_query_params,
+ user_agent_header,
+ accept_header_value,
+ _globals=None,
+ security=None,
+ timeout_ms: Optional[int] = None,
+ get_serialized_body: Optional[
+ Callable[[], Optional[SerializedRequestBody]]
+ ] = None,
+ url_override: Optional[str] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ) -> httpx.Request:
+ client = self.sdk_configuration.async_client
+ return self._build_request_with_client(
+ client,
+ method,
+ path,
+ base_url,
+ url_variables,
+ request,
+ request_body_required,
+ request_has_path_params,
+ request_has_query_params,
+ user_agent_header,
+ accept_header_value,
+ _globals,
+ security,
+ timeout_ms,
+ get_serialized_body,
+ url_override,
+ http_headers,
+ )
+
+ def _build_request(
+ self,
+ method,
+ path,
+ base_url,
+ url_variables,
+ request,
+ request_body_required,
+ request_has_path_params,
+ request_has_query_params,
+ user_agent_header,
+ accept_header_value,
+ _globals=None,
+ security=None,
+ timeout_ms: Optional[int] = None,
+ get_serialized_body: Optional[
+ Callable[[], Optional[SerializedRequestBody]]
+ ] = None,
+ url_override: Optional[str] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ) -> httpx.Request:
+ client = self.sdk_configuration.client
+ return self._build_request_with_client(
+ client,
+ method,
+ path,
+ base_url,
+ url_variables,
+ request,
+ request_body_required,
+ request_has_path_params,
+ request_has_query_params,
+ user_agent_header,
+ accept_header_value,
+ _globals,
+ security,
+ timeout_ms,
+ get_serialized_body,
+ url_override,
+ http_headers,
+ )
+
+ def _build_request_with_client(
+ self,
+ client,
+ method,
+ path,
+ base_url,
+ url_variables,
+ request,
+ request_body_required,
+ request_has_path_params,
+ request_has_query_params,
+ user_agent_header,
+ accept_header_value,
+ _globals=None,
+ security=None,
+ timeout_ms: Optional[int] = None,
+ get_serialized_body: Optional[
+ Callable[[], Optional[SerializedRequestBody]]
+ ] = None,
+ url_override: Optional[str] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ) -> httpx.Request:
+ query_params = {}
+
+ url = url_override
+ if url is None:
+ url = utils.generate_url(
+ self._get_url(base_url, url_variables),
+ path,
+ request if request_has_path_params else None,
+ _globals if request_has_path_params else None,
+ )
+
+ query_params = utils.get_query_params(
+ request if request_has_query_params else None,
+ _globals if request_has_query_params else None,
+ )
+ else:
+ # Pick up the query parameter from the override so they can be
+ # preserved when building the request later on (necessary as of
+ # httpx 0.28).
+ parsed_override = urlparse(str(url_override))
+ query_params = parse_qs(parsed_override.query, keep_blank_values=True)
+
+ headers = utils.get_headers(request, _globals)
+ headers["Accept"] = accept_header_value
+ headers[user_agent_header] = self.sdk_configuration.user_agent
+
+ if security is not None:
+ if callable(security):
+ security = security()
+
+ if security is not None:
+ security_headers, security_query_params = utils.get_security(security)
+ headers = {**headers, **security_headers}
+ query_params = {**query_params, **security_query_params}
+
+ serialized_request_body = SerializedRequestBody()
+ if get_serialized_body is not None:
+ rb = get_serialized_body()
+ if request_body_required and rb is None:
+ raise ValueError("request body is required")
+
+ if rb is not None:
+ serialized_request_body = rb
+
+ if (
+ serialized_request_body.media_type is not None
+ and serialized_request_body.media_type
+ not in (
+ "multipart/form-data",
+ "multipart/mixed",
+ )
+ ):
+ headers["content-type"] = serialized_request_body.media_type
+
+ if http_headers is not None:
+ for header, value in http_headers.items():
+ headers[header] = value
+
+ timeout = timeout_ms / 1000 if timeout_ms is not None else None
+
+ return client.build_request(
+ method,
+ url,
+ params=query_params,
+ content=serialized_request_body.content,
+ data=serialized_request_body.data,
+ files=serialized_request_body.files,
+ headers=headers,
+ timeout=timeout,
+ )
+
+ def do_request(
+ self,
+ hook_ctx,
+ request,
+ error_status_codes,
+ stream=False,
+ retry_config: Optional[Tuple[RetryConfig, List[str]]] = None,
+ ) -> httpx.Response:
+ client = self.sdk_configuration.client
+ logger = self.sdk_configuration.debug_logger
+
+ def do():
+ http_res = None
+ try:
+ req = self.sdk_configuration.get_hooks().before_request(
+ BeforeRequestContext(hook_ctx), request
+ )
+ logger.debug(
+ "Request:\nMethod: %s\nURL: %s\nHeaders: %s\nBody: %s",
+ req.method,
+ req.url,
+ req.headers,
+ get_body_content(req),
+ )
+ http_res = client.send(req, stream=stream)
+ except Exception as e:
+ _, e = self.sdk_configuration.get_hooks().after_error(
+ AfterErrorContext(hook_ctx), None, e
+ )
+ if e is not None:
+ logger.debug("Request Exception", exc_info=True)
+ raise e
+
+ if http_res is None:
+ logger.debug("Raising no response SDK error")
+ raise models.SDKError("No response received")
+
+ logger.debug(
+ "Response:\nStatus Code: %s\nURL: %s\nHeaders: %s\nBody: %s",
+ http_res.status_code,
+ http_res.url,
+ http_res.headers,
+ "" if stream else http_res.text,
+ )
+
+ if utils.match_status_codes(error_status_codes, http_res.status_code):
+ result, err = self.sdk_configuration.get_hooks().after_error(
+ AfterErrorContext(hook_ctx), http_res, None
+ )
+ if err is not None:
+ logger.debug("Request Exception", exc_info=True)
+ raise err
+ if result is not None:
+ http_res = result
+ else:
+ logger.debug("Raising unexpected SDK error")
+ raise models.SDKError("Unexpected error occurred")
+
+ return http_res
+
+ if retry_config is not None:
+ http_res = utils.retry(do, utils.Retries(retry_config[0], retry_config[1]))
+ else:
+ http_res = do()
+
+ if not utils.match_status_codes(error_status_codes, http_res.status_code):
+ http_res = self.sdk_configuration.get_hooks().after_success(
+ AfterSuccessContext(hook_ctx), http_res
+ )
+
+ return http_res
+
+ async def do_request_async(
+ self,
+ hook_ctx,
+ request,
+ error_status_codes,
+ stream=False,
+ retry_config: Optional[Tuple[RetryConfig, List[str]]] = None,
+ ) -> httpx.Response:
+ client = self.sdk_configuration.async_client
+ logger = self.sdk_configuration.debug_logger
+
+ async def do():
+ http_res = None
+ try:
+ req = self.sdk_configuration.get_hooks().before_request(
+ BeforeRequestContext(hook_ctx), request
+ )
+ logger.debug(
+ "Request:\nMethod: %s\nURL: %s\nHeaders: %s\nBody: %s",
+ req.method,
+ req.url,
+ req.headers,
+ get_body_content(req),
+ )
+ http_res = await client.send(req, stream=stream)
+ except Exception as e:
+ _, e = self.sdk_configuration.get_hooks().after_error(
+ AfterErrorContext(hook_ctx), None, e
+ )
+ if e is not None:
+ logger.debug("Request Exception", exc_info=True)
+ raise e
+
+ if http_res is None:
+ logger.debug("Raising no response SDK error")
+ raise models.SDKError("No response received")
+
+ logger.debug(
+ "Response:\nStatus Code: %s\nURL: %s\nHeaders: %s\nBody: %s",
+ http_res.status_code,
+ http_res.url,
+ http_res.headers,
+ "" if stream else http_res.text,
+ )
+
+ if utils.match_status_codes(error_status_codes, http_res.status_code):
+ result, err = self.sdk_configuration.get_hooks().after_error(
+ AfterErrorContext(hook_ctx), http_res, None
+ )
+ if err is not None:
+ logger.debug("Request Exception", exc_info=True)
+ raise err
+ if result is not None:
+ http_res = result
+ else:
+ logger.debug("Raising unexpected SDK error")
+ raise models.SDKError("Unexpected error occurred")
+
+ return http_res
+
+ if retry_config is not None:
+ http_res = await utils.retry_async(
+ do, utils.Retries(retry_config[0], retry_config[1])
+ )
+ else:
+ http_res = await do()
+
+ if not utils.match_status_codes(error_status_codes, http_res.status_code):
+ http_res = self.sdk_configuration.get_hooks().after_success(
+ AfterSuccessContext(hook_ctx), http_res
+ )
+
+ return http_res
diff --git a/submission/src/epilot_submission/httpclient.py b/submission/src/epilot_submission/httpclient.py
new file mode 100644
index 000000000..9dc43cb0a
--- /dev/null
+++ b/submission/src/epilot_submission/httpclient.py
@@ -0,0 +1,134 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+# pyright: reportReturnType = false
+import asyncio
+from concurrent.futures import ThreadPoolExecutor
+from typing_extensions import Protocol, runtime_checkable
+import httpx
+from typing import Any, Optional, Union
+
+
+@runtime_checkable
+class HttpClient(Protocol):
+ def send(
+ self,
+ request: httpx.Request,
+ *,
+ stream: bool = False,
+ auth: Union[
+ httpx._types.AuthTypes, httpx._client.UseClientDefault, None
+ ] = httpx.USE_CLIENT_DEFAULT,
+ follow_redirects: Union[
+ bool, httpx._client.UseClientDefault
+ ] = httpx.USE_CLIENT_DEFAULT,
+ ) -> httpx.Response:
+ pass
+
+ def build_request(
+ self,
+ method: str,
+ url: httpx._types.URLTypes,
+ *,
+ content: Optional[httpx._types.RequestContent] = None,
+ data: Optional[httpx._types.RequestData] = None,
+ files: Optional[httpx._types.RequestFiles] = None,
+ json: Optional[Any] = None,
+ params: Optional[httpx._types.QueryParamTypes] = None,
+ headers: Optional[httpx._types.HeaderTypes] = None,
+ cookies: Optional[httpx._types.CookieTypes] = None,
+ timeout: Union[
+ httpx._types.TimeoutTypes, httpx._client.UseClientDefault
+ ] = httpx.USE_CLIENT_DEFAULT,
+ extensions: Optional[httpx._types.RequestExtensions] = None,
+ ) -> httpx.Request:
+ pass
+
+ def close(self) -> None:
+ pass
+
+
+@runtime_checkable
+class AsyncHttpClient(Protocol):
+ async def send(
+ self,
+ request: httpx.Request,
+ *,
+ stream: bool = False,
+ auth: Union[
+ httpx._types.AuthTypes, httpx._client.UseClientDefault, None
+ ] = httpx.USE_CLIENT_DEFAULT,
+ follow_redirects: Union[
+ bool, httpx._client.UseClientDefault
+ ] = httpx.USE_CLIENT_DEFAULT,
+ ) -> httpx.Response:
+ pass
+
+ def build_request(
+ self,
+ method: str,
+ url: httpx._types.URLTypes,
+ *,
+ content: Optional[httpx._types.RequestContent] = None,
+ data: Optional[httpx._types.RequestData] = None,
+ files: Optional[httpx._types.RequestFiles] = None,
+ json: Optional[Any] = None,
+ params: Optional[httpx._types.QueryParamTypes] = None,
+ headers: Optional[httpx._types.HeaderTypes] = None,
+ cookies: Optional[httpx._types.CookieTypes] = None,
+ timeout: Union[
+ httpx._types.TimeoutTypes, httpx._client.UseClientDefault
+ ] = httpx.USE_CLIENT_DEFAULT,
+ extensions: Optional[httpx._types.RequestExtensions] = None,
+ ) -> httpx.Request:
+ pass
+
+ async def aclose(self) -> None:
+ pass
+
+
+class ClientOwner(Protocol):
+ client: Union[HttpClient, None]
+ async_client: Union[AsyncHttpClient, None]
+
+
+def close_clients(
+ owner: ClientOwner,
+ sync_client: Union[HttpClient, None],
+ async_client: Union[AsyncHttpClient, None],
+) -> None:
+ """
+ A finalizer function that is meant to be used with weakref.finalize to close
+ httpx clients used by an SDK so that underlying resources can be garbage
+ collected.
+ """
+
+ # Unset the client/async_client properties so there are no more references
+ # to them from the owning SDK instance and they can be reaped.
+ owner.client = None
+ owner.async_client = None
+
+ if sync_client is not None:
+ try:
+ sync_client.close()
+ except Exception:
+ pass
+
+ if async_client is not None:
+ is_async = False
+ try:
+ asyncio.get_running_loop()
+ is_async = True
+ except RuntimeError:
+ pass
+
+ try:
+ # If this function is called in an async loop then start another
+ # loop in a separate thread to close the async http client.
+ if is_async:
+ with ThreadPoolExecutor(max_workers=1) as executor:
+ future = executor.submit(asyncio.run, async_client.aclose())
+ future.result()
+ else:
+ asyncio.run(async_client.aclose())
+ except Exception:
+ pass
diff --git a/submission/src/epilot_submission/models/__init__.py b/submission/src/epilot_submission/models/__init__.py
new file mode 100644
index 000000000..530c6343b
--- /dev/null
+++ b/submission/src/epilot_submission/models/__init__.py
@@ -0,0 +1,32 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from .optin import OptIn, OptInTypedDict
+from .s3reference import S3Reference, S3ReferenceTypedDict
+from .sdkerror import SDKError
+from .security import Security, SecurityTypedDict
+from .submissionentity import (
+ Files,
+ FilesTypedDict,
+ Schema,
+ SubmissionEntity,
+ SubmissionEntityTypedDict,
+)
+from .submissionpayload import SubmissionPayload, SubmissionPayloadTypedDict
+
+
+__all__ = [
+ "Files",
+ "FilesTypedDict",
+ "OptIn",
+ "OptInTypedDict",
+ "S3Reference",
+ "S3ReferenceTypedDict",
+ "SDKError",
+ "Schema",
+ "Security",
+ "SecurityTypedDict",
+ "SubmissionEntity",
+ "SubmissionEntityTypedDict",
+ "SubmissionPayload",
+ "SubmissionPayloadTypedDict",
+]
diff --git a/submission/src/epilot_submission/models/optin.py b/submission/src/epilot_submission/models/optin.py
new file mode 100644
index 000000000..2a044711e
--- /dev/null
+++ b/submission/src/epilot_submission/models/optin.py
@@ -0,0 +1,22 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from __future__ import annotations
+from epilot_submission.types import BaseModel
+from typing import Any, Dict, Optional
+from typing_extensions import NotRequired, TypedDict
+
+
+class OptInTypedDict(TypedDict):
+ identifier: NotRequired[str]
+ r"""Consent identifier"""
+ meta: NotRequired[Dict[str, Any]]
+ topic: NotRequired[str]
+
+
+class OptIn(BaseModel):
+ identifier: Optional[str] = None
+ r"""Consent identifier"""
+
+ meta: Optional[Dict[str, Any]] = None
+
+ topic: Optional[str] = None
diff --git a/submission/src/epilot_submission/models/s3reference.py b/submission/src/epilot_submission/models/s3reference.py
new file mode 100644
index 000000000..4f6ae27b5
--- /dev/null
+++ b/submission/src/epilot_submission/models/s3reference.py
@@ -0,0 +1,20 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from __future__ import annotations
+from epilot_submission.types import BaseModel
+from typing_extensions import TypedDict
+
+
+class S3ReferenceTypedDict(TypedDict):
+ r"""S3 Reference from File API"""
+
+ bucket: str
+ key: str
+
+
+class S3Reference(BaseModel):
+ r"""S3 Reference from File API"""
+
+ bucket: str
+
+ key: str
diff --git a/submission/src/epilot_submission/models/sdkerror.py b/submission/src/epilot_submission/models/sdkerror.py
new file mode 100644
index 000000000..03216cbf5
--- /dev/null
+++ b/submission/src/epilot_submission/models/sdkerror.py
@@ -0,0 +1,22 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from dataclasses import dataclass
+from typing import Optional
+import httpx
+
+
+@dataclass
+class SDKError(Exception):
+ """Represents an error returned by the API."""
+
+ message: str
+ status_code: int = -1
+ body: str = ""
+ raw_response: Optional[httpx.Response] = None
+
+ def __str__(self):
+ body = ""
+ if len(self.body) > 0:
+ body = f"\n{self.body}"
+
+ return f"{self.message}: Status {self.status_code}{body}"
diff --git a/submission/src/epilot_submission/models/security.py b/submission/src/epilot_submission/models/security.py
new file mode 100644
index 000000000..e7c5cd78a
--- /dev/null
+++ b/submission/src/epilot_submission/models/security.py
@@ -0,0 +1,25 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from __future__ import annotations
+from epilot_submission.types import BaseModel
+from epilot_submission.utils import FieldMetadata, SecurityMetadata
+from typing import Optional
+from typing_extensions import Annotated, NotRequired, TypedDict
+
+
+class SecurityTypedDict(TypedDict):
+ epilot_auth: NotRequired[str]
+
+
+class Security(BaseModel):
+ epilot_auth: Annotated[
+ Optional[str],
+ FieldMetadata(
+ security=SecurityMetadata(
+ scheme=True,
+ scheme_type="http",
+ sub_type="bearer",
+ field_name="Authorization",
+ )
+ ),
+ ] = None
diff --git a/submission/src/epilot_submission/models/submissionentity.py b/submission/src/epilot_submission/models/submissionentity.py
new file mode 100644
index 000000000..4f2b71261
--- /dev/null
+++ b/submission/src/epilot_submission/models/submissionentity.py
@@ -0,0 +1,91 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from __future__ import annotations
+from .s3reference import S3Reference, S3ReferenceTypedDict
+from enum import Enum
+from epilot_submission.types import BaseModel
+import pydantic
+from pydantic import ConfigDict
+from typing import Any, Dict, List, Optional
+from typing_extensions import Annotated, NotRequired, TypedDict
+
+
+class Schema(str, Enum):
+ SUBMISSION = "submission"
+
+
+class FilesTypedDict(TypedDict):
+ s3ref: S3ReferenceTypedDict
+ r"""S3 Reference from File API"""
+ tags: NotRequired[List[str]]
+ r"""List of tags for File entities"""
+ filename: NotRequired[str]
+ r"""Override the file name"""
+ relation_tags: NotRequired[List[str]]
+ r"""List of relation labels for File attachments"""
+
+
+class Files(BaseModel):
+ model_config = ConfigDict(
+ populate_by_name=True, arbitrary_types_allowed=True, extra="allow"
+ )
+ __pydantic_extra__: Dict[str, Any] = pydantic.Field(init=False)
+
+ s3ref: S3Reference
+ r"""S3 Reference from File API"""
+
+ tags: Annotated[Optional[List[str]], pydantic.Field(alias="_tags")] = None
+ r"""List of tags for File entities"""
+
+ filename: Optional[str] = None
+ r"""Override the file name"""
+
+ relation_tags: Optional[List[str]] = None
+ r"""List of relation labels for File attachments"""
+
+ @property
+ def additional_properties(self):
+ return self.__pydantic_extra__
+
+ @additional_properties.setter
+ def additional_properties(self, value):
+ self.__pydantic_extra__ = value # pyright: ignore[reportIncompatibleVariableOverride]
+
+
+class SubmissionEntityTypedDict(TypedDict):
+ r"""The submission entity to create"""
+
+ schema_: Schema
+ description: NotRequired[str]
+ r"""Readable description of the submission. Will be used as the title if passed"""
+ files: NotRequired[List[FilesTypedDict]]
+ r"""Files to attach to Submission Entity as a relation (s3refs from File API)
+
+ """
+
+
+class SubmissionEntity(BaseModel):
+ r"""The submission entity to create"""
+
+ model_config = ConfigDict(
+ populate_by_name=True, arbitrary_types_allowed=True, extra="allow"
+ )
+ __pydantic_extra__: Dict[str, Any] = pydantic.Field(init=False)
+
+ schema_: Annotated[Schema, pydantic.Field(alias="_schema")]
+
+ description: Optional[str] = None
+ r"""Readable description of the submission. Will be used as the title if passed"""
+
+ files: Optional[List[Files]] = None
+ r"""Files to attach to Submission Entity as a relation (s3refs from File API)
+
+ """
+
+ @property
+ def additional_properties(self):
+ return self.__pydantic_extra__
+
+ @additional_properties.setter
+ def additional_properties(self, value):
+ self.__pydantic_extra__ = value # pyright: ignore[reportIncompatibleVariableOverride]
diff --git a/submission/src/epilot_submission/models/submissionpayload.py b/submission/src/epilot_submission/models/submissionpayload.py
new file mode 100644
index 000000000..ed97892ce
--- /dev/null
+++ b/submission/src/epilot_submission/models/submissionpayload.py
@@ -0,0 +1,67 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from __future__ import annotations
+from .optin import OptIn, OptInTypedDict
+from .submissionentity import SubmissionEntity, SubmissionEntityTypedDict
+from epilot_submission.types import BaseModel
+import pydantic
+from typing import List, Optional
+from typing_extensions import Annotated, NotRequired, TypedDict
+
+
+class SubmissionPayloadTypedDict(TypedDict):
+ r"""Holds content and meta information"""
+
+ entities: List[SubmissionEntityTypedDict]
+ r"""Entities to create from submission
+
+ """
+ organization_id: str
+ r"""organization id"""
+ source_id: str
+ r"""identifier for source e.g. journey ID or frontend ID"""
+ source_type: str
+ r"""type of source, e.g. journey or frontend"""
+ ivy_opportunity_ids: NotRequired[List[str]]
+ r"""Related Ivy Opportunity Ids"""
+ journey_submit_id: NotRequired[str]
+ r"""journey submit uid"""
+ opt_ins: NotRequired[List[OptInTypedDict]]
+ r"""Opt-ins to create from submission
+
+ """
+
+
+class SubmissionPayload(BaseModel):
+ r"""Holds content and meta information"""
+
+ entities: List[SubmissionEntity]
+ r"""Entities to create from submission
+
+ """
+
+ organization_id: str
+ r"""organization id"""
+
+ source_id: str
+ r"""identifier for source e.g. journey ID or frontend ID"""
+
+ source_type: str
+ r"""type of source, e.g. journey or frontend"""
+
+ ivy_opportunity_ids: Annotated[
+ Optional[List[str]],
+ pydantic.Field(
+ deprecated="warning: ** DEPRECATED ** - This will be removed in a future release, please migrate away from it as soon as possible.",
+ alias="_ivy_opportunity_ids",
+ ),
+ ] = None
+ r"""Related Ivy Opportunity Ids"""
+
+ journey_submit_id: Optional[str] = None
+ r"""journey submit uid"""
+
+ opt_ins: Optional[List[OptIn]] = None
+ r"""Opt-ins to create from submission
+
+ """
diff --git a/submission/src/epilot_submission/py.typed b/submission/src/epilot_submission/py.typed
new file mode 100644
index 000000000..3e38f1a92
--- /dev/null
+++ b/submission/src/epilot_submission/py.typed
@@ -0,0 +1 @@
+# Marker file for PEP 561. The package enables type hints.
diff --git a/submission/src/epilot_submission/sdk.py b/submission/src/epilot_submission/sdk.py
new file mode 100644
index 000000000..0bede0666
--- /dev/null
+++ b/submission/src/epilot_submission/sdk.py
@@ -0,0 +1,125 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from .basesdk import BaseSDK
+from .httpclient import AsyncHttpClient, ClientOwner, HttpClient, close_clients
+from .sdkconfiguration import SDKConfiguration
+from .utils.logger import Logger, get_default_logger
+from .utils.retries import RetryConfig
+from epilot_submission import models, utils
+from epilot_submission._hooks import SDKHooks
+from epilot_submission.submissions import Submissions
+from epilot_submission.types import OptionalNullable, UNSET
+import httpx
+from typing import Any, Callable, Dict, Optional, Union, cast
+import weakref
+
+
+class Epilot(BaseSDK):
+ r"""Submission API: Use this API to handle submissions entities from external sources e.g. journeys and frontends"""
+
+ submissions: Submissions
+ r"""Journey Submission"""
+
+ def __init__(
+ self,
+ epilot_auth: Optional[Union[Optional[str], Callable[[], Optional[str]]]] = None,
+ server_idx: Optional[int] = None,
+ server_url: Optional[str] = None,
+ url_params: Optional[Dict[str, str]] = None,
+ client: Optional[HttpClient] = None,
+ async_client: Optional[AsyncHttpClient] = None,
+ retry_config: OptionalNullable[RetryConfig] = UNSET,
+ timeout_ms: Optional[int] = None,
+ debug_logger: Optional[Logger] = None,
+ ) -> None:
+ r"""Instantiates the SDK configuring it with the provided parameters.
+
+ :param epilot_auth: The epilot_auth required for authentication
+ :param server_idx: The index of the server to use for all methods
+ :param server_url: The server URL to use for all methods
+ :param url_params: Parameters to optionally template the server URL with
+ :param client: The HTTP client to use for all synchronous methods
+ :param async_client: The Async HTTP client to use for all asynchronous methods
+ :param retry_config: The retry configuration to use for all supported methods
+ :param timeout_ms: Optional request timeout applied to each operation in milliseconds
+ """
+ if client is None:
+ client = httpx.Client()
+
+ assert issubclass(
+ type(client), HttpClient
+ ), "The provided client must implement the HttpClient protocol."
+
+ if async_client is None:
+ async_client = httpx.AsyncClient()
+
+ if debug_logger is None:
+ debug_logger = get_default_logger()
+
+ assert issubclass(
+ type(async_client), AsyncHttpClient
+ ), "The provided async_client must implement the AsyncHttpClient protocol."
+
+ security: Any = None
+ if callable(epilot_auth):
+ # pylint: disable=unnecessary-lambda-assignment
+ security = lambda: models.Security(epilot_auth=epilot_auth())
+ else:
+ security = models.Security(epilot_auth=epilot_auth)
+
+ if server_url is not None:
+ if url_params is not None:
+ server_url = utils.template_url(server_url, url_params)
+
+ BaseSDK.__init__(
+ self,
+ SDKConfiguration(
+ client=client,
+ async_client=async_client,
+ security=security,
+ server_url=server_url,
+ server_idx=server_idx,
+ retry_config=retry_config,
+ timeout_ms=timeout_ms,
+ debug_logger=debug_logger,
+ ),
+ )
+
+ hooks = SDKHooks()
+
+ current_server_url, *_ = self.sdk_configuration.get_server_details()
+ server_url, self.sdk_configuration.client = hooks.sdk_init(
+ current_server_url, self.sdk_configuration.client
+ )
+ if current_server_url != server_url:
+ self.sdk_configuration.server_url = server_url
+
+ # pylint: disable=protected-access
+ self.sdk_configuration.__dict__["_hooks"] = hooks
+
+ weakref.finalize(
+ self,
+ close_clients,
+ cast(ClientOwner, self.sdk_configuration),
+ self.sdk_configuration.client,
+ self.sdk_configuration.async_client,
+ )
+
+ self._init_sdks()
+
+ def _init_sdks(self):
+ self.submissions = Submissions(self.sdk_configuration)
+
+ def __enter__(self):
+ return self
+
+ async def __aenter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if self.sdk_configuration.client is not None:
+ self.sdk_configuration.client.close()
+
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
+ if self.sdk_configuration.async_client is not None:
+ await self.sdk_configuration.async_client.aclose()
diff --git a/submission/src/epilot_submission/sdkconfiguration.py b/submission/src/epilot_submission/sdkconfiguration.py
new file mode 100644
index 000000000..fe83b5f76
--- /dev/null
+++ b/submission/src/epilot_submission/sdkconfiguration.py
@@ -0,0 +1,53 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from ._hooks import SDKHooks
+from ._version import (
+ __gen_version__,
+ __openapi_doc_version__,
+ __user_agent__,
+ __version__,
+)
+from .httpclient import AsyncHttpClient, HttpClient
+from .utils import Logger, RetryConfig, remove_suffix
+from dataclasses import dataclass
+from epilot_submission import models
+from epilot_submission.types import OptionalNullable, UNSET
+from pydantic import Field
+from typing import Callable, Dict, Optional, Tuple, Union
+
+
+SERVERS = [
+ "https://submission.sls.epilot.io",
+]
+"""Contains the list of servers available to the SDK"""
+
+
+@dataclass
+class SDKConfiguration:
+ client: HttpClient
+ async_client: AsyncHttpClient
+ debug_logger: Logger
+ security: Optional[Union[models.Security, Callable[[], models.Security]]] = None
+ server_url: Optional[str] = ""
+ server_idx: Optional[int] = 0
+ language: str = "python"
+ openapi_doc_version: str = __openapi_doc_version__
+ sdk_version: str = __version__
+ gen_version: str = __gen_version__
+ user_agent: str = __user_agent__
+ retry_config: OptionalNullable[RetryConfig] = Field(default_factory=lambda: UNSET)
+ timeout_ms: Optional[int] = None
+
+ def __post_init__(self):
+ self._hooks = SDKHooks()
+
+ def get_server_details(self) -> Tuple[str, Dict[str, str]]:
+ if self.server_url is not None and self.server_url:
+ return remove_suffix(self.server_url, "/"), {}
+ if self.server_idx is None:
+ self.server_idx = 0
+
+ return SERVERS[self.server_idx], {}
+
+ def get_hooks(self) -> SDKHooks:
+ return self._hooks
diff --git a/submission/src/epilot_submission/submissions.py b/submission/src/epilot_submission/submissions.py
new file mode 100644
index 000000000..5fae39847
--- /dev/null
+++ b/submission/src/epilot_submission/submissions.py
@@ -0,0 +1,199 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from .basesdk import BaseSDK
+from epilot_submission import models, utils
+from epilot_submission._hooks import HookContext
+from epilot_submission.types import BaseModel, OptionalNullable, UNSET
+from typing import Mapping, Optional, Union, cast
+
+
+class Submissions(BaseSDK):
+ r"""Journey Submission"""
+
+ def create_submission(
+ self,
+ *,
+ request: Optional[
+ Union[models.SubmissionPayload, models.SubmissionPayloadTypedDict]
+ ] = None,
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
+ server_url: Optional[str] = None,
+ timeout_ms: Optional[int] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ):
+ r"""createSubmission
+
+ Creates a submission from a public facing Journey
+
+
+ :param request: The request object to send.
+ :param retries: Override the default retry configuration for this method
+ :param server_url: Override the default server URL for this method
+ :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
+ :param http_headers: Additional headers to set or replace on requests.
+ """
+ base_url = None
+ url_variables = None
+ if timeout_ms is None:
+ timeout_ms = self.sdk_configuration.timeout_ms
+
+ if server_url is not None:
+ base_url = server_url
+
+ if not isinstance(request, BaseModel):
+ request = utils.unmarshal(request, Optional[models.SubmissionPayload])
+ request = cast(Optional[models.SubmissionPayload], request)
+
+ req = self._build_request(
+ method="POST",
+ path="/v1/submission/submissions",
+ base_url=base_url,
+ url_variables=url_variables,
+ request=request,
+ request_body_required=False,
+ request_has_path_params=False,
+ request_has_query_params=True,
+ user_agent_header="user-agent",
+ accept_header_value="*/*",
+ http_headers=http_headers,
+ security=self.sdk_configuration.security,
+ get_serialized_body=lambda: utils.serialize_request_body(
+ request, False, True, "json", Optional[models.SubmissionPayload]
+ ),
+ timeout_ms=timeout_ms,
+ )
+
+ if retries == UNSET:
+ if self.sdk_configuration.retry_config is not UNSET:
+ retries = self.sdk_configuration.retry_config
+
+ retry_config = None
+ if isinstance(retries, utils.RetryConfig):
+ retry_config = (retries, ["429", "500", "502", "503", "504"])
+
+ http_res = self.do_request(
+ hook_ctx=HookContext(
+ operation_id="createSubmission",
+ oauth2_scopes=[],
+ security_source=self.sdk_configuration.security,
+ ),
+ request=req,
+ error_status_codes=["4XX", "5XX"],
+ retry_config=retry_config,
+ )
+
+ if utils.match_response(http_res, "201", "*"):
+ return
+ if utils.match_response(http_res, "4XX", "*"):
+ http_res_text = utils.stream_to_text(http_res)
+ raise models.SDKError(
+ "API error occurred", http_res.status_code, http_res_text, http_res
+ )
+ if utils.match_response(http_res, "5XX", "*"):
+ http_res_text = utils.stream_to_text(http_res)
+ raise models.SDKError(
+ "API error occurred", http_res.status_code, http_res_text, http_res
+ )
+
+ content_type = http_res.headers.get("Content-Type")
+ http_res_text = utils.stream_to_text(http_res)
+ raise models.SDKError(
+ f"Unexpected response received (code: {http_res.status_code}, type: {content_type})",
+ http_res.status_code,
+ http_res_text,
+ http_res,
+ )
+
+ async def create_submission_async(
+ self,
+ *,
+ request: Optional[
+ Union[models.SubmissionPayload, models.SubmissionPayloadTypedDict]
+ ] = None,
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
+ server_url: Optional[str] = None,
+ timeout_ms: Optional[int] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ):
+ r"""createSubmission
+
+ Creates a submission from a public facing Journey
+
+
+ :param request: The request object to send.
+ :param retries: Override the default retry configuration for this method
+ :param server_url: Override the default server URL for this method
+ :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
+ :param http_headers: Additional headers to set or replace on requests.
+ """
+ base_url = None
+ url_variables = None
+ if timeout_ms is None:
+ timeout_ms = self.sdk_configuration.timeout_ms
+
+ if server_url is not None:
+ base_url = server_url
+
+ if not isinstance(request, BaseModel):
+ request = utils.unmarshal(request, Optional[models.SubmissionPayload])
+ request = cast(Optional[models.SubmissionPayload], request)
+
+ req = self._build_request_async(
+ method="POST",
+ path="/v1/submission/submissions",
+ base_url=base_url,
+ url_variables=url_variables,
+ request=request,
+ request_body_required=False,
+ request_has_path_params=False,
+ request_has_query_params=True,
+ user_agent_header="user-agent",
+ accept_header_value="*/*",
+ http_headers=http_headers,
+ security=self.sdk_configuration.security,
+ get_serialized_body=lambda: utils.serialize_request_body(
+ request, False, True, "json", Optional[models.SubmissionPayload]
+ ),
+ timeout_ms=timeout_ms,
+ )
+
+ if retries == UNSET:
+ if self.sdk_configuration.retry_config is not UNSET:
+ retries = self.sdk_configuration.retry_config
+
+ retry_config = None
+ if isinstance(retries, utils.RetryConfig):
+ retry_config = (retries, ["429", "500", "502", "503", "504"])
+
+ http_res = await self.do_request_async(
+ hook_ctx=HookContext(
+ operation_id="createSubmission",
+ oauth2_scopes=[],
+ security_source=self.sdk_configuration.security,
+ ),
+ request=req,
+ error_status_codes=["4XX", "5XX"],
+ retry_config=retry_config,
+ )
+
+ if utils.match_response(http_res, "201", "*"):
+ return
+ if utils.match_response(http_res, "4XX", "*"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise models.SDKError(
+ "API error occurred", http_res.status_code, http_res_text, http_res
+ )
+ if utils.match_response(http_res, "5XX", "*"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise models.SDKError(
+ "API error occurred", http_res.status_code, http_res_text, http_res
+ )
+
+ content_type = http_res.headers.get("Content-Type")
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise models.SDKError(
+ f"Unexpected response received (code: {http_res.status_code}, type: {content_type})",
+ http_res.status_code,
+ http_res_text,
+ http_res,
+ )
diff --git a/submission/src/epilot_submission/types/__init__.py b/submission/src/epilot_submission/types/__init__.py
new file mode 100644
index 000000000..fc76fe0c5
--- /dev/null
+++ b/submission/src/epilot_submission/types/__init__.py
@@ -0,0 +1,21 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from .basemodel import (
+ BaseModel,
+ Nullable,
+ OptionalNullable,
+ UnrecognizedInt,
+ UnrecognizedStr,
+ UNSET,
+ UNSET_SENTINEL,
+)
+
+__all__ = [
+ "BaseModel",
+ "Nullable",
+ "OptionalNullable",
+ "UnrecognizedInt",
+ "UnrecognizedStr",
+ "UNSET",
+ "UNSET_SENTINEL",
+]
diff --git a/submission/src/epilot_submission/types/basemodel.py b/submission/src/epilot_submission/types/basemodel.py
new file mode 100644
index 000000000..a6187efa6
--- /dev/null
+++ b/submission/src/epilot_submission/types/basemodel.py
@@ -0,0 +1,39 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from pydantic import ConfigDict, model_serializer
+from pydantic import BaseModel as PydanticBaseModel
+from typing import TYPE_CHECKING, Literal, Optional, TypeVar, Union, NewType
+from typing_extensions import TypeAliasType, TypeAlias
+
+
+class BaseModel(PydanticBaseModel):
+ model_config = ConfigDict(
+ populate_by_name=True, arbitrary_types_allowed=True, protected_namespaces=()
+ )
+
+
+class Unset(BaseModel):
+ @model_serializer(mode="plain")
+ def serialize_model(self):
+ return UNSET_SENTINEL
+
+ def __bool__(self) -> Literal[False]:
+ return False
+
+
+UNSET = Unset()
+UNSET_SENTINEL = "~?~unset~?~sentinel~?~"
+
+
+T = TypeVar("T")
+if TYPE_CHECKING:
+ Nullable: TypeAlias = Union[T, None]
+ OptionalNullable: TypeAlias = Union[Optional[Nullable[T]], Unset]
+else:
+ Nullable = TypeAliasType("Nullable", Union[T, None], type_params=(T,))
+ OptionalNullable = TypeAliasType(
+ "OptionalNullable", Union[Optional[Nullable[T]], Unset], type_params=(T,)
+ )
+
+UnrecognizedInt = NewType("UnrecognizedInt", int)
+UnrecognizedStr = NewType("UnrecognizedStr", str)
diff --git a/submission/src/epilot_submission/utils/__init__.py b/submission/src/epilot_submission/utils/__init__.py
new file mode 100644
index 000000000..26d51ae8f
--- /dev/null
+++ b/submission/src/epilot_submission/utils/__init__.py
@@ -0,0 +1,97 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from .annotations import get_discriminator
+from .enums import OpenEnumMeta
+from .headers import get_headers, get_response_headers
+from .metadata import (
+ FieldMetadata,
+ find_metadata,
+ FormMetadata,
+ HeaderMetadata,
+ MultipartFormMetadata,
+ PathParamMetadata,
+ QueryParamMetadata,
+ RequestMetadata,
+ SecurityMetadata,
+)
+from .queryparams import get_query_params
+from .retries import BackoffStrategy, Retries, retry, retry_async, RetryConfig
+from .requestbodies import serialize_request_body, SerializedRequestBody
+from .security import get_security
+from .serializers import (
+ get_pydantic_model,
+ marshal_json,
+ unmarshal,
+ unmarshal_json,
+ serialize_decimal,
+ serialize_float,
+ serialize_int,
+ stream_to_text,
+ stream_to_text_async,
+ stream_to_bytes,
+ stream_to_bytes_async,
+ validate_const,
+ validate_decimal,
+ validate_float,
+ validate_int,
+ validate_open_enum,
+)
+from .url import generate_url, template_url, remove_suffix
+from .values import (
+ get_global_from_env,
+ match_content_type,
+ match_status_codes,
+ match_response,
+)
+from .logger import Logger, get_body_content, get_default_logger
+
+__all__ = [
+ "BackoffStrategy",
+ "FieldMetadata",
+ "find_metadata",
+ "FormMetadata",
+ "generate_url",
+ "get_body_content",
+ "get_default_logger",
+ "get_discriminator",
+ "get_global_from_env",
+ "get_headers",
+ "get_pydantic_model",
+ "get_query_params",
+ "get_response_headers",
+ "get_security",
+ "HeaderMetadata",
+ "Logger",
+ "marshal_json",
+ "match_content_type",
+ "match_status_codes",
+ "match_response",
+ "MultipartFormMetadata",
+ "OpenEnumMeta",
+ "PathParamMetadata",
+ "QueryParamMetadata",
+ "remove_suffix",
+ "Retries",
+ "retry",
+ "retry_async",
+ "RetryConfig",
+ "RequestMetadata",
+ "SecurityMetadata",
+ "serialize_decimal",
+ "serialize_float",
+ "serialize_int",
+ "serialize_request_body",
+ "SerializedRequestBody",
+ "stream_to_text",
+ "stream_to_text_async",
+ "stream_to_bytes",
+ "stream_to_bytes_async",
+ "template_url",
+ "unmarshal",
+ "unmarshal_json",
+ "validate_decimal",
+ "validate_const",
+ "validate_float",
+ "validate_int",
+ "validate_open_enum",
+]
diff --git a/submission/src/epilot_submission/utils/annotations.py b/submission/src/epilot_submission/utils/annotations.py
new file mode 100644
index 000000000..387874edd
--- /dev/null
+++ b/submission/src/epilot_submission/utils/annotations.py
@@ -0,0 +1,55 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from enum import Enum
+from typing import Any, Optional
+
+def get_discriminator(model: Any, fieldname: str, key: str) -> str:
+ """
+ Recursively search for the discriminator attribute in a model.
+
+ Args:
+ model (Any): The model to search within.
+ fieldname (str): The name of the field to search for.
+ key (str): The key to search for in dictionaries.
+
+ Returns:
+ str: The name of the discriminator attribute.
+
+ Raises:
+ ValueError: If the discriminator attribute is not found.
+ """
+ upper_fieldname = fieldname.upper()
+
+ def get_field_discriminator(field: Any) -> Optional[str]:
+ """Search for the discriminator attribute in a given field."""
+
+ if isinstance(field, dict):
+ if key in field:
+ return f'{field[key]}'
+
+ if hasattr(field, fieldname):
+ attr = getattr(field, fieldname)
+ if isinstance(attr, Enum):
+ return f'{attr.value}'
+ return f'{attr}'
+
+ if hasattr(field, upper_fieldname):
+ attr = getattr(field, upper_fieldname)
+ if isinstance(attr, Enum):
+ return f'{attr.value}'
+ return f'{attr}'
+
+ return None
+
+
+ if isinstance(model, list):
+ for field in model:
+ discriminator = get_field_discriminator(field)
+ if discriminator is not None:
+ return discriminator
+
+ discriminator = get_field_discriminator(model)
+ if discriminator is not None:
+ return discriminator
+
+ raise ValueError(f'Could not find discriminator field {fieldname} in {model}')
diff --git a/submission/src/epilot_submission/utils/enums.py b/submission/src/epilot_submission/utils/enums.py
new file mode 100644
index 000000000..c650b10cb
--- /dev/null
+++ b/submission/src/epilot_submission/utils/enums.py
@@ -0,0 +1,34 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+import enum
+
+
+class OpenEnumMeta(enum.EnumMeta):
+ def __call__(
+ cls, value, names=None, *, module=None, qualname=None, type=None, start=1
+ ):
+ # The `type` kwarg also happens to be a built-in that pylint flags as
+ # redeclared. Safe to ignore this lint rule with this scope.
+ # pylint: disable=redefined-builtin
+
+ if names is not None:
+ return super().__call__(
+ value,
+ names=names,
+ module=module,
+ qualname=qualname,
+ type=type,
+ start=start,
+ )
+
+ try:
+ return super().__call__(
+ value,
+ names=names, # pyright: ignore[reportArgumentType]
+ module=module,
+ qualname=qualname,
+ type=type,
+ start=start,
+ )
+ except ValueError:
+ return value
diff --git a/submission/src/epilot_submission/utils/eventstreaming.py b/submission/src/epilot_submission/utils/eventstreaming.py
new file mode 100644
index 000000000..74a63f757
--- /dev/null
+++ b/submission/src/epilot_submission/utils/eventstreaming.py
@@ -0,0 +1,238 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+import re
+import json
+from typing import (
+ Callable,
+ Generic,
+ TypeVar,
+ Optional,
+ Generator,
+ AsyncGenerator,
+ Tuple,
+)
+import httpx
+
+T = TypeVar("T")
+
+
+class EventStream(Generic[T]):
+ response: httpx.Response
+ generator: Generator[T, None, None]
+
+ def __init__(
+ self,
+ response: httpx.Response,
+ decoder: Callable[[str], T],
+ sentinel: Optional[str] = None,
+ ):
+ self.response = response
+ self.generator = stream_events(response, decoder, sentinel)
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return next(self.generator)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.response.close()
+
+
+class EventStreamAsync(Generic[T]):
+ response: httpx.Response
+ generator: AsyncGenerator[T, None]
+
+ def __init__(
+ self,
+ response: httpx.Response,
+ decoder: Callable[[str], T],
+ sentinel: Optional[str] = None,
+ ):
+ self.response = response
+ self.generator = stream_events_async(response, decoder, sentinel)
+
+ def __aiter__(self):
+ return self
+
+ async def __anext__(self):
+ return await self.generator.__anext__()
+
+ async def __aenter__(self):
+ return self
+
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
+ await self.response.aclose()
+
+
+class ServerEvent:
+ id: Optional[str] = None
+ event: Optional[str] = None
+ data: Optional[str] = None
+ retry: Optional[int] = None
+
+
+MESSAGE_BOUNDARIES = [
+ b"\r\n\r\n",
+ b"\n\n",
+ b"\r\r",
+]
+
+
+async def stream_events_async(
+ response: httpx.Response,
+ decoder: Callable[[str], T],
+ sentinel: Optional[str] = None,
+) -> AsyncGenerator[T, None]:
+ buffer = bytearray()
+ position = 0
+ discard = False
+ async for chunk in response.aiter_bytes():
+ # We've encountered the sentinel value and should no longer process
+ # incoming data. Instead we throw new data away until the server closes
+ # the connection.
+ if discard:
+ continue
+
+ buffer += chunk
+ for i in range(position, len(buffer)):
+ char = buffer[i : i + 1]
+ seq: Optional[bytes] = None
+ if char in [b"\r", b"\n"]:
+ for boundary in MESSAGE_BOUNDARIES:
+ seq = _peek_sequence(i, buffer, boundary)
+ if seq is not None:
+ break
+ if seq is None:
+ continue
+
+ block = buffer[position:i]
+ position = i + len(seq)
+ event, discard = _parse_event(block, decoder, sentinel)
+ if event is not None:
+ yield event
+
+ if position > 0:
+ buffer = buffer[position:]
+ position = 0
+
+ event, discard = _parse_event(buffer, decoder, sentinel)
+ if event is not None:
+ yield event
+
+
+def stream_events(
+ response: httpx.Response,
+ decoder: Callable[[str], T],
+ sentinel: Optional[str] = None,
+) -> Generator[T, None, None]:
+ buffer = bytearray()
+ position = 0
+ discard = False
+ for chunk in response.iter_bytes():
+ # We've encountered the sentinel value and should no longer process
+ # incoming data. Instead we throw new data away until the server closes
+ # the connection.
+ if discard:
+ continue
+
+ buffer += chunk
+ for i in range(position, len(buffer)):
+ char = buffer[i : i + 1]
+ seq: Optional[bytes] = None
+ if char in [b"\r", b"\n"]:
+ for boundary in MESSAGE_BOUNDARIES:
+ seq = _peek_sequence(i, buffer, boundary)
+ if seq is not None:
+ break
+ if seq is None:
+ continue
+
+ block = buffer[position:i]
+ position = i + len(seq)
+ event, discard = _parse_event(block, decoder, sentinel)
+ if event is not None:
+ yield event
+
+ if position > 0:
+ buffer = buffer[position:]
+ position = 0
+
+ event, discard = _parse_event(buffer, decoder, sentinel)
+ if event is not None:
+ yield event
+
+
+def _parse_event(
+ raw: bytearray, decoder: Callable[[str], T], sentinel: Optional[str] = None
+) -> Tuple[Optional[T], bool]:
+ block = raw.decode()
+ lines = re.split(r"\r?\n|\r", block)
+ publish = False
+ event = ServerEvent()
+ data = ""
+ for line in lines:
+ if not line:
+ continue
+
+ delim = line.find(":")
+ if delim <= 0:
+ continue
+
+ field = line[0:delim]
+ value = line[delim + 1 :] if delim < len(line) - 1 else ""
+ if len(value) and value[0] == " ":
+ value = value[1:]
+
+ if field == "event":
+ event.event = value
+ publish = True
+ elif field == "data":
+ data += value + "\n"
+ publish = True
+ elif field == "id":
+ event.id = value
+ publish = True
+ elif field == "retry":
+ event.retry = int(value) if value.isdigit() else None
+ publish = True
+
+ if sentinel and data == f"{sentinel}\n":
+ return None, True
+
+ if data:
+ data = data[:-1]
+ event.data = data
+
+ data_is_primitive = (
+ data.isnumeric() or data == "true" or data == "false" or data == "null"
+ )
+ data_is_json = (
+ data.startswith("{") or data.startswith("[") or data.startswith('"')
+ )
+
+ if data_is_primitive or data_is_json:
+ try:
+ event.data = json.loads(data)
+ except Exception:
+ pass
+
+ out = None
+ if publish:
+ out = decoder(json.dumps(event.__dict__))
+
+ return out, False
+
+
+def _peek_sequence(position: int, buffer: bytearray, sequence: bytes):
+ if len(sequence) > (len(buffer) - position):
+ return None
+
+ for i, seq in enumerate(sequence):
+ if buffer[position + i] != seq:
+ return None
+
+ return sequence
diff --git a/submission/src/epilot_submission/utils/forms.py b/submission/src/epilot_submission/utils/forms.py
new file mode 100644
index 000000000..0472aba8b
--- /dev/null
+++ b/submission/src/epilot_submission/utils/forms.py
@@ -0,0 +1,202 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from typing import (
+ Any,
+ Dict,
+ get_type_hints,
+ List,
+ Tuple,
+)
+from pydantic import BaseModel
+from pydantic.fields import FieldInfo
+
+from .serializers import marshal_json
+
+from .metadata import (
+ FormMetadata,
+ MultipartFormMetadata,
+ find_field_metadata,
+)
+from .values import _is_set, _val_to_string
+
+
+def _populate_form(
+ field_name: str,
+ explode: bool,
+ obj: Any,
+ delimiter: str,
+ form: Dict[str, List[str]],
+):
+ if not _is_set(obj):
+ return form
+
+ if isinstance(obj, BaseModel):
+ items = []
+
+ obj_fields: Dict[str, FieldInfo] = obj.__class__.model_fields
+ for name in obj_fields:
+ obj_field = obj_fields[name]
+ obj_field_name = obj_field.alias if obj_field.alias is not None else name
+ if obj_field_name == "":
+ continue
+
+ val = getattr(obj, name)
+ if not _is_set(val):
+ continue
+
+ if explode:
+ form[obj_field_name] = [_val_to_string(val)]
+ else:
+ items.append(f"{obj_field_name}{delimiter}{_val_to_string(val)}")
+
+ if len(items) > 0:
+ form[field_name] = [delimiter.join(items)]
+ elif isinstance(obj, Dict):
+ items = []
+ for key, value in obj.items():
+ if not _is_set(value):
+ continue
+
+ if explode:
+ form[key] = [_val_to_string(value)]
+ else:
+ items.append(f"{key}{delimiter}{_val_to_string(value)}")
+
+ if len(items) > 0:
+ form[field_name] = [delimiter.join(items)]
+ elif isinstance(obj, List):
+ items = []
+
+ for value in obj:
+ if not _is_set(value):
+ continue
+
+ if explode:
+ if not field_name in form:
+ form[field_name] = []
+ form[field_name].append(_val_to_string(value))
+ else:
+ items.append(_val_to_string(value))
+
+ if len(items) > 0:
+ form[field_name] = [delimiter.join([str(item) for item in items])]
+ else:
+ form[field_name] = [_val_to_string(obj)]
+
+ return form
+
+
+def serialize_multipart_form(
+ media_type: str, request: Any
+) -> Tuple[str, Dict[str, Any], Dict[str, Any]]:
+ form: Dict[str, Any] = {}
+ files: Dict[str, Any] = {}
+
+ if not isinstance(request, BaseModel):
+ raise TypeError("invalid request body type")
+
+ request_fields: Dict[str, FieldInfo] = request.__class__.model_fields
+ request_field_types = get_type_hints(request.__class__)
+
+ for name in request_fields:
+ field = request_fields[name]
+
+ val = getattr(request, name)
+ if not _is_set(val):
+ continue
+
+ field_metadata = find_field_metadata(field, MultipartFormMetadata)
+ if not field_metadata:
+ continue
+
+ f_name = field.alias if field.alias else name
+
+ if field_metadata.file:
+ file_fields: Dict[str, FieldInfo] = val.__class__.model_fields
+
+ file_name = ""
+ content = None
+ content_type = None
+
+ for file_field_name in file_fields:
+ file_field = file_fields[file_field_name]
+
+ file_metadata = find_field_metadata(file_field, MultipartFormMetadata)
+ if file_metadata is None:
+ continue
+
+ if file_metadata.content:
+ content = getattr(val, file_field_name, None)
+ elif file_field_name == "content_type":
+ content_type = getattr(val, file_field_name, None)
+ else:
+ file_name = getattr(val, file_field_name)
+
+ if file_name == "" or content is None:
+ raise ValueError("invalid multipart/form-data file")
+
+ if content_type is not None:
+ files[f_name] = (file_name, content, content_type)
+ else:
+ files[f_name] = (file_name, content)
+ elif field_metadata.json:
+ files[f_name] = (
+ None,
+ marshal_json(val, request_field_types[name]),
+ "application/json",
+ )
+ else:
+ if isinstance(val, List):
+ values = []
+
+ for value in val:
+ if not _is_set(value):
+ continue
+ values.append(_val_to_string(value))
+
+ form[f_name + "[]"] = values
+ else:
+ form[f_name] = _val_to_string(val)
+ return media_type, form, files
+
+
+def serialize_form_data(data: Any) -> Dict[str, Any]:
+ form: Dict[str, List[str]] = {}
+
+ if isinstance(data, BaseModel):
+ data_fields: Dict[str, FieldInfo] = data.__class__.model_fields
+ data_field_types = get_type_hints(data.__class__)
+ for name in data_fields:
+ field = data_fields[name]
+
+ val = getattr(data, name)
+ if not _is_set(val):
+ continue
+
+ metadata = find_field_metadata(field, FormMetadata)
+ if metadata is None:
+ continue
+
+ f_name = field.alias if field.alias is not None else name
+
+ if metadata.json:
+ form[f_name] = [marshal_json(val, data_field_types[name])]
+ else:
+ if metadata.style == "form":
+ _populate_form(
+ f_name,
+ metadata.explode,
+ val,
+ ",",
+ form,
+ )
+ else:
+ raise ValueError(f"Invalid form style for field {name}")
+ elif isinstance(data, Dict):
+ for key, value in data.items():
+ if _is_set(value):
+ form[key] = [_val_to_string(value)]
+ else:
+ raise TypeError(f"Invalid request body type {type(data)} for form data")
+
+ return form
diff --git a/submission/src/epilot_submission/utils/headers.py b/submission/src/epilot_submission/utils/headers.py
new file mode 100644
index 000000000..37864cbbb
--- /dev/null
+++ b/submission/src/epilot_submission/utils/headers.py
@@ -0,0 +1,136 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from typing import (
+ Any,
+ Dict,
+ List,
+ Optional,
+)
+from httpx import Headers
+from pydantic import BaseModel
+from pydantic.fields import FieldInfo
+
+from .metadata import (
+ HeaderMetadata,
+ find_field_metadata,
+)
+
+from .values import _is_set, _populate_from_globals, _val_to_string
+
+
+def get_headers(headers_params: Any, gbls: Optional[Any] = None) -> Dict[str, str]:
+ headers: Dict[str, str] = {}
+
+ globals_already_populated = []
+ if _is_set(headers_params):
+ globals_already_populated = _populate_headers(headers_params, gbls, headers, [])
+ if _is_set(gbls):
+ _populate_headers(gbls, None, headers, globals_already_populated)
+
+ return headers
+
+
+def _populate_headers(
+ headers_params: Any,
+ gbls: Any,
+ header_values: Dict[str, str],
+ skip_fields: List[str],
+) -> List[str]:
+ globals_already_populated: List[str] = []
+
+ if not isinstance(headers_params, BaseModel):
+ return globals_already_populated
+
+ param_fields: Dict[str, FieldInfo] = headers_params.__class__.model_fields
+ for name in param_fields:
+ if name in skip_fields:
+ continue
+
+ field = param_fields[name]
+ f_name = field.alias if field.alias is not None else name
+
+ metadata = find_field_metadata(field, HeaderMetadata)
+ if metadata is None:
+ continue
+
+ value, global_found = _populate_from_globals(
+ name, getattr(headers_params, name), HeaderMetadata, gbls
+ )
+ if global_found:
+ globals_already_populated.append(name)
+ value = _serialize_header(metadata.explode, value)
+
+ if value != "":
+ header_values[f_name] = value
+
+ return globals_already_populated
+
+
+def _serialize_header(explode: bool, obj: Any) -> str:
+ if not _is_set(obj):
+ return ""
+
+ if isinstance(obj, BaseModel):
+ items = []
+ obj_fields: Dict[str, FieldInfo] = obj.__class__.model_fields
+ for name in obj_fields:
+ obj_field = obj_fields[name]
+ obj_param_metadata = find_field_metadata(obj_field, HeaderMetadata)
+
+ if not obj_param_metadata:
+ continue
+
+ f_name = obj_field.alias if obj_field.alias is not None else name
+
+ val = getattr(obj, name)
+ if not _is_set(val):
+ continue
+
+ if explode:
+ items.append(f"{f_name}={_val_to_string(val)}")
+ else:
+ items.append(f_name)
+ items.append(_val_to_string(val))
+
+ if len(items) > 0:
+ return ",".join(items)
+ elif isinstance(obj, Dict):
+ items = []
+
+ for key, value in obj.items():
+ if not _is_set(value):
+ continue
+
+ if explode:
+ items.append(f"{key}={_val_to_string(value)}")
+ else:
+ items.append(key)
+ items.append(_val_to_string(value))
+
+ if len(items) > 0:
+ return ",".join([str(item) for item in items])
+ elif isinstance(obj, List):
+ items = []
+
+ for value in obj:
+ if not _is_set(value):
+ continue
+
+ items.append(_val_to_string(value))
+
+ if len(items) > 0:
+ return ",".join(items)
+ elif _is_set(obj):
+ return f"{_val_to_string(obj)}"
+
+ return ""
+
+
+def get_response_headers(headers: Headers) -> Dict[str, List[str]]:
+ res: Dict[str, List[str]] = {}
+ for k, v in headers.items():
+ if not k in res:
+ res[k] = []
+
+ res[k].append(v)
+ return res
diff --git a/submission/src/epilot_submission/utils/logger.py b/submission/src/epilot_submission/utils/logger.py
new file mode 100644
index 000000000..b661aff65
--- /dev/null
+++ b/submission/src/epilot_submission/utils/logger.py
@@ -0,0 +1,22 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+import httpx
+from typing import Any, Protocol
+
+
+class Logger(Protocol):
+ def debug(self, msg: str, *args: Any, **kwargs: Any) -> None:
+ pass
+
+
+class NoOpLogger:
+ def debug(self, msg: str, *args: Any, **kwargs: Any) -> None:
+ pass
+
+
+def get_body_content(req: httpx.Request) -> str:
+ return "" if not hasattr(req, "_content") else str(req.content)
+
+
+def get_default_logger() -> Logger:
+ return NoOpLogger()
diff --git a/submission/src/epilot_submission/utils/metadata.py b/submission/src/epilot_submission/utils/metadata.py
new file mode 100644
index 000000000..173b3e5ce
--- /dev/null
+++ b/submission/src/epilot_submission/utils/metadata.py
@@ -0,0 +1,118 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from typing import Optional, Type, TypeVar, Union
+from dataclasses import dataclass
+from pydantic.fields import FieldInfo
+
+
+T = TypeVar("T")
+
+
+@dataclass
+class SecurityMetadata:
+ option: bool = False
+ scheme: bool = False
+ scheme_type: Optional[str] = None
+ sub_type: Optional[str] = None
+ field_name: Optional[str] = None
+
+ def get_field_name(self, default: str) -> str:
+ return self.field_name or default
+
+
+@dataclass
+class ParamMetadata:
+ serialization: Optional[str] = None
+ style: str = "simple"
+ explode: bool = False
+
+
+@dataclass
+class PathParamMetadata(ParamMetadata):
+ pass
+
+
+@dataclass
+class QueryParamMetadata(ParamMetadata):
+ style: str = "form"
+ explode: bool = True
+
+
+@dataclass
+class HeaderMetadata(ParamMetadata):
+ pass
+
+
+@dataclass
+class RequestMetadata:
+ media_type: str = "application/octet-stream"
+
+
+@dataclass
+class MultipartFormMetadata:
+ file: bool = False
+ content: bool = False
+ json: bool = False
+
+
+@dataclass
+class FormMetadata:
+ json: bool = False
+ style: str = "form"
+ explode: bool = True
+
+
+class FieldMetadata:
+ security: Optional[SecurityMetadata] = None
+ path: Optional[PathParamMetadata] = None
+ query: Optional[QueryParamMetadata] = None
+ header: Optional[HeaderMetadata] = None
+ request: Optional[RequestMetadata] = None
+ form: Optional[FormMetadata] = None
+ multipart: Optional[MultipartFormMetadata] = None
+
+ def __init__(
+ self,
+ security: Optional[SecurityMetadata] = None,
+ path: Optional[Union[PathParamMetadata, bool]] = None,
+ query: Optional[Union[QueryParamMetadata, bool]] = None,
+ header: Optional[Union[HeaderMetadata, bool]] = None,
+ request: Optional[Union[RequestMetadata, bool]] = None,
+ form: Optional[Union[FormMetadata, bool]] = None,
+ multipart: Optional[Union[MultipartFormMetadata, bool]] = None,
+ ):
+ self.security = security
+ self.path = PathParamMetadata() if isinstance(path, bool) else path
+ self.query = QueryParamMetadata() if isinstance(query, bool) else query
+ self.header = HeaderMetadata() if isinstance(header, bool) else header
+ self.request = RequestMetadata() if isinstance(request, bool) else request
+ self.form = FormMetadata() if isinstance(form, bool) else form
+ self.multipart = (
+ MultipartFormMetadata() if isinstance(multipart, bool) else multipart
+ )
+
+
+def find_field_metadata(field_info: FieldInfo, metadata_type: Type[T]) -> Optional[T]:
+ metadata = find_metadata(field_info, FieldMetadata)
+ if not metadata:
+ return None
+
+ fields = metadata.__dict__
+
+ for field in fields:
+ if isinstance(fields[field], metadata_type):
+ return fields[field]
+
+ return None
+
+
+def find_metadata(field_info: FieldInfo, metadata_type: Type[T]) -> Optional[T]:
+ metadata = field_info.metadata
+ if not metadata:
+ return None
+
+ for md in metadata:
+ if isinstance(md, metadata_type):
+ return md
+
+ return None
diff --git a/submission/src/epilot_submission/utils/queryparams.py b/submission/src/epilot_submission/utils/queryparams.py
new file mode 100644
index 000000000..37a6e7f9a
--- /dev/null
+++ b/submission/src/epilot_submission/utils/queryparams.py
@@ -0,0 +1,205 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from typing import (
+ Any,
+ Dict,
+ get_type_hints,
+ List,
+ Optional,
+)
+
+from pydantic import BaseModel
+from pydantic.fields import FieldInfo
+
+from .metadata import (
+ QueryParamMetadata,
+ find_field_metadata,
+)
+from .values import (
+ _get_serialized_params,
+ _is_set,
+ _populate_from_globals,
+ _val_to_string,
+)
+from .forms import _populate_form
+
+
+def get_query_params(
+ query_params: Any,
+ gbls: Optional[Any] = None,
+) -> Dict[str, List[str]]:
+ params: Dict[str, List[str]] = {}
+
+ globals_already_populated = _populate_query_params(query_params, gbls, params, [])
+ if _is_set(gbls):
+ _populate_query_params(gbls, None, params, globals_already_populated)
+
+ return params
+
+
+def _populate_query_params(
+ query_params: Any,
+ gbls: Any,
+ query_param_values: Dict[str, List[str]],
+ skip_fields: List[str],
+) -> List[str]:
+ globals_already_populated: List[str] = []
+
+ if not isinstance(query_params, BaseModel):
+ return globals_already_populated
+
+ param_fields: Dict[str, FieldInfo] = query_params.__class__.model_fields
+ param_field_types = get_type_hints(query_params.__class__)
+ for name in param_fields:
+ if name in skip_fields:
+ continue
+
+ field = param_fields[name]
+
+ metadata = find_field_metadata(field, QueryParamMetadata)
+ if not metadata:
+ continue
+
+ value = getattr(query_params, name) if _is_set(query_params) else None
+
+ value, global_found = _populate_from_globals(
+ name, value, QueryParamMetadata, gbls
+ )
+ if global_found:
+ globals_already_populated.append(name)
+
+ f_name = field.alias if field.alias is not None else name
+ serialization = metadata.serialization
+ if serialization is not None:
+ serialized_parms = _get_serialized_params(
+ metadata, f_name, value, param_field_types[name]
+ )
+ for key, value in serialized_parms.items():
+ if key in query_param_values:
+ query_param_values[key].extend(value)
+ else:
+ query_param_values[key] = [value]
+ else:
+ style = metadata.style
+ if style == "deepObject":
+ _populate_deep_object_query_params(f_name, value, query_param_values)
+ elif style == "form":
+ _populate_delimited_query_params(
+ metadata, f_name, value, ",", query_param_values
+ )
+ elif style == "pipeDelimited":
+ _populate_delimited_query_params(
+ metadata, f_name, value, "|", query_param_values
+ )
+ else:
+ raise NotImplementedError(
+ f"query param style {style} not yet supported"
+ )
+
+ return globals_already_populated
+
+
+def _populate_deep_object_query_params(
+ field_name: str,
+ obj: Any,
+ params: Dict[str, List[str]],
+):
+ if not _is_set(obj):
+ return
+
+ if isinstance(obj, BaseModel):
+ _populate_deep_object_query_params_basemodel(field_name, obj, params)
+ elif isinstance(obj, Dict):
+ _populate_deep_object_query_params_dict(field_name, obj, params)
+
+
+def _populate_deep_object_query_params_basemodel(
+ prior_params_key: str,
+ obj: Any,
+ params: Dict[str, List[str]],
+):
+ if not _is_set(obj) or not isinstance(obj, BaseModel):
+ return
+
+ obj_fields: Dict[str, FieldInfo] = obj.__class__.model_fields
+ for name in obj_fields:
+ obj_field = obj_fields[name]
+
+ f_name = obj_field.alias if obj_field.alias is not None else name
+
+ params_key = f"{prior_params_key}[{f_name}]"
+
+ obj_param_metadata = find_field_metadata(obj_field, QueryParamMetadata)
+ if not _is_set(obj_param_metadata):
+ continue
+
+ obj_val = getattr(obj, name)
+ if not _is_set(obj_val):
+ continue
+
+ if isinstance(obj_val, BaseModel):
+ _populate_deep_object_query_params_basemodel(params_key, obj_val, params)
+ elif isinstance(obj_val, Dict):
+ _populate_deep_object_query_params_dict(params_key, obj_val, params)
+ elif isinstance(obj_val, List):
+ _populate_deep_object_query_params_list(params_key, obj_val, params)
+ else:
+ params[params_key] = [_val_to_string(obj_val)]
+
+
+def _populate_deep_object_query_params_dict(
+ prior_params_key: str,
+ value: Dict,
+ params: Dict[str, List[str]],
+):
+ if not _is_set(value):
+ return
+
+ for key, val in value.items():
+ if not _is_set(val):
+ continue
+
+ params_key = f"{prior_params_key}[{key}]"
+
+ if isinstance(val, BaseModel):
+ _populate_deep_object_query_params_basemodel(params_key, val, params)
+ elif isinstance(val, Dict):
+ _populate_deep_object_query_params_dict(params_key, val, params)
+ elif isinstance(val, List):
+ _populate_deep_object_query_params_list(params_key, val, params)
+ else:
+ params[params_key] = [_val_to_string(val)]
+
+
+def _populate_deep_object_query_params_list(
+ params_key: str,
+ value: List,
+ params: Dict[str, List[str]],
+):
+ if not _is_set(value):
+ return
+
+ for val in value:
+ if not _is_set(val):
+ continue
+
+ if params.get(params_key) is None:
+ params[params_key] = []
+
+ params[params_key].append(_val_to_string(val))
+
+
+def _populate_delimited_query_params(
+ metadata: QueryParamMetadata,
+ field_name: str,
+ obj: Any,
+ delimiter: str,
+ query_param_values: Dict[str, List[str]],
+):
+ _populate_form(
+ field_name,
+ metadata.explode,
+ obj,
+ delimiter,
+ query_param_values,
+ )
diff --git a/submission/src/epilot_submission/utils/requestbodies.py b/submission/src/epilot_submission/utils/requestbodies.py
new file mode 100644
index 000000000..d5240dd5f
--- /dev/null
+++ b/submission/src/epilot_submission/utils/requestbodies.py
@@ -0,0 +1,66 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+import io
+from dataclasses import dataclass
+import re
+from typing import (
+ Any,
+ Optional,
+)
+
+from .forms import serialize_form_data, serialize_multipart_form
+
+from .serializers import marshal_json
+
+SERIALIZATION_METHOD_TO_CONTENT_TYPE = {
+ "json": "application/json",
+ "form": "application/x-www-form-urlencoded",
+ "multipart": "multipart/form-data",
+ "raw": "application/octet-stream",
+ "string": "text/plain",
+}
+
+
+@dataclass
+class SerializedRequestBody:
+ media_type: Optional[str] = None
+ content: Optional[Any] = None
+ data: Optional[Any] = None
+ files: Optional[Any] = None
+
+
+def serialize_request_body(
+ request_body: Any,
+ nullable: bool,
+ optional: bool,
+ serialization_method: str,
+ request_body_type,
+) -> Optional[SerializedRequestBody]:
+ if request_body is None:
+ if not nullable and optional:
+ return None
+
+ media_type = SERIALIZATION_METHOD_TO_CONTENT_TYPE[serialization_method]
+
+ serialized_request_body = SerializedRequestBody(media_type)
+
+ if re.match(r"(application|text)\/.*?\+*json.*", media_type) is not None:
+ serialized_request_body.content = marshal_json(request_body, request_body_type)
+ elif re.match(r"multipart\/.*", media_type) is not None:
+ (
+ serialized_request_body.media_type,
+ serialized_request_body.data,
+ serialized_request_body.files,
+ ) = serialize_multipart_form(media_type, request_body)
+ elif re.match(r"application\/x-www-form-urlencoded.*", media_type) is not None:
+ serialized_request_body.data = serialize_form_data(request_body)
+ elif isinstance(request_body, (bytes, bytearray, io.BytesIO, io.BufferedReader)):
+ serialized_request_body.content = request_body
+ elif isinstance(request_body, str):
+ serialized_request_body.content = request_body
+ else:
+ raise TypeError(
+ f"invalid request body type {type(request_body)} for mediaType {media_type}"
+ )
+
+ return serialized_request_body
diff --git a/submission/src/epilot_submission/utils/retries.py b/submission/src/epilot_submission/utils/retries.py
new file mode 100644
index 000000000..4d6086716
--- /dev/null
+++ b/submission/src/epilot_submission/utils/retries.py
@@ -0,0 +1,217 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+import asyncio
+import random
+import time
+from typing import List
+
+import httpx
+
+
+class BackoffStrategy:
+ initial_interval: int
+ max_interval: int
+ exponent: float
+ max_elapsed_time: int
+
+ def __init__(
+ self,
+ initial_interval: int,
+ max_interval: int,
+ exponent: float,
+ max_elapsed_time: int,
+ ):
+ self.initial_interval = initial_interval
+ self.max_interval = max_interval
+ self.exponent = exponent
+ self.max_elapsed_time = max_elapsed_time
+
+
+class RetryConfig:
+ strategy: str
+ backoff: BackoffStrategy
+ retry_connection_errors: bool
+
+ def __init__(
+ self, strategy: str, backoff: BackoffStrategy, retry_connection_errors: bool
+ ):
+ self.strategy = strategy
+ self.backoff = backoff
+ self.retry_connection_errors = retry_connection_errors
+
+
+class Retries:
+ config: RetryConfig
+ status_codes: List[str]
+
+ def __init__(self, config: RetryConfig, status_codes: List[str]):
+ self.config = config
+ self.status_codes = status_codes
+
+
+class TemporaryError(Exception):
+ response: httpx.Response
+
+ def __init__(self, response: httpx.Response):
+ self.response = response
+
+
+class PermanentError(Exception):
+ inner: Exception
+
+ def __init__(self, inner: Exception):
+ self.inner = inner
+
+
+def retry(func, retries: Retries):
+ if retries.config.strategy == "backoff":
+
+ def do_request() -> httpx.Response:
+ res: httpx.Response
+ try:
+ res = func()
+
+ for code in retries.status_codes:
+ if "X" in code.upper():
+ code_range = int(code[0])
+
+ status_major = res.status_code / 100
+
+ if code_range <= status_major < code_range + 1:
+ raise TemporaryError(res)
+ else:
+ parsed_code = int(code)
+
+ if res.status_code == parsed_code:
+ raise TemporaryError(res)
+ except httpx.ConnectError as exception:
+ if retries.config.retry_connection_errors:
+ raise
+
+ raise PermanentError(exception) from exception
+ except httpx.TimeoutException as exception:
+ if retries.config.retry_connection_errors:
+ raise
+
+ raise PermanentError(exception) from exception
+ except TemporaryError:
+ raise
+ except Exception as exception:
+ raise PermanentError(exception) from exception
+
+ return res
+
+ return retry_with_backoff(
+ do_request,
+ retries.config.backoff.initial_interval,
+ retries.config.backoff.max_interval,
+ retries.config.backoff.exponent,
+ retries.config.backoff.max_elapsed_time,
+ )
+
+ return func()
+
+
+async def retry_async(func, retries: Retries):
+ if retries.config.strategy == "backoff":
+
+ async def do_request() -> httpx.Response:
+ res: httpx.Response
+ try:
+ res = await func()
+
+ for code in retries.status_codes:
+ if "X" in code.upper():
+ code_range = int(code[0])
+
+ status_major = res.status_code / 100
+
+ if code_range <= status_major < code_range + 1:
+ raise TemporaryError(res)
+ else:
+ parsed_code = int(code)
+
+ if res.status_code == parsed_code:
+ raise TemporaryError(res)
+ except httpx.ConnectError as exception:
+ if retries.config.retry_connection_errors:
+ raise
+
+ raise PermanentError(exception) from exception
+ except httpx.TimeoutException as exception:
+ if retries.config.retry_connection_errors:
+ raise
+
+ raise PermanentError(exception) from exception
+ except TemporaryError:
+ raise
+ except Exception as exception:
+ raise PermanentError(exception) from exception
+
+ return res
+
+ return await retry_with_backoff_async(
+ do_request,
+ retries.config.backoff.initial_interval,
+ retries.config.backoff.max_interval,
+ retries.config.backoff.exponent,
+ retries.config.backoff.max_elapsed_time,
+ )
+
+ return await func()
+
+
+def retry_with_backoff(
+ func,
+ initial_interval=500,
+ max_interval=60000,
+ exponent=1.5,
+ max_elapsed_time=3600000,
+):
+ start = round(time.time() * 1000)
+ retries = 0
+
+ while True:
+ try:
+ return func()
+ except PermanentError as exception:
+ raise exception.inner
+ except Exception as exception: # pylint: disable=broad-exception-caught
+ now = round(time.time() * 1000)
+ if now - start > max_elapsed_time:
+ if isinstance(exception, TemporaryError):
+ return exception.response
+
+ raise
+ sleep = (initial_interval / 1000) * exponent**retries + random.uniform(0, 1)
+ sleep = min(sleep, max_interval / 1000)
+ time.sleep(sleep)
+ retries += 1
+
+
+async def retry_with_backoff_async(
+ func,
+ initial_interval=500,
+ max_interval=60000,
+ exponent=1.5,
+ max_elapsed_time=3600000,
+):
+ start = round(time.time() * 1000)
+ retries = 0
+
+ while True:
+ try:
+ return await func()
+ except PermanentError as exception:
+ raise exception.inner
+ except Exception as exception: # pylint: disable=broad-exception-caught
+ now = round(time.time() * 1000)
+ if now - start > max_elapsed_time:
+ if isinstance(exception, TemporaryError):
+ return exception.response
+
+ raise
+ sleep = (initial_interval / 1000) * exponent**retries + random.uniform(0, 1)
+ sleep = min(sleep, max_interval / 1000)
+ await asyncio.sleep(sleep)
+ retries += 1
diff --git a/submission/src/epilot_submission/utils/security.py b/submission/src/epilot_submission/utils/security.py
new file mode 100644
index 000000000..295a3f400
--- /dev/null
+++ b/submission/src/epilot_submission/utils/security.py
@@ -0,0 +1,174 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+import base64
+from typing import (
+ Any,
+ Dict,
+ List,
+ Tuple,
+)
+from pydantic import BaseModel
+from pydantic.fields import FieldInfo
+
+from .metadata import (
+ SecurityMetadata,
+ find_field_metadata,
+)
+
+
+def get_security(security: Any) -> Tuple[Dict[str, str], Dict[str, List[str]]]:
+ headers: Dict[str, str] = {}
+ query_params: Dict[str, List[str]] = {}
+
+ if security is None:
+ return headers, query_params
+
+ if not isinstance(security, BaseModel):
+ raise TypeError("security must be a pydantic model")
+
+ sec_fields: Dict[str, FieldInfo] = security.__class__.model_fields
+ for name in sec_fields:
+ sec_field = sec_fields[name]
+
+ value = getattr(security, name)
+ if value is None:
+ continue
+
+ metadata = find_field_metadata(sec_field, SecurityMetadata)
+ if metadata is None:
+ continue
+ if metadata.option:
+ _parse_security_option(headers, query_params, value)
+ return headers, query_params
+ if metadata.scheme:
+ # Special case for basic auth or custom auth which could be a flattened model
+ if metadata.sub_type in ["basic", "custom"] and not isinstance(
+ value, BaseModel
+ ):
+ _parse_security_scheme(headers, query_params, metadata, name, security)
+ else:
+ _parse_security_scheme(headers, query_params, metadata, name, value)
+
+ return headers, query_params
+
+
+def _parse_security_option(
+ headers: Dict[str, str], query_params: Dict[str, List[str]], option: Any
+):
+ if not isinstance(option, BaseModel):
+ raise TypeError("security option must be a pydantic model")
+
+ opt_fields: Dict[str, FieldInfo] = option.__class__.model_fields
+ for name in opt_fields:
+ opt_field = opt_fields[name]
+
+ metadata = find_field_metadata(opt_field, SecurityMetadata)
+ if metadata is None or not metadata.scheme:
+ continue
+ _parse_security_scheme(
+ headers, query_params, metadata, name, getattr(option, name)
+ )
+
+
+def _parse_security_scheme(
+ headers: Dict[str, str],
+ query_params: Dict[str, List[str]],
+ scheme_metadata: SecurityMetadata,
+ field_name: str,
+ scheme: Any,
+):
+ scheme_type = scheme_metadata.scheme_type
+ sub_type = scheme_metadata.sub_type
+
+ if isinstance(scheme, BaseModel):
+ if scheme_type == "http":
+ if sub_type == "basic":
+ _parse_basic_auth_scheme(headers, scheme)
+ return
+ if sub_type == "custom":
+ return
+
+ scheme_fields: Dict[str, FieldInfo] = scheme.__class__.model_fields
+ for name in scheme_fields:
+ scheme_field = scheme_fields[name]
+
+ metadata = find_field_metadata(scheme_field, SecurityMetadata)
+ if metadata is None or metadata.field_name is None:
+ continue
+
+ value = getattr(scheme, name)
+
+ _parse_security_scheme_value(
+ headers, query_params, scheme_metadata, metadata, name, value
+ )
+ else:
+ _parse_security_scheme_value(
+ headers, query_params, scheme_metadata, scheme_metadata, field_name, scheme
+ )
+
+
+def _parse_security_scheme_value(
+ headers: Dict[str, str],
+ query_params: Dict[str, List[str]],
+ scheme_metadata: SecurityMetadata,
+ security_metadata: SecurityMetadata,
+ field_name: str,
+ value: Any,
+):
+ scheme_type = scheme_metadata.scheme_type
+ sub_type = scheme_metadata.sub_type
+
+ header_name = security_metadata.get_field_name(field_name)
+
+ if scheme_type == "apiKey":
+ if sub_type == "header":
+ headers[header_name] = value
+ elif sub_type == "query":
+ query_params[header_name] = [value]
+ else:
+ raise ValueError("sub type {sub_type} not supported")
+ elif scheme_type == "openIdConnect":
+ headers[header_name] = _apply_bearer(value)
+ elif scheme_type == "oauth2":
+ if sub_type != "client_credentials":
+ headers[header_name] = _apply_bearer(value)
+ elif scheme_type == "http":
+ if sub_type == "bearer":
+ headers[header_name] = _apply_bearer(value)
+ elif sub_type == "custom":
+ return
+ else:
+ raise ValueError("sub type {sub_type} not supported")
+ else:
+ raise ValueError("scheme type {scheme_type} not supported")
+
+
+def _apply_bearer(token: str) -> str:
+ return token.lower().startswith("bearer ") and token or f"Bearer {token}"
+
+
+def _parse_basic_auth_scheme(headers: Dict[str, str], scheme: Any):
+ username = ""
+ password = ""
+
+ if not isinstance(scheme, BaseModel):
+ raise TypeError("basic auth scheme must be a pydantic model")
+
+ scheme_fields: Dict[str, FieldInfo] = scheme.__class__.model_fields
+ for name in scheme_fields:
+ scheme_field = scheme_fields[name]
+
+ metadata = find_field_metadata(scheme_field, SecurityMetadata)
+ if metadata is None or metadata.field_name is None:
+ continue
+
+ field_name = metadata.field_name
+ value = getattr(scheme, name)
+
+ if field_name == "username":
+ username = value
+ if field_name == "password":
+ password = value
+
+ data = f"{username}:{password}".encode()
+ headers["Authorization"] = f"Basic {base64.b64encode(data).decode()}"
diff --git a/submission/src/epilot_submission/utils/serializers.py b/submission/src/epilot_submission/utils/serializers.py
new file mode 100644
index 000000000..c5eb36597
--- /dev/null
+++ b/submission/src/epilot_submission/utils/serializers.py
@@ -0,0 +1,215 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from decimal import Decimal
+import json
+from typing import Any, Dict, List, Union, get_args
+import httpx
+from typing_extensions import get_origin
+from pydantic import ConfigDict, create_model
+from pydantic_core import from_json
+from typing_inspect import is_optional_type
+
+from ..types.basemodel import BaseModel, Nullable, OptionalNullable, Unset
+
+
+def serialize_decimal(as_str: bool):
+ def serialize(d):
+ if is_optional_type(type(d)) and d is None:
+ return None
+ if isinstance(d, Unset):
+ return d
+
+ if not isinstance(d, Decimal):
+ raise ValueError("Expected Decimal object")
+
+ return str(d) if as_str else float(d)
+
+ return serialize
+
+
+def validate_decimal(d):
+ if d is None:
+ return None
+
+ if isinstance(d, (Decimal, Unset)):
+ return d
+
+ if not isinstance(d, (str, int, float)):
+ raise ValueError("Expected string, int or float")
+
+ return Decimal(str(d))
+
+
+def serialize_float(as_str: bool):
+ def serialize(f):
+ if is_optional_type(type(f)) and f is None:
+ return None
+ if isinstance(f, Unset):
+ return f
+
+ if not isinstance(f, float):
+ raise ValueError("Expected float")
+
+ return str(f) if as_str else f
+
+ return serialize
+
+
+def validate_float(f):
+ if f is None:
+ return None
+
+ if isinstance(f, (float, Unset)):
+ return f
+
+ if not isinstance(f, str):
+ raise ValueError("Expected string")
+
+ return float(f)
+
+
+def serialize_int(as_str: bool):
+ def serialize(i):
+ if is_optional_type(type(i)) and i is None:
+ return None
+ if isinstance(i, Unset):
+ return i
+
+ if not isinstance(i, int):
+ raise ValueError("Expected int")
+
+ return str(i) if as_str else i
+
+ return serialize
+
+
+def validate_int(b):
+ if b is None:
+ return None
+
+ if isinstance(b, (int, Unset)):
+ return b
+
+ if not isinstance(b, str):
+ raise ValueError("Expected string")
+
+ return int(b)
+
+
+def validate_open_enum(is_int: bool):
+ def validate(e):
+ if e is None:
+ return None
+
+ if isinstance(e, Unset):
+ return e
+
+ if is_int:
+ if not isinstance(e, int):
+ raise ValueError("Expected int")
+ else:
+ if not isinstance(e, str):
+ raise ValueError("Expected string")
+
+ return e
+
+ return validate
+
+
+def validate_const(v):
+ def validate(c):
+ if is_optional_type(type(c)) and c is None:
+ return None
+
+ if v != c:
+ raise ValueError(f"Expected {v}")
+
+ return c
+
+ return validate
+
+
+def unmarshal_json(raw, typ: Any) -> Any:
+ return unmarshal(from_json(raw), typ)
+
+
+def unmarshal(val, typ: Any) -> Any:
+ unmarshaller = create_model(
+ "Unmarshaller",
+ body=(typ, ...),
+ __config__=ConfigDict(populate_by_name=True, arbitrary_types_allowed=True),
+ )
+
+ m = unmarshaller(body=val)
+
+ # pyright: ignore[reportAttributeAccessIssue]
+ return m.body # type: ignore
+
+
+def marshal_json(val, typ):
+ if is_nullable(typ) and val is None:
+ return "null"
+
+ marshaller = create_model(
+ "Marshaller",
+ body=(typ, ...),
+ __config__=ConfigDict(populate_by_name=True, arbitrary_types_allowed=True),
+ )
+
+ m = marshaller(body=val)
+
+ d = m.model_dump(by_alias=True, mode="json", exclude_none=True)
+
+ if len(d) == 0:
+ return ""
+
+ return json.dumps(d[next(iter(d))], separators=(",", ":"), sort_keys=True)
+
+
+def is_nullable(field):
+ origin = get_origin(field)
+ if origin is Nullable or origin is OptionalNullable:
+ return True
+
+ if not origin is Union or type(None) not in get_args(field):
+ return False
+
+ for arg in get_args(field):
+ if get_origin(arg) is Nullable or get_origin(arg) is OptionalNullable:
+ return True
+
+ return False
+
+
+def stream_to_text(stream: httpx.Response) -> str:
+ return "".join(stream.iter_text())
+
+
+async def stream_to_text_async(stream: httpx.Response) -> str:
+ return "".join([chunk async for chunk in stream.aiter_text()])
+
+
+def stream_to_bytes(stream: httpx.Response) -> bytes:
+ return stream.content
+
+
+async def stream_to_bytes_async(stream: httpx.Response) -> bytes:
+ return await stream.aread()
+
+
+def get_pydantic_model(data: Any, typ: Any) -> Any:
+ if not _contains_pydantic_model(data):
+ return unmarshal(data, typ)
+
+ return data
+
+
+def _contains_pydantic_model(data: Any) -> bool:
+ if isinstance(data, BaseModel):
+ return True
+ if isinstance(data, List):
+ return any(_contains_pydantic_model(item) for item in data)
+ if isinstance(data, Dict):
+ return any(_contains_pydantic_model(value) for value in data.values())
+
+ return False
diff --git a/submission/src/epilot_submission/utils/url.py b/submission/src/epilot_submission/utils/url.py
new file mode 100644
index 000000000..c78ccbae4
--- /dev/null
+++ b/submission/src/epilot_submission/utils/url.py
@@ -0,0 +1,155 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from decimal import Decimal
+from typing import (
+ Any,
+ Dict,
+ get_type_hints,
+ List,
+ Optional,
+ Union,
+ get_args,
+ get_origin,
+)
+from pydantic import BaseModel
+from pydantic.fields import FieldInfo
+
+from .metadata import (
+ PathParamMetadata,
+ find_field_metadata,
+)
+from .values import (
+ _get_serialized_params,
+ _is_set,
+ _populate_from_globals,
+ _val_to_string,
+)
+
+
+def generate_url(
+ server_url: str,
+ path: str,
+ path_params: Any,
+ gbls: Optional[Any] = None,
+) -> str:
+ path_param_values: Dict[str, str] = {}
+
+ globals_already_populated = _populate_path_params(
+ path_params, gbls, path_param_values, []
+ )
+ if _is_set(gbls):
+ _populate_path_params(gbls, None, path_param_values, globals_already_populated)
+
+ for key, value in path_param_values.items():
+ path = path.replace("{" + key + "}", value, 1)
+
+ return remove_suffix(server_url, "/") + path
+
+
+def _populate_path_params(
+ path_params: Any,
+ gbls: Any,
+ path_param_values: Dict[str, str],
+ skip_fields: List[str],
+) -> List[str]:
+ globals_already_populated: List[str] = []
+
+ if not isinstance(path_params, BaseModel):
+ return globals_already_populated
+
+ path_param_fields: Dict[str, FieldInfo] = path_params.__class__.model_fields
+ path_param_field_types = get_type_hints(path_params.__class__)
+ for name in path_param_fields:
+ if name in skip_fields:
+ continue
+
+ field = path_param_fields[name]
+
+ param_metadata = find_field_metadata(field, PathParamMetadata)
+ if param_metadata is None:
+ continue
+
+ param = getattr(path_params, name) if _is_set(path_params) else None
+ param, global_found = _populate_from_globals(
+ name, param, PathParamMetadata, gbls
+ )
+ if global_found:
+ globals_already_populated.append(name)
+
+ if not _is_set(param):
+ continue
+
+ f_name = field.alias if field.alias is not None else name
+ serialization = param_metadata.serialization
+ if serialization is not None:
+ serialized_params = _get_serialized_params(
+ param_metadata, f_name, param, path_param_field_types[name]
+ )
+ for key, value in serialized_params.items():
+ path_param_values[key] = value
+ else:
+ pp_vals: List[str] = []
+ if param_metadata.style == "simple":
+ if isinstance(param, List):
+ for pp_val in param:
+ if not _is_set(pp_val):
+ continue
+ pp_vals.append(_val_to_string(pp_val))
+ path_param_values[f_name] = ",".join(pp_vals)
+ elif isinstance(param, Dict):
+ for pp_key in param:
+ if not _is_set(param[pp_key]):
+ continue
+ if param_metadata.explode:
+ pp_vals.append(f"{pp_key}={_val_to_string(param[pp_key])}")
+ else:
+ pp_vals.append(f"{pp_key},{_val_to_string(param[pp_key])}")
+ path_param_values[f_name] = ",".join(pp_vals)
+ elif not isinstance(param, (str, int, float, complex, bool, Decimal)):
+ param_fields: Dict[str, FieldInfo] = param.__class__.model_fields
+ for name in param_fields:
+ param_field = param_fields[name]
+
+ param_value_metadata = find_field_metadata(
+ param_field, PathParamMetadata
+ )
+ if param_value_metadata is None:
+ continue
+
+ param_name = (
+ param_field.alias if param_field.alias is not None else name
+ )
+
+ param_field_val = getattr(param, name)
+ if not _is_set(param_field_val):
+ continue
+ if param_metadata.explode:
+ pp_vals.append(
+ f"{param_name}={_val_to_string(param_field_val)}"
+ )
+ else:
+ pp_vals.append(
+ f"{param_name},{_val_to_string(param_field_val)}"
+ )
+ path_param_values[f_name] = ",".join(pp_vals)
+ elif _is_set(param):
+ path_param_values[f_name] = _val_to_string(param)
+
+ return globals_already_populated
+
+
+def is_optional(field):
+ return get_origin(field) is Union and type(None) in get_args(field)
+
+
+def template_url(url_with_params: str, params: Dict[str, str]) -> str:
+ for key, value in params.items():
+ url_with_params = url_with_params.replace("{" + key + "}", value)
+
+ return url_with_params
+
+
+def remove_suffix(input_string, suffix):
+ if suffix and input_string.endswith(suffix):
+ return input_string[: -len(suffix)]
+ return input_string
diff --git a/submission/src/epilot_submission/utils/values.py b/submission/src/epilot_submission/utils/values.py
new file mode 100644
index 000000000..2b4b68321
--- /dev/null
+++ b/submission/src/epilot_submission/utils/values.py
@@ -0,0 +1,134 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from datetime import datetime
+from enum import Enum
+from email.message import Message
+import os
+from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union
+
+from httpx import Response
+from pydantic import BaseModel
+from pydantic.fields import FieldInfo
+
+from ..types.basemodel import Unset
+
+from .serializers import marshal_json
+
+from .metadata import ParamMetadata, find_field_metadata
+
+
+def match_content_type(content_type: str, pattern: str) -> bool:
+ if pattern in (content_type, "*", "*/*"):
+ return True
+
+ msg = Message()
+ msg["content-type"] = content_type
+ media_type = msg.get_content_type()
+
+ if media_type == pattern:
+ return True
+
+ parts = media_type.split("/")
+ if len(parts) == 2:
+ if pattern in (f"{parts[0]}/*", f"*/{parts[1]}"):
+ return True
+
+ return False
+
+
+def match_status_codes(status_codes: List[str], status_code: int) -> bool:
+ if "default" in status_codes:
+ return True
+
+ for code in status_codes:
+ if code == str(status_code):
+ return True
+
+ if code.endswith("XX") and code.startswith(str(status_code)[:1]):
+ return True
+ return False
+
+
+T = TypeVar("T")
+
+
+def get_global_from_env(
+ value: Optional[T], env_key: str, type_cast: Callable[[str], T]
+) -> Optional[T]:
+ if value is not None:
+ return value
+ env_value = os.getenv(env_key)
+ if env_value is not None:
+ try:
+ return type_cast(env_value)
+ except ValueError:
+ pass
+ return None
+
+
+def match_response(
+ response: Response, code: Union[str, List[str]], content_type: str
+) -> bool:
+ codes = code if isinstance(code, list) else [code]
+ return match_status_codes(codes, response.status_code) and match_content_type(
+ response.headers.get("content-type", "application/octet-stream"), content_type
+ )
+
+
+def _populate_from_globals(
+ param_name: str, value: Any, param_metadata_type: type, gbls: Any
+) -> Tuple[Any, bool]:
+ if gbls is None:
+ return value, False
+
+ if not isinstance(gbls, BaseModel):
+ raise TypeError("globals must be a pydantic model")
+
+ global_fields: Dict[str, FieldInfo] = gbls.__class__.model_fields
+ found = False
+ for name in global_fields:
+ field = global_fields[name]
+ if name is not param_name:
+ continue
+
+ found = True
+
+ if value is not None:
+ return value, True
+
+ global_value = getattr(gbls, name)
+
+ param_metadata = find_field_metadata(field, param_metadata_type)
+ if param_metadata is None:
+ return value, True
+
+ return global_value, True
+
+ return value, found
+
+
+def _val_to_string(val) -> str:
+ if isinstance(val, bool):
+ return str(val).lower()
+ if isinstance(val, datetime):
+ return str(val.isoformat().replace("+00:00", "Z"))
+ if isinstance(val, Enum):
+ return str(val.value)
+
+ return str(val)
+
+
+def _get_serialized_params(
+ metadata: ParamMetadata, field_name: str, obj: Any, typ: type
+) -> Dict[str, str]:
+ params: Dict[str, str] = {}
+
+ serialization = metadata.serialization
+ if serialization == "json":
+ params[field_name] = marshal_json(obj, typ)
+
+ return params
+
+
+def _is_set(value: Any) -> bool:
+ return value is not None and not isinstance(value, Unset)