diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0054954 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Generated by Scalar SDK Generator. +__pycache__/ +*.py[cod] +*.egg-info/ +.venv/ +dist/ +build/ +.mypy_cache/ +.pytest_cache/ +.ruff_cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c063e08 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.2.5 + +- Initial generated SDK release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a121855 --- /dev/null +++ b/LICENSE @@ -0,0 +1,3 @@ +SPDX-License-Identifier: MIT + +See the project repository for the full license text. diff --git a/README.md b/README.md index d6af3b4..52b30b8 100644 --- a/README.md +++ b/README.md @@ -1 +1,226 @@ -# scalar-python \ No newline at end of file +# Scalar API + +Generated Python SDK for Scalar API. +API for managing Scalar platform resources. + +## TypeScript SDK + +For TypeScript, we provide a SDK that makes using our API even easier. + +### Install + +```bash +npm add @scalar/sdk +``` + +### Get a Scalar API key + +Create an API key in your Scalar account: + +- Dashboard: https://dashboard.scalar.com/account +- Store it in `.env`, for example: + +```bash +SCALAR_API_KEY=your_personal_token +``` + +### Exchange your API key for an access token + +The personal token is not an access token. Exchange it first with `postv1AuthExchange`. + +If you use the personal token directly for authenticated API calls, the API returns `401 Invalid authentication token`. + +```ts +import { Scalar } from '@scalar/sdk' + +const scalar = new Scalar() + +const exchange = await scalar.auth.postv1AuthExchange({ + personalToken: process.env.SCALAR_API_KEY!, +}) + +const accessToken = exchange.accessToken +``` + +### Use the access token + +Construct a second client with bearer auth. Use this authenticated client for API calls. + +```ts +import { Scalar } from '@scalar/sdk' + +const scalar = new Scalar() + +const exchange = await scalar.auth.postv1AuthExchange({ + personalToken: process.env.SCALAR_API_KEY!, +}) + +const authedScalar = new Scalar({ + bearerAuth: exchange.accessToken, +}) +``` + +### Notes + +- The exchange request itself can be made from a client constructed with no arguments (`new Scalar()`). +- The exchanged access token is valid for 12 hours. +- Timestamps are Unix seconds. + +### Read more + +- [@scalar/sdk on npm](https://www.npmjs.com/package/@scalar/sdk) + +
+ +## Contents + +- [Installation](#installation) +- [Usage](#usage) +- [API Reference](./api.md) +- [Async](#async) +- [Authentication](#authentication) +- [Errors](#errors) +- [Client Options](#client-options) +- [Retries and Timeouts](#retries-and-timeouts) +- [Helpers](#helpers) +- [Logging](#logging) +- [Requirements](#requirements) + +
+ +## Installation + +```sh +pip install scalarApi +``` + +
+ +## Usage + +```python +import os + +from scalar_api import Scalar + +client = Scalar( + bearer_auth=os.environ.get("BEARER_AUTH"), +) + +registry = client.registry.list_all_api_documents() +print(registry) +``` + +The examples in the following sections assume a `client` configured as shown above. + +See the [API reference](./api.md) for every available operation. + +
+ +## Async + +Every client has an `Async` counterpart (`AsyncScalar`) exposing the same resource tree with `await`. + +```python +import asyncio + +from scalar_api import AsyncScalar + +async def main() -> None: + client = AsyncScalar() + registry = await client.registry.list_all_api_documents() + +asyncio.run(main()) +``` + +
+ +## Authentication + +Pass credentials to the generated client constructor. Environment variables are read automatically when supported by the target runtime. + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `bearer_auth` | `string \| provider` | - | Credential for the BearerAuth scheme. Defaults to BEARER_AUTH. | + +Declared schemes: + +- `BearerAuth` bearer token + +
+ +## Errors + +Non-success responses throw generated API errors. Error objects expose status, headers, response body, and request metadata where the target runtime supports it. + +```python +from scalar_api import APIStatusError + +try: + registry = client.registry.list_all_api_documents() +except APIStatusError as err: + print(err.status_code, err.message) + raise +``` + +Documented error statuses: `400`, `401`, `403`, `404`, `422`, `500`. + +
+ +## Client Options + +Configure the generated client by setting any of these options when you create it. + +```python +from scalar_api import Scalar + +client = Scalar( + timeout=60.0, + max_retries=2, +) +``` + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `bearer_auth` | `str \| None` | `os.environ.get("BEARER_AUTH")` | Credential for the BearerAuth scheme. | +| `base_url` | `str \| httpx.URL \| None` | - | Override the default API base URL. | +| `timeout` | `float \| Timeout \| None` | `60.0` | Maximum time in seconds to wait for a response before aborting a request. | +| `max_retries` | `int` | `2` | Number of retries for temporary failures. | +| `default_headers` | `Mapping[str, str] \| None` | - | Headers sent with every request. | +| `default_query` | `Mapping[str, object] \| None` | - | Query parameters sent with every request. | + +
+ +## Retries and Timeouts + +Generated clients support request timeouts and retry temporary failures such as network errors, 408, 409, 429, and 5xx responses. Retry delays honor `Retry-After` headers when present. Tune the retry and timeout client options shown above, or override them per request. + +
+ +## Helpers + +- Use `client.with_raw_response..(...)` to access the raw `httpx.Response` and parse it yourself. +- Use `client.with_streaming_response..(...)` to stream a response body without buffering it. + +
+ +## Logging + +- Set the `SCALAR_LOG` environment variable to `info` or `debug` to enable HTTP logging. +- Logs are emitted through the standard `logging` module under the `scalar_api` logger. + +
+ +## Requirements + +- Python 3.8 or newer + +Powered by Scalar. + + +## Contributions + +This SDK is generated programmatically. Manual edits to generated files will be +overwritten on the next build. + +### SDK created by [Scalar](https://www.scalar.com/?utm_source=scalar-typescript-sdk-python&utm_campaign=sdk) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..22d4cea --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +# Security Policy + +Please report any vulnerabilities through the standard issue tracker. diff --git a/api.md b/api.md new file mode 100644 index 0000000..48ac070 --- /dev/null +++ b/api.md @@ -0,0 +1,837 @@ +# Scalar Python API + +Complete reference of every operation, grouped by resource. See [the README](./README.md) for usage and configuration. + +## Contents + +- [`Registry`](#registry) + - [List all API Documents](#list-all-api-documents) + - [List API Documents in a namespace](#list-api-documents-in-a-namespace) + - [Create API Document](#create-api-document) + - [Update API Document metadata](#update-api-document-metadata) + - [Delete API Document](#delete-api-document) + - [Get API Document](#get-api-document) + - [Update API Document version](#update-api-document-version) + - [Delete API Document version](#delete-api-document-version) + - [Get API Document version metadata](#get-api-document-version-metadata) + - [Create API Document version](#create-api-document-version) + - [Add access group](#add-access-group) + - [Remove access group](#remove-access-group) +- [`Schemas`](#schemas) + - [List all shared components](#list-all-shared-components) + - [Create a shared component](#create-a-shared-component) + - [Update shared component metadata](#update-shared-component-metadata) + - [Delete a shared component](#delete-a-shared-component) + - [`Schemas Version`](#schemas-version) + - [Get a shared component document](#get-a-shared-component-document) + - [Delete a shared component version](#delete-a-shared-component-version) + - [Create a shared component version](#create-a-shared-component-version) + - [`Schemas AccessGroup`](#schemas-accessgroup) + - [Add shared component access group](#add-shared-component-access-group) + - [Remove shared component access group](#remove-shared-component-access-group) +- [`LoginPortals`](#loginportals) + - [Get a login portal](#get-a-login-portal) + - [Update portal metadata](#update-portal-metadata) + - [Delete a login portal](#delete-a-login-portal) + - [Create a portal](#create-a-portal) + - [List all portals](#list-all-portals) +- [`Rules`](#rules) + - [List all rules](#list-all-rules) + - [Create a rule](#create-a-rule) + - [Update rule metadata](#update-rule-metadata) + - [Delete a rule](#delete-a-rule) + - [Get a rule](#get-a-rule) + - [Add rule access group](#add-rule-access-group) + - [Remove rule access group](#remove-rule-access-group) +- [`Themes`](#themes) + - [List all themes](#list-all-themes) + - [Create a theme](#create-a-theme) + - [Update theme metadata](#update-theme-metadata) + - [Update theme document](#update-theme-document) + - [Delete a theme](#delete-a-theme) + - [Get a theme](#get-a-theme) +- [`Teams`](#teams) + - [List teams](#list-teams) +- [`ScalarDocs`](#scalardocs) + - [List all projects](#list-all-projects) + - [Create a project](#create-a-project) + - [Publish a project](#publish-a-project) +- [`Namespaces`](#namespaces) + - [List namespaces](#list-namespaces) +- [`Authentication`](#authentication) + - [Exchange token](#exchange-token) + - [Get current user](#get-current-user) + +## Setup + +```python +import os + +from scalar_api import Scalar + +client = Scalar( + bearer_auth=os.environ.get("BEARER_AUTH"), +) +``` + +## `Registry` + +### List all API Documents + +List all API documents across every namespace the caller can access. + +| Direction | Type | +| --- | --- | +| Response | [`RegistryListAllApiDocumentsResponse`](./src/types/registry_list_all_api_documents_response.py) | + +```python +registry = client.registry.list_all_api_documents() +``` + +### List API Documents in a namespace + +List API documents in a namespace. + +| Direction | Type | +| --- | --- | +| Response | [`RegistryListApiDocumentsResponse`](./src/types/registry_list_api_documents_response.py) | + +```python +registry = client.registry.list_api_documents( + namespace="namespace", +) +``` + +### Create API Document + +Create an API document. + +| Direction | Type | +| --- | --- | +| Request | [`RegistryCreateApiDocumentParams`](./src/types/registry_create_api_document_params.py) | +| Response | [`RegistryCreateApiDocumentResponse`](./src/types/registry_create_api_document_response.py) | + +```python +registry = client.registry.create_api_document( + namespace="namespace", + title="", + version="", + slug="", + document="", + idempotency_key="", +) +``` + +### Update API Document metadata + +Update metadata for an API document. + +| Direction | Type | +| --- | --- | +| Request | [`RegistryUpdateApiDocumentParams`](./src/types/registry_update_api_document_params.py) | +| Response | [`RegistryUpdateApiDocumentResponse`](./src/types/registry_update_api_document_response.py) | + +```python +registry = client.registry.update_api_document( + namespace="namespace", + slug="slug", + idempotency_key="", +) +``` + +### Delete API Document + +Delete an API document and all versions. + +| Direction | Type | +| --- | --- | +| Response | [`RegistryDeleteApiDocumentResponse`](./src/types/registry_delete_api_document_response.py) | + +```python +registry = client.registry.delete_api_document( + namespace="namespace", + slug="slug", + idempotency_key="", +) +``` + +### Get API Document + +Get a specific API document version. + +| Direction | Type | +| --- | --- | +| Response | [`RegistryRetrieveApiDocumentVersionResponse`](./src/types/registry_retrieve_api_document_version_response.py) | + +```python +registry = client.registry.retrieve_api_document_version( + namespace="namespace", + slug="slug", + semver="semver", +) +``` + +### Update API Document version + +Update the registry file content for an API document version. + +| Direction | Type | +| --- | --- | +| Request | [`RegistryUpdateApiDocumentVersionParams`](./src/types/registry_update_api_document_version_params.py) | +| Response | [`RegistryUpdateApiDocumentVersionResponse`](./src/types/registry_update_api_document_version_response.py) | + +```python +registry = client.registry.update_api_document_version( + namespace="namespace", + slug="slug", + semver="semver", + document="", + idempotency_key="", +) +``` + +### Delete API Document version + +Delete a specific API document version. + +| Direction | Type | +| --- | --- | +| Response | [`RegistryDeleteApiDocumentVersionResponse`](./src/types/registry_delete_api_document_version_response.py) | + +```python +registry = client.registry.delete_api_document_version( + namespace="namespace", + slug="slug", + semver="semver", + idempotency_key="", +) +``` + +### Get API Document version metadata + +Get metadata (uid, content shas, version sha, tags) for a specific API document version. + +| Direction | Type | +| --- | --- | +| Response | [`RegistryListApiDocumentVersionMetadataResponse`](./src/types/registry_list_api_document_version_metadata_response.py) | + +```python +registry = client.registry.list_api_document_version_metadata( + namespace="namespace", + slug="slug", + semver="semver", +) +``` + +### Create API Document version + +Create a new API document version. + +| Direction | Type | +| --- | --- | +| Request | [`RegistryCreateApiDocumentVersionParams`](./src/types/registry_create_api_document_version_params.py) | +| Response | [`RegistryCreateApiDocumentVersionResponse`](./src/types/registry_create_api_document_version_response.py) | + +```python +registry = client.registry.create_api_document_version( + namespace="namespace", + slug="slug", + version="", + document="", + idempotency_key="", +) +``` + +### Add access group + +Add an access group to an API document. + +| Direction | Type | +| --- | --- | +| Request | [`RegistryCreateApiDocumentAccessGroupParams`](./src/types/registry_create_api_document_access_group_params.py) | +| Response | [`RegistryCreateApiDocumentAccessGroupResponse`](./src/types/registry_create_api_document_access_group_response.py) | + +```python +registry = client.registry.create_api_document_access_group( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", +) +``` + +### Remove access group + +Remove an access group from an API document. + +| Direction | Type | +| --- | --- | +| Request | [`RegistryDeleteApiDocumentAccessGroupParams`](./src/types/registry_delete_api_document_access_group_params.py) | +| Response | [`RegistryDeleteApiDocumentAccessGroupResponse`](./src/types/registry_delete_api_document_access_group_response.py) | + +```python +registry = client.registry.delete_api_document_access_group( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", +) +``` + +## `Schemas` + +### List all shared components + +List schemas in a namespace. + +| Direction | Type | +| --- | --- | +| Response | [`SchemaListResponse`](./src/types/schema_list_response.py) | + +```python +schema = client.schemas.list( + namespace="namespace", +) +``` + +### Create a shared component + +Create a schema in a namespace. + +| Direction | Type | +| --- | --- | +| Request | [`SchemaCreateParams`](./src/types/schema_create_params.py) | +| Response | [`SchemaCreateResponse`](./src/types/schema_create_response.py) | + +```python +schema = client.schemas.create( + namespace="namespace", + title="", + version="", + slug="", + document="", + idempotency_key="", +) +``` + +### Update shared component metadata + +Update schema metadata. + +| Direction | Type | +| --- | --- | +| Request | [`SchemaUpdateParams`](./src/types/schema_update_params.py) | +| Response | [`SchemaUpdateResponse`](./src/types/schema_update_response.py) | + +```python +schema = client.schemas.update( + namespace="namespace", + slug="slug", + idempotency_key="", +) +``` + +### Delete a shared component + +Delete a schema and all related versions. + +| Direction | Type | +| --- | --- | +| Response | [`SchemaDeleteResponse`](./src/types/schema_delete_response.py) | + +```python +schema = client.schemas.delete( + namespace="namespace", + slug="slug", + idempotency_key="", +) +``` + +### `Schemas Version` + +#### Get a shared component document + +Get a specific schema version document. + +| Direction | Type | +| --- | --- | +| Response | [`VersionRetrieveSchemaResponse`](./src/types/schemas/version_retrieve_schema_response.py) | + +```python +version = client.schemas.version.retrieve_schema( + namespace="namespace", + slug="slug", + semver="semver", +) +``` + +#### Delete a shared component version + +Delete a schema version. + +| Direction | Type | +| --- | --- | +| Response | [`VersionDeleteSchemaResponse`](./src/types/schemas/version_delete_schema_response.py) | + +```python +version = client.schemas.version.delete_schema( + namespace="namespace", + slug="slug", + semver="semver", + idempotency_key="", +) +``` + +#### Create a shared component version + +Create a schema version. + +| Direction | Type | +| --- | --- | +| Request | [`VersionCreateSchemaParams`](./src/types/schemas/version_create_schema_params.py) | +| Response | [`VersionCreateSchemaResponse`](./src/types/schemas/version_create_schema_response.py) | + +```python +version = client.schemas.version.create_schema( + namespace="namespace", + slug="slug", + version="", + document="", + idempotency_key="", +) +``` + +### `Schemas AccessGroup` + +#### Add shared component access group + +Add an access group to a schema. + +| Direction | Type | +| --- | --- | +| Request | [`AccessGroupCreateSchemaParams`](./src/types/schemas/access_group_create_schema_params.py) | +| Response | [`AccessGroupCreateSchemaResponse`](./src/types/schemas/access_group_create_schema_response.py) | + +```python +access_group = client.schemas.access_group.create_schema( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", +) +``` + +#### Remove shared component access group + +Remove an access group from a schema. + +| Direction | Type | +| --- | --- | +| Request | [`AccessGroupDeleteSchemaParams`](./src/types/schemas/access_group_delete_schema_params.py) | +| Response | [`AccessGroupDeleteSchemaResponse`](./src/types/schemas/access_group_delete_schema_response.py) | + +```python +access_group = client.schemas.access_group.delete_schema( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", +) +``` + +## `LoginPortals` + +### Get a login portal + +Get a login portal by slug. + +| Direction | Type | +| --- | --- | +| Response | [`LoginPortalRetrieveResponse`](./src/types/login_portal_retrieve_response.py) | + +```python +login_portal = client.login_portals.retrieve( + slug="slug", +) +``` + +### Update portal metadata + +Update metadata for a login portal. + +| Direction | Type | +| --- | --- | +| Request | [`LoginPortalUpdateParams`](./src/types/login_portal_update_params.py) | +| Response | [`LoginPortalUpdateResponse`](./src/types/login_portal_update_response.py) | + +```python +login_portal = client.login_portals.update( + slug="slug", + idempotency_key="", +) +``` + +### Delete a login portal + +Delete a login portal. + +| Direction | Type | +| --- | --- | +| Response | [`LoginPortalDeleteResponse`](./src/types/login_portal_delete_response.py) | + +```python +login_portal = client.login_portals.delete( + slug="slug", + idempotency_key="", +) +``` + +### Create a portal + +Create a login portal for the current team. + +| Direction | Type | +| --- | --- | +| Request | [`LoginPortalCreateParams`](./src/types/login_portal_create_params.py) | +| Response | [`LoginPortalCreateResponse`](./src/types/login_portal_create_response.py) | + +```python +login_portal = client.login_portals.create( + title="", + slug="", + email={"logo": "", "logo_size": "100", "button_text": "Login", "message": "Click to access private documentation hosted by scalar.com", "title": "Private Docs", "main_color": "#2a2f45", "main_background": "#f6f6f6", "card_color": "2a2f45", "card_background": "#fff", "button_color": "#fff", "button_background": "#0f0f0f"}, + page={"title": "Scalar Private Docs", "description": "Login to access your documentation", "head": "", "script": "", "theme": "", "company_name": "", "logo": "", "logo_url": "", "favicon": "", "terms_link": "", "privacy_link": "", "form_title": "Scalar Private Docs", "form_description": "Login to access your documentation", "form_image": ""}, + idempotency_key="", +) +``` + +### List all portals + +List all login portals for the current team. + +| Direction | Type | +| --- | --- | +| Response | [`LoginPortalListResponse`](./src/types/login_portal_list_response.py) | + +```python +login_portal = client.login_portals.list() +``` + +## `Rules` + +### List all rules + +List all rulesets in a namespace. + +| Direction | Type | +| --- | --- | +| Response | [`RuleListRulesetsResponse`](./src/types/rule_list_rulesets_response.py) | + +```python +rule = client.rules.list_rulesets( + namespace="namespace", +) +``` + +### Create a rule + +Create a rule in a namespace. + +| Direction | Type | +| --- | --- | +| Request | [`RuleCreateRulesetParams`](./src/types/rule_create_ruleset_params.py) | +| Response | [`RuleCreateRulesetResponse`](./src/types/rule_create_ruleset_response.py) | + +```python +rule = client.rules.create_ruleset( + namespace="namespace", + title="", + slug="", + document="", + idempotency_key="", +) +``` + +### Update rule metadata + +Update rule metadata by slug. + +| Direction | Type | +| --- | --- | +| Request | [`RuleUpdateRulesetParams`](./src/types/rule_update_ruleset_params.py) | +| Response | [`RuleUpdateRulesetResponse`](./src/types/rule_update_ruleset_response.py) | + +```python +rule = client.rules.update_ruleset( + namespace="namespace", + slug="slug", + idempotency_key="", +) +``` + +### Delete a rule + +Delete a rule by slug. + +| Direction | Type | +| --- | --- | +| Response | [`RuleDeleteRulesetResponse`](./src/types/rule_delete_ruleset_response.py) | + +```python +rule = client.rules.delete_ruleset( + namespace="namespace", + slug="slug", + idempotency_key="", +) +``` + +### Get a rule + +Get a rule document by slug. + +| Direction | Type | +| --- | --- | +| Response | [`RuleRetrieveRulesetDocumentResponse`](./src/types/rule_retrieve_ruleset_document_response.py) | + +```python +rule = client.rules.retrieve_ruleset_document( + namespace="namespace", + slug="slug", +) +``` + +### Add rule access group + +Grant an access group to a rule. + +| Direction | Type | +| --- | --- | +| Request | [`RuleCreateRulesetAccessGroupParams`](./src/types/rule_create_ruleset_access_group_params.py) | +| Response | [`RuleCreateRulesetAccessGroupResponse`](./src/types/rule_create_ruleset_access_group_response.py) | + +```python +rule = client.rules.create_ruleset_access_group( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", +) +``` + +### Remove rule access group + +Remove an access group from a rule. + +| Direction | Type | +| --- | --- | +| Request | [`RuleDeleteRulesetAccessGroupParams`](./src/types/rule_delete_ruleset_access_group_params.py) | +| Response | [`RuleDeleteRulesetAccessGroupResponse`](./src/types/rule_delete_ruleset_access_group_response.py) | + +```python +rule = client.rules.delete_ruleset_access_group( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", +) +``` + +## `Themes` + +### List all themes + +List all team themes. + +| Direction | Type | +| --- | --- | +| Response | [`ThemeListResponse`](./src/types/theme_list_response.py) | + +```python +theme = client.themes.list() +``` + +### Create a theme + +Create a team theme. + +| Direction | Type | +| --- | --- | +| Request | [`ThemeCreateParams`](./src/types/theme_create_params.py) | +| Response | [`ThemeCreateResponse`](./src/types/theme_create_response.py) | + +```python +theme = client.themes.create( + name="", + slug="", + document="", + idempotency_key="", +) +``` + +### Update theme metadata + +Update theme metadata. + +| Direction | Type | +| --- | --- | +| Request | [`ThemeUpdateParams`](./src/types/theme_update_params.py) | +| Response | [`ThemeUpdateResponse`](./src/types/theme_update_response.py) | + +```python +theme = client.themes.update( + slug="slug", + idempotency_key="", +) +``` + +### Update theme document + +Replace the theme document. + +| Direction | Type | +| --- | --- | +| Request | [`ThemeReplaceDocumentParams`](./src/types/theme_replace_document_params.py) | +| Response | [`ThemeReplaceDocumentResponse`](./src/types/theme_replace_document_response.py) | + +```python +theme = client.themes.replace_document( + slug="slug", + document="", + idempotency_key="", +) +``` + +### Delete a theme + +Delete a theme by slug. + +| Direction | Type | +| --- | --- | +| Response | [`ThemeDeleteResponse`](./src/types/theme_delete_response.py) | + +```python +theme = client.themes.delete( + slug="slug", + idempotency_key="", +) +``` + +### Get a theme + +Get the theme document by slug. + +| Direction | Type | +| --- | --- | +| Response | [`ThemeRetrieveResponse`](./src/types/theme_retrieve_response.py) | + +```python +theme = client.themes.retrieve( + slug="slug", +) +``` + +## `Teams` + +### List teams + +List all available teams + +| Direction | Type | +| --- | --- | +| Response | [`TeamListResponse`](./src/types/team_list_response.py) | + +```python +team = client.teams.list() +``` + +## `ScalarDocs` + +### List all projects + +List all guide projects. + +| Direction | Type | +| --- | --- | +| Response | [`ScalarDocListGuidesResponse`](./src/types/scalar_doc_list_guides_response.py) | + +```python +scalar_doc = client.scalar_docs.list_guides() +``` + +### Create a project + +Create a guide project. + +| Direction | Type | +| --- | --- | +| Request | [`ScalarDocCreateGuideParams`](./src/types/scalar_doc_create_guide_params.py) | +| Response | [`ScalarDocCreateGuideResponse`](./src/types/scalar_doc_create_guide_response.py) | + +```python +scalar_doc = client.scalar_docs.create_guide( + name="", + is_private=False, + allowed_users=[], + allowed_domains=[], + idempotency_key="", +) +``` + +### Publish a project + +Start a new publish process. + +| Direction | Type | +| --- | --- | +| Response | [`ScalarDocPublishGuideResponse`](./src/types/scalar_doc_publish_guide_response.py) | + +```python +scalar_doc = client.scalar_docs.publish_guide( + slug="slug", + idempotency_key="", +) +``` + +## `Namespaces` + +### List namespaces + +Get all namespaces for the current team + +| Direction | Type | +| --- | --- | +| Response | [`NamespaceListResponse`](./src/types/namespace_list_response.py) | + +```python +namespace = client.namespaces.list() +``` + +## `Authentication` + +### Exchange token + +Exchange an API key for an access token. + +| Direction | Type | +| --- | --- | +| Request | [`AuthenticationExchangePersonalTokenParams`](./src/types/authentication_exchange_personal_token_params.py) | +| Response | [`AuthenticationExchangePersonalTokenResponse`](./src/types/authentication_exchange_personal_token_response.py) | + +```python +authentication = client.authentication.exchange_personal_token( + personal_token="", + idempotency_key="", +) +``` + +### Get current user + +Get the authenticated user, including their available teams and theme. + +| Direction | Type | +| --- | --- | +| Response | [`AuthenticationListCurrentUserResponse`](./src/types/authentication_list_current_user_response.py) | + +```python +authentication = client.authentication.list_current_user() +``` diff --git a/openapi.augmented.json b/openapi.augmented.json new file mode 100644 index 0000000..1686f0a --- /dev/null +++ b/openapi.augmented.json @@ -0,0 +1,6687 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "Scalar API", + "description": "API for managing Scalar platform resources.\n\n## TypeScript SDK\n\nFor TypeScript, we provide a SDK that makes using our API even easier.\n\n### Install\n\n```bash\nnpm add @scalar/sdk\n```\n\n### Get a Scalar API key\n\nCreate an API key in your Scalar account:\n\n- Dashboard: https://dashboard.scalar.com/account\n- Store it in `.env`, for example:\n\n```bash\nSCALAR_API_KEY=your_personal_token\n```\n\n### Exchange your API key for an access token\n\nThe personal token is not an access token. Exchange it first with `postv1AuthExchange`.\n\nIf you use the personal token directly for authenticated API calls, the API returns `401 Invalid authentication token`.\n\n```ts\nimport { Scalar } from '@scalar/sdk'\n\nconst scalar = new Scalar()\n\nconst exchange = await scalar.auth.postv1AuthExchange({\n personalToken: process.env.SCALAR_API_KEY!,\n})\n\nconst accessToken = exchange.accessToken\n```\n\n### Use the access token\n\nConstruct a second client with bearer auth. Use this authenticated client for API calls.\n\n```ts\nimport { Scalar } from '@scalar/sdk'\n\nconst scalar = new Scalar()\n\nconst exchange = await scalar.auth.postv1AuthExchange({\n personalToken: process.env.SCALAR_API_KEY!,\n})\n\nconst authedScalar = new Scalar({\n bearerAuth: exchange.accessToken,\n})\n```\n\n### Notes\n\n- The exchange request itself can be made from a client constructed with no arguments (`new Scalar()`).\n- The exchanged access token is valid for 12 hours.\n- Timestamps are Unix seconds.\n\n### Read more\n\n- [@scalar/sdk on npm](https://www.npmjs.com/package/@scalar/sdk)", + "version": "0.2.0", + "contact": { + "name": "Marc from Scalar", + "url": "https://scalar.com", + "email": "support@scalar.com" + }, + "x-scalar-sdk-installation": [ + { + "lang": "TypeScript", + "description": "```sh\nnpm install @scalar/sdk\n```" + }, + { + "lang": "Python", + "description": "```sh\npip install scalarApi\n```" + } + ] + }, + "servers": [ + { + "url": "https://access.scalar.com" + } + ], + "tags": [ + { + "name": "Registry", + "description": "Registry" + }, + { + "name": "Schemas", + "description": "Schemas" + }, + { + "name": "Login Portals", + "description": "Login Portals" + }, + { + "name": "Rules", + "description": "Rules" + }, + { + "name": "Themes", + "description": "Themes" + }, + { + "name": "Teams", + "description": "Teams" + }, + { + "name": "Scalar Docs", + "description": "Scalar Docs" + }, + { + "name": "Namespaces", + "description": "Namespaces" + }, + { + "name": "Authentication", + "description": "Authentication" + } + ], + "components": { + "securitySchemes": { + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "400": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message", + "code" + ] + }, + "401": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message", + "code" + ] + }, + "403": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message", + "code" + ] + }, + "404": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message", + "code" + ] + }, + "422": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message", + "code" + ] + }, + "500": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message", + "code" + ] + }, + "api-document": { + "type": "object", + "properties": { + "uid": { + "default": "nanoid()", + "$ref": "#/components/schemas/nanoid" + }, + "version": { + "$ref": "#/components/schemas/version" + }, + "title": { + "default": "", + "type": "string", + "maxLength": 100 + }, + "slug": { + "default": "randomManagedDocSlug()", + "$ref": "#/components/schemas/slug" + }, + "description": { + "default": "", + "type": "string" + }, + "namespace": { + "$ref": "#/components/schemas/namespace" + }, + "isPrivate": { + "default": false, + "type": "boolean" + }, + "tags": { + "default": [] + }, + "versions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/managed-doc-version" + } + } + }, + "required": [ + "uid", + "version", + "title", + "slug", + "description", + "namespace", + "isPrivate", + "tags", + "versions" + ], + "additionalProperties": false + }, + "nanoid": { + "type": "string", + "minLength": 5 + }, + "version": { + "type": "string", + "minLength": 1 + }, + "slug": { + "type": "string", + "minLength": 3, + "maxLength": 60, + "pattern": "^[a-z](?:[a-z0-9-]*[a-z0-9])?$" + }, + "namespace": { + "type": "string", + "minLength": 3, + "maxLength": 50, + "pattern": "^[a-zA-Z0-9-_]+$" + }, + "managed-doc-version": { + "type": "object", + "properties": { + "uid": { + "$ref": "#/components/schemas/nanoid" + }, + "createdAt": { + "type": "number" + }, + "version": { + "$ref": "#/components/schemas/version" + }, + "upgraded": { + "default": false, + "type": "boolean" + }, + "embedStatus": { + "default": null, + "anyOf": [ + { + "type": "string", + "enum": [ + "complete", + "failed" + ] + }, + { + "type": "null" + } + ] + }, + "tags": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "tools": { + "type": "array", + "items": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "method": { + "$ref": "#/components/schemas/method" + }, + "enabledTools": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "execute-request", + "get-mini-openapi-spec" + ] + } + } + }, + "required": [ + "path", + "method", + "enabledTools" + ], + "additionalProperties": false + } + }, + "yamlSha": { + "type": "string" + }, + "jsonSha": { + "type": "string" + }, + "versionSha": { + "type": "string" + } + }, + "required": [ + "uid", + "createdAt", + "version", + "upgraded", + "embedStatus", + "tags" + ], + "additionalProperties": false + }, + "method": { + "type": "string", + "enum": [ + "delete", + "get", + "head", + "options", + "patch", + "post", + "put", + "trace" + ] + }, + "access-group": { + "type": "object", + "properties": { + "accessGroupSlug": { + "$ref": "#/components/schemas/slug" + } + }, + "required": [ + "accessGroupSlug" + ], + "additionalProperties": false + }, + "schema": { + "type": "object", + "properties": { + "uid": { + "default": "nanoid()", + "$ref": "#/components/schemas/nanoid" + }, + "title": { + "default": "", + "type": "string", + "maxLength": 100 + }, + "description": { + "default": "", + "type": "string" + }, + "slug": { + "default": "randomManagedDocSlug()", + "$ref": "#/components/schemas/slug" + }, + "namespace": { + "$ref": "#/components/schemas/namespace" + }, + "isPrivate": { + "default": false, + "type": "boolean" + }, + "versions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/managed-schema-version" + } + } + }, + "required": [ + "uid", + "title", + "description", + "slug", + "namespace", + "isPrivate", + "versions" + ], + "additionalProperties": false + }, + "managed-schema-version": { + "type": "object", + "properties": { + "uid": { + "default": "nanoid()", + "$ref": "#/components/schemas/nanoid" + }, + "createdAt": { + "default": "unixTimestamp()", + "$ref": "#/components/schemas/timestamp" + }, + "updatedAt": { + "default": "unixTimestamp()", + "$ref": "#/components/schemas/timestamp" + }, + "version": { + "default": "0.0.1", + "$ref": "#/components/schemas/version" + } + }, + "required": [ + "uid", + "createdAt", + "updatedAt", + "version" + ], + "additionalProperties": false + }, + "timestamp": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "uid": { + "type": "object", + "properties": { + "uid": { + "$ref": "#/components/schemas/nanoid" + } + }, + "required": [ + "uid" + ], + "additionalProperties": false + }, + "login-portal-email": { + "type": "object", + "properties": { + "logo": { + "default": "", + "type": "string" + }, + "logoSize": { + "default": "100", + "type": "string" + }, + "buttonText": { + "default": "Login", + "type": "string", + "maxLength": 50 + }, + "message": { + "default": "Click to access private documentation hosted by scalar.com", + "type": "string", + "maxLength": 1000 + }, + "title": { + "default": "Private Docs", + "type": "string", + "maxLength": 100 + }, + "mainColor": { + "default": "#2a2f45", + "type": "string", + "maxLength": 100 + }, + "mainBackground": { + "default": "#f6f6f6", + "type": "string", + "maxLength": 100 + }, + "cardColor": { + "default": "2a2f45", + "type": "string", + "maxLength": 100 + }, + "cardBackground": { + "default": "#fff", + "type": "string", + "maxLength": 100 + }, + "buttonColor": { + "default": "#fff", + "type": "string", + "maxLength": 100 + }, + "buttonBackground": { + "default": "#0f0f0f", + "type": "string", + "maxLength": 100 + } + }, + "required": [ + "logo", + "logoSize", + "buttonText", + "message", + "title", + "mainColor", + "mainBackground", + "cardColor", + "cardBackground", + "buttonColor", + "buttonBackground" + ], + "additionalProperties": false + }, + "login-portal-page": { + "type": "object", + "properties": { + "title": { + "default": "Scalar Private Docs", + "type": "string", + "maxLength": 100 + }, + "description": { + "default": "Login to access your documentation", + "type": "string", + "maxLength": 500 + }, + "head": { + "default": "", + "type": "string" + }, + "script": { + "default": "", + "type": "string" + }, + "theme": { + "default": "", + "type": "string" + }, + "companyName": { + "default": "", + "type": "string", + "maxLength": 100 + }, + "logo": { + "default": "", + "type": "string" + }, + "logoURL": { + "default": "", + "type": "string" + }, + "favicon": { + "default": "", + "type": "string" + }, + "termsLink": { + "default": "", + "type": "string" + }, + "privacyLink": { + "default": "", + "type": "string" + }, + "formTitle": { + "default": "Scalar Private Docs", + "type": "string", + "maxLength": 100 + }, + "formDescription": { + "default": "Login to access your documentation", + "type": "string", + "maxLength": 500 + }, + "formImage": { + "default": "", + "type": "string" + } + }, + "required": [ + "title", + "description", + "head", + "script", + "theme", + "companyName", + "logo", + "logoURL", + "favicon", + "termsLink", + "privacyLink", + "formTitle", + "formDescription", + "formImage" + ], + "additionalProperties": false + }, + "login-portal": { + "type": "object", + "properties": { + "uid": { + "$ref": "#/components/schemas/nanoid" + }, + "title": { + "type": "string", + "maxLength": 200 + }, + "slug": { + "$ref": "#/components/schemas/slug" + } + }, + "required": [ + "uid", + "title", + "slug" + ], + "additionalProperties": false + }, + "rule": { + "type": "object", + "properties": { + "uid": { + "default": "nanoid()", + "$ref": "#/components/schemas/nanoid" + }, + "title": { + "default": "", + "type": "string", + "maxLength": 100 + }, + "description": { + "default": "", + "type": "string" + }, + "slug": { + "default": "randomManagedDocSlug()", + "$ref": "#/components/schemas/slug" + }, + "namespace": { + "$ref": "#/components/schemas/namespace" + }, + "isPrivate": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "uid", + "title", + "description", + "slug", + "namespace", + "isPrivate" + ], + "additionalProperties": false + }, + "theme": { + "type": "object", + "properties": { + "uid": { + "$ref": "#/components/schemas/nanoid" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "slug": { + "$ref": "#/components/schemas/slug" + } + }, + "required": [ + "uid", + "name", + "description", + "slug" + ], + "additionalProperties": false + }, + "team": { + "type": "object", + "properties": { + "uid": { + "$ref": "#/components/schemas/nanoid" + }, + "name": { + "$ref": "#/components/schemas/team-name" + }, + "imageUri": { + "$ref": "#/components/schemas/team-image" + }, + "slug": { + "$ref": "#/components/schemas/slug" + }, + "theme": { + "type": "string" + } + }, + "required": [ + "uid", + "name", + "slug", + "theme" + ], + "additionalProperties": false + }, + "team-name": { + "type": "string" + }, + "team-image": { + "type": "string" + }, + "github-project": { + "type": "object", + "properties": { + "uid": { + "default": "nanoid()", + "$ref": "#/components/schemas/nanoid" + }, + "createdAt": { + "default": "unixTimestamp()", + "$ref": "#/components/schemas/timestamp" + }, + "updatedAt": { + "default": "unixTimestamp()", + "$ref": "#/components/schemas/timestamp" + }, + "name": { + "type": "string" + }, + "activeDeployment": { + "default": null, + "anyOf": [ + { + "$ref": "#/components/schemas/active-deployment" + }, + { + "type": "null" + } + ] + }, + "lastPublished": { + "default": null, + "anyOf": [ + { + "$ref": "#/components/schemas/timestamp" + }, + { + "type": "null" + } + ] + }, + "lastPublishedUid": { + "default": null, + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "loginPortalUid": { + "default": "", + "type": "string" + }, + "activeThemeId": { + "default": "", + "type": "string" + }, + "typesenseId": { + "type": "number" + }, + "isPrivate": { + "default": false, + "type": "boolean" + }, + "agentEnabled": { + "default": false, + "type": "boolean" + }, + "accessGroups": { + "default": [] + }, + "slug": { + "$ref": "#/components/schemas/slug" + }, + "publishStatus": { + "default": "", + "type": "string" + }, + "publishMessage": { + "default": "", + "type": "string" + }, + "repository": { + "anyOf": [ + { + "$ref": "#/components/schemas/github-project-repository" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "uid", + "createdAt", + "updatedAt", + "name", + "activeDeployment", + "lastPublished", + "lastPublishedUid", + "loginPortalUid", + "activeThemeId", + "isPrivate", + "agentEnabled", + "accessGroups", + "slug", + "publishStatus", + "publishMessage" + ], + "additionalProperties": false + }, + "active-deployment": { + "type": "object", + "properties": { + "uid": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "publishedAt": { + "$ref": "#/components/schemas/timestamp" + } + }, + "required": [ + "uid", + "domain", + "publishedAt" + ], + "additionalProperties": false + }, + "github-project-repository": { + "type": "object", + "properties": { + "linkedBy": { + "type": "string" + }, + "id": { + "type": "number" + }, + "name": { + "type": "string", + "minLength": 2 + }, + "configPath": { + "default": "", + "type": "string" + }, + "branch": { + "default": "", + "type": "string" + }, + "publishOnMerge": { + "default": false, + "type": "boolean" + }, + "publishPreviews": { + "default": false, + "type": "boolean" + }, + "prComments": { + "default": false, + "type": "boolean" + }, + "expired": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "linkedBy", + "id", + "name", + "configPath", + "branch", + "publishOnMerge", + "publishPreviews", + "prComments", + "expired" + ], + "additionalProperties": false + }, + "email": { + "type": "string", + "format": "email", + "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" + }, + "team-summary": { + "type": "object", + "properties": { + "uid": { + "$ref": "#/components/schemas/nanoid" + }, + "name": { + "$ref": "#/components/schemas/team-name" + }, + "imageUri": { + "$ref": "#/components/schemas/team-image" + } + }, + "required": [ + "uid", + "name" + ], + "additionalProperties": false + }, + "user": { + "type": "object", + "properties": { + "uid": { + "default": "nanoid()", + "$ref": "#/components/schemas/nanoid" + }, + "createdAt": { + "default": "unixTimestamp()", + "$ref": "#/components/schemas/timestamp" + }, + "updatedAt": { + "default": "unixTimestamp()", + "$ref": "#/components/schemas/timestamp" + }, + "email": { + "$ref": "#/components/schemas/email" + }, + "theme": { + "type": "string" + }, + "activeTeamId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "hasGithub": { + "default": false, + "type": "boolean" + }, + "teams": { + "type": "array", + "items": { + "$ref": "#/components/schemas/team-summary" + } + } + }, + "required": [ + "uid", + "createdAt", + "updatedAt", + "email", + "activeTeamId", + "hasGithub", + "teams" + ], + "additionalProperties": false + } + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "paths": { + "/v1/apis": { + "get": { + "tags": [ + "Registry" + ], + "description": "List all API documents across every namespace the caller can access.", + "summary": "List all API Documents", + "operationId": "listAllApiDocuments", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/api-document" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst listAllAPIDocuments = await client.registry.listAllAPIDocuments();\nconsole.log(listAllAPIDocuments);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry list-all-api-documents --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.list_all_api_documents()\nprint(registry)" + } + ] + } + }, + "/v1/apis/{namespace}": { + "get": { + "tags": [ + "Registry" + ], + "description": "List API documents in a namespace.", + "summary": "List API Documents in a namespace", + "operationId": "listApiDocuments", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/api-document" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst listAPIDocuments = await client.registry.listAPIDocuments(\"namespace\");\nconsole.log(listAPIDocuments);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry list-api-documents 'namespace' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.list_api_documents(\n namespace=\"namespace\",\n)\nprint(registry)" + } + ] + }, + "post": { + "tags": [ + "Registry" + ], + "description": "Create an API document.", + "summary": "Create API Document", + "operationId": "createApiDocument", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uid": { + "type": "string" + }, + "versionUid": { + "type": "string" + }, + "title": { + "type": "string" + }, + "jsonSha": { + "type": "string" + }, + "yamlSha": { + "type": "string" + }, + "versionSha": { + "type": "string" + } + }, + "required": [ + "uid", + "versionUid", + "title", + "jsonSha", + "yamlSha", + "versionSha" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "version": { + "$ref": "#/components/schemas/version" + }, + "slug": { + "type": "string" + }, + "ruleset": { + "type": "string" + }, + "isPrivate": { + "type": "boolean" + }, + "document": { + "type": "string" + } + }, + "required": [ + "title", + "version", + "slug", + "document" + ], + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst createAPIDocument = await client.registry.createAPIDocument(\"namespace\", {\n title: \"\",\n version: \"\",\n slug: \"\",\n document: \"\",\n});\nconsole.log(createAPIDocument);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry create-api-document 'namespace' --bearer-auth \"$BEARER_AUTH\" --title 'title' --version-command 'version' --slug 'slug' --document 'document'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.create_api_document(\n namespace=\"namespace\",\n title=\"\",\n version=\"\",\n slug=\"\",\n document=\"\",\n idempotency_key=\"\",\n)\nprint(registry)" + } + ] + } + }, + "/v1/apis/{namespace}/{slug}": { + "patch": { + "tags": [ + "Registry" + ], + "description": "Update metadata for an API document.", + "summary": "Update API Document metadata", + "operationId": "updateApiDocument", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "isPrivate": { + "type": "boolean" + }, + "ruleset": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.registry.updateAPIDocument(\"namespace\", \"slug\", {});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry update-api-document 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.update_api_document(\n namespace=\"namespace\",\n slug=\"slug\",\n idempotency_key=\"\",\n)\nprint(registry)" + } + ] + }, + "delete": { + "tags": [ + "Registry" + ], + "description": "Delete an API document and all versions.", + "summary": "Delete API Document", + "operationId": "deleteApiDocument", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.registry.deleteAPIDocument(\"namespace\", \"slug\");" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry delete-api-document 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.delete_api_document(\n namespace=\"namespace\",\n slug=\"slug\",\n idempotency_key=\"\",\n)\nprint(registry)" + } + ] + } + }, + "/v1/apis/{namespace}/{slug}/version/{semver}": { + "get": { + "tags": [ + "Registry" + ], + "description": "Get a specific API document version.", + "summary": "Get API Document", + "operationId": "getApiDocumentVersion", + "responses": { + "200": { + "description": "Default Response", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "semver", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst string_ = await client.registry.retrieveAPIDocumentVersion(\"namespace\", \"slug\", \"semver\");\nconsole.log(string_);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry retrieve-api-document-version 'namespace' 'slug' 'semver' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.retrieve_api_document_version(\n namespace=\"namespace\",\n slug=\"slug\",\n semver=\"semver\",\n)\nprint(registry)" + } + ] + }, + "patch": { + "tags": [ + "Registry" + ], + "description": "Update the registry file content for an API document version.", + "summary": "Update API Document version", + "operationId": "updateApiDocumentVersion", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jsonSha": { + "type": "string" + }, + "yamlSha": { + "type": "string" + }, + "versionSha": { + "type": "string" + } + }, + "required": [ + "jsonSha", + "yamlSha", + "versionSha" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "document": { + "type": "string" + }, + "lastKnownVersionSha": { + "type": "string" + } + }, + "required": [ + "document" + ], + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "semver", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst updateAPIDocumentVersion = await client.registry.updateAPIDocumentVersion(\"namespace\", \"slug\", \"semver\", {\n document: \"\",\n});\nconsole.log(updateAPIDocumentVersion);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry update-api-document-version 'namespace' 'slug' 'semver' --bearer-auth \"$BEARER_AUTH\" --document 'document'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.update_api_document_version(\n namespace=\"namespace\",\n slug=\"slug\",\n semver=\"semver\",\n document=\"\",\n idempotency_key=\"\",\n)\nprint(registry)" + } + ] + }, + "delete": { + "tags": [ + "Registry" + ], + "description": "Delete a specific API document version.", + "summary": "Delete API Document version", + "operationId": "deleteApiDocumentVersion", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "semver", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.registry.deleteAPIDocumentVersion(\"namespace\", \"slug\", \"semver\");" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry delete-api-document-version 'namespace' 'slug' 'semver' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.delete_api_document_version(\n namespace=\"namespace\",\n slug=\"slug\",\n semver=\"semver\",\n idempotency_key=\"\",\n)\nprint(registry)" + } + ] + } + }, + "/v1/apis/{namespace}/{slug}/version/{semver}/metadata": { + "get": { + "tags": [ + "Registry" + ], + "description": "Get metadata (uid, content shas, version sha, tags) for a specific API document version.", + "summary": "Get API Document version metadata", + "operationId": "getApiDocumentVersionMetadata", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/managed-doc-version" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "semver", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst managedDocVersion = await client.registry.listAPIDocumentVersionMetadata(\"namespace\", \"slug\", \"semver\");\nconsole.log(managedDocVersion);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry list-api-document-version-metadata 'namespace' 'slug' 'semver' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.list_api_document_version_metadata(\n namespace=\"namespace\",\n slug=\"slug\",\n semver=\"semver\",\n)\nprint(registry)" + } + ] + } + }, + "/v1/apis/{namespace}/{slug}/version": { + "post": { + "tags": [ + "Registry" + ], + "description": "Create a new API document version.", + "summary": "Create API Document version", + "operationId": "createApiDocumentVersion", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/managed-doc-version" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { + "$ref": "#/components/schemas/version" + }, + "document": { + "type": "string" + }, + "force": { + "type": "boolean" + }, + "lastKnownVersionSha": { + "type": "string" + } + }, + "required": [ + "version", + "document" + ], + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst managedDocVersion = await client.registry.createAPIDocumentVersion(\"namespace\", \"slug\", {\n version: \"\",\n document: \"\",\n});\nconsole.log(managedDocVersion);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry create-api-document-version 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\" --version-command 'version' --document 'document'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.create_api_document_version(\n namespace=\"namespace\",\n slug=\"slug\",\n version=\"\",\n document=\"\",\n idempotency_key=\"\",\n)\nprint(registry)" + } + ] + } + }, + "/v1/apis/{namespace}/{slug}/access-group": { + "post": { + "tags": [ + "Registry" + ], + "description": "Add an access group to an API document.", + "summary": "Add access group", + "operationId": "addApiDocumentAccessGroup", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/access-group" + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.registry.createAPIDocumentAccessGroup(\"namespace\", \"slug\", {\n accessGroupSlug: \"\",\n});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry create-api-document-access-group 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\" --access-group-slug 'accessGroupSlug'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.create_api_document_access_group(\n namespace=\"namespace\",\n slug=\"slug\",\n access_group_slug=\"\",\n idempotency_key=\"\",\n)\nprint(registry)" + } + ] + }, + "delete": { + "tags": [ + "Registry" + ], + "description": "Remove an access group from an API document.", + "summary": "Remove access group", + "operationId": "removeApiDocumentAccessGroup", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/access-group" + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.registry.deleteAPIDocumentAccessGroup(\"namespace\", \"slug\", {\n accessGroupSlug: \"\",\n});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi registry delete-api-document-access-group 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\" --access-group-slug 'accessGroupSlug'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nregistry = client.registry.delete_api_document_access_group(\n namespace=\"namespace\",\n slug=\"slug\",\n access_group_slug=\"\",\n idempotency_key=\"\",\n)\nprint(registry)" + } + ] + } + }, + "/v1/schemas/{namespace}": { + "get": { + "tags": [ + "Schemas" + ], + "description": "List schemas in a namespace.", + "summary": "List all shared components", + "operationId": "listSchemas", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/schema" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst list = await client.schemas.list(\"namespace\");\nconsole.log(list);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi schemas list 'namespace' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nschema = client.schemas.list(\n namespace=\"namespace\",\n)\nprint(schema)" + } + ] + }, + "post": { + "tags": [ + "Schemas" + ], + "description": "Create a schema in a namespace.", + "summary": "Create a shared component", + "operationId": "createSchema", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/uid" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "version": { + "$ref": "#/components/schemas/version" + }, + "slug": { + "type": "string" + }, + "isPrivate": { + "type": "boolean" + }, + "document": { + "type": "string" + } + }, + "required": [ + "title", + "version", + "slug", + "document" + ], + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst uID = await client.schemas.create(\"namespace\", {\n title: \"\",\n version: \"\",\n slug: \"\",\n document: \"\",\n});\nconsole.log(uID);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi schemas create 'namespace' --bearer-auth \"$BEARER_AUTH\" --title 'title' --version-command 'version' --slug 'slug' --document 'document'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nschema = client.schemas.create(\n namespace=\"namespace\",\n title=\"\",\n version=\"\",\n slug=\"\",\n document=\"\",\n idempotency_key=\"\",\n)\nprint(schema)" + } + ] + } + }, + "/v1/schemas/{namespace}/{slug}": { + "patch": { + "tags": [ + "Schemas" + ], + "description": "Update schema metadata.", + "summary": "Update shared component metadata", + "operationId": "updateSchema", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "isPrivate": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.schemas.update(\"namespace\", \"slug\", {});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi schemas update 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nschema = client.schemas.update(\n namespace=\"namespace\",\n slug=\"slug\",\n idempotency_key=\"\",\n)\nprint(schema)" + } + ] + }, + "delete": { + "tags": [ + "Schemas" + ], + "description": "Delete a schema and all related versions.", + "summary": "Delete a shared component", + "operationId": "deleteSchema", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.schemas.delete(\"namespace\", \"slug\");" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi schemas delete 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nschema = client.schemas.delete(\n namespace=\"namespace\",\n slug=\"slug\",\n idempotency_key=\"\",\n)\nprint(schema)" + } + ] + } + }, + "/v1/schemas/{namespace}/{slug}/version/{semver}": { + "get": { + "tags": [ + "Schemas" + ], + "description": "Get a specific schema version document.", + "summary": "Get a shared component document", + "operationId": "getSchemaVersion", + "responses": { + "200": { + "description": "Default Response", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "semver", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst string_ = await client.schemas.version.retrieveSchema(\"namespace\", \"slug\", \"semver\");\nconsole.log(string_);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi schemas:version-command retrieve-schema 'namespace' 'slug' 'semver' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nversion = client.schemas.version.retrieve_schema(\n namespace=\"namespace\",\n slug=\"slug\",\n semver=\"semver\",\n)\nprint(version)" + } + ] + }, + "delete": { + "tags": [ + "Schemas" + ], + "description": "Delete a schema version.", + "summary": "Delete a shared component version", + "operationId": "deleteSchemaVersion", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "semver", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.schemas.version.deleteSchema(\"namespace\", \"slug\", \"semver\");" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi schemas:version-command delete-schema 'namespace' 'slug' 'semver' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nversion = client.schemas.version.delete_schema(\n namespace=\"namespace\",\n slug=\"slug\",\n semver=\"semver\",\n idempotency_key=\"\",\n)\nprint(version)" + } + ] + } + }, + "/v1/schemas/{namespace}/{slug}/version": { + "post": { + "tags": [ + "Schemas" + ], + "description": "Create a schema version.", + "summary": "Create a shared component version", + "operationId": "createSchemaVersion", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/uid" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { + "$ref": "#/components/schemas/version" + }, + "document": { + "type": "string" + } + }, + "required": [ + "version", + "document" + ], + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst uID = await client.schemas.version.createSchema(\"namespace\", \"slug\", {\n version: \"\",\n document: \"\",\n});\nconsole.log(uID);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi schemas:version-command create-schema 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\" --version-command 'version' --document 'document'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nversion = client.schemas.version.create_schema(\n namespace=\"namespace\",\n slug=\"slug\",\n version=\"\",\n document=\"\",\n idempotency_key=\"\",\n)\nprint(version)" + } + ] + } + }, + "/v1/schemas/{namespace}/{slug}/access-group": { + "post": { + "tags": [ + "Schemas" + ], + "description": "Add an access group to a schema.", + "summary": "Add shared component access group", + "operationId": "addSchemaAccessGroup", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/access-group" + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.schemas.accessGroup.createSchema(\"namespace\", \"slug\", {\n accessGroupSlug: \"\",\n});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi schemas:access-group create-schema 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\" --access-group-slug 'accessGroupSlug'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\naccess_group = client.schemas.access_group.create_schema(\n namespace=\"namespace\",\n slug=\"slug\",\n access_group_slug=\"\",\n idempotency_key=\"\",\n)\nprint(access_group)" + } + ] + }, + "delete": { + "tags": [ + "Schemas" + ], + "description": "Remove an access group from a schema.", + "summary": "Remove shared component access group", + "operationId": "removeSchemaAccessGroup", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/access-group" + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.schemas.accessGroup.deleteSchema(\"namespace\", \"slug\", {\n accessGroupSlug: \"\",\n});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi schemas:access-group delete-schema 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\" --access-group-slug 'accessGroupSlug'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\naccess_group = client.schemas.access_group.delete_schema(\n namespace=\"namespace\",\n slug=\"slug\",\n access_group_slug=\"\",\n idempotency_key=\"\",\n)\nprint(access_group)" + } + ] + } + }, + "/v1/login-portals/{slug}": { + "get": { + "tags": [ + "Login Portals" + ], + "description": "Get a login portal by slug.", + "summary": "Get a login portal", + "operationId": "getLoginPortal", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uid": { + "type": "string" + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "email": { + "$ref": "#/components/schemas/login-portal-email" + }, + "page": { + "$ref": "#/components/schemas/login-portal-page" + } + }, + "required": [ + "uid", + "title", + "slug", + "email", + "page" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst retrieve = await client.loginPortals.retrieve(\"slug\");\nconsole.log(retrieve);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi login-portals retrieve 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nlogin_portal = client.login_portals.retrieve(\n slug=\"slug\",\n)\nprint(login_portal)" + } + ] + }, + "patch": { + "tags": [ + "Login Portals" + ], + "description": "Update metadata for a login portal.", + "summary": "Update portal metadata", + "operationId": "updateLoginPortal", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.loginPortals.update(\"slug\", {});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi login-portals update 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nlogin_portal = client.login_portals.update(\n slug=\"slug\",\n idempotency_key=\"\",\n)\nprint(login_portal)" + } + ] + }, + "delete": { + "tags": [ + "Login Portals" + ], + "description": "Delete a login portal.", + "summary": "Delete a login portal", + "operationId": "deleteLoginPortal", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.loginPortals.delete(\"slug\");" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi login-portals delete 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nlogin_portal = client.login_portals.delete(\n slug=\"slug\",\n idempotency_key=\"\",\n)\nprint(login_portal)" + } + ] + } + }, + "/v1/login-portals": { + "post": { + "tags": [ + "Login Portals" + ], + "description": "Create a login portal for the current team.", + "summary": "Create a portal", + "operationId": "createLoginPortal", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/uid" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "email": { + "$ref": "#/components/schemas/login-portal-email" + }, + "page": { + "$ref": "#/components/schemas/login-portal-page" + } + }, + "required": [ + "title", + "slug", + "email", + "page" + ], + "additionalProperties": false + } + } + }, + "required": true + }, + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst uID = await client.loginPortals.create({\n title: \"\",\n slug: \"\",\n email: {\n logo: \"\",\n logoSize: \"100\",\n buttonText: \"Login\",\n message: \"Click to access private documentation hosted by scalar.com\",\n title: \"Private Docs\",\n mainColor: \"#2a2f45\",\n mainBackground: \"#f6f6f6\",\n cardColor: \"2a2f45\",\n cardBackground: \"#fff\",\n buttonColor: \"#fff\",\n buttonBackground: \"#0f0f0f\",\n },\n page: {\n title: \"Scalar Private Docs\",\n description: \"Login to access your documentation\",\n head: \"\",\n script: \"\",\n theme: \"\",\n companyName: \"\",\n logo: \"\",\n logoURL: \"\",\n favicon: \"\",\n termsLink: \"\",\n privacyLink: \"\",\n formTitle: \"Scalar Private Docs\",\n formDescription: \"Login to access your documentation\",\n formImage: \"\",\n },\n});\nconsole.log(uID);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi login-portals create --bearer-auth \"$BEARER_AUTH\" --title 'title' --slug 'slug' --email '{\"logo\":\"\",\"logoSize\":\"100\",\"buttonText\":\"Login\",\"message\":\"Click to access private documentation hosted by scalar.com\",\"title\":\"Private Docs\",\"mainColor\":\"#2a2f45\",\"mainBackground\":\"#f6f6f6\",\"cardColor\":\"2a2f45\",\"cardBackground\":\"#fff\",\"buttonColor\":\"#fff\",\"buttonBackground\":\"#0f0f0f\"}' --page '{\"title\":\"Scalar Private Docs\",\"description\":\"Login to access your documentation\",\"head\":\"\",\"script\":\"\",\"theme\":\"\",\"companyName\":\"\",\"logo\":\"\",\"logoURL\":\"\",\"favicon\":\"\",\"termsLink\":\"\",\"privacyLink\":\"\",\"formTitle\":\"Scalar Private Docs\",\"formDescription\":\"Login to access your documentation\",\"formImage\":\"\"}'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nlogin_portal = client.login_portals.create(\n title=\"\",\n slug=\"\",\n email={\"logo\": \"\", \"logo_size\": \"100\", \"button_text\": \"Login\", \"message\": \"Click to access private documentation hosted by scalar.com\", \"title\": \"Private Docs\", \"main_color\": \"#2a2f45\", \"main_background\": \"#f6f6f6\", \"card_color\": \"2a2f45\", \"card_background\": \"#fff\", \"button_color\": \"#fff\", \"button_background\": \"#0f0f0f\"},\n page={\"title\": \"Scalar Private Docs\", \"description\": \"Login to access your documentation\", \"head\": \"\", \"script\": \"\", \"theme\": \"\", \"company_name\": \"\", \"logo\": \"\", \"logo_url\": \"\", \"favicon\": \"\", \"terms_link\": \"\", \"privacy_link\": \"\", \"form_title\": \"Scalar Private Docs\", \"form_description\": \"Login to access your documentation\", \"form_image\": \"\"},\n idempotency_key=\"\",\n)\nprint(login_portal)" + } + ] + }, + "get": { + "tags": [ + "Login Portals" + ], + "description": "List all login portals for the current team.", + "summary": "List all portals", + "operationId": "listLoginPortals", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/login-portal" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst list = await client.loginPortals.list();\nconsole.log(list);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi login-portals list --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nlogin_portal = client.login_portals.list()\nprint(login_portal)" + } + ] + } + }, + "/v1/rulesets/{namespace}": { + "get": { + "tags": [ + "Rules" + ], + "description": "List all rulesets in a namespace.", + "summary": "List all rules", + "operationId": "listRulesets", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/rule" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst listRulesets = await client.rules.listRulesets(\"namespace\");\nconsole.log(listRulesets);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi rules list-rulesets 'namespace' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nrule = client.rules.list_rulesets(\n namespace=\"namespace\",\n)\nprint(rule)" + } + ] + }, + "post": { + "tags": [ + "Rules" + ], + "description": "Create a rule in a namespace.", + "summary": "Create a rule", + "operationId": "createRuleset", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/uid" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "isPrivate": { + "type": "boolean" + }, + "document": { + "type": "string" + } + }, + "required": [ + "title", + "slug", + "document" + ], + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst uID = await client.rules.createRuleset(\"namespace\", {\n title: \"\",\n slug: \"\",\n document: \"\",\n});\nconsole.log(uID);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi rules create-ruleset 'namespace' --bearer-auth \"$BEARER_AUTH\" --title 'title' --slug 'slug' --document 'document'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nrule = client.rules.create_ruleset(\n namespace=\"namespace\",\n title=\"\",\n slug=\"\",\n document=\"\",\n idempotency_key=\"\",\n)\nprint(rule)" + } + ] + } + }, + "/v1/rulesets/{namespace}/{slug}": { + "patch": { + "tags": [ + "Rules" + ], + "description": "Update rule metadata by slug.", + "summary": "Update rule metadata", + "operationId": "updateRuleset", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "namespace": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "isPrivate": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.rules.updateRuleset(\"namespace\", \"slug\", {});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi rules update-ruleset 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nrule = client.rules.update_ruleset(\n namespace=\"namespace\",\n slug=\"slug\",\n idempotency_key=\"\",\n)\nprint(rule)" + } + ] + }, + "delete": { + "tags": [ + "Rules" + ], + "description": "Delete a rule by slug.", + "summary": "Delete a rule", + "operationId": "deleteRuleset", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.rules.deleteRuleset(\"namespace\", \"slug\");" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi rules delete-ruleset 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nrule = client.rules.delete_ruleset(\n namespace=\"namespace\",\n slug=\"slug\",\n idempotency_key=\"\",\n)\nprint(rule)" + } + ] + }, + "get": { + "tags": [ + "Rules" + ], + "description": "Get a rule document by slug.", + "summary": "Get a rule", + "operationId": "getRulesetDocument", + "responses": { + "200": { + "description": "Default Response", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst string_ = await client.rules.retrieveRulesetDocument(\"namespace\", \"slug\");\nconsole.log(string_);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi rules retrieve-ruleset-document 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nrule = client.rules.retrieve_ruleset_document(\n namespace=\"namespace\",\n slug=\"slug\",\n)\nprint(rule)" + } + ] + } + }, + "/v1/rulesets/{namespace}/{slug}/access-group": { + "post": { + "tags": [ + "Rules" + ], + "description": "Grant an access group to a rule.", + "summary": "Add rule access group", + "operationId": "addRulesetAccessGroup", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/access-group" + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.rules.createRulesetAccessGroup(\"namespace\", \"slug\", {\n accessGroupSlug: \"\",\n});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi rules create-ruleset-access-group 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\" --access-group-slug 'accessGroupSlug'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nrule = client.rules.create_ruleset_access_group(\n namespace=\"namespace\",\n slug=\"slug\",\n access_group_slug=\"\",\n idempotency_key=\"\",\n)\nprint(rule)" + } + ] + }, + "delete": { + "tags": [ + "Rules" + ], + "description": "Remove an access group from a rule.", + "summary": "Remove rule access group", + "operationId": "removeRulesetAccessGroup", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/access-group" + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "namespace", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.rules.deleteRulesetAccessGroup(\"namespace\", \"slug\", {\n accessGroupSlug: \"\",\n});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi rules delete-ruleset-access-group 'namespace' 'slug' --bearer-auth \"$BEARER_AUTH\" --access-group-slug 'accessGroupSlug'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nrule = client.rules.delete_ruleset_access_group(\n namespace=\"namespace\",\n slug=\"slug\",\n access_group_slug=\"\",\n idempotency_key=\"\",\n)\nprint(rule)" + } + ] + } + }, + "/v1/themes": { + "get": { + "tags": [ + "Themes" + ], + "description": "List all team themes.", + "summary": "List all themes", + "operationId": "listThemes", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/theme" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst list = await client.themes.list();\nconsole.log(list);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi themes list --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\ntheme = client.themes.list()\nprint(theme)" + } + ] + }, + "post": { + "tags": [ + "Themes" + ], + "description": "Create a team theme.", + "summary": "Create a theme", + "operationId": "createTheme", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/uid" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "document": { + "type": "string" + } + }, + "required": [ + "name", + "slug", + "document" + ], + "additionalProperties": false + } + } + }, + "required": true + }, + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst uID = await client.themes.create({\n name: \"\",\n slug: \"\",\n document: \"\",\n});\nconsole.log(uID);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi themes create --bearer-auth \"$BEARER_AUTH\" --name 'name' --slug 'slug' --document 'document'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\ntheme = client.themes.create(\n name=\"\",\n slug=\"\",\n document=\"\",\n idempotency_key=\"\",\n)\nprint(theme)" + } + ] + } + }, + "/v1/themes/{slug}": { + "patch": { + "tags": [ + "Themes" + ], + "description": "Update theme metadata.", + "summary": "Update theme metadata", + "operationId": "updateTheme", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.themes.update(\"slug\", {});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi themes update 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\ntheme = client.themes.update(\n slug=\"slug\",\n idempotency_key=\"\",\n)\nprint(theme)" + } + ] + }, + "put": { + "tags": [ + "Themes" + ], + "description": "Replace the theme document.", + "summary": "Update theme document", + "operationId": "replaceThemeDocument", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "document": { + "type": "string" + } + }, + "required": [ + "document" + ], + "additionalProperties": false + } + } + }, + "required": true + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.themes.replaceDocument(\"slug\", {\n document: \"\",\n});" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi themes replace-document 'slug' --bearer-auth \"$BEARER_AUTH\" --document 'document'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\ntheme = client.themes.replace_document(\n slug=\"slug\",\n document=\"\",\n idempotency_key=\"\",\n)\nprint(theme)" + } + ] + }, + "delete": { + "tags": [ + "Themes" + ], + "description": "Delete a theme by slug.", + "summary": "Delete a theme", + "operationId": "deleteTheme", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "null" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nawait client.themes.delete(\"slug\");" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi themes delete 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\ntheme = client.themes.delete(\n slug=\"slug\",\n idempotency_key=\"\",\n)\nprint(theme)" + } + ] + }, + "get": { + "tags": [ + "Themes" + ], + "description": "Get the theme document by slug.", + "summary": "Get a theme", + "operationId": "getTheme", + "responses": { + "200": { + "description": "Default Response", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst string_ = await client.themes.retrieve(\"slug\");\nconsole.log(string_);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi themes retrieve 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\ntheme = client.themes.retrieve(\n slug=\"slug\",\n)\nprint(theme)" + } + ] + } + }, + "/v1/teams": { + "get": { + "tags": [ + "Teams" + ], + "description": "List all available teams", + "summary": "List teams", + "operationId": "listTeams", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/team" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst list = await client.teams.list();\nconsole.log(list);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi teams list --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nteam = client.teams.list()\nprint(team)" + } + ] + } + }, + "/v1/guides": { + "get": { + "tags": [ + "Scalar Docs" + ], + "description": "List all guide projects.", + "summary": "List all projects", + "operationId": "listGuides", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/github-project" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst listGuides = await client.scalarDocs.listGuides();\nconsole.log(listGuides);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi scalar-docs list-guides --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nscalar_doc = client.scalar_docs.list_guides()\nprint(scalar_doc)" + } + ] + }, + "post": { + "tags": [ + "Scalar Docs" + ], + "description": "Create a guide project.", + "summary": "Create a project", + "operationId": "createGuide", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uid": { + "type": "string" + }, + "slug": { + "type": "string" + } + }, + "required": [ + "uid", + "slug" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "slug": { + "$ref": "#/components/schemas/slug" + }, + "isPrivate": { + "default": false, + "type": "boolean" + }, + "allowedUsers": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "allowedDomains": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "name", + "isPrivate", + "allowedUsers", + "allowedDomains" + ], + "additionalProperties": false + } + } + }, + "required": true + }, + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst createGuide = await client.scalarDocs.createGuide({\n name: \"\",\n isPrivate: false,\n allowedUsers: [],\n allowedDomains: [],\n});\nconsole.log(createGuide);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi scalar-docs create-guide --bearer-auth \"$BEARER_AUTH\" --name 'name' --is-private" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nscalar_doc = client.scalar_docs.create_guide(\n name=\"\",\n is_private=False,\n allowed_users=[],\n allowed_domains=[],\n idempotency_key=\"\",\n)\nprint(scalar_doc)" + } + ] + } + }, + "/v1/guides/{slug}/publish": { + "post": { + "tags": [ + "Scalar Docs" + ], + "description": "Start a new publish process.", + "summary": "Publish a project", + "operationId": "publishGuide", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "publishUid": { + "type": "string" + } + }, + "required": [ + "publishUid" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "slug", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst publishGuide = await client.scalarDocs.publishGuide(\"slug\");\nconsole.log(publishGuide);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi scalar-docs publish-guide 'slug' --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nscalar_doc = client.scalar_docs.publish_guide(\n slug=\"slug\",\n idempotency_key=\"\",\n)\nprint(scalar_doc)" + } + ] + } + }, + "/v1/namespaces": { + "get": { + "tags": [ + "Namespaces" + ], + "description": "Get all namespaces for the current team", + "summary": "List namespaces", + "operationId": "listNamespaces", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst list = await client.namespaces.list();\nconsole.log(list);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi namespaces list --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nnamespace = client.namespaces.list()\nprint(namespace)" + } + ] + } + }, + "/v1/auth/exchange": { + "post": { + "tags": [ + "Authentication" + ], + "description": "Exchange an API key for an access token.", + "summary": "Exchange token", + "operationId": "exchangePersonalToken", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "accessToken": { + "type": "string" + } + }, + "required": [ + "accessToken" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "security": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "personalToken": { + "type": "string" + } + }, + "required": [ + "personalToken" + ], + "additionalProperties": false + } + } + }, + "required": true + }, + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst exchangePersonalToken = await client.authentication.exchangePersonalToken({\n personalToken: \"\",\n});\nconsole.log(exchangePersonalToken);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi authentication exchange-personal-token --bearer-auth \"$BEARER_AUTH\" --personal-token 'personalToken'" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nauthentication = client.authentication.exchange_personal_token(\n personal_token=\"\",\n idempotency_key=\"\",\n)\nprint(authentication)" + } + ] + } + }, + "/v1/auth/me": { + "get": { + "tags": [ + "Authentication" + ], + "description": "Get the authenticated user, including their available teams and theme.", + "summary": "Get current user", + "operationId": "getCurrentUser", + "responses": { + "200": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400" + } + } + } + }, + "401": { + "description": "No auth", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/401" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/403" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404" + } + } + } + }, + "422": { + "description": "Invalid payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422" + } + } + } + }, + "500": { + "description": "Uncaught error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/500" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Scalar from \"@scalar/sdk\";\n\nconst client = new Scalar({\n bearerAuth: process.env[\"BEARER_AUTH\"], // defaults to the BEARER_AUTH env var\n environment: \"production\",\n});\n\nconst user = await client.authentication.listCurrentUser();\nconsole.log(user);" + }, + { + "lang": "Shell", + "label": "CLI", + "source": "scalarapi authentication list-current-user --bearer-auth \"$BEARER_AUTH\"" + }, + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom scalar_api import Scalar\n\nclient = Scalar(\n bearer_auth=os.environ.get(\"BEARER_AUTH\"),\n)\n\nauthentication = client.authentication.list_current_user()\nprint(authentication)" + } + ] + } + } + } +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..18e50fc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "scalarApi" +version = "0.2.5" +description = "API for managing Scalar platform resources.\n\n## TypeScript SDK\n\nFor TypeScript, we provide a SDK that makes using our API even easier.\n\n### Install\n\n```bash\nnpm add @scalar/sdk\n```\n\n### Get a Scalar API key\n\nCreate an API key in your Scalar account:\n\n- Dashboard: https://dashboard.scalar.com/account\n- Store it in `.env`, for example:\n\n```bash\nSCALAR_API_KEY=your_personal_token\n```\n\n### Exchange your API key for an access token\n\nThe personal token is not an access token. Exchange it first with `postv1AuthExchange`.\n\nIf you use the personal token directly for authenticated API calls, the API returns `401 Invalid authentication token`.\n\n```ts\nimport { Scalar } from '@scalar/sdk'\n\nconst scalar = new Scalar()\n\nconst exchange = await scalar.auth.postv1AuthExchange({\n personalToken: process.env.SCALAR_API_KEY!,\n})\n\nconst accessToken = exchange.accessToken\n```\n\n### Use the access token\n\nConstruct a second client with bearer auth. Use this authenticated client for API calls.\n\n```ts\nimport { Scalar } from '@scalar/sdk'\n\nconst scalar = new Scalar()\n\nconst exchange = await scalar.auth.postv1AuthExchange({\n personalToken: process.env.SCALAR_API_KEY!,\n})\n\nconst authedScalar = new Scalar({\n bearerAuth: exchange.accessToken,\n})\n```\n\n### Notes\n\n- The exchange request itself can be made from a client constructed with no arguments (`new Scalar()`).\n- The exchanged access token is valid for 12 hours.\n- Timestamps are Unix seconds.\n\n### Read more\n\n- [@scalar/sdk on npm](https://www.npmjs.com/package/@scalar/sdk)" +license = "MIT" +requires-python = ">= 3.9" +dependencies = [ + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.14, <5", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", +] + + +[build-system] +requires = ["hatchling>=1.18.0"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src"] + +[tool.hatch.build.targets.wheel.sources] +"src" = "scalar_api" diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..eb6c26e --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,8 @@ +{ + "packages": { + ".": { + "package-name": "scalarApi", + "release-type": "python" + } + } +} diff --git a/scalar-sdk.manifest.json b/scalar-sdk.manifest.json new file mode 100644 index 0000000..cd22ddd --- /dev/null +++ b/scalar-sdk.manifest.json @@ -0,0 +1,9662 @@ +{ + "name": "ScalarApi", + "slug": "scalarApi", + "version": "0.2.4", + "servers": [ + "https://access.scalar.com" + ], + "environments": { + "production": "https://access.scalar.com", + "local": "http://127.0.0.1:4010" + }, + "environmentOrder": [ + "production", + "local" + ], + "auth": [ + "bearer" + ], + "authDetails": [ + { + "kind": "bearer", + "id": "BearerAuth", + "bearerFormat": "JWT" + } + ], + "clientHeaderParams": [], + "schemas": [ + { + "name": "_400", + "source": "_400", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "message", + "publicName": "message", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "code", + "publicName": "code", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "_401", + "source": "_401", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "message", + "publicName": "message", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "code", + "publicName": "code", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "_403", + "source": "_403", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "message", + "publicName": "message", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "code", + "publicName": "code", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "_404", + "source": "_404", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "message", + "publicName": "message", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "code", + "publicName": "code", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "_422", + "source": "_422", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "message", + "publicName": "message", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "code", + "publicName": "code", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "_500", + "source": "_500", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "message", + "publicName": "message", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "code", + "publicName": "code", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ApiDocument", + "source": "ApiDocument", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + }, + { + "name": "version", + "publicName": "version", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Version" + } + }, + { + "name": "title", + "publicName": "title", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "slug", + "publicName": "slug", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Slug" + } + }, + { + "name": "description", + "publicName": "description", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "namespace", + "publicName": "namespace", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Namespace" + } + }, + { + "name": "isPrivate", + "publicName": "isPrivate", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "tags", + "publicName": "tags", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "versions", + "publicName": "versions", + "required": true, + "deprecated": false, + "type": { + "kind": "array", + "items": { + "kind": "ref", + "name": "ManagedDocVersion" + } + } + } + ], + "additionalProperties": false + } + }, + { + "name": "Nanoid", + "source": "Nanoid", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "minLength": 5 + } + } + }, + { + "name": "Version", + "source": "Version", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "minLength": 1 + } + } + }, + { + "name": "Slug", + "source": "Slug", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "pattern": "^[a-z](?:[a-z0-9-]*[a-z0-9])?$", + "minLength": 3, + "maxLength": 60 + } + } + }, + { + "name": "Namespace", + "source": "Namespace", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "pattern": "^[a-zA-Z0-9-_]+$", + "minLength": 3, + "maxLength": 50 + } + } + }, + { + "name": "ManagedDocVersion", + "source": "ManagedDocVersion", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + }, + { + "name": "createdAt", + "publicName": "createdAt", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "number" + } + }, + { + "name": "version", + "publicName": "version", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Version" + } + }, + { + "name": "upgraded", + "publicName": "upgraded", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "embedStatus", + "publicName": "embedStatus", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "enum", + "values": [ + "complete", + "failed" + ], + "names": [ + "Complete", + "Failed" + ], + "deprecations": [ + false, + false + ] + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "tags", + "publicName": "tags", + "required": true, + "deprecated": false, + "type": { + "kind": "array", + "items": { + "kind": "primitive", + "type": "string" + } + } + }, + { + "name": "tools", + "publicName": "tools", + "required": false, + "deprecated": false, + "type": { + "kind": "array", + "items": { + "kind": "object", + "properties": [ + { + "name": "path", + "publicName": "path", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "method", + "publicName": "method", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Method" + } + }, + { + "name": "enabledTools", + "publicName": "enabledTools", + "required": true, + "deprecated": false, + "type": { + "kind": "array", + "items": { + "kind": "enum", + "values": [ + "execute-request", + "get-mini-openapi-spec" + ], + "names": [ + "ExecuteRequest", + "GetMiniOpenapiSpec" + ], + "deprecations": [ + false, + false + ] + } + } + } + ], + "additionalProperties": false + } + } + }, + { + "name": "yamlSha", + "publicName": "yamlSha", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "jsonSha", + "publicName": "jsonSha", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "versionSha", + "publicName": "versionSha", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "Method", + "source": "Method", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "delete", + "get", + "head", + "options", + "patch", + "post", + "put", + "trace" + ], + "names": [ + "Delete", + "Get", + "Head", + "Options", + "Patch", + "Post", + "Put", + "Trace" + ], + "deprecations": [ + false, + false, + false, + false, + false, + false, + false, + false + ] + } + }, + { + "name": "AccessGroup", + "source": "AccessGroup", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "accessGroupSlug", + "publicName": "accessGroupSlug", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Slug" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "Schema", + "source": "Schema", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + }, + { + "name": "title", + "publicName": "title", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "description", + "publicName": "description", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "slug", + "publicName": "slug", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Slug" + } + }, + { + "name": "namespace", + "publicName": "namespace", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Namespace" + } + }, + { + "name": "isPrivate", + "publicName": "isPrivate", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "versions", + "publicName": "versions", + "required": true, + "deprecated": false, + "type": { + "kind": "array", + "items": { + "kind": "ref", + "name": "ManagedSchemaVersion" + } + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ManagedSchemaVersion", + "source": "ManagedSchemaVersion", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + }, + { + "name": "createdAt", + "publicName": "createdAt", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Timestamp" + } + }, + { + "name": "updatedAt", + "publicName": "updatedAt", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Timestamp" + } + }, + { + "name": "version", + "publicName": "version", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Version" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "Timestamp", + "source": "Timestamp", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "validation": {} + } + }, + { + "name": "Uid", + "source": "Uid", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "LoginPortalEmail", + "source": "LoginPortalEmail", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "logo", + "publicName": "logo", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "logoSize", + "publicName": "logoSize", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "buttonText", + "publicName": "buttonText", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 50 + } + } + }, + { + "name": "message", + "publicName": "message", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 1000 + } + } + }, + { + "name": "title", + "publicName": "title", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "mainColor", + "publicName": "mainColor", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "mainBackground", + "publicName": "mainBackground", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "cardColor", + "publicName": "cardColor", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "cardBackground", + "publicName": "cardBackground", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "buttonColor", + "publicName": "buttonColor", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "buttonBackground", + "publicName": "buttonBackground", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + } + ], + "additionalProperties": false + } + }, + { + "name": "LoginPortalPage", + "source": "LoginPortalPage", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "title", + "publicName": "title", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "description", + "publicName": "description", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 500 + } + } + }, + { + "name": "head", + "publicName": "head", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "script", + "publicName": "script", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "theme", + "publicName": "theme", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "companyName", + "publicName": "companyName", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "logo", + "publicName": "logo", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "logoURL", + "publicName": "logoURL", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "favicon", + "publicName": "favicon", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "termsLink", + "publicName": "termsLink", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "privacyLink", + "publicName": "privacyLink", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "formTitle", + "publicName": "formTitle", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "formDescription", + "publicName": "formDescription", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 500 + } + } + }, + { + "name": "formImage", + "publicName": "formImage", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "LoginPortal", + "source": "LoginPortal", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + }, + { + "name": "title", + "publicName": "title", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 200 + } + } + }, + { + "name": "slug", + "publicName": "slug", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Slug" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "Rule", + "source": "Rule", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + }, + { + "name": "title", + "publicName": "title", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "maxLength": 100 + } + } + }, + { + "name": "description", + "publicName": "description", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "slug", + "publicName": "slug", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Slug" + } + }, + { + "name": "namespace", + "publicName": "namespace", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Namespace" + } + }, + { + "name": "isPrivate", + "publicName": "isPrivate", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "Theme", + "source": "Theme", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + }, + { + "name": "name", + "publicName": "name", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "description", + "publicName": "description", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "slug", + "publicName": "slug", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Slug" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "Team", + "source": "Team", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + }, + { + "name": "name", + "publicName": "name", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "TeamName" + } + }, + { + "name": "imageUri", + "publicName": "imageUri", + "required": false, + "deprecated": false, + "type": { + "kind": "ref", + "name": "TeamImage" + } + }, + { + "name": "slug", + "publicName": "slug", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Slug" + } + }, + { + "name": "theme", + "publicName": "theme", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "TeamName", + "source": "TeamName", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "TeamImage", + "source": "TeamImage", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "GithubProject", + "source": "GithubProject", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + }, + { + "name": "createdAt", + "publicName": "createdAt", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Timestamp" + } + }, + { + "name": "updatedAt", + "publicName": "updatedAt", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Timestamp" + } + }, + { + "name": "name", + "publicName": "name", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "activeDeployment", + "publicName": "activeDeployment", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "ref", + "name": "ActiveDeployment" + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "lastPublished", + "publicName": "lastPublished", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "ref", + "name": "Timestamp" + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "lastPublishedUid", + "publicName": "lastPublishedUid", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "loginPortalUid", + "publicName": "loginPortalUid", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "activeThemeId", + "publicName": "activeThemeId", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "typesenseId", + "publicName": "typesenseId", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "number" + } + }, + { + "name": "isPrivate", + "publicName": "isPrivate", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "agentEnabled", + "publicName": "agentEnabled", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "accessGroups", + "publicName": "accessGroups", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "slug", + "publicName": "slug", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Slug" + } + }, + { + "name": "publishStatus", + "publicName": "publishStatus", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "publishMessage", + "publicName": "publishMessage", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "repository", + "publicName": "repository", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "ref", + "name": "GithubProjectRepository" + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ActiveDeployment", + "source": "ActiveDeployment", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "domain", + "publicName": "domain", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "publishedAt", + "publicName": "publishedAt", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Timestamp" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "GithubProjectRepository", + "source": "GithubProjectRepository", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "linkedBy", + "publicName": "linkedBy", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "id", + "publicName": "id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "number" + } + }, + { + "name": "name", + "publicName": "name", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "minLength": 2 + } + } + }, + { + "name": "configPath", + "publicName": "configPath", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "branch", + "publicName": "branch", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "publishOnMerge", + "publicName": "publishOnMerge", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "publishPreviews", + "publicName": "publishPreviews", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "prComments", + "publicName": "prComments", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "expired", + "publicName": "expired", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "Email", + "source": "Email", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "email", + "validation": { + "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$" + } + } + }, + { + "name": "TeamSummary", + "source": "TeamSummary", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + }, + { + "name": "name", + "publicName": "name", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "TeamName" + } + }, + { + "name": "imageUri", + "publicName": "imageUri", + "required": false, + "deprecated": false, + "type": { + "kind": "ref", + "name": "TeamImage" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "User", + "source": "User", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "uid", + "publicName": "uid", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Nanoid" + } + }, + { + "name": "createdAt", + "publicName": "createdAt", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Timestamp" + } + }, + { + "name": "updatedAt", + "publicName": "updatedAt", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Timestamp" + } + }, + { + "name": "email", + "publicName": "email", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "Email" + } + }, + { + "name": "theme", + "publicName": "theme", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "activeTeamId", + "publicName": "activeTeamId", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "hasGithub", + "publicName": "hasGithub", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "teams", + "publicName": "teams", + "required": true, + "deprecated": false, + "type": { + "kind": "array", + "items": { + "kind": "ref", + "name": "TeamSummary" + } + } + } + ], + "additionalProperties": false + } + } + ], + "resources": [ + "registry", + "schemas", + "schemas.version", + "schemas.accessGroup", + "loginPortals", + "rules", + "themes", + "teams", + "scalarDocs", + "namespaces", + "authentication" + ], + "publicResources": [ + "registry", + "schemas", + "schemas.version", + "schemas.accessGroup", + "loginPortals", + "rules", + "themes", + "teams", + "scalarDocs", + "namespaces", + "authentication" + ], + "operations": [ + { + "resource": "registry", + "publicResource": "registry", + "operation": "listAllApiDocuments", + "publicOperation": "listAllApiDocuments", + "deprecated": false, + "method": "GET", + "path": "/v1/apis", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "registry", + "publicResource": "registry", + "operation": "listApiDocuments", + "publicOperation": "listApiDocuments", + "deprecated": false, + "method": "GET", + "path": "/v1/apis/{namespace}", + "pathParams": [ + "namespace" + ], + "publicPathParams": [ + "namespace" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RegistryListApiDocumentsParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "registry", + "publicResource": "registry", + "operation": "createApiDocument", + "publicOperation": "createApiDocument", + "deprecated": false, + "method": "POST", + "path": "/v1/apis/{namespace}", + "pathParams": [ + "namespace" + ], + "publicPathParams": [ + "namespace" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "title", + "description", + "version", + "slug", + "ruleset", + "isPrivate", + "document" + ], + "publicBodyParams": [ + "title", + "description", + "version", + "slug", + "ruleset", + "isPrivate", + "document" + ], + "publicPositionalParams": [ + "namespace" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RegistryCreateApiDocumentParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "registry", + "publicResource": "registry", + "operation": "updateApiDocument", + "publicOperation": "updateApiDocument", + "deprecated": false, + "method": "PATCH", + "path": "/v1/apis/{namespace}/{slug}", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "title", + "description", + "isPrivate", + "ruleset" + ], + "publicBodyParams": [ + "title", + "description", + "isPrivate", + "ruleset" + ], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RegistryUpdateApiDocumentParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "registry", + "publicResource": "registry", + "operation": "deleteApiDocument", + "publicOperation": "deleteApiDocument", + "deprecated": false, + "method": "DELETE", + "path": "/v1/apis/{namespace}/{slug}", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RegistryDeleteApiDocumentParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "registry", + "publicResource": "registry", + "operation": "retrieveApiDocumentVersion", + "publicOperation": "retrieveApiDocumentVersion", + "deprecated": false, + "method": "GET", + "path": "/v1/apis/{namespace}/{slug}/version/{semver}", + "pathParams": [ + "namespace", + "slug", + "semver" + ], + "publicPathParams": [ + "namespace", + "slug", + "semver" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace", + "slug", + "semver" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "semver", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RegistryRetrieveApiDocumentVersionParams" + }, + "response": { + "status": "200", + "contentType": "text/plain", + "encoding": "text", + "contents": [ + { + "contentType": "text/plain", + "encoding": "text" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "registry", + "publicResource": "registry", + "operation": "updateApiDocumentVersion", + "publicOperation": "updateApiDocumentVersion", + "deprecated": false, + "method": "PATCH", + "path": "/v1/apis/{namespace}/{slug}/version/{semver}", + "pathParams": [ + "namespace", + "slug", + "semver" + ], + "publicPathParams": [ + "namespace", + "slug", + "semver" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "document", + "lastKnownVersionSha" + ], + "publicBodyParams": [ + "document", + "lastKnownVersionSha" + ], + "publicPositionalParams": [ + "namespace", + "slug", + "semver" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "semver", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RegistryUpdateApiDocumentVersionParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "registry", + "publicResource": "registry", + "operation": "deleteApiDocumentVersion", + "publicOperation": "deleteApiDocumentVersion", + "deprecated": false, + "method": "DELETE", + "path": "/v1/apis/{namespace}/{slug}/version/{semver}", + "pathParams": [ + "namespace", + "slug", + "semver" + ], + "publicPathParams": [ + "namespace", + "slug", + "semver" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace", + "slug", + "semver" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "semver", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RegistryDeleteApiDocumentVersionParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "registry", + "publicResource": "registry", + "operation": "listApiDocumentVersionMetadata", + "publicOperation": "listApiDocumentVersionMetadata", + "deprecated": false, + "method": "GET", + "path": "/v1/apis/{namespace}/{slug}/version/{semver}/metadata", + "pathParams": [ + "namespace", + "slug", + "semver" + ], + "publicPathParams": [ + "namespace", + "slug", + "semver" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace", + "slug", + "semver" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "semver", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RegistryListApiDocumentVersionMetadataParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "ManagedDocVersion", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "registry", + "publicResource": "registry", + "operation": "createApiDocumentVersion", + "publicOperation": "createApiDocumentVersion", + "deprecated": false, + "method": "POST", + "path": "/v1/apis/{namespace}/{slug}/version", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "version", + "document", + "force", + "lastKnownVersionSha" + ], + "publicBodyParams": [ + "version", + "document", + "force", + "lastKnownVersionSha" + ], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RegistryCreateApiDocumentVersionParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "ManagedDocVersion", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "registry", + "publicResource": "registry", + "operation": "createApiDocumentAccessGroup", + "publicOperation": "createApiDocumentAccessGroup", + "deprecated": false, + "method": "POST", + "path": "/v1/apis/{namespace}/{slug}/access-group", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "accessGroupSlug" + ], + "publicBodyParams": [ + "accessGroupSlug" + ], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RegistryCreateApiDocumentAccessGroupParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "registry", + "publicResource": "registry", + "operation": "deleteApiDocumentAccessGroup", + "publicOperation": "deleteApiDocumentAccessGroup", + "deprecated": false, + "method": "DELETE", + "path": "/v1/apis/{namespace}/{slug}/access-group", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "accessGroupSlug" + ], + "publicBodyParams": [ + "accessGroupSlug" + ], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RegistryDeleteApiDocumentAccessGroupParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "schemas", + "publicResource": "schemas", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/v1/schemas/{namespace}", + "pathParams": [ + "namespace" + ], + "publicPathParams": [ + "namespace" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "SchemaListParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "schemas", + "publicResource": "schemas", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/v1/schemas/{namespace}", + "pathParams": [ + "namespace" + ], + "publicPathParams": [ + "namespace" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "title", + "description", + "version", + "slug", + "isPrivate", + "document" + ], + "publicBodyParams": [ + "title", + "description", + "version", + "slug", + "isPrivate", + "document" + ], + "publicPositionalParams": [ + "namespace" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "SchemaCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "Uid", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "schemas", + "publicResource": "schemas", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "PATCH", + "path": "/v1/schemas/{namespace}/{slug}", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "title", + "description", + "isPrivate" + ], + "publicBodyParams": [ + "title", + "description", + "isPrivate" + ], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "SchemaUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "schemas", + "publicResource": "schemas", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/v1/schemas/{namespace}/{slug}", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "SchemaDeleteParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "schemas.version", + "publicResource": "schemas.version", + "operation": "retrieveSchema", + "publicOperation": "retrieveSchema", + "deprecated": false, + "method": "GET", + "path": "/v1/schemas/{namespace}/{slug}/version/{semver}", + "pathParams": [ + "namespace", + "slug", + "semver" + ], + "publicPathParams": [ + "namespace", + "slug", + "semver" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace", + "slug", + "semver" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "semver", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VersionRetrieveSchemaParams" + }, + "response": { + "status": "200", + "contentType": "text/plain", + "encoding": "text", + "contents": [ + { + "contentType": "text/plain", + "encoding": "text" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "schemas.version", + "publicResource": "schemas.version", + "operation": "deleteSchema", + "publicOperation": "deleteSchema", + "deprecated": false, + "method": "DELETE", + "path": "/v1/schemas/{namespace}/{slug}/version/{semver}", + "pathParams": [ + "namespace", + "slug", + "semver" + ], + "publicPathParams": [ + "namespace", + "slug", + "semver" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace", + "slug", + "semver" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "semver", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VersionDeleteSchemaParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "schemas.version", + "publicResource": "schemas.version", + "operation": "createSchema", + "publicOperation": "createSchema", + "deprecated": false, + "method": "POST", + "path": "/v1/schemas/{namespace}/{slug}/version", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "version", + "document" + ], + "publicBodyParams": [ + "version", + "document" + ], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "VersionCreateSchemaParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "Uid", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "schemas.accessGroup", + "publicResource": "schemas.accessGroup", + "operation": "createSchema", + "publicOperation": "createSchema", + "deprecated": false, + "method": "POST", + "path": "/v1/schemas/{namespace}/{slug}/access-group", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "accessGroupSlug" + ], + "publicBodyParams": [ + "accessGroupSlug" + ], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "AccessGroupCreateSchemaParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "schemas.accessGroup", + "publicResource": "schemas.accessGroup", + "operation": "deleteSchema", + "publicOperation": "deleteSchema", + "deprecated": false, + "method": "DELETE", + "path": "/v1/schemas/{namespace}/{slug}/access-group", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "accessGroupSlug" + ], + "publicBodyParams": [ + "accessGroupSlug" + ], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "AccessGroupDeleteSchemaParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "loginPortals", + "publicResource": "loginPortals", + "operation": "retrieve", + "publicOperation": "retrieve", + "deprecated": false, + "method": "GET", + "path": "/v1/login-portals/{slug}", + "pathParams": [ + "slug" + ], + "publicPathParams": [ + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "slug" + ], + "pathParamDetails": [ + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LoginPortalRetrieveParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "loginPortals", + "publicResource": "loginPortals", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "PATCH", + "path": "/v1/login-portals/{slug}", + "pathParams": [ + "slug" + ], + "publicPathParams": [ + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "title" + ], + "publicBodyParams": [ + "title" + ], + "publicPositionalParams": [ + "slug" + ], + "pathParamDetails": [ + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LoginPortalUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "loginPortals", + "publicResource": "loginPortals", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/v1/login-portals/{slug}", + "pathParams": [ + "slug" + ], + "publicPathParams": [ + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "slug" + ], + "pathParamDetails": [ + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LoginPortalDeleteParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "loginPortals", + "publicResource": "loginPortals", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/v1/login-portals", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "title", + "slug", + "email", + "page" + ], + "publicBodyParams": [ + "title", + "slug", + "email", + "page" + ], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "LoginPortalCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "Uid", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "loginPortals", + "publicResource": "loginPortals", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/v1/login-portals", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "rules", + "publicResource": "rules", + "operation": "listRulesets", + "publicOperation": "listRulesets", + "deprecated": false, + "method": "GET", + "path": "/v1/rulesets/{namespace}", + "pathParams": [ + "namespace" + ], + "publicPathParams": [ + "namespace" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RuleListRulesetsParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "rules", + "publicResource": "rules", + "operation": "createRuleset", + "publicOperation": "createRuleset", + "deprecated": false, + "method": "POST", + "path": "/v1/rulesets/{namespace}", + "pathParams": [ + "namespace" + ], + "publicPathParams": [ + "namespace" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "title", + "description", + "slug", + "isPrivate", + "document" + ], + "publicBodyParams": [ + "title", + "description", + "slug", + "isPrivate", + "document" + ], + "publicPositionalParams": [ + "namespace" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RuleCreateRulesetParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "Uid", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "rules", + "publicResource": "rules", + "operation": "updateRuleset", + "publicOperation": "updateRuleset", + "deprecated": false, + "method": "PATCH", + "path": "/v1/rulesets/{namespace}/{slug}", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "namespace", + "slug", + "title", + "description", + "isPrivate" + ], + "publicBodyParams": [ + "namespace", + "slug", + "title", + "description", + "isPrivate" + ], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RuleUpdateRulesetParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "rules", + "publicResource": "rules", + "operation": "deleteRuleset", + "publicOperation": "deleteRuleset", + "deprecated": false, + "method": "DELETE", + "path": "/v1/rulesets/{namespace}/{slug}", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RuleDeleteRulesetParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "rules", + "publicResource": "rules", + "operation": "retrieveRulesetDocument", + "publicOperation": "retrieveRulesetDocument", + "deprecated": false, + "method": "GET", + "path": "/v1/rulesets/{namespace}/{slug}", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RuleRetrieveRulesetDocumentParams" + }, + "response": { + "status": "200", + "contentType": "text/plain", + "encoding": "text", + "contents": [ + { + "contentType": "text/plain", + "encoding": "text" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "rules", + "publicResource": "rules", + "operation": "createRulesetAccessGroup", + "publicOperation": "createRulesetAccessGroup", + "deprecated": false, + "method": "POST", + "path": "/v1/rulesets/{namespace}/{slug}/access-group", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "accessGroupSlug" + ], + "publicBodyParams": [ + "accessGroupSlug" + ], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RuleCreateRulesetAccessGroupParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "rules", + "publicResource": "rules", + "operation": "deleteRulesetAccessGroup", + "publicOperation": "deleteRulesetAccessGroup", + "deprecated": false, + "method": "DELETE", + "path": "/v1/rulesets/{namespace}/{slug}/access-group", + "pathParams": [ + "namespace", + "slug" + ], + "publicPathParams": [ + "namespace", + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "accessGroupSlug" + ], + "publicBodyParams": [ + "accessGroupSlug" + ], + "publicPositionalParams": [ + "namespace", + "slug" + ], + "pathParamDetails": [ + { + "name": "namespace", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "RuleDeleteRulesetAccessGroupParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "themes", + "publicResource": "themes", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/v1/themes", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "themes", + "publicResource": "themes", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/v1/themes", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "name", + "description", + "slug", + "document" + ], + "publicBodyParams": [ + "name", + "description", + "slug", + "document" + ], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ThemeCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "Uid", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "themes", + "publicResource": "themes", + "operation": "update", + "publicOperation": "update", + "deprecated": false, + "method": "PATCH", + "path": "/v1/themes/{slug}", + "pathParams": [ + "slug" + ], + "publicPathParams": [ + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "name", + "description" + ], + "publicBodyParams": [ + "name", + "description" + ], + "publicPositionalParams": [ + "slug" + ], + "pathParamDetails": [ + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ThemeUpdateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "themes", + "publicResource": "themes", + "operation": "replaceDocument", + "publicOperation": "replaceDocument", + "deprecated": false, + "method": "PUT", + "path": "/v1/themes/{slug}", + "pathParams": [ + "slug" + ], + "publicPathParams": [ + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "document" + ], + "publicBodyParams": [ + "document" + ], + "publicPositionalParams": [ + "slug" + ], + "pathParamDetails": [ + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ThemeReplaceDocumentParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "themes", + "publicResource": "themes", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/v1/themes/{slug}", + "pathParams": [ + "slug" + ], + "publicPathParams": [ + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "slug" + ], + "pathParamDetails": [ + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ThemeDeleteParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "themes", + "publicResource": "themes", + "operation": "retrieve", + "publicOperation": "retrieve", + "deprecated": false, + "method": "GET", + "path": "/v1/themes/{slug}", + "pathParams": [ + "slug" + ], + "publicPathParams": [ + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "slug" + ], + "pathParamDetails": [ + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ThemeRetrieveParams" + }, + "response": { + "status": "200", + "contentType": "text/plain", + "encoding": "text", + "contents": [ + { + "contentType": "text/plain", + "encoding": "text" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "teams", + "publicResource": "teams", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/v1/teams", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "scalarDocs", + "publicResource": "scalarDocs", + "operation": "listGuides", + "publicOperation": "listGuides", + "deprecated": false, + "method": "GET", + "path": "/v1/guides", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "scalarDocs", + "publicResource": "scalarDocs", + "operation": "createGuide", + "publicOperation": "createGuide", + "deprecated": false, + "method": "POST", + "path": "/v1/guides", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "name", + "slug", + "isPrivate", + "allowedUsers", + "allowedDomains" + ], + "publicBodyParams": [ + "name", + "slug", + "isPrivate", + "allowedUsers", + "allowedDomains" + ], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ScalarDocCreateGuideParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "scalarDocs", + "publicResource": "scalarDocs", + "operation": "publishGuide", + "publicOperation": "publishGuide", + "deprecated": false, + "method": "POST", + "path": "/v1/guides/{slug}/publish", + "pathParams": [ + "slug" + ], + "publicPathParams": [ + "slug" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [ + "slug" + ], + "pathParamDetails": [ + { + "name": "slug", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "ScalarDocPublishGuideParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "namespaces", + "publicResource": "namespaces", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/v1/namespaces", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "authentication", + "publicResource": "authentication", + "operation": "exchangePersonalToken", + "publicOperation": "exchangePersonalToken", + "deprecated": false, + "method": "POST", + "path": "/v1/auth/exchange", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [ + "personalToken" + ], + "publicBodyParams": [ + "personalToken" + ], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "AuthenticationExchangePersonalTokenParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "authentication", + "publicResource": "authentication", + "operation": "listCurrentUser", + "publicOperation": "listCurrentUser", + "deprecated": false, + "method": "GET", + "path": "/v1/auth/me", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "User", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "404", + "422", + "500" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_400", + "publicAliases": [] + } + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_401", + "publicAliases": [] + } + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_403", + "publicAliases": [] + } + }, + { + "status": "404", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_404", + "publicAliases": [] + } + }, + { + "status": "422", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_422", + "publicAliases": [] + } + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "model": { + "name": "_500", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + } + ], + "webhooks": [] +} diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..7bedc5e --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,86 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +import typing as _t + +from . import types +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given +from ._utils import file_from_path +from ._client import Client, Stream, Scalar, Timeout, AsyncClient, AsyncStream, AsyncScalar, RequestOptions, ENVIRONMENTS +from ._models import BaseModel +from ._version import __title__, __version__ +from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse +from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS +from ._exceptions import ( + APIError, + ScalarError, + ConflictError, + NotFoundError, + APIStatusError, + RateLimitError, + APITimeoutError, + BadRequestError, + APIConnectionError, + AuthenticationError, + InternalServerError, + PermissionDeniedError, + UnprocessableEntityError, + APIResponseValidationError, +) +from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient +from ._utils._logs import setup_logging as _setup_logging + +__all__ = [ + "ENVIRONMENTS", + "types", + "__version__", + "__title__", + "NoneType", + "Transport", + "ProxiesTypes", + "NotGiven", + "NOT_GIVEN", + "not_given", + "Omit", + "omit", + "ScalarError", + "APIError", + "APIStatusError", + "APITimeoutError", + "APIConnectionError", + "APIResponseValidationError", + "BadRequestError", + "AuthenticationError", + "PermissionDeniedError", + "NotFoundError", + "ConflictError", + "UnprocessableEntityError", + "RateLimitError", + "InternalServerError", + "Timeout", + "RequestOptions", + "Client", + "AsyncClient", + "Stream", + "AsyncStream", + "Scalar", + "AsyncScalar", + "file_from_path", + "BaseModel", + "DEFAULT_TIMEOUT", + "DEFAULT_MAX_RETRIES", + "DEFAULT_CONNECTION_LIMITS", + "DefaultHttpxClient", + "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", +] + +_setup_logging() + +__locals = locals() +__module_name = __name__ +for __export_name in __all__: + if not __export_name.startswith("__"): + try: + __locals[__export_name].__module__ = __module_name + except (TypeError, AttributeError): + pass diff --git a/src/_base_client.py b/src/_base_client.py new file mode 100644 index 0000000..f99d69d --- /dev/null +++ b/src/_base_client.py @@ -0,0 +1,2229 @@ +from __future__ import annotations + +import sys +import json +import time +import uuid +import email +import asyncio +import inspect +import logging +import platform +import warnings +import email.utils +from types import TracebackType +from random import random +from dataclasses import field, dataclass +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Type, + Union, + Generic, + Mapping, + TypeVar, + Iterable, + Iterator, + Optional, + Generator, + AsyncIterator, + cast, + overload, +) +from typing_extensions import Literal, override, get_origin + +import anyio +import httpx +import distro +import pydantic +from httpx import URL +from pydantic import PrivateAttr + +from . import _exceptions +from ._qs import Querystring +from ._files import to_httpx_files, async_to_httpx_files +from ._types import ( + Body, + Omit, + Query, + Headers, + Timeout, + NotGiven, + ResponseT, + AnyMapping, + PostParser, + BinaryTypes, + RequestFiles, + HttpxSendArgs, + RequestOptions, + AsyncBinaryTypes, + HttpxRequestFiles, + ModelBuilderProtocol, + not_given, +) +from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping +from ._compat import PYDANTIC_V1, model_copy, model_dump +from ._models import GenericModel, SecurityOptions, FinalRequestOptions, validate_type, construct_type +from ._response import ( + APIResponse, + BaseAPIResponse, + AsyncAPIResponse, + extract_response_type, +) +from ._constants import ( + DEFAULT_TIMEOUT, + MAX_RETRY_DELAY, + DEFAULT_MAX_RETRIES, + INITIAL_RETRY_DELAY, + RAW_RESPONSE_HEADER, + OVERRIDE_CAST_TO_HEADER, + DEFAULT_CONNECTION_LIMITS, +) +from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder +from ._exceptions import ( + APIStatusError, + APITimeoutError, + APIConnectionError, + APIResponseValidationError, +) +from ._utils._json import openapi_dumps + +log: logging.Logger = logging.getLogger(__name__) + +# TODO: make base page type vars covariant +SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]") +AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]") + + +_T = TypeVar("_T") +_T_co = TypeVar("_T_co", covariant=True) + +_StreamT = TypeVar("_StreamT", bound=Stream[Any]) +_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any]) + +if TYPE_CHECKING: + from httpx._config import ( + DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage] + ) + + HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG +else: + try: + from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT + except ImportError: + # taken from https://github.com/encode/httpx/blob/3ba5fe0d7ac70222590e759c31442b1cab263791/httpx/_config.py#L366 + HTTPX_DEFAULT_TIMEOUT = Timeout(5.0) + + +@dataclass +class CursorPageConfig: + """Per-operation cursor pagination descriptor baked into a page by the generated list method. + + The Python view of the shared, language-neutral descriptor: where the items array lives, how to + read the next cursor, which request param carries it, and on which carrier (query vs body). + Driving extraction from this config — rather than typed `items`/`next_cursor` model fields — lets + one page class serve every cursor scheme, including `cursor_id` (cursor read from the last item) + and body-carried cursors. + """ + + # Body path to the items array (e.g. ["items"], ["data"]). + items_path: List[str] = field(default_factory=list) + # "field" (a top-level response field) or "item" (a field on the last item — `cursor_id`). + cursor_kind: str = "field" + # Path to the cursor value: from the body root when cursor_kind is "field", or from the last item. + cursor_path: List[str] = field(default_factory=list) + # Request param wire name that carries the cursor. + cursor_param: str = "cursor" + # "query" or "body": the carrier the cursor param rides on the next request. + cursor_location: str = "query" + # Keep paginating across an empty page when set. + continue_on_empty_items: bool = False + # Body path to the scheme's `has_more` boolean. Only an explicit False stops pagination; + # a missing or non-boolean value falls back to cursor-presence semantics. + has_more_path: List[str] = field(default_factory=list) + + +class PageInfo: + """Stores the necessary information to build the request to retrieve the next page. + + Exactly one of `url`, `params` (query carrier), or `json` (body carrier) must be set. + """ + + url: URL | NotGiven + params: Query | NotGiven + json: Body | NotGiven + + @overload + def __init__( + self, + *, + url: URL, + ) -> None: ... + + @overload + def __init__( + self, + *, + params: Query, + ) -> None: ... + + @overload + def __init__( + self, + *, + json: Body, + ) -> None: ... + + def __init__( + self, + *, + url: URL | NotGiven = not_given, + json: Body | NotGiven = not_given, + params: Query | NotGiven = not_given, + ) -> None: + self.url = url + self.json = json + self.params = params + + @override + def __repr__(self) -> str: + if self.url: + return f"{self.__class__.__name__}(url={self.url})" + if self.json: + return f"{self.__class__.__name__}(json={self.json})" + return f"{self.__class__.__name__}(params={self.params})" + + +class BasePage(GenericModel, Generic[_T]): + """ + Defines the core interface for pagination. + + Type Args: + ModelT: The pydantic model that represents an item in the response. + + Methods: + has_next_page(): Check if there is another page available + next_page_info(): Get the necessary information to make a request for the next page + """ + + _options: FinalRequestOptions = PrivateAttr() + _model: Type[_T] = PrivateAttr() + # Per-operation cursor descriptor; set by the paginated method via _set_private_attributes and + # carried forward across pages so every page extracts items/cursor identically. None for pages + # that predate metadata-driven pagination. + _cursor_config: Optional[CursorPageConfig] = PrivateAttr(None) + + def has_next_page(self) -> bool: + items = self._get_page_items() + if not items: + return False + return self.next_page_info() is not None + + def next_page_info(self) -> Optional[PageInfo]: ... + + def _get_page_items(self) -> Iterable[_T]: # type: ignore[empty-body] + ... + + def _params_from_url(self, url: URL) -> httpx.QueryParams: + # TODO: do we have to preprocess params here? + return httpx.QueryParams(cast(Any, self._options.params)).merge(url.params) + + def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: + options = model_copy(self._options) + options._strip_raw_response_header() + + if not isinstance(info.params, NotGiven): + options.params = {**options.params, **info.params} + return options + + if not isinstance(info.url, NotGiven): + params = self._params_from_url(info.url) + url = info.url.copy_with(params=params) + options.params = dict(url.params) + options.url = str(url) + return options + + if not isinstance(info.json, NotGiven): + if not is_mapping(info.json): + raise TypeError("Pagination is only supported with mappings") + + if not options.json_data: + options.json_data = {**info.json} + else: + if not is_mapping(options.json_data): + raise TypeError("Pagination is only supported with mappings") + + options.json_data = {**options.json_data, **info.json} + return options + + raise ValueError("Unexpected PageInfo state") + + +class BaseSyncPage(BasePage[_T], Generic[_T]): + _client: SyncAPIClient = pydantic.PrivateAttr() + + def _set_private_attributes( + self, + client: SyncAPIClient, + model: Type[_T], + options: FinalRequestOptions, + cursor_config: Optional[CursorPageConfig] = None, + ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + + self._model = model + self._client = client + self._options = options + self._cursor_config = cursor_config + + # Pydantic uses a custom `__iter__` method to support casting BaseModels + # to dictionaries. e.g. dict(model). + # As we want to support `for item in page`, this is inherently incompatible + # with the default pydantic behaviour. It is not possible to support both + # use cases at once. Fortunately, this is not a big deal as all other pydantic + # methods should continue to work as expected as there is an alternative method + # to cast a model to a dictionary, model.dict(), which is used internally + # by pydantic. + def __iter__(self) -> Iterator[_T]: # type: ignore + for page in self.iter_pages(): + for item in page._get_page_items(): + yield item + + def iter_pages(self: SyncPageT) -> Iterator[SyncPageT]: + page = self + while True: + yield page + if page.has_next_page(): + page = page.get_next_page() + else: + return + + def get_next_page(self: SyncPageT) -> SyncPageT: + info = self.next_page_info() + if not info: + raise RuntimeError( + "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`." + ) + + options = self._info_to_options(info) + # Carry the cursor descriptor forward so the next page extracts items/cursor identically. + return self._client._request_api_list( + self._model, page=self.__class__, options=options, cursor_config=self._cursor_config + ) + + +class AsyncPaginator(Generic[_T, AsyncPageT]): + def __init__( + self, + client: AsyncAPIClient, + options: FinalRequestOptions, + page_cls: Type[AsyncPageT], + model: Type[_T], + cursor_config: Optional[CursorPageConfig] = None, + ) -> None: + self._model = model + self._client = client + self._options = options + self._page_cls = page_cls + self._cursor_config = cursor_config + + def __await__(self) -> Generator[Any, None, AsyncPageT]: + return self._get_page().__await__() + + async def _get_page(self) -> AsyncPageT: + def _parser(resp: AsyncPageT) -> AsyncPageT: + resp._set_private_attributes( + model=self._model, + options=self._options, + client=self._client, + cursor_config=self._cursor_config, + ) + return resp + + self._options.post_parser = _parser + + return await self._client.request(self._page_cls, self._options) + + async def __aiter__(self) -> AsyncIterator[_T]: + # https://github.com/microsoft/pyright/issues/3464 + page = cast( + AsyncPageT, + await self, # type: ignore + ) + async for item in page: + yield item + + +class BaseAsyncPage(BasePage[_T], Generic[_T]): + _client: AsyncAPIClient = pydantic.PrivateAttr() + + def _set_private_attributes( + self, + model: Type[_T], + client: AsyncAPIClient, + options: FinalRequestOptions, + cursor_config: Optional[CursorPageConfig] = None, + ) -> None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + + self._model = model + self._client = client + self._options = options + self._cursor_config = cursor_config + + async def __aiter__(self) -> AsyncIterator[_T]: + async for page in self.iter_pages(): + for item in page._get_page_items(): + yield item + + async def iter_pages(self: AsyncPageT) -> AsyncIterator[AsyncPageT]: + page = self + while True: + yield page + if page.has_next_page(): + page = await page.get_next_page() + else: + return + + async def get_next_page(self: AsyncPageT) -> AsyncPageT: + info = self.next_page_info() + if not info: + raise RuntimeError( + "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`." + ) + + options = self._info_to_options(info) + # Carry the cursor descriptor forward so the next page extracts items/cursor identically. + return await self._client._request_api_list( + self._model, page=self.__class__, options=options, cursor_config=self._cursor_config + ) + + +_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient]) +_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]]) + + +class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): + _client: _HttpxClientT + _version: str + _base_url: URL + max_retries: int + timeout: Union[float, Timeout, None] + _strict_response_validation: bool + _idempotency_header: str | None + _default_stream_cls: type[_DefaultStreamT] | None = None + + def __init__( + self, + *, + version: str, + base_url: str | URL, + _strict_response_validation: bool, + max_retries: int = DEFAULT_MAX_RETRIES, + timeout: float | Timeout | None = DEFAULT_TIMEOUT, + custom_headers: Mapping[str, str] | None = None, + custom_query: Mapping[str, object] | None = None, + ) -> None: + self._version = version + self._base_url = self._enforce_trailing_slash(URL(base_url)) + self.max_retries = max_retries + self.timeout = timeout + self._custom_headers = custom_headers or {} + self._custom_query = custom_query or {} + self._strict_response_validation = _strict_response_validation + self._idempotency_header = None + self._platform: Platform | None = None + + if max_retries is None: # pyright: ignore[reportUnnecessaryComparison] + raise TypeError( + "max_retries cannot be None. If you want to disable retries, pass `0`; if you want unlimited retries, pass `math.inf` or a very high number; if you want the default behavior, pass `scalar_api.DEFAULT_MAX_RETRIES`" + ) + + def _enforce_trailing_slash(self, url: URL) -> URL: + if url.raw_path.endswith(b"/"): + return url + return url.copy_with(raw_path=url.raw_path + b"/") + + def _make_status_error_from_response( + self, + response: httpx.Response, + ) -> APIStatusError: + if response.is_closed and not response.is_stream_consumed: + # We can't read the response body as it has been closed + # before it was read. This can happen if an event hook + # raises a status error. + body = None + err_msg = f"Error code: {response.status_code}" + else: + err_text = response.text.strip() + body = err_text + + try: + body = json.loads(err_text) + err_msg = f"Error code: {response.status_code} - {body}" + except Exception: + err_msg = err_text or f"Error code: {response.status_code}" + + return self._make_status_error(err_msg, body=body, response=response) + + def _make_status_error( + self, + err_msg: str, + *, + body: object, + response: httpx.Response, + ) -> _exceptions.APIStatusError: + raise NotImplementedError() + + def _auth_headers( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> dict[str, str]: + return {} + + def _auth_query( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> dict[str, str]: + return {} + + def _auth_cookies( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> dict[str, str]: + return {} + + def _custom_auth( + self, + security: SecurityOptions, # noqa: ARG002 + ) -> httpx.Auth | None: + return None + + def _build_headers( + self, + options: FinalRequestOptions, + *, + params: Query, + cookies: Mapping[str, str], + retries_taken: int = 0, + ) -> httpx.Headers: + custom_headers = options.headers or {} + headers_dict = _merge_mappings({**self._auth_headers(options.security), **self.default_headers}, custom_headers) + self._validate_headers(headers_dict, custom_headers, params, cookies) + + # headers are case-insensitive while dictionaries are not. + headers = httpx.Headers(headers_dict) + + idempotency_header = self._idempotency_header + if idempotency_header and options.idempotency_key and idempotency_header not in headers: + headers[idempotency_header] = options.idempotency_key + + # Don't set these headers if they were already set or removed by the caller. We check + # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case. + lower_custom_headers = [header.lower() for header in custom_headers] + if "x-scalar-retry-count" not in lower_custom_headers: + headers["x-scalar-retry-count"] = str(retries_taken) + if "x-scalar-read-timeout" not in lower_custom_headers: + timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout + if isinstance(timeout, Timeout): + timeout = timeout.read + if timeout is not None: + headers["x-scalar-read-timeout"] = str(timeout) + + return headers + + def _prepare_url(self, url: str) -> URL: + """ + Merge a URL argument together with any 'base_url' on the client, + to create the URL used for the outgoing request. + """ + # Copied from httpx's `_merge_url` method. + merge_url = URL(url) + if merge_url.is_relative_url: + merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/") + return self.base_url.copy_with(raw_path=merge_raw_path) + + return merge_url + + def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder: + return SSEDecoder() + + def _build_request( + self, + options: FinalRequestOptions, + *, + retries_taken: int = 0, + ) -> httpx.Request: + if log.isEnabledFor(logging.DEBUG): + log.debug( + "Request options: %s", + model_dump( + options, + exclude_unset=True, + # Pydantic v1 can't dump every type we support in content, so we exclude it for now. + exclude={ + "content", + } + if PYDANTIC_V1 + else {}, + ), + ) + kwargs: dict[str, Any] = {} + + json_data = options.json_data + if options.extra_json is not None: + if json_data is None: + json_data = cast(Body, options.extra_json) + elif is_mapping(json_data): + json_data = _merge_mappings(json_data, options.extra_json) + else: + raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`") + + params = _merge_mappings({**self._auth_query(options.security), **self.default_query}, options.params) + cookies = self._auth_cookies(options.security) + headers = self._build_headers(options, retries_taken=retries_taken, params=params, cookies=cookies) + content_type = headers.get("Content-Type") + files = options.files + + # If the given Content-Type header is multipart/form-data then it + # has to be removed so that httpx can generate the header with + # additional information for us as it has to be in this form + # for the server to be able to correctly parse the request: + # multipart/form-data; boundary=---abc-- + if content_type is not None and content_type.startswith("multipart/form-data"): + if "boundary" not in content_type: + # only remove the header if the boundary hasn't been explicitly set + # as the caller doesn't want httpx to come up with their own boundary + headers.pop("Content-Type") + + # As we are now sending multipart/form-data instead of application/json + # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding + if json_data: + if not is_dict(json_data): + raise TypeError( + f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead." + ) + kwargs["data"] = self._serialize_multipartform(json_data) + + # httpx determines whether or not to send a "multipart/form-data" + # request based on the truthiness of the "files" argument. + # This gets around that issue by generating a dict value that + # evaluates to true. + # + # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186 + if not files: + files = cast(HttpxRequestFiles, ForceMultipartDict()) + + prepared_url = self._prepare_url(options.url) + # preserve hard-coded query params from the url + if params and prepared_url.query: + params = {**dict(prepared_url.params.items()), **params} + prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0]) + if "_" in prepared_url.host: + # work around https://github.com/encode/httpx/discussions/2880 + kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} + + is_body_allowed = options.method.lower() != "get" + + if is_body_allowed: + if options.content is not None and json_data is not None: + raise TypeError("Passing both `content` and `json_data` is not supported") + if options.content is not None and files is not None: + raise TypeError("Passing both `content` and `files` is not supported") + if options.content is not None: + kwargs["content"] = options.content + elif isinstance(json_data, bytes): + kwargs["content"] = json_data + elif not files: + # Don't set content when JSON is sent as multipart/form-data, + # since httpx's content param overrides other body arguments + kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None + kwargs["files"] = files + else: + headers.pop("Content-Type", None) + kwargs.pop("data", None) + + # TODO: report this error to httpx + return self._client.build_request( # pyright: ignore[reportUnknownMemberType] + headers=headers, + timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout, + method=options.method, + url=prepared_url, + # the `Query` type that we use is incompatible with qs' + # `Params` type as it needs to be typed as `Mapping[str, object]` + # so that passing a `TypedDict` doesn't cause an error. + # https://github.com/microsoft/pyright/issues/3526#event-6715453066 + params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, + cookies=cookies or None, + **kwargs, + ) + + def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, object]: + items = self.qs.stringify_items( + # TODO: type ignore is required as stringify_items is well typed but we can't be + # well typed without heavy validation. + data, # type: ignore + array_format="brackets", + ) + serialized: dict[str, object] = {} + for key, value in items: + existing = serialized.get(key) + + if not existing: + serialized[key] = value + continue + + # If a value has already been set for this key then that + # means we're sending data like `array[]=[1, 2, 3]` and we + # need to tell httpx that we want to send multiple values with + # the same key which is done by using a list or a tuple. + # + # Note: 2d arrays should never result in the same key at both + # levels so it's safe to assume that if the value is a list, + # it was because we changed it to be a list. + if is_list(existing): + existing.append(value) + else: + serialized[key] = [existing, value] + + return serialized + + def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalRequestOptions) -> type[ResponseT]: + if not is_given(options.headers): + return cast_to + + # make a copy of the headers so we don't mutate user-input + headers = dict(options.headers) + + # we internally support defining a temporary header to override the + # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` + # see _response.py for implementation details + override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given) + if is_given(override_cast_to): + options.headers = headers + return cast(Type[ResponseT], override_cast_to) + + return cast_to + + def _should_stream_response_body(self, request: httpx.Request) -> bool: + return request.headers.get(RAW_RESPONSE_HEADER) == "stream" # type: ignore[no-any-return] + + def _process_response_data( + self, + *, + data: object, + cast_to: type[ResponseT], + response: httpx.Response, + ) -> ResponseT: + if data is None: + return cast(ResponseT, None) + + if cast_to is object: + return cast(ResponseT, data) + + try: + if inspect.isclass(cast_to) and issubclass(cast_to, ModelBuilderProtocol): + return cast(ResponseT, cast_to.build(response=response, data=data)) + + if self._strict_response_validation: + return cast(ResponseT, validate_type(type_=cast_to, value=data)) + + return cast(ResponseT, construct_type(type_=cast_to, value=data)) + except pydantic.ValidationError as err: + raise APIResponseValidationError(response=response, body=data) from err + + @property + def qs(self) -> Querystring: + return Querystring() + + @property + def custom_auth(self) -> httpx.Auth | None: + return None + + @property + def auth_headers(self) -> dict[str, str]: + return {} + + @property + def default_headers(self) -> dict[str, str | Omit]: + return { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": self.user_agent, + **self.platform_headers(), + **self._custom_headers, + } + + @property + def default_query(self) -> dict[str, object]: + return { + **self._custom_query, + } + + def _validate_headers( + self, + headers: Headers, # noqa: ARG002 + custom_headers: Headers, # noqa: ARG002 + params: Query, # noqa: ARG002 + cookies: Mapping[str, str], # noqa: ARG002 + ) -> None: + """Validate merged auth-bearing headers, query params, and cookies. + + Does nothing by default. + """ + return + + @property + def user_agent(self) -> str: + return f"{self.__class__.__name__}/Python {self._version}" + + @property + def base_url(self) -> URL: + return self._base_url + + @base_url.setter + def base_url(self, url: URL | str) -> None: + self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url)) + + def platform_headers(self) -> Dict[str, str]: + # the actual implementation is in a separate `lru_cache` decorated + # function because adding `lru_cache` to methods will leak memory + # https://github.com/python/cpython/issues/88476 + return platform_headers(self._version, platform=self._platform) + + def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None: + """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified. + + About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax + """ + if response_headers is None: + return None + + # First, try the non-standard `retry-after-ms` header for milliseconds, + # which is more precise than integer-seconds `retry-after` + try: + retry_ms_header = response_headers.get("retry-after-ms", None) + return float(retry_ms_header) / 1000 + except (TypeError, ValueError): + pass + + # Next, try parsing `retry-after` header as seconds (allowing nonstandard floats). + retry_header = response_headers.get("retry-after") + try: + # note: the spec indicates that this should only ever be an integer + # but if someone sends a float there's no reason for us to not respect it + return float(retry_header) + except (TypeError, ValueError): + pass + + # Last, try parsing `retry-after` as a date. + retry_date_tuple = email.utils.parsedate_tz(retry_header) + if retry_date_tuple is None: + return None + + retry_date = email.utils.mktime_tz(retry_date_tuple) + return float(retry_date - time.time()) + + def _calculate_retry_timeout( + self, + remaining_retries: int, + options: FinalRequestOptions, + response_headers: Optional[httpx.Headers] = None, + ) -> float: + max_retries = options.get_max_retries(self.max_retries) + + # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says. + retry_after = self._parse_retry_after_header(response_headers) + if retry_after is not None and 0 < retry_after <= 60: + return retry_after + + # Also cap retry count to 1000 to avoid any potential overflows with `pow` + nb_retries = min(max_retries - remaining_retries, 1000) + + # Apply exponential backoff, but not more than the max. + sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY) + + # Apply some jitter, plus-or-minus half a second. + jitter = 1 - 0.25 * random() + timeout = sleep_seconds * jitter + return timeout if timeout >= 0 else 0 + + def _should_retry(self, response: httpx.Response) -> bool: + # Note: this is not a standard header + should_retry_header = response.headers.get("x-should-retry") + + # If the server explicitly says whether or not to retry, obey. + if should_retry_header == "true": + log.debug("Retrying as header `x-should-retry` is set to `true`") + return True + if should_retry_header == "false": + log.debug("Not retrying as header `x-should-retry` is set to `false`") + return False + + # Retry on request timeouts. + if response.status_code == 408: + log.debug("Retrying due to status code %i", response.status_code) + return True + + # Retry on lock timeouts. + if response.status_code == 409: + log.debug("Retrying due to status code %i", response.status_code) + return True + + # Retry on rate limits. + if response.status_code == 429: + log.debug("Retrying due to status code %i", response.status_code) + return True + + # Retry internal errors. + if response.status_code >= 500: + log.debug("Retrying due to status code %i", response.status_code) + return True + + log.debug("Not retrying") + return False + + def _idempotency_key(self) -> str: + return f"scalar-python-retry-{uuid.uuid4()}" + + +class _DefaultHttpxClient(httpx.Client): + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) + + +if TYPE_CHECKING: + DefaultHttpxClient = httpx.Client + """An alias to `httpx.Client` that provides the same defaults that this SDK + uses internally. + + This is useful because overriding the `http_client` with your own instance of + `httpx.Client` will result in httpx's defaults being used, not ours. + """ +else: + DefaultHttpxClient = _DefaultHttpxClient + + +class SyncHttpxClientWrapper(DefaultHttpxClient): + def __del__(self) -> None: + if self.is_closed: + return + + try: + self.close() + except Exception: + pass + + +class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]): + _client: httpx.Client + _default_stream_cls: type[Stream[Any]] | None = None + + def __init__( + self, + *, + version: str, + base_url: str | URL, + max_retries: int = DEFAULT_MAX_RETRIES, + timeout: float | Timeout | None | NotGiven = not_given, + http_client: httpx.Client | None = None, + custom_headers: Mapping[str, str] | None = None, + custom_query: Mapping[str, object] | None = None, + _strict_response_validation: bool, + **kwargs: Any, + ) -> None: + if not is_given(timeout): + # if the user passed in a custom http client with a non-default + # timeout set then we use that timeout. + # + # note: there is an edge case here where the user passes in a client + # where they've explicitly set the timeout to match the default timeout + # as this check is structural, meaning that we'll think they didn't + # pass in a timeout and will ignore it + if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT: + timeout = http_client.timeout + else: + timeout = DEFAULT_TIMEOUT + + if http_client is not None and not isinstance(http_client, httpx.Client): # pyright: ignore[reportUnnecessaryIsInstance] + raise TypeError( + f"Invalid `http_client` argument; Expected an instance of `httpx.Client` but got {type(http_client)}" + ) + + super().__init__( + version=version, + # cast to a valid type because mypy doesn't understand our type narrowing + timeout=cast(Timeout, timeout), + base_url=base_url, + max_retries=max_retries, + custom_query=custom_query, + custom_headers=custom_headers, + _strict_response_validation=_strict_response_validation, + ) + self._client = http_client or SyncHttpxClientWrapper( + base_url=base_url, + # cast to a valid type because mypy doesn't understand our type narrowing + timeout=cast(Timeout, timeout), + # Used by generated `with_options(_extra_kwargs=...)` for HTTPX client configuration. + **kwargs, + ) + + def is_closed(self) -> bool: + return self._client.is_closed + + def close(self) -> None: + """Close the underlying HTTPX client. + + The client will *not* be usable after this. + """ + # If an error is thrown while constructing a client, self._client + # may not be present + if hasattr(self, "_client"): + self._client.close() + + def __enter__(self: _T) -> _T: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def _prepare_options( + self, + options: FinalRequestOptions, # noqa: ARG002 + ) -> FinalRequestOptions: + """Hook for mutating the given options""" + return options + + def _prepare_request( + self, + request: httpx.Request, # noqa: ARG002 + ) -> None: + """This method is used as a callback for mutating the `Request` object + after it has been constructed. + This is useful for cases where you want to add certain headers based off of + the request properties, e.g. `url`, `method` etc. + """ + return None + + @overload + def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: Literal[True], + stream_cls: Type[_StreamT], + ) -> _StreamT: ... + + @overload + def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: bool = False, + stream_cls: Type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: ... + + def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: bool = False, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: + cast_to = self._maybe_override_cast_to(cast_to, options) + + # create a copy of the options we were given so that if the + # options are mutated later & we then retry, the retries are + # given the original options + input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() + + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) + + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = self._prepare_options(options) + + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + self._prepare_request(request) + + kwargs: HttpxSendArgs = {} + custom_auth = self._custom_auth(options.security) + if custom_auth is not None: + kwargs["auth"] = custom_auth + + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + + log.debug("Sending HTTP Request: %s %s", request.method, request.url) + + response = None + try: + response = self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, + ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) + + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + err.response.close() + self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue + + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + err.response.read() + + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None + + break + + assert response is not None, "could not resolve response (should never happen)" + return self._process_response( + cast_to=cast_to, + options=options, + response=response, + stream=stream, + stream_cls=stream_cls, + retries_taken=retries_taken, + ) + + def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken + if remaining_retries == 1: + log.debug("1 retry left") + else: + log.debug("%i retries left", remaining_retries) + + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) + log.info("Retrying request to %s in %f seconds", options.url, timeout) + + time.sleep(timeout) + + def _process_response( + self, + *, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + response: httpx.Response, + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + retries_taken: int = 0, + ) -> ResponseT: + origin = get_origin(cast_to) or cast_to + + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): + if not issubclass(origin, APIResponse): + raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") + + response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) + return cast( + ResponseT, + response_cls( + raw=response, + client=self, + cast_to=extract_response_type(response_cls), + stream=stream, + stream_cls=stream_cls, + options=options, + retries_taken=retries_taken, + ), + ) + + if cast_to == httpx.Response: + return cast(ResponseT, response) + + api_response = APIResponse( + raw=response, + client=self, + cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] + stream=stream, + stream_cls=stream_cls, + options=options, + retries_taken=retries_taken, + ) + if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): + return cast(ResponseT, api_response) + + return api_response.parse() + + def _request_api_list( + self, + model: Type[object], + page: Type[SyncPageT], + options: FinalRequestOptions, + cursor_config: Optional[CursorPageConfig] = None, + ) -> SyncPageT: + def _parser(resp: SyncPageT) -> SyncPageT: + resp._set_private_attributes( + client=self, + model=model, + options=options, + cursor_config=cursor_config, + ) + return resp + + options.post_parser = _parser + + return self.request(page, options, stream=False) + + @overload + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[True], + stream_cls: type[_StreamT], + ) -> _StreamT: ... + + @overload + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: ... + + def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool = False, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: + opts = FinalRequestOptions.construct(method="get", url=path, **options) + # cast is required because mypy complains about returning Any even though + # it understands the type variables + return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) + + @overload + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: Literal[True], + stream_cls: type[_StreamT], + ) -> _StreamT: ... + + @overload + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: bool, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: ... + + def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + options: RequestOptions = {}, + files: RequestFiles | None = None, + stream: bool = False, + stream_cls: type[_StreamT] | None = None, + ) -> ResponseT | _StreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options + ) + return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) + + def patch( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options + ) + return self.request(cast_to, opts) + + def put( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options + ) + return self.request(cast_to, opts) + + def delete( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: BinaryTypes | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) + return self.request(cast_to, opts) + + def get_api_list( + self, + path: str, + *, + model: Type[object], + page: Type[SyncPageT], + body: Body | None = None, + options: RequestOptions = {}, + method: str = "get", + cursor_config: Optional[CursorPageConfig] = None, + ) -> SyncPageT: + opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options) + return self._request_api_list(model, page, opts, cursor_config=cursor_config) + + +class _DefaultAsyncHttpxClient(httpx.AsyncClient): + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + super().__init__(**kwargs) + + +try: + import httpx_aiohttp +except ImportError: + + class _DefaultAioHttpClient(httpx.AsyncClient): + def __init__(self, **_kwargs: Any) -> None: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") +else: + + class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + + super().__init__(**kwargs) + + +if TYPE_CHECKING: + DefaultAsyncHttpxClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK + uses internally. + + This is useful because overriding the `http_client` with your own instance of + `httpx.AsyncClient` will result in httpx's defaults being used, not ours. + """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" +else: + DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient + + +class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): + def __del__(self) -> None: + if self.is_closed: + return + + try: + # TODO(someday): support non asyncio runtimes here + asyncio.get_running_loop().create_task(self.aclose()) + except Exception: + pass + + +class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]): + _client: httpx.AsyncClient + _default_stream_cls: type[AsyncStream[Any]] | None = None + + def __init__( + self, + *, + version: str, + base_url: str | URL, + _strict_response_validation: bool, + max_retries: int = DEFAULT_MAX_RETRIES, + timeout: float | Timeout | None | NotGiven = not_given, + http_client: httpx.AsyncClient | None = None, + custom_headers: Mapping[str, str] | None = None, + custom_query: Mapping[str, object] | None = None, + **kwargs: Any, + ) -> None: + if not is_given(timeout): + # if the user passed in a custom http client with a non-default + # timeout set then we use that timeout. + # + # note: there is an edge case here where the user passes in a client + # where they've explicitly set the timeout to match the default timeout + # as this check is structural, meaning that we'll think they didn't + # pass in a timeout and will ignore it + if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT: + timeout = http_client.timeout + else: + timeout = DEFAULT_TIMEOUT + + if http_client is not None and not isinstance(http_client, httpx.AsyncClient): # pyright: ignore[reportUnnecessaryIsInstance] + raise TypeError( + f"Invalid `http_client` argument; Expected an instance of `httpx.AsyncClient` but got {type(http_client)}" + ) + + super().__init__( + version=version, + base_url=base_url, + # cast to a valid type because mypy doesn't understand our type narrowing + timeout=cast(Timeout, timeout), + max_retries=max_retries, + custom_query=custom_query, + custom_headers=custom_headers, + _strict_response_validation=_strict_response_validation, + ) + self._client = http_client or AsyncHttpxClientWrapper( + base_url=base_url, + # cast to a valid type because mypy doesn't understand our type narrowing + timeout=cast(Timeout, timeout), + # Used by generated `with_options(_extra_kwargs=...)` for HTTPX client configuration. + **kwargs, + ) + + def is_closed(self) -> bool: + return self._client.is_closed + + async def close(self) -> None: + """Close the underlying HTTPX client. + + The client will *not* be usable after this. + """ + await self._client.aclose() + + async def __aenter__(self: _T) -> _T: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.close() + + async def _prepare_options( + self, + options: FinalRequestOptions, # noqa: ARG002 + ) -> FinalRequestOptions: + """Hook for mutating the given options""" + return options + + async def _prepare_request( + self, + request: httpx.Request, # noqa: ARG002 + ) -> None: + """This method is used as a callback for mutating the `Request` object + after it has been constructed. + This is useful for cases where you want to add certain headers based off of + the request properties, e.g. `url`, `method` etc. + """ + return None + + @overload + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: Literal[True], + stream_cls: type[_AsyncStreamT], + ) -> _AsyncStreamT: ... + + @overload + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: bool, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: ... + + async def request( + self, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + *, + stream: bool = False, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: + if self._platform is None: + # `get_platform` can make blocking IO calls so we + # execute it earlier while we are in an async context + self._platform = await asyncify(get_platform)() + + cast_to = self._maybe_override_cast_to(cast_to, options) + + # create a copy of the options we were given so that if the + # options are mutated later & we then retry, the retries are + # given the original options + input_options = model_copy(options) + if input_options.idempotency_key is None and input_options.method.lower() != "get": + # ensure the idempotency key is reused between requests + input_options.idempotency_key = self._idempotency_key() + + response: httpx.Response | None = None + max_retries = input_options.get_max_retries(self.max_retries) + + retries_taken = 0 + for retries_taken in range(max_retries + 1): + options = model_copy(input_options) + options = await self._prepare_options(options) + + remaining_retries = max_retries - retries_taken + request = self._build_request(options, retries_taken=retries_taken) + await self._prepare_request(request) + + kwargs: HttpxSendArgs = {} + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth + + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + + log.debug("Sending HTTP Request: %s %s", request.method, request.url) + + response = None + try: + response = await self._client.send( + request, + stream=stream or self._should_stream_response_body(request=request), + **kwargs, + ) + except httpx.TimeoutException as err: + log.debug("Encountered httpx.TimeoutException", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising timeout error") + raise APITimeoutError(request=request) from err + except Exception as err: + log.debug("Encountered Exception", exc_info=True) + + if remaining_retries > 0: + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=None, + ) + continue + + log.debug("Raising connection error") + raise APIConnectionError(request=request) from err + + log.debug( + 'HTTP Response: %s %s "%i %s" %s', + request.method, + request.url, + response.status_code, + response.reason_phrase, + response.headers, + ) + + try: + response.raise_for_status() + except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code + log.debug("Encountered httpx.HTTPStatusError", exc_info=True) + + if remaining_retries > 0 and self._should_retry(err.response): + await err.response.aclose() + await self._sleep_for_retry( + retries_taken=retries_taken, + max_retries=max_retries, + options=input_options, + response=response, + ) + continue + + # If the response is streamed then we need to explicitly read the response + # to completion before attempting to access the response text. + if not err.response.is_closed: + await err.response.aread() + + log.debug("Re-raising status error") + raise self._make_status_error_from_response(err.response) from None + + break + + assert response is not None, "could not resolve response (should never happen)" + return await self._process_response( + cast_to=cast_to, + options=options, + response=response, + stream=stream, + stream_cls=stream_cls, + retries_taken=retries_taken, + ) + + async def _sleep_for_retry( + self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None + ) -> None: + remaining_retries = max_retries - retries_taken + if remaining_retries == 1: + log.debug("1 retry left") + else: + log.debug("%i retries left", remaining_retries) + + timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None) + log.info("Retrying request to %s in %f seconds", options.url, timeout) + + await anyio.sleep(timeout) + + async def _process_response( + self, + *, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + response: httpx.Response, + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + retries_taken: int = 0, + ) -> ResponseT: + origin = get_origin(cast_to) or cast_to + + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): + if not issubclass(origin, AsyncAPIResponse): + raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") + + response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) + return cast( + "ResponseT", + response_cls( + raw=response, + client=self, + cast_to=extract_response_type(response_cls), + stream=stream, + stream_cls=stream_cls, + options=options, + retries_taken=retries_taken, + ), + ) + + if cast_to == httpx.Response: + return cast(ResponseT, response) + + api_response = AsyncAPIResponse( + raw=response, + client=self, + cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] + stream=stream, + stream_cls=stream_cls, + options=options, + retries_taken=retries_taken, + ) + if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): + return cast(ResponseT, api_response) + + return await api_response.parse() + + def _request_api_list( + self, + model: Type[_T], + page: Type[AsyncPageT], + options: FinalRequestOptions, + cursor_config: Optional[CursorPageConfig] = None, + ) -> AsyncPaginator[_T, AsyncPageT]: + return AsyncPaginator( + client=self, options=options, page_cls=page, model=model, cursor_config=cursor_config + ) + + @overload + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: Literal[True], + stream_cls: type[_AsyncStreamT], + ) -> _AsyncStreamT: ... + + @overload + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: ... + + async def get( + self, + path: str, + *, + cast_to: Type[ResponseT], + options: RequestOptions = {}, + stream: bool = False, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: + opts = FinalRequestOptions.construct(method="get", url=path, **options) + return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) + + @overload + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: Literal[False] = False, + ) -> ResponseT: ... + + @overload + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: Literal[True], + stream_cls: type[_AsyncStreamT], + ) -> _AsyncStreamT: ... + + @overload + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: bool, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: ... + + async def post( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + stream: bool = False, + stream_cls: type[_AsyncStreamT] | None = None, + ) -> ResponseT | _AsyncStreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options + ) + return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) + + async def patch( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="patch", + url=path, + json_data=body, + content=content, + files=await async_to_httpx_files(files), + **options, + ) + return await self.request(cast_to, opts) + + async def put( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + files: RequestFiles | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct( + method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options + ) + return await self.request(cast_to, opts) + + async def delete( + self, + path: str, + *, + cast_to: Type[ResponseT], + body: Body | None = None, + content: AsyncBinaryTypes | None = None, + options: RequestOptions = {}, + ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) + return await self.request(cast_to, opts) + + def get_api_list( + self, + path: str, + *, + model: Type[_T], + page: Type[AsyncPageT], + body: Body | None = None, + options: RequestOptions = {}, + method: str = "get", + cursor_config: Optional[CursorPageConfig] = None, + ) -> AsyncPaginator[_T, AsyncPageT]: + opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options) + return self._request_api_list(model, page, opts, cursor_config=cursor_config) + + +def make_request_options( + *, + query: Query | None = None, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + idempotency_key: str | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + post_parser: PostParser | NotGiven = not_given, + security: SecurityOptions | None = None, +) -> RequestOptions: + """Create a dict of type RequestOptions without keys of NotGiven values.""" + options: RequestOptions = {} + if extra_headers is not None: + options["headers"] = extra_headers + + if extra_body is not None: + options["extra_json"] = cast(AnyMapping, extra_body) + + if query is not None: + options["params"] = query + + if extra_query is not None: + options["params"] = {**options.get("params", {}), **extra_query} + + if not isinstance(timeout, NotGiven): + options["timeout"] = timeout + + if idempotency_key is not None: + options["idempotency_key"] = idempotency_key + + if is_given(post_parser): + # internal + options["post_parser"] = post_parser # type: ignore + + if security is not None: + options["security"] = security + + return options + + +class ForceMultipartDict(Dict[str, None]): + def __bool__(self) -> bool: + return True + + +class OtherPlatform: + def __init__(self, name: str) -> None: + self.name = name + + @override + def __str__(self) -> str: + return f"Other:{self.name}" + + +Platform = Union[ + OtherPlatform, + Literal[ + "MacOS", + "Linux", + "Windows", + "FreeBSD", + "OpenBSD", + "iOS", + "Android", + "Unknown", + ], +] + + +def get_platform() -> Platform: + try: + system = platform.system().lower() + platform_name = platform.platform().lower() + except Exception: + return "Unknown" + + if "iphone" in platform_name or "ipad" in platform_name: + # Tested using Python3IDE on an iPhone 11 and Pythonista on an iPad 7 + # system is Darwin and platform_name is a string like: + # - Darwin-21.6.0-iPhone12,1-64bit + # - Darwin-21.6.0-iPad7,11-64bit + return "iOS" + + if system == "darwin": + return "MacOS" + + if system == "windows": + return "Windows" + + if "android" in platform_name: + # Tested using Pydroid 3 + # system is Linux and platform_name is a string like 'Linux-5.10.81-android12-9-00001-geba40aecb3b7-ab8534902-aarch64-with-libc' + return "Android" + + if system == "linux": + # https://distro.readthedocs.io/en/latest/#distro.id + distro_id = distro.id() + if distro_id == "freebsd": + return "FreeBSD" + + if distro_id == "openbsd": + return "OpenBSD" + + return "Linux" + + if platform_name: + return OtherPlatform(platform_name) + + return "Unknown" + + +@lru_cache(maxsize=None) +def platform_headers(version: str, *, platform: Platform | None) -> Dict[str, str]: + return { + "X-Scalar-Lang": "python", + "X-Scalar-Package-Version": version, + "X-Scalar-OS": str(platform or get_platform()), + "X-Scalar-Arch": str(get_architecture()), + "X-Scalar-Runtime": get_python_runtime(), + "X-Scalar-Runtime-Version": get_python_version(), + } + + +class OtherArch: + def __init__(self, name: str) -> None: + self.name = name + + @override + def __str__(self) -> str: + return f"other:{self.name}" + + +Arch = Union[OtherArch, Literal["x32", "x64", "arm", "arm64", "unknown"]] + + +def get_python_runtime() -> str: + try: + return platform.python_implementation() + except Exception: + return "unknown" + + +def get_python_version() -> str: + try: + return platform.python_version() + except Exception: + return "unknown" + + +def get_architecture() -> Arch: + try: + machine = platform.machine().lower() + except Exception: + return "unknown" + + if machine in ("arm64", "aarch64"): + return "arm64" + + # TODO: untested + if machine == "arm": + return "arm" + + if machine == "x86_64": + return "x64" + + # TODO: untested + if sys.maxsize <= 2**32: + return "x32" + + if machine: + return OtherArch(machine) + + return "unknown" + + +def _merge_mappings( + obj1: Mapping[_T_co, Union[_T, Omit]], + obj2: Mapping[_T_co, Union[_T, Omit]], +) -> Dict[_T_co, _T]: + """Merge two mappings of the same type, removing any values that are instances of `Omit`. + + In cases with duplicate keys the second mapping takes precedence. + """ + merged = {**obj1, **obj2} + return {key: value for key, value in merged.items() if not isinstance(value, Omit)} diff --git a/src/_client.py b/src/_client.py new file mode 100644 index 0000000..38bce51 --- /dev/null +++ b/src/_client.py @@ -0,0 +1,794 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import os +import threading +from typing import TYPE_CHECKING, Any, Mapping +from typing_extensions import Literal, Self, override + +import httpx + +from . import _exceptions +from ._qs import Querystring +from ._types import ( + Omit, + Headers, + Timeout, + NotGiven, + Transport, + ProxiesTypes, + RequestOptions, + not_given, +) +from ._utils import is_given, is_mapping_t, get_async_library +from ._compat import cached_property +from ._exceptions import APIStatusError +from ._base_client import ( + DEFAULT_MAX_RETRIES, + SyncAPIClient, + AsyncAPIClient, +) +from ._streaming import Stream as Stream, AsyncStream as AsyncStream +from ._version import __version__ + +if TYPE_CHECKING: + from .resources.registry import RegistryResource, AsyncRegistryResource + from .resources.schemas import SchemasResource, AsyncSchemasResource + from .resources.login_portals import LoginPortalsResource, AsyncLoginPortalsResource + from .resources.rules import RulesResource, AsyncRulesResource + from .resources.themes import ThemesResource, AsyncThemesResource + from .resources.teams import TeamsResource, AsyncTeamsResource + from .resources.scalar_docs import ScalarDocsResource, AsyncScalarDocsResource + from .resources.namespaces import NamespacesResource, AsyncNamespacesResource + from .resources.authentication import AuthenticationResource, AsyncAuthenticationResource + +# Serializes lazy resource imports so concurrent cold access from multiple +# threads cannot deadlock on CPython import locks (see CPython 3.14). +_RESOURCE_IMPORT_LOCK = threading.RLock() + +ENVIRONMENTS: dict[str, str] = { + "production": "https://access.scalar.com", + "local": "http://127.0.0.1:4010", +} + +__all__ = ["ENVIRONMENTS", "Scalar", "AsyncScalar", "Client", "AsyncClient", "Timeout", "Transport", "ProxiesTypes", "RequestOptions"] + + +class Scalar(SyncAPIClient): + # client options + bearer_auth: str | None + + def __init__( + self, + *, + bearer_auth: str | None = None, + environment: Literal["production", "local"] | NotGiven = not_given, + base_url: str | httpx.URL | None | NotGiven = not_given, + timeout: float | Timeout | None | NotGiven = not_given, + max_retries: int = DEFAULT_MAX_RETRIES, + default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + http_client: httpx.Client | None = None, + _strict_response_validation: bool = False, + **kwargs: Any, + ) -> None: + if bearer_auth is None: + bearer_auth = os.environ.get("BEARER_AUTH") + self.bearer_auth = bearer_auth + self._environment = environment + base_url_env = os.environ.get("SCALAR_BASE_URL") + if is_given(base_url) and base_url is not None: + if is_given(environment): + raise ValueError( + "Ambiguous URL; the `base_url` and `environment` arguments are both set. Pass `base_url=None` to use the environment.", + ) + elif is_given(environment): + if base_url_env and base_url is not None: + raise ValueError( + "Ambiguous URL; the base URL environment variable and the `environment` argument are both set. Pass base_url=None to use the environment.", + ) + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc + elif base_url_env is not None: + base_url = base_url_env + else: + self._environment = environment = "production" + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc + # Extra keyword args flow through `with_options(_extra_kwargs=...)` to the default HTTPX client. + super().__init__( + version=__version__, + base_url=base_url, + max_retries=max_retries, + timeout=timeout, + http_client=http_client, + custom_headers=default_headers, + custom_query=default_query, + _strict_response_validation=_strict_response_validation, + **kwargs, + ) + self._idempotency_header = "Idempotency-Key" + self._default_stream_cls = Stream + self._streaming_handlers: list[dict[str, object]] = [] + + @cached_property + def registry(self) -> "RegistryResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.registry import RegistryResource + return RegistryResource(self) + + @cached_property + def schemas(self) -> "SchemasResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.schemas import SchemasResource + return SchemasResource(self) + + @cached_property + def login_portals(self) -> "LoginPortalsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.login_portals import LoginPortalsResource + return LoginPortalsResource(self) + + @cached_property + def rules(self) -> "RulesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.rules import RulesResource + return RulesResource(self) + + @cached_property + def themes(self) -> "ThemesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.themes import ThemesResource + return ThemesResource(self) + + @cached_property + def teams(self) -> "TeamsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.teams import TeamsResource + return TeamsResource(self) + + @cached_property + def scalar_docs(self) -> "ScalarDocsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.scalar_docs import ScalarDocsResource + return ScalarDocsResource(self) + + @cached_property + def namespaces(self) -> "NamespacesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.namespaces import NamespacesResource + return NamespacesResource(self) + + @cached_property + def authentication(self) -> "AuthenticationResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.authentication import AuthenticationResource + return AuthenticationResource(self) + + @cached_property + def with_raw_response(self) -> ScalarWithRawResponse: + return ScalarWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ScalarWithStreamingResponse: + return ScalarWithStreamingResponse(self) + + def copy( + self, + *, + bearer_auth: str | None = None, + environment: str | None = None, + base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = not_given, + http_client: httpx.Client | None = None, + max_retries: int | NotGiven = not_given, + default_headers: Mapping[str, str] | None = None, + set_default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + set_default_query: Mapping[str, object] | None = None, + _extra_kwargs: Mapping[str, Any] | None = None, + ) -> Self: + """Create a new client reusing this client's options with optional overrides.""" + if default_headers is not None and set_default_headers is not None: + raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive") + if default_query is not None and set_default_query is not None: + raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive") + headers = self._custom_headers + if default_headers is not None: + headers = {**headers, **default_headers} + elif set_default_headers is not None: + headers = set_default_headers + params = self._custom_query + if default_query is not None: + params = {**params, **default_query} + elif set_default_query is not None: + params = set_default_query + http_client = http_client or self._client + copied_base_url = base_url if base_url is not None else self.base_url + # Environment overrides must resolve their own URL instead of reusing this client's host. + if environment is not None and base_url is None: + copied_base_url = None + return self.__class__( + bearer_auth=bearer_auth if bearer_auth is not None else self.bearer_auth, + environment=environment if environment is not None else self._environment, + base_url=copied_base_url, + timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, + http_client=http_client, + max_retries=max_retries if is_given(max_retries) else self.max_retries, + default_headers=headers, + default_query=params, + _strict_response_validation=self._strict_response_validation, + **(_extra_kwargs or {}), + ) + + with_options = copy + + @property + @override + def qs(self) -> Querystring: + return Querystring(array_format="repeat") + + @override + def _auth_headers(self, security: dict[str, bool]) -> dict[str, str]: + _ = security + return { + **self._bearer_auth_header_auth, + } + + @override + def _auth_query(self, security: dict[str, bool]) -> dict[str, str]: + _ = security + return { + } + + @override + def _auth_cookies(self, security: dict[str, bool]) -> dict[str, str]: + _ = security + return { + } + + @property + def _bearer_auth_header_auth(self) -> dict[str, str]: + value = self.bearer_auth + if value is None: + return {} + return {"Authorization": f"Bearer {value}"} + + + @override + def _validate_headers( + self, + headers: Headers, + custom_headers: Headers, + params: Mapping[str, object], + cookies: Mapping[str, str], + ) -> None: + if headers.get("Authorization"): + return + if isinstance(custom_headers.get("Authorization"), Omit): + return + raise TypeError("Could not resolve authentication method. Expected Authorization to be set.") + + @property + @override + def default_headers(self) -> dict[str, str | Omit]: + return { + **super().default_headers, + "X-Scalar-Async": "false", + **self._custom_headers, + } + + @override + def _make_status_error(self, err_msg: str, *, body: object, response: httpx.Response) -> APIStatusError: + if response.status_code == 400: + return _exceptions.BadRequestError(err_msg, response=response, body=body) + if response.status_code == 401: + return _exceptions.AuthenticationError(err_msg, response=response, body=body) + if response.status_code == 403: + return _exceptions.PermissionDeniedError(err_msg, response=response, body=body) + if response.status_code == 404: + return _exceptions.NotFoundError(err_msg, response=response, body=body) + if response.status_code == 409: + return _exceptions.ConflictError(err_msg, response=response, body=body) + if response.status_code == 422: + return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body) + if response.status_code == 429: + return _exceptions.RateLimitError(err_msg, response=response, body=body) + if response.status_code >= 500: + return _exceptions.InternalServerError(err_msg, response=response, body=body) + return APIStatusError(err_msg, response=response, body=body) + + +class AsyncScalar(AsyncAPIClient): + # client options + bearer_auth: str | None + + def __init__( + self, + *, + bearer_auth: str | None = None, + environment: Literal["production", "local"] | NotGiven = not_given, + base_url: str | httpx.URL | None | NotGiven = not_given, + timeout: float | Timeout | None | NotGiven = not_given, + max_retries: int = DEFAULT_MAX_RETRIES, + default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + http_client: httpx.AsyncClient | None = None, + _strict_response_validation: bool = False, + **kwargs: Any, + ) -> None: + if bearer_auth is None: + bearer_auth = os.environ.get("BEARER_AUTH") + self.bearer_auth = bearer_auth + self._environment = environment + base_url_env = os.environ.get("SCALAR_BASE_URL") + if is_given(base_url) and base_url is not None: + if is_given(environment): + raise ValueError( + "Ambiguous URL; the `base_url` and `environment` arguments are both set. Pass `base_url=None` to use the environment.", + ) + elif is_given(environment): + if base_url_env and base_url is not None: + raise ValueError( + "Ambiguous URL; the base URL environment variable and the `environment` argument are both set. Pass base_url=None to use the environment.", + ) + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc + elif base_url_env is not None: + base_url = base_url_env + else: + self._environment = environment = "production" + try: + base_url = ENVIRONMENTS[environment] + except KeyError as exc: + raise ValueError(f"Unknown environment: {environment}") from exc + # Extra keyword args flow through `with_options(_extra_kwargs=...)` to the default HTTPX client. + super().__init__( + version=__version__, + base_url=base_url, + max_retries=max_retries, + timeout=timeout, + http_client=http_client, + custom_headers=default_headers, + custom_query=default_query, + _strict_response_validation=_strict_response_validation, + **kwargs, + ) + self._idempotency_header = "Idempotency-Key" + self._default_stream_cls = AsyncStream + self._streaming_handlers: list[dict[str, object]] = [] + + @cached_property + def registry(self) -> "AsyncRegistryResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.registry import AsyncRegistryResource + return AsyncRegistryResource(self) + + @cached_property + def schemas(self) -> "AsyncSchemasResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.schemas import AsyncSchemasResource + return AsyncSchemasResource(self) + + @cached_property + def login_portals(self) -> "AsyncLoginPortalsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.login_portals import AsyncLoginPortalsResource + return AsyncLoginPortalsResource(self) + + @cached_property + def rules(self) -> "AsyncRulesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.rules import AsyncRulesResource + return AsyncRulesResource(self) + + @cached_property + def themes(self) -> "AsyncThemesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.themes import AsyncThemesResource + return AsyncThemesResource(self) + + @cached_property + def teams(self) -> "AsyncTeamsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.teams import AsyncTeamsResource + return AsyncTeamsResource(self) + + @cached_property + def scalar_docs(self) -> "AsyncScalarDocsResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.scalar_docs import AsyncScalarDocsResource + return AsyncScalarDocsResource(self) + + @cached_property + def namespaces(self) -> "AsyncNamespacesResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.namespaces import AsyncNamespacesResource + return AsyncNamespacesResource(self) + + @cached_property + def authentication(self) -> "AsyncAuthenticationResource": + with _RESOURCE_IMPORT_LOCK: + from .resources.authentication import AsyncAuthenticationResource + return AsyncAuthenticationResource(self) + + @cached_property + def with_raw_response(self) -> AsyncScalarWithRawResponse: + return AsyncScalarWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncScalarWithStreamingResponse: + return AsyncScalarWithStreamingResponse(self) + + def copy( + self, + *, + bearer_auth: str | None = None, + environment: str | None = None, + base_url: str | httpx.URL | None = None, + timeout: float | Timeout | None | NotGiven = not_given, + http_client: httpx.AsyncClient | None = None, + max_retries: int | NotGiven = not_given, + default_headers: Mapping[str, str] | None = None, + set_default_headers: Mapping[str, str] | None = None, + default_query: Mapping[str, object] | None = None, + set_default_query: Mapping[str, object] | None = None, + _extra_kwargs: Mapping[str, Any] | None = None, + ) -> Self: + """Create a new client reusing this client's options with optional overrides.""" + if default_headers is not None and set_default_headers is not None: + raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive") + if default_query is not None and set_default_query is not None: + raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive") + headers = self._custom_headers + if default_headers is not None: + headers = {**headers, **default_headers} + elif set_default_headers is not None: + headers = set_default_headers + params = self._custom_query + if default_query is not None: + params = {**params, **default_query} + elif set_default_query is not None: + params = set_default_query + http_client = http_client or self._client + copied_base_url = base_url if base_url is not None else self.base_url + # Environment overrides must resolve their own URL instead of reusing this client's host. + if environment is not None and base_url is None: + copied_base_url = None + return self.__class__( + bearer_auth=bearer_auth if bearer_auth is not None else self.bearer_auth, + environment=environment if environment is not None else self._environment, + base_url=copied_base_url, + timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, + http_client=http_client, + max_retries=max_retries if is_given(max_retries) else self.max_retries, + default_headers=headers, + default_query=params, + _strict_response_validation=self._strict_response_validation, + **(_extra_kwargs or {}), + ) + + with_options = copy + + @property + @override + def qs(self) -> Querystring: + return Querystring(array_format="repeat") + + @override + def _auth_headers(self, security: dict[str, bool]) -> dict[str, str]: + _ = security + return { + **self._bearer_auth_header_auth, + } + + @override + def _auth_query(self, security: dict[str, bool]) -> dict[str, str]: + _ = security + return { + } + + @override + def _auth_cookies(self, security: dict[str, bool]) -> dict[str, str]: + _ = security + return { + } + + @property + def _bearer_auth_header_auth(self) -> dict[str, str]: + value = self.bearer_auth + if value is None: + return {} + return {"Authorization": f"Bearer {value}"} + + + @override + def _validate_headers( + self, + headers: Headers, + custom_headers: Headers, + params: Mapping[str, object], + cookies: Mapping[str, str], + ) -> None: + if headers.get("Authorization"): + return + if isinstance(custom_headers.get("Authorization"), Omit): + return + raise TypeError("Could not resolve authentication method. Expected Authorization to be set.") + + @property + @override + def default_headers(self) -> dict[str, str | Omit]: + return { + **super().default_headers, + "X-Scalar-Async": f"async:{get_async_library()}", + **self._custom_headers, + } + + @override + def _make_status_error(self, err_msg: str, *, body: object, response: httpx.Response) -> APIStatusError: + if response.status_code == 400: + return _exceptions.BadRequestError(err_msg, response=response, body=body) + if response.status_code == 401: + return _exceptions.AuthenticationError(err_msg, response=response, body=body) + if response.status_code == 403: + return _exceptions.PermissionDeniedError(err_msg, response=response, body=body) + if response.status_code == 404: + return _exceptions.NotFoundError(err_msg, response=response, body=body) + if response.status_code == 409: + return _exceptions.ConflictError(err_msg, response=response, body=body) + if response.status_code == 422: + return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body) + if response.status_code == 429: + return _exceptions.RateLimitError(err_msg, response=response, body=body) + if response.status_code >= 500: + return _exceptions.InternalServerError(err_msg, response=response, body=body) + return APIStatusError(err_msg, response=response, body=body) + + +class ScalarWithRawResponse: + def __init__(self, client: Scalar) -> None: + self._client = client + + @cached_property + def registry(self) -> "RegistryResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.registry import RegistryResourceWithRawResponse + return RegistryResourceWithRawResponse(self._client.registry) + + @cached_property + def schemas(self) -> "SchemasResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.schemas import SchemasResourceWithRawResponse + return SchemasResourceWithRawResponse(self._client.schemas) + + @cached_property + def login_portals(self) -> "LoginPortalsResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.login_portals import LoginPortalsResourceWithRawResponse + return LoginPortalsResourceWithRawResponse(self._client.login_portals) + + @cached_property + def rules(self) -> "RulesResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.rules import RulesResourceWithRawResponse + return RulesResourceWithRawResponse(self._client.rules) + + @cached_property + def themes(self) -> "ThemesResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.themes import ThemesResourceWithRawResponse + return ThemesResourceWithRawResponse(self._client.themes) + + @cached_property + def teams(self) -> "TeamsResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.teams import TeamsResourceWithRawResponse + return TeamsResourceWithRawResponse(self._client.teams) + + @cached_property + def scalar_docs(self) -> "ScalarDocsResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.scalar_docs import ScalarDocsResourceWithRawResponse + return ScalarDocsResourceWithRawResponse(self._client.scalar_docs) + + @cached_property + def namespaces(self) -> "NamespacesResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.namespaces import NamespacesResourceWithRawResponse + return NamespacesResourceWithRawResponse(self._client.namespaces) + + @cached_property + def authentication(self) -> "AuthenticationResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.authentication import AuthenticationResourceWithRawResponse + return AuthenticationResourceWithRawResponse(self._client.authentication) + + +class AsyncScalarWithRawResponse: + def __init__(self, client: AsyncScalar) -> None: + self._client = client + + @cached_property + def registry(self) -> "AsyncRegistryResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.registry import AsyncRegistryResourceWithRawResponse + return AsyncRegistryResourceWithRawResponse(self._client.registry) + + @cached_property + def schemas(self) -> "AsyncSchemasResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.schemas import AsyncSchemasResourceWithRawResponse + return AsyncSchemasResourceWithRawResponse(self._client.schemas) + + @cached_property + def login_portals(self) -> "AsyncLoginPortalsResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.login_portals import AsyncLoginPortalsResourceWithRawResponse + return AsyncLoginPortalsResourceWithRawResponse(self._client.login_portals) + + @cached_property + def rules(self) -> "AsyncRulesResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.rules import AsyncRulesResourceWithRawResponse + return AsyncRulesResourceWithRawResponse(self._client.rules) + + @cached_property + def themes(self) -> "AsyncThemesResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.themes import AsyncThemesResourceWithRawResponse + return AsyncThemesResourceWithRawResponse(self._client.themes) + + @cached_property + def teams(self) -> "AsyncTeamsResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.teams import AsyncTeamsResourceWithRawResponse + return AsyncTeamsResourceWithRawResponse(self._client.teams) + + @cached_property + def scalar_docs(self) -> "AsyncScalarDocsResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.scalar_docs import AsyncScalarDocsResourceWithRawResponse + return AsyncScalarDocsResourceWithRawResponse(self._client.scalar_docs) + + @cached_property + def namespaces(self) -> "AsyncNamespacesResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.namespaces import AsyncNamespacesResourceWithRawResponse + return AsyncNamespacesResourceWithRawResponse(self._client.namespaces) + + @cached_property + def authentication(self) -> "AsyncAuthenticationResourceWithRawResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.authentication import AsyncAuthenticationResourceWithRawResponse + return AsyncAuthenticationResourceWithRawResponse(self._client.authentication) + + +class ScalarWithStreamingResponse: + def __init__(self, client: Scalar) -> None: + self._client = client + + @cached_property + def registry(self) -> "RegistryResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.registry import RegistryResourceWithStreamingResponse + return RegistryResourceWithStreamingResponse(self._client.registry) + + @cached_property + def schemas(self) -> "SchemasResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.schemas import SchemasResourceWithStreamingResponse + return SchemasResourceWithStreamingResponse(self._client.schemas) + + @cached_property + def login_portals(self) -> "LoginPortalsResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.login_portals import LoginPortalsResourceWithStreamingResponse + return LoginPortalsResourceWithStreamingResponse(self._client.login_portals) + + @cached_property + def rules(self) -> "RulesResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.rules import RulesResourceWithStreamingResponse + return RulesResourceWithStreamingResponse(self._client.rules) + + @cached_property + def themes(self) -> "ThemesResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.themes import ThemesResourceWithStreamingResponse + return ThemesResourceWithStreamingResponse(self._client.themes) + + @cached_property + def teams(self) -> "TeamsResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.teams import TeamsResourceWithStreamingResponse + return TeamsResourceWithStreamingResponse(self._client.teams) + + @cached_property + def scalar_docs(self) -> "ScalarDocsResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.scalar_docs import ScalarDocsResourceWithStreamingResponse + return ScalarDocsResourceWithStreamingResponse(self._client.scalar_docs) + + @cached_property + def namespaces(self) -> "NamespacesResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.namespaces import NamespacesResourceWithStreamingResponse + return NamespacesResourceWithStreamingResponse(self._client.namespaces) + + @cached_property + def authentication(self) -> "AuthenticationResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.authentication import AuthenticationResourceWithStreamingResponse + return AuthenticationResourceWithStreamingResponse(self._client.authentication) + + +class AsyncScalarWithStreamingResponse: + def __init__(self, client: AsyncScalar) -> None: + self._client = client + + @cached_property + def registry(self) -> "AsyncRegistryResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.registry import AsyncRegistryResourceWithStreamingResponse + return AsyncRegistryResourceWithStreamingResponse(self._client.registry) + + @cached_property + def schemas(self) -> "AsyncSchemasResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.schemas import AsyncSchemasResourceWithStreamingResponse + return AsyncSchemasResourceWithStreamingResponse(self._client.schemas) + + @cached_property + def login_portals(self) -> "AsyncLoginPortalsResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.login_portals import AsyncLoginPortalsResourceWithStreamingResponse + return AsyncLoginPortalsResourceWithStreamingResponse(self._client.login_portals) + + @cached_property + def rules(self) -> "AsyncRulesResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.rules import AsyncRulesResourceWithStreamingResponse + return AsyncRulesResourceWithStreamingResponse(self._client.rules) + + @cached_property + def themes(self) -> "AsyncThemesResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.themes import AsyncThemesResourceWithStreamingResponse + return AsyncThemesResourceWithStreamingResponse(self._client.themes) + + @cached_property + def teams(self) -> "AsyncTeamsResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.teams import AsyncTeamsResourceWithStreamingResponse + return AsyncTeamsResourceWithStreamingResponse(self._client.teams) + + @cached_property + def scalar_docs(self) -> "AsyncScalarDocsResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.scalar_docs import AsyncScalarDocsResourceWithStreamingResponse + return AsyncScalarDocsResourceWithStreamingResponse(self._client.scalar_docs) + + @cached_property + def namespaces(self) -> "AsyncNamespacesResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.namespaces import AsyncNamespacesResourceWithStreamingResponse + return AsyncNamespacesResourceWithStreamingResponse(self._client.namespaces) + + @cached_property + def authentication(self) -> "AsyncAuthenticationResourceWithStreamingResponse": + with _RESOURCE_IMPORT_LOCK: + from .resources.authentication import AsyncAuthenticationResourceWithStreamingResponse + return AsyncAuthenticationResourceWithStreamingResponse(self._client.authentication) + + +# Alias names for the documented `Client` / `AsyncClient` symbols. +Client = Scalar +AsyncClient = AsyncScalar diff --git a/src/_compat.py b/src/_compat.py new file mode 100644 index 0000000..e6690a4 --- /dev/null +++ b/src/_compat.py @@ -0,0 +1,226 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload +from datetime import date, datetime +from typing_extensions import Self, Literal, TypedDict + +import pydantic +from pydantic.fields import FieldInfo + +from ._types import IncEx, StrBytesIntFloat + +_T = TypeVar("_T") +_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) + +# --------------- Pydantic v2, v3 compatibility --------------- + +# Pyright incorrectly reports some of our functions as overriding a method when they don't +# pyright: reportIncompatibleMethodOverride=false + +PYDANTIC_V1 = pydantic.VERSION.startswith("1.") + +if TYPE_CHECKING: + + def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001 + ... + + def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: # noqa: ARG001 + ... + + def get_args(t: type[Any]) -> tuple[Any, ...]: # noqa: ARG001 + ... + + def is_union(tp: type[Any] | None) -> bool: # noqa: ARG001 + ... + + def get_origin(t: type[Any]) -> type[Any] | None: # noqa: ARG001 + ... + + def is_literal_type(type_: type[Any]) -> bool: # noqa: ARG001 + ... + + def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001 + ... + +else: + # v1 re-exports + if PYDANTIC_V1: + from pydantic.typing import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + is_typeddict as is_typeddict, + is_literal_type as is_literal_type, + ) + from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime + else: + from ._utils import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + parse_date as parse_date, + is_typeddict as is_typeddict, + parse_datetime as parse_datetime, + is_literal_type as is_literal_type, + ) + + +# refactored config +if TYPE_CHECKING: + from pydantic import ConfigDict as ConfigDict +else: + if PYDANTIC_V1: + # TODO: provide an error message here? + ConfigDict = None + else: + from pydantic import ConfigDict as ConfigDict + + +# renamed methods / properties +def parse_obj(model: type[_ModelT], value: object) -> _ModelT: + if PYDANTIC_V1: + return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + else: + return model.model_validate(value) + + +def field_is_required(field: FieldInfo) -> bool: + if PYDANTIC_V1: + return field.required # type: ignore + return field.is_required() + + +def field_get_default(field: FieldInfo) -> Any: + value = field.get_default() + if PYDANTIC_V1: + return value + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None + return value + + +def field_outer_type(field: FieldInfo) -> Any: + if PYDANTIC_V1: + return field.outer_type_ # type: ignore + return field.annotation + + +def get_model_config(model: type[pydantic.BaseModel]) -> Any: + if PYDANTIC_V1: + return model.__config__ # type: ignore + return model.model_config + + +def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]: + if PYDANTIC_V1: + return model.__fields__ # type: ignore + return model.model_fields + + +def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT: + if PYDANTIC_V1: + return model.copy(deep=deep) # type: ignore + return model.model_copy(deep=deep) + + +def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str: + if PYDANTIC_V1: + return model.json(indent=indent) # type: ignore + return model.model_dump_json(indent=indent) + + +class _ModelDumpKwargs(TypedDict, total=False): + by_alias: bool + + +def model_dump( + model: pydantic.BaseModel, + *, + exclude: IncEx | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + warnings: bool = True, + mode: Literal["json", "python"] = "python", + by_alias: bool | None = None, +) -> dict[str, Any]: + if (not PYDANTIC_V1) or hasattr(model, "model_dump"): + kwargs: _ModelDumpKwargs = {} + if by_alias is not None: + kwargs["by_alias"] = by_alias + return model.model_dump( + mode=mode, + exclude=exclude, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + # warnings are not supported in Pydantic v1 + warnings=True if PYDANTIC_V1 else warnings, + **kwargs, + ) + return cast( + "dict[str, Any]", + model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias) + ), + ) + + +def model_parse(model: type[_ModelT], data: Any) -> _ModelT: + if PYDANTIC_V1: + return model.parse_obj(data) # pyright: ignore[reportDeprecated] + return model.model_validate(data) + + +# generic models +if TYPE_CHECKING: + + class GenericModel(pydantic.BaseModel): ... + +else: + if PYDANTIC_V1: + import pydantic.generics + + class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... + else: + # there no longer needs to be a distinction in v2 but + # we still have to create our own subclass to avoid + # inconsistent MRO ordering errors + class GenericModel(pydantic.BaseModel): ... + + +# cached properties +if TYPE_CHECKING: + cached_property = property + + # we define a separate type (copied from typeshed) + # that represents that `cached_property` is `set`able + # at runtime, which differs from `@property`. + # + # this is a separate type as editors likely special case + # `@property` and we don't want to cause issues just to have + # more helpful internal types. + + class typed_cached_property(Generic[_T]): + func: Callable[[Any], _T] + attrname: str | None + + def __init__(self, func: Callable[[Any], _T]) -> None: ... + + @overload + def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ... + + @overload + def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ... + + def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self: + raise NotImplementedError() + + def __set_name__(self, owner: type[Any], name: str) -> None: ... + + # __set__ is not defined at runtime, but @cached_property is designed to be settable + def __set__(self, instance: object, value: _T) -> None: ... +else: + from functools import cached_property as cached_property + + typed_cached_property = cached_property diff --git a/src/_constants.py b/src/_constants.py new file mode 100644 index 0000000..969d075 --- /dev/null +++ b/src/_constants.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Scalar. See CONTRIBUTING.md for details. + +import httpx + +RAW_RESPONSE_HEADER = "X-Scalar-Raw-Response" +OVERRIDE_CAST_TO_HEADER = "____scalar_override_cast_to" + +# default timeout is 1 minute +DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0) +DEFAULT_MAX_RETRIES = 2 +DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) + +INITIAL_RETRY_DELAY = 0.5 +MAX_RETRY_DELAY = 8.0 diff --git a/src/_event_handler.py b/src/_event_handler.py new file mode 100644 index 0000000..d84a24d --- /dev/null +++ b/src/_event_handler.py @@ -0,0 +1,85 @@ +# File generated from our OpenAPI spec by Scalar. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import threading +from typing import Any, Callable + +EventHandler = Callable[..., Any] + + +class EventHandlerRegistry: + """Thread-safe (optional) registry of event handlers.""" + + def __init__(self, *, use_lock: bool = False) -> None: + self._handlers: dict[str, list[EventHandler]] = {} + self._once_ids: set[int] = set() + self._lock: threading.Lock | None = threading.Lock() if use_lock else None + + def _acquire(self) -> None: + if self._lock is not None: + self._lock.acquire() + + def _release(self) -> None: + if self._lock is not None: + self._lock.release() + + def add(self, event_type: str, handler: EventHandler, *, once: bool = False) -> None: + self._acquire() + try: + handlers = self._handlers.setdefault(event_type, []) + handlers.append(handler) + if once: + self._once_ids.add(id(handler)) + finally: + self._release() + + def remove(self, event_type: str, handler: EventHandler) -> None: + self._acquire() + try: + handlers = self._handlers.get(event_type) + if handlers is not None: + try: + handlers.remove(handler) + except ValueError: + pass + self._once_ids.discard(id(handler)) + finally: + self._release() + + def get_handlers(self, event_type: str) -> list[EventHandler]: + """Return a snapshot of handlers for the given event type, removing once-handlers.""" + self._acquire() + try: + handlers = self._handlers.get(event_type) + if not handlers: + return [] + result = list(handlers) + to_remove = [h for h in result if id(h) in self._once_ids] + for h in to_remove: + handlers.remove(h) + self._once_ids.discard(id(h)) + return result + finally: + self._release() + + def has_handlers(self, event_type: str) -> bool: + self._acquire() + try: + handlers = self._handlers.get(event_type) + return bool(handlers) + finally: + self._release() + + def merge_into(self, target: EventHandlerRegistry) -> None: + """Move all handlers from this registry into *target*, then clear self.""" + self._acquire() + try: + for event_type, handlers in self._handlers.items(): + for handler in handlers: + once = id(handler) in self._once_ids + target.add(event_type, handler, once=once) + self._handlers.clear() + self._once_ids.clear() + finally: + self._release() diff --git a/src/_exceptions.py b/src/_exceptions.py new file mode 100644 index 0000000..aabeb95 --- /dev/null +++ b/src/_exceptions.py @@ -0,0 +1,126 @@ +# File generated from our OpenAPI spec by Scalar. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +__all__ = [ + "BadRequestError", + "AuthenticationError", + "PermissionDeniedError", + "NotFoundError", + "ConflictError", + "UnprocessableEntityError", + "RateLimitError", + "InternalServerError", + "WebSocketConnectionClosedError", + "WebSocketQueueFullError", +] + + +class ScalarError(Exception): + pass + + +class APIError(ScalarError): + message: str + request: httpx.Request + + body: object | None + """The API response body. + + If the API responded with a valid JSON structure then this property will be the + decoded result. + + If it isn't a valid JSON structure then this will be the raw response. + + If there was no response associated with this error then it will be `None`. + """ + + def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None: # noqa: ARG002 + super().__init__(message) + self.request = request + self.message = message + self.body = body + + +class APIResponseValidationError(APIError): + response: httpx.Response + status_code: int + + def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None: + super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body) + self.response = response + self.status_code = response.status_code + + +class APIStatusError(APIError): + """Raised when an API response has a status code of 4xx or 5xx.""" + + response: httpx.Response + status_code: int + + def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None: + super().__init__(message, response.request, body=body) + self.response = response + self.status_code = response.status_code + + +class APIConnectionError(APIError): + def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None: + super().__init__(message, request, body=None) + + +class APITimeoutError(APIConnectionError): + def __init__(self, request: httpx.Request) -> None: + super().__init__(message="Request timed out.", request=request) + + +class BadRequestError(APIStatusError): + status_code: Literal[400] = 400 # pyright: ignore[reportIncompatibleVariableOverride] + + +class AuthenticationError(APIStatusError): + status_code: Literal[401] = 401 # pyright: ignore[reportIncompatibleVariableOverride] + + +class PermissionDeniedError(APIStatusError): + status_code: Literal[403] = 403 # pyright: ignore[reportIncompatibleVariableOverride] + + +class NotFoundError(APIStatusError): + status_code: Literal[404] = 404 # pyright: ignore[reportIncompatibleVariableOverride] + + +class ConflictError(APIStatusError): + status_code: Literal[409] = 409 # pyright: ignore[reportIncompatibleVariableOverride] + + +class UnprocessableEntityError(APIStatusError): + status_code: Literal[422] = 422 # pyright: ignore[reportIncompatibleVariableOverride] + + +class RateLimitError(APIStatusError): + status_code: Literal[429] = 429 # pyright: ignore[reportIncompatibleVariableOverride] + + +class InternalServerError(APIStatusError): + pass + + +class WebSocketConnectionClosedError(ScalarError): + """Raised when a WebSocket connection closes with unsent messages.""" + + unsent_messages: list[str] + + def __init__(self, message: str, *, unsent_messages: list[str]) -> None: + super().__init__(message) + self.unsent_messages = unsent_messages + + +class WebSocketQueueFullError(ScalarError): + """Raised when the outgoing WebSocket message queue exceeds its byte-size limit.""" + + pass diff --git a/src/_files.py b/src/_files.py new file mode 100644 index 0000000..76da9e0 --- /dev/null +++ b/src/_files.py @@ -0,0 +1,173 @@ +from __future__ import annotations + +import io +import os +import pathlib +from typing import Sequence, cast, overload +from typing_extensions import TypeVar, TypeGuard + +import anyio + +from ._types import ( + FileTypes, + FileContent, + RequestFiles, + HttpxFileTypes, + Base64FileInput, + HttpxFileContent, + HttpxRequestFiles, +) +from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t + +_T = TypeVar("_T") + + +def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: + return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) + + +def is_file_content(obj: object) -> TypeGuard[FileContent]: + return ( + isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) + ) + + +def assert_is_file_content(obj: object, *, key: str | None = None) -> None: + if not is_file_content(obj): + prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" + raise RuntimeError( + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." + ) from None + + +@overload +def to_httpx_files(files: None) -> None: ... + + +@overload +def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ... + + +def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None: + if files is None: + return None + + if is_mapping_t(files): + files = {key: _transform_file(file) for key, file in files.items()} + elif is_sequence_t(files): + files = [(key, _transform_file(file)) for key, file in files] + else: + raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence") + + return files + + +def _transform_file(file: FileTypes) -> HttpxFileTypes: + if is_file_content(file): + if isinstance(file, os.PathLike): + path = pathlib.Path(file) + return (path.name, path.read_bytes()) + + return file + + if is_tuple_t(file): + return (file[0], read_file_content(file[1]), *file[2:]) + + raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") + + +def read_file_content(file: FileContent) -> HttpxFileContent: + if isinstance(file, os.PathLike): + return pathlib.Path(file).read_bytes() + return file + + +@overload +async def async_to_httpx_files(files: None) -> None: ... + + +@overload +async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ... + + +async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None: + if files is None: + return None + + if is_mapping_t(files): + files = {key: await _async_transform_file(file) for key, file in files.items()} + elif is_sequence_t(files): + files = [(key, await _async_transform_file(file)) for key, file in files] + else: + raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence") + + return files + + +async def _async_transform_file(file: FileTypes) -> HttpxFileTypes: + if is_file_content(file): + if isinstance(file, os.PathLike): + path = anyio.Path(file) + return (path.name, await path.read_bytes()) + + return file + + if is_tuple_t(file): + return (file[0], await async_read_file_content(file[1]), *file[2:]) + + raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") + + +async def async_read_file_content(file: FileContent) -> HttpxFileContent: + if isinstance(file, os.PathLike): + return await anyio.Path(file).read_bytes() + + return file + + +def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T: + """Copy only the containers along the given paths. + + Used to guard against mutation by extract_files without copying the entire structure. + Only dicts and lists that lie on a path are copied; everything else + is returned by reference. + + For example, given paths=[["foo", "files", "file"]] and the structure: + { + "foo": { + "bar": {"baz": {}}, + "files": {"file": } + } + } + The root dict, "foo", and "files" are copied (they lie on the path). + "bar" and "baz" are returned by reference (off the path). + """ + return _deepcopy_with_paths(item, paths, 0) + + +def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T: + if not paths: + return item + if is_mapping(item): + key_to_paths: dict[str, list[Sequence[str]]] = {} + for path in paths: + if index < len(path): + key_to_paths.setdefault(path[index], []).append(path) + + # if no path continues through this mapping, it won't be mutated and copying it is redundant + if not key_to_paths: + return item + + result = dict(item) + for key, subpaths in key_to_paths.items(): + if key in result: + result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1) + return cast(_T, result) + if is_list(item): + array_paths = [path for path in paths if index < len(path) and path[index] == ""] + + # if no path expects a list here, nothing will be mutated inside it - return by reference + if not array_paths: + return cast(_T, item) + return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item]) + return item diff --git a/src/_models.py b/src/_models.py new file mode 100644 index 0000000..ce728eb --- /dev/null +++ b/src/_models.py @@ -0,0 +1,962 @@ +from __future__ import annotations + +import os +import inspect +import weakref +from typing import ( + IO, + TYPE_CHECKING, + Any, + Type, + Union, + Generic, + TypeVar, + Callable, + Iterable, + Optional, + AsyncIterable, + cast, +) +from datetime import date, datetime +from typing_extensions import ( + List, + Unpack, + Literal, + ClassVar, + Protocol, + Required, + Annotated, + ParamSpec, + TypeAlias, + TypedDict, + TypeGuard, + final, + override, + runtime_checkable, +) + +import pydantic +from pydantic.fields import FieldInfo + +from ._types import ( + Body, + IncEx, + Query, + ModelT, + Headers, + Timeout, + NotGiven, + AnyMapping, + HttpxRequestFiles, +) +from ._utils import ( + PropertyInfo, + is_list, + is_given, + json_safe, + lru_cache, + is_mapping, + parse_date, + coerce_boolean, + parse_datetime, + strip_not_given, + extract_type_arg, + is_annotated_type, + is_type_alias_type, + strip_annotated_type, +) +from ._compat import ( + PYDANTIC_V1, + ConfigDict, + GenericModel as BaseGenericModel, + get_args, + is_union, + parse_obj, + get_origin, + is_literal_type, + get_model_config, + get_model_fields, + field_get_default, +) +from ._constants import RAW_RESPONSE_HEADER + +if TYPE_CHECKING: + from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler + from pydantic_core import CoreSchema, core_schema + from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema +else: + try: + from pydantic_core import CoreSchema, core_schema + except ImportError: + CoreSchema = None + core_schema = None + +__all__ = ["BaseModel", "GenericModel"] + +_T = TypeVar("_T") +_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel") + +P = ParamSpec("P") + + +@runtime_checkable +class _ConfigProtocol(Protocol): + allow_population_by_field_name: bool + + +class BaseModel(pydantic.BaseModel): + if PYDANTIC_V1: + + @property + @override + def model_fields_set(self) -> set[str]: + # a forwards-compat shim for pydantic v2 + return self.__fields_set__ # type: ignore + + class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] + extra: Any = pydantic.Extra.allow # type: ignore + else: + model_config: ClassVar[ConfigDict] = ConfigDict( + extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) + ) + + def to_dict( + self, + *, + mode: Literal["json", "python"] = "python", + use_api_names: bool = True, + exclude_unset: bool = True, + exclude_defaults: bool = False, + exclude_none: bool = False, + warnings: bool = True, + ) -> dict[str, object]: + """Recursively generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + By default, fields that were not set by the API will not be included, + and keys will match the API response, *not* the property names from the model. + + For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property, + the output will use the `"fooBar"` key (unless `use_api_names=False` is passed). + + Args: + mode: + If mode is 'json', the dictionary will only contain JSON serializable types. e.g. `datetime` will be turned into a string, `"2024-3-22T18:11:19.117000Z"`. + If mode is 'python', the dictionary may contain any Python objects. e.g. `datetime(2024, 3, 22)` + + use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value from the output. + exclude_none: Whether to exclude fields that have a value of `None` from the output. + warnings: Whether to log warnings when invalid fields are encountered. This is only supported in Pydantic v2. + """ + return self.model_dump( + mode=mode, + by_alias=use_api_names, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + warnings=warnings, + ) + + def to_json( + self, + *, + indent: int | None = 2, + use_api_names: bool = True, + exclude_unset: bool = True, + exclude_defaults: bool = False, + exclude_none: bool = False, + warnings: bool = True, + ) -> str: + """Generates a JSON string representing this model as it would be received from or sent to the API (but with indentation). + + By default, fields that were not set by the API will not be included, + and keys will match the API response, *not* the property names from the model. + + For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property, + the output will use the `"fooBar"` key (unless `use_api_names=False` is passed). + + Args: + indent: Indentation to use in the JSON output. If `None` is passed, the output will be compact. Defaults to `2` + use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that have the default value. + exclude_none: Whether to exclude fields that have a value of `None`. + warnings: Whether to show any warnings that occurred during serialization. This is only supported in Pydantic v2. + """ + return self.model_dump_json( + indent=indent, + by_alias=use_api_names, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + warnings=warnings, + ) + + @override + def __str__(self) -> str: + # mypy complains about an invalid self arg + return f"{self.__repr_name__()}({self.__repr_str__(', ')})" # type: ignore[misc] + + # Override the 'construct' method in a way that supports recursive parsing without validation. + # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836. + @classmethod + @override + def construct( # pyright: ignore[reportIncompatibleMethodOverride] + __cls: Type[ModelT], + _fields_set: set[str] | None = None, + **values: object, + ) -> ModelT: + m = __cls.__new__(__cls) + fields_values: dict[str, object] = {} + + config = get_model_config(__cls) + populate_by_name = ( + config.allow_population_by_field_name + if isinstance(config, _ConfigProtocol) + else config.get("populate_by_name") + ) + + if _fields_set is None: + _fields_set = set() + + model_fields = get_model_fields(__cls) + for name, field in model_fields.items(): + key = field.alias + if key is None or (key not in values and populate_by_name): + key = name + + if key in values: + fields_values[name] = _construct_field(value=values[key], field=field, key=key) + _fields_set.add(name) + else: + fields_values[name] = field_get_default(field) + + extra_field_type = _get_extra_fields_type(__cls) + + _extra = {} + for key, value in values.items(): + if key not in model_fields: + parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value + + if PYDANTIC_V1: + _fields_set.add(key) + fields_values[key] = parsed + else: + _extra[key] = parsed + + object.__setattr__(m, "__dict__", fields_values) + + if PYDANTIC_V1: + # init_private_attributes() does not exist in v2 + m._init_private_attributes() # type: ignore + + # copied from Pydantic v1's `construct()` method + object.__setattr__(m, "__fields_set__", _fields_set) + else: + # these properties are copied from Pydantic's `model_construct()` method + object.__setattr__(m, "__pydantic_private__", None) + object.__setattr__(m, "__pydantic_extra__", _extra) + object.__setattr__(m, "__pydantic_fields_set__", _fields_set) + + return m + + if not TYPE_CHECKING: + # type checkers incorrectly complain about this assignment + # because the type signatures are technically different + # although not in practice + model_construct = construct + + if PYDANTIC_V1: + # we define aliases for some of the new pydantic v2 methods so + # that we can just document these methods without having to specify + # a specific pydantic version as some users may not know which + # pydantic version they are currently using + + @override + def model_dump( + self, + *, + mode: Literal["json", "python"] | str = "python", + include: IncEx | None = None, + exclude: IncEx | None = None, + context: Any | None = None, + by_alias: bool | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + exclude_computed_fields: bool = False, + round_trip: bool = False, + warnings: bool | Literal["none", "warn", "error"] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + ) -> dict[str, Any]: + """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump + + Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + Args: + mode: The mode in which `to_python` should run. + If mode is 'json', the output will only contain JSON serializable types. + If mode is 'python', the output may contain non-JSON-serializable Python objects. + include: A set of fields to include in the output. + exclude: A set of fields to exclude from the output. + context: Additional context to pass to the serializer. + by_alias: Whether to use the field's alias in the dictionary key if defined. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + + Returns: + A dictionary representation of the model. + """ + if mode not in {"json", "python"}: + raise ValueError("mode must be either 'json' or 'python'") + if round_trip != False: + raise ValueError("round_trip is only supported in Pydantic v2") + if warnings != True: + raise ValueError("warnings is only supported in Pydantic v2") + if context is not None: + raise ValueError("context is only supported in Pydantic v2") + if serialize_as_any != False: + raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") + dumped = super().dict( # pyright: ignore[reportDeprecated] + include=include, + exclude=exclude, + by_alias=by_alias if by_alias is not None else False, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped + + @override + def model_dump_json( + self, + *, + indent: int | None = None, + ensure_ascii: bool = False, + include: IncEx | None = None, + exclude: IncEx | None = None, + context: Any | None = None, + by_alias: bool | None = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + exclude_computed_fields: bool = False, + round_trip: bool = False, + warnings: bool | Literal["none", "warn", "error"] = True, + fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, + ) -> str: + """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json + + Generates a JSON representation of the model using Pydantic's `to_json` method. + + Args: + indent: Indentation to use in the JSON output. If None is passed, the output will be compact. + include: Field(s) to include in the JSON output. Can take either a string or set of strings. + exclude: Field(s) to exclude from the JSON output. Can take either a string or set of strings. + by_alias: Whether to serialize using field aliases. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that have the default value. + exclude_none: Whether to exclude fields that have a value of `None`. + round_trip: Whether to use serialization/deserialization between JSON and class instance. + warnings: Whether to show any warnings that occurred during serialization. + + Returns: + A JSON string representation of the model. + """ + if round_trip != False: + raise ValueError("round_trip is only supported in Pydantic v2") + if warnings != True: + raise ValueError("warnings is only supported in Pydantic v2") + if context is not None: + raise ValueError("context is only supported in Pydantic v2") + if serialize_as_any != False: + raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") + if ensure_ascii != False: + raise ValueError("ensure_ascii is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") + return super().json( # type: ignore[reportDeprecated] + indent=indent, + include=include, + exclude=exclude, + by_alias=by_alias if by_alias is not None else False, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + +class _EagerIterable(list[_T], Generic[_T]): + """ + Accepts any Iterable[T] input (including generators), consumes it + eagerly, and validates all items upfront. + + Validation preserves the original container type where possible + (e.g. a set[T] stays a set[T]). Serialization (model_dump / JSON) + always emits a list — round-tripping through model_dump() will not + restore the original container type. + """ + + @classmethod + def __get_pydantic_core_schema__( + cls, + source_type: Any, + handler: GetCoreSchemaHandler, + ) -> CoreSchema: + (item_type,) = get_args(source_type) or (Any,) + item_schema: CoreSchema = handler.generate_schema(item_type) + list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema) + + return core_schema.no_info_wrap_validator_function( + cls._validate, + list_of_items_schema, + serialization=core_schema.plain_serializer_function_ser_schema( + cls._serialize, + info_arg=False, + ), + ) + + @staticmethod + def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any: + original_type: type[Any] = type(v) + + # Normalize to list so list_schema can validate each item + if isinstance(v, list): + items: list[_T] = v + else: + try: + items = list(v) + except TypeError as e: + raise TypeError("Value is not iterable") from e + + # Validate items against the inner schema + validated: list[_T] = handler(items) + + # Reconstruct original container type + if original_type is list: + return validated + # str(list) produces the list's repr, not a string built from items, + # so skip reconstruction for str and its subclasses. + if issubclass(original_type, str): + return validated + try: + return original_type(validated) + except (TypeError, ValueError): + # If the type cannot be reconstructed, just return the validated list + return validated + + @staticmethod + def _serialize(v: Iterable[_T]) -> list[_T]: + """Always serialize as a list so Pydantic's JSON encoder is happy.""" + if isinstance(v, list): + return v + return list(v) + + +EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable] + + +def _construct_field(value: object, field: FieldInfo, key: str) -> object: + if value is None: + return field_get_default(field) + + if PYDANTIC_V1: + type_ = cast(type, field.outer_type_) # type: ignore + else: + type_ = field.annotation # type: ignore + + if type_ is None: + raise RuntimeError(f"Unexpected field type is None for {key}") + + return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) + + +def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: + if PYDANTIC_V1: + # TODO + return None + + schema = cls.__pydantic_core_schema__ + if schema["type"] == "model": + fields = schema["schema"] + if fields["type"] == "model-fields": + extras = fields.get("extras_schema") + if extras and "cls" in extras: + # mypy can't narrow the type + return extras["cls"] # type: ignore[no-any-return] + + return None + + +def is_basemodel(type_: type) -> bool: + """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`""" + if is_union(type_): + for variant in get_args(type_): + if is_basemodel(variant): + return True + + return False + + return is_basemodel_type(type_) + + +def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]: + origin = get_origin(type_) or type_ + if not inspect.isclass(origin): + return False + return issubclass(origin, BaseModel) or issubclass(origin, GenericModel) + + +def build( + base_model_cls: Callable[P, _BaseModelT], + *args: P.args, + **kwargs: P.kwargs, +) -> _BaseModelT: + """Construct a BaseModel class without validation. + + This is useful for cases where you need to instantiate a `BaseModel` + from an API response as this provides type-safe params which isn't supported + by helpers like `construct_type()`. + + ```py + build(MyModel, my_field_a="foo", my_field_b=123) + ``` + """ + if args: + raise TypeError( + "Received positional arguments which are not supported; Keyword arguments must be used instead", + ) + + return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs)) + + +def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: + """Loose coercion to the expected type with construction of nested values. + + Note: the returned value from this function is not guaranteed to match the + given type. + """ + return cast(_T, construct_type(value=value, type_=type_)) + + +def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: + """Loose coercion to the expected type with construction of nested values. + + If the given value does not match the expected type then it is returned as-is. + """ + + # store a reference to the original type we were given before we extract any inner + # types so that we can properly resolve forward references in `TypeAliasType` annotations + original_type = None + + # we allow `object` as the input type because otherwise, passing things like + # `Literal['value']` will be reported as a type error by type checkers + type_ = cast("type[object]", type_) + if is_type_alias_type(type_): + original_type = type_ # type: ignore[unreachable] + type_ = type_.__value__ # type: ignore[unreachable] + + # unwrap `Annotated[T, ...]` -> `T` + if metadata is not None and len(metadata) > 0: + meta: tuple[Any, ...] = tuple(metadata) + elif is_annotated_type(type_): + meta = get_args(type_)[1:] + type_ = extract_type_arg(type_, 0) + else: + meta = tuple() + + # we need to use the origin class for any types that are subscripted generics + # e.g. Dict[str, object] + origin = get_origin(type_) or type_ + args = get_args(type_) + + if is_union(origin): + try: + return validate_type(type_=cast("type[object]", original_type or type_), value=value) + except Exception: + pass + + # if the type is a discriminated union then we want to construct the right variant + # in the union, even if the data doesn't match exactly, otherwise we'd break code + # that relies on the constructed class types, e.g. + # + # class FooType: + # kind: Literal['foo'] + # value: str + # + # class BarType: + # kind: Literal['bar'] + # value: int + # + # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then + # we'd end up constructing `FooType` when it should be `BarType`. + discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta) + if discriminator and is_mapping(value): + variant_value = value.get(discriminator.field_alias_from or discriminator.field_name) + if variant_value and isinstance(variant_value, str): + variant_type = discriminator.mapping.get(variant_value) + if variant_type: + return construct_type(type_=variant_type, value=value) + + # if the data is not valid, use the first variant that doesn't fail while deserializing + for variant in args: + try: + return construct_type(value=value, type_=variant) + except Exception: + continue + + raise RuntimeError(f"Could not convert data into a valid instance of {type_}") + + if origin == dict: + if not is_mapping(value): + return value + + _, items_type = get_args(type_) # Dict[_, items_type] + return {key: construct_type(value=item, type_=items_type) for key, item in value.items()} + + if ( + not is_literal_type(type_) + and inspect.isclass(origin) + and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)) + ): + if is_list(value): + return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value] + + if is_mapping(value): + if issubclass(type_, BaseModel): + return type_.construct(**value) # type: ignore[arg-type] + + return cast(Any, type_).construct(**value) + + if origin == list: + if not is_list(value): + return value + + inner_type = args[0] # List[inner_type] + return [construct_type(value=entry, type_=inner_type) for entry in value] + + if origin == float: + if isinstance(value, int): + coerced = float(value) + if coerced != value: + return value + return coerced + + return value + + if type_ == datetime: + try: + return parse_datetime(value) # type: ignore + except Exception: + return value + + if type_ == date: + try: + return parse_date(value) # type: ignore + except Exception: + return value + + return value + + +@runtime_checkable +class CachedDiscriminatorType(Protocol): + __discriminator__: DiscriminatorDetails + + +DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary() + + +class DiscriminatorDetails: + field_name: str + """The name of the discriminator field in the variant class, e.g. + + ```py + class Foo(BaseModel): + type: Literal['foo'] + ``` + + Will result in field_name='type' + """ + + field_alias_from: str | None + """The name of the discriminator field in the API response, e.g. + + ```py + class Foo(BaseModel): + type: Literal['foo'] = Field(alias='type_from_api') + ``` + + Will result in field_alias_from='type_from_api' + """ + + mapping: dict[str, type] + """Mapping of discriminator value to variant type, e.g. + + {'foo': FooVariant, 'bar': BarVariant} + """ + + def __init__( + self, + *, + mapping: dict[str, type], + discriminator_field: str, + discriminator_alias: str | None, + ) -> None: + self.mapping = mapping + self.field_name = discriminator_field + self.field_alias_from = discriminator_alias + + +def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None: + cached = DISCRIMINATOR_CACHE.get(union) + if cached is not None: + return cached + + discriminator_field_name: str | None = None + + for annotation in meta_annotations: + if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None: + discriminator_field_name = annotation.discriminator + break + + if not discriminator_field_name: + return None + + mapping: dict[str, type] = {} + discriminator_alias: str | None = None + + for variant in get_args(union): + variant = strip_annotated_type(variant) + if is_basemodel_type(variant): + if PYDANTIC_V1: + field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + if not field_info: + continue + + # Note: if one variant defines an alias then they all should + discriminator_alias = field_info.alias + + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): + if isinstance(entry, str): + mapping[entry] = variant + else: + field = _extract_field_schema_pv2(variant, discriminator_field_name) + if not field: + continue + + # Note: if one variant defines an alias then they all should + discriminator_alias = field.get("serialization_alias") + + field_schema = field["schema"] + + if field_schema["type"] == "literal": + for entry in cast("LiteralSchema", field_schema)["expected"]: + if isinstance(entry, str): + mapping[entry] = variant + + if not mapping: + return None + + details = DiscriminatorDetails( + mapping=mapping, + discriminator_field=discriminator_field_name, + discriminator_alias=discriminator_alias, + ) + DISCRIMINATOR_CACHE.setdefault(union, details) + return details + + +def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None: + schema = model.__pydantic_core_schema__ + if schema["type"] == "definitions": + schema = schema["schema"] + + if schema["type"] != "model": + return None + + schema = cast("ModelSchema", schema) + fields_schema = schema["schema"] + if fields_schema["type"] != "model-fields": + return None + + fields_schema = cast("ModelFieldsSchema", fields_schema) + field = fields_schema["fields"].get(field_name) + if not field: + return None + + return cast("ModelField", field) # pyright: ignore[reportUnnecessaryCast] + + +def validate_type(*, type_: type[_T], value: object) -> _T: + """Strict validation that the given value matches the expected type""" + if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): + return cast(_T, parse_obj(type_, value)) + + return cast(_T, _validate_non_model_type(type_=type_, value=value)) + + +def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None: + """Add a pydantic config for the given type. + + Note: this is a no-op on Pydantic v1. + """ + setattr(typ, "__pydantic_config__", config) # noqa: B010 + + +# our use of subclassing here causes weirdness for type checkers, +# so we just pretend that we don't subclass +if TYPE_CHECKING: + GenericModel = BaseModel +else: + + class GenericModel(BaseGenericModel, BaseModel): + pass + + +if not PYDANTIC_V1: + from pydantic import TypeAdapter as _TypeAdapter + + _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter)) + + if TYPE_CHECKING: + from pydantic import TypeAdapter + else: + TypeAdapter = _CachedTypeAdapter + + def _validate_non_model_type(*, type_: type[_T], value: object) -> _T: + return TypeAdapter(type_).validate_python(value) + +elif not TYPE_CHECKING: # TODO: condition is weird + + class RootModel(GenericModel, Generic[_T]): + """Used as a placeholder to easily convert runtime types to a Pydantic format + to provide validation. + + For example: + ```py + validated = RootModel[int](__root__="5").__root__ + # validated: 5 + ``` + """ + + __root__: _T + + def _validate_non_model_type(*, type_: type[_T], value: object) -> _T: + model = _create_pydantic_model(type_).validate(value) + return cast(_T, model.__root__) + + def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]: + return RootModel[type_] # type: ignore + + +class SecurityOptions(TypedDict, total=False): + api_key_auth: bool + bearer_auth: bool + + +class FinalRequestOptionsInput(TypedDict, total=False): + method: Required[str] + url: Required[str] + params: Query + headers: Headers + max_retries: int + timeout: float | Timeout | None + files: HttpxRequestFiles | None + idempotency_key: str + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] + json_data: Body + extra_json: AnyMapping + follow_redirects: bool + security: SecurityOptions + + +@final +class FinalRequestOptions(pydantic.BaseModel): + method: str + url: str + params: Query = {} + headers: Union[Headers, NotGiven] = NotGiven() + max_retries: Union[int, NotGiven] = NotGiven() + timeout: Union[float, Timeout, None, NotGiven] = NotGiven() + files: Union[HttpxRequestFiles, None] = None + idempotency_key: Union[str, None] = None + post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() + follow_redirects: Union[bool, None] = None + security: SecurityOptions = { + "api_key_auth": True, + "bearer_auth": True, + } + + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None + # It should be noted that we cannot use `json` here as that would override + # a BaseModel method in an incompatible fashion. + json_data: Union[Body, None] = None + extra_json: Union[AnyMapping, None] = None + + if PYDANTIC_V1: + + class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] + arbitrary_types_allowed: bool = True + else: + model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) + + def get_max_retries(self, max_retries: int) -> int: + if isinstance(self.max_retries, NotGiven): + return max_retries + return self.max_retries + + def _strip_raw_response_header(self) -> None: + if not is_given(self.headers): + return + + if self.headers.get(RAW_RESPONSE_HEADER): + self.headers = {**self.headers} + self.headers.pop(RAW_RESPONSE_HEADER) + + # override the `construct` method so that we can run custom transformations. + # this is necessary as we don't want to do any actual runtime type checking + # (which means we can't use validators) but we do want to ensure that `NotGiven` + # values are not present + # + # type ignore required because we're adding explicit types to `**values` + @classmethod + def construct( # type: ignore + cls, + _fields_set: set[str] | None = None, + **values: Unpack[FinalRequestOptionsInput], + ) -> FinalRequestOptions: + kwargs: dict[str, Any] = { + # we unconditionally call `strip_not_given` on any value + # as it will just ignore any non-mapping types + key: strip_not_given(value) + for key, value in values.items() + } + if PYDANTIC_V1: + return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + return super().model_construct(_fields_set, **kwargs) + + if not TYPE_CHECKING: + # type checkers incorrectly complain about this assignment + model_construct = construct diff --git a/src/_qs.py b/src/_qs.py new file mode 100644 index 0000000..4127c19 --- /dev/null +++ b/src/_qs.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +from typing import Any, List, Tuple, Union, Mapping, TypeVar +from urllib.parse import parse_qs, urlencode +from typing_extensions import get_args + +from ._types import NotGiven, ArrayFormat, NestedFormat, not_given +from ._utils import flatten + +_T = TypeVar("_T") + +PrimitiveData = Union[str, int, float, bool, None] +# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"] +# https://github.com/microsoft/pyright/issues/3555 +Data = Union[PrimitiveData, List[Any], Tuple[Any], "Mapping[str, Any]"] +Params = Mapping[str, Data] + + +class Querystring: + array_format: ArrayFormat + nested_format: NestedFormat + + def __init__( + self, + *, + array_format: ArrayFormat = "repeat", + nested_format: NestedFormat = "brackets", + ) -> None: + self.array_format = array_format + self.nested_format = nested_format + + def parse(self, query: str) -> Mapping[str, object]: + # Note: custom format syntax is not supported yet + return parse_qs(query) + + def stringify( + self, + params: Params, + *, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, + ) -> str: + return urlencode( + self.stringify_items( + params, + array_format=array_format, + nested_format=nested_format, + ) + ) + + def stringify_items( + self, + params: Params, + *, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, + ) -> list[tuple[str, str]]: + opts = Options( + qs=self, + array_format=array_format, + nested_format=nested_format, + ) + return flatten([self._stringify_item(key, value, opts) for key, value in params.items()]) + + def _stringify_item( + self, + key: str, + value: Data, + opts: Options, + ) -> list[tuple[str, str]]: + if isinstance(value, Mapping): + items: list[tuple[str, str]] = [] + nested_format = opts.nested_format + for subkey, subvalue in value.items(): + items.extend( + self._stringify_item( + # TODO: error if unknown format + f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]", + subvalue, + opts, + ) + ) + return items + + if isinstance(value, (list, tuple)): + array_format = opts.array_format + if array_format == "comma": + return [ + ( + key, + ",".join(self._primitive_value_to_str(item) for item in value if item is not None), + ), + ] + elif array_format == "repeat": + items = [] + for item in value: + items.extend(self._stringify_item(key, item, opts)) + return items + elif array_format == "indices": + items = [] + for i, item in enumerate(value): + items.extend(self._stringify_item(f"{key}[{i}]", item, opts)) + return items + elif array_format == "brackets": + items = [] + key = key + "[]" + for item in value: + items.extend(self._stringify_item(key, item, opts)) + return items + else: + raise NotImplementedError( + f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" + ) + + serialised = self._primitive_value_to_str(value) + if not serialised: + return [] + return [(key, serialised)] + + def _primitive_value_to_str(self, value: PrimitiveData) -> str: + # copied from httpx + if value is True: + return "true" + elif value is False: + return "false" + elif value is None: + return "" + return str(value) + + +_qs = Querystring() +parse = _qs.parse +stringify = _qs.stringify +stringify_items = _qs.stringify_items + + +class Options: + array_format: ArrayFormat + nested_format: NestedFormat + + def __init__( + self, + qs: Querystring = _qs, + *, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, + ) -> None: + self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format + self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format diff --git a/src/_resource.py b/src/_resource.py new file mode 100644 index 0000000..3222d0b --- /dev/null +++ b/src/_resource.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Scalar. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import time +from typing import TYPE_CHECKING + +import anyio + +if TYPE_CHECKING: + from ._client import Scalar, AsyncScalar + + +class SyncAPIResource: + _client: Scalar + + def __init__(self, client: Scalar) -> None: + self._client = client + self._get = client.get + self._post = client.post + self._patch = client.patch + self._put = client.put + self._delete = client.delete + self._get_api_list = client.get_api_list + + def _sleep(self, seconds: float) -> None: + time.sleep(seconds) + + +class AsyncAPIResource: + _client: AsyncScalar + + def __init__(self, client: AsyncScalar) -> None: + self._client = client + self._get = client.get + self._post = client.post + self._patch = client.patch + self._put = client.put + self._delete = client.delete + self._get_api_list = client.get_api_list + + async def _sleep(self, seconds: float) -> None: + await anyio.sleep(seconds) diff --git a/src/_response.py b/src/_response.py new file mode 100644 index 0000000..fafcb48 --- /dev/null +++ b/src/_response.py @@ -0,0 +1,832 @@ +from __future__ import annotations + +import os +import inspect +import logging +import datetime +import functools +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Union, + Generic, + TypeVar, + Callable, + Iterator, + AsyncIterator, + cast, + overload, +) +from typing_extensions import Awaitable, ParamSpec, override, get_origin + +import anyio +import httpx +import pydantic + +from ._types import NoneType +from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base +from ._models import BaseModel, is_basemodel +from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER +from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type +from ._exceptions import ScalarError, APIResponseValidationError + +if TYPE_CHECKING: + from ._models import FinalRequestOptions + from ._base_client import BaseClient + + +P = ParamSpec("P") +R = TypeVar("R") +_T = TypeVar("_T") +_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]") +_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]") + +log: logging.Logger = logging.getLogger(__name__) + + +class BaseAPIResponse(Generic[R]): + _cast_to: type[R] + _client: BaseClient[Any, Any] + _parsed_by_type: dict[type[Any], Any] + _is_sse_stream: bool + _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None + _options: FinalRequestOptions + + http_response: httpx.Response + + retries_taken: int + """The number of retries made. If no retries happened this will be `0`""" + + def __init__( + self, + *, + raw: httpx.Response, + cast_to: type[R], + client: BaseClient[Any, Any], + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + options: FinalRequestOptions, + retries_taken: int = 0, + ) -> None: + self._cast_to = cast_to + self._client = client + self._parsed_by_type = {} + self._is_sse_stream = stream + self._stream_cls = stream_cls + self._options = options + self.http_response = raw + self.retries_taken = retries_taken + + @property + def headers(self) -> httpx.Headers: + return self.http_response.headers + + @property + def http_request(self) -> httpx.Request: + """Returns the httpx Request instance associated with the current response.""" + return self.http_response.request + + @property + def status_code(self) -> int: + return self.http_response.status_code + + @property + def url(self) -> httpx.URL: + """Returns the URL for which the request was made.""" + return self.http_response.url + + @property + def method(self) -> str: + return self.http_request.method + + @property + def http_version(self) -> str: + return self.http_response.http_version + + @property + def elapsed(self) -> datetime.timedelta: + """The time taken for the complete request/response cycle to complete.""" + return self.http_response.elapsed + + @property + def is_closed(self) -> bool: + """Whether or not the response body has been closed. + + If this is False then there is response data that has not been read yet. + You must either fully consume the response body or call `.close()` + before discarding the response to prevent resource leaks. + """ + return self.http_response.is_closed + + @override + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>" + ) + + def _parse(self, *, to: type[_T] | None = None) -> R | _T: + cast_to = to if to is not None else self._cast_to + + # unwrap `TypeAlias('Name', T)` -> `T` + if is_type_alias_type(cast_to): + cast_to = cast_to.__value__ # type: ignore[unreachable] + + # unwrap `Annotated[T, ...]` -> `T` + if cast_to and is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) + + origin = get_origin(cast_to) or cast_to + + if self._is_sse_stream: + if to: + if not is_stream_class_type(to): + raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}") + + return cast( + _T, + to( + cast_to=extract_stream_chunk_type( + to, + failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]", + ), + response=self.http_response, + client=cast(Any, self._client), + options=self._options, + ), + ) + + if self._stream_cls: + return cast( + R, + self._stream_cls( + cast_to=extract_stream_chunk_type(self._stream_cls), + response=self.http_response, + client=cast(Any, self._client), + options=self._options, + ), + ) + + stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls) + if stream_cls is None: + raise MissingStreamClassError() + + return cast( + R, + stream_cls( + cast_to=cast_to, + response=self.http_response, + client=cast(Any, self._client), + options=self._options, + ), + ) + + if cast_to is None or cast_to is NoneType: + return cast(R, None) + + response = self.http_response + if cast_to == str: + return cast(R, response.text) + + if cast_to == bytes: + return cast(R, response.content) + + if cast_to == int: + return cast(R, int(response.text)) + + if cast_to == float: + return cast(R, float(response.text)) + + if cast_to == bool: + return cast(R, response.text.lower() == "true") + + if origin == APIResponse: + raise RuntimeError("Unexpected state - cast_to is `APIResponse`") + + if inspect.isclass(origin) and issubclass(origin, httpx.Response): + # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response + # and pass that class to our request functions. We cannot change the variance to be either + # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct + # the response class ourselves but that is something that should be supported directly in httpx + # as it would be easy to incorrectly construct the Response object due to the multitude of arguments. + if cast_to != httpx.Response: + raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") + return cast(R, response) + + origin_is_class = inspect.isclass(origin) + origin_is_base_model = origin_is_class and issubclass(origin, BaseModel) + + if origin_is_class and not origin_is_base_model and issubclass(origin, pydantic.BaseModel): + raise TypeError( + "Pydantic models must subclass our base model type, e.g. `from scalar_api import BaseModel`" + ) + + if ( + cast_to is not object + and not origin is list + and not origin is dict + and not origin is Union + and not origin_is_base_model + ): + raise RuntimeError( + f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}." + ) + + # split is required to handle cases where additional information is included + # in the response, e.g. application/json; charset=utf-8 + content_type, *_ = response.headers.get("content-type", "*").split(";") + if not content_type.endswith("json"): + if is_basemodel(cast_to): + try: + data = response.json() + except Exception as exc: + log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc) + else: + return self._client._process_response_data( + data=data, + cast_to=cast_to, # type: ignore + response=response, + ) + + if self._client._strict_response_validation: + raise APIResponseValidationError( + response=response, + message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.", + body=response.text, + ) + + # If the API responds with content that isn't JSON then we just return + # the (decoded) text without performing any parsing so that you can still + # handle the response however you need to. + return response.text # type: ignore + + data = response.json() + + return self._client._process_response_data( + data=data, + cast_to=cast_to, # type: ignore + response=response, + ) + + +class APIResponse(BaseAPIResponse[R]): + @overload + def parse(self, *, to: type[_T]) -> _T: ... + + @overload + def parse(self) -> R: ... + + def parse(self, *, to: type[_T] | None = None) -> R | _T: + """Returns the rich python representation of this response's data. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + + You can customise the type that the response is parsed into through + the `to` argument, e.g. + + ```py + from scalar_api import BaseModel + + + class MyModel(BaseModel): + foo: str + + + obj = response.parse(to=MyModel) + print(obj.foo) + ``` + + We support parsing: + - `BaseModel` + - `dict` + - `list` + - `Union` + - `str` + - `int` + - `float` + - `httpx.Response` + """ + cache_key = to if to is not None else self._cast_to + cached = self._parsed_by_type.get(cache_key) + if cached is not None: + return cached # type: ignore[no-any-return] + + if not self._is_sse_stream: + self.read() + + parsed = self._parse(to=to) + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed_by_type[cache_key] = parsed + return parsed + + def read(self) -> bytes: + """Read and return the binary response content.""" + try: + return self.http_response.read() + except httpx.StreamConsumed as exc: + # The default error raised by httpx isn't very + # helpful in our case so we re-raise it with + # a different error message. + raise StreamAlreadyConsumed() from exc + + def text(self) -> str: + """Read and decode the response content into a string.""" + self.read() + return self.http_response.text + + def json(self) -> object: + """Read and decode the JSON response content.""" + self.read() + return self.http_response.json() + + def close(self) -> None: + """Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + self.http_response.close() + + def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]: + """ + A byte-iterator over the decoded response content. + + This automatically handles gzip, deflate and brotli encoded responses. + """ + for chunk in self.http_response.iter_bytes(chunk_size): + yield chunk + + def iter_text(self, chunk_size: int | None = None) -> Iterator[str]: + """A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + for chunk in self.http_response.iter_text(chunk_size): + yield chunk + + def iter_lines(self) -> Iterator[str]: + """Like `iter_text()` but will only yield chunks for each line""" + for chunk in self.http_response.iter_lines(): + yield chunk + + +class AsyncAPIResponse(BaseAPIResponse[R]): + @overload + async def parse(self, *, to: type[_T]) -> _T: ... + + @overload + async def parse(self) -> R: ... + + async def parse(self, *, to: type[_T] | None = None) -> R | _T: + """Returns the rich python representation of this response's data. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + + You can customise the type that the response is parsed into through + the `to` argument, e.g. + + ```py + from scalar_api import BaseModel + + + class MyModel(BaseModel): + foo: str + + + obj = response.parse(to=MyModel) + print(obj.foo) + ``` + + We support parsing: + - `BaseModel` + - `dict` + - `list` + - `Union` + - `str` + - `httpx.Response` + """ + cache_key = to if to is not None else self._cast_to + cached = self._parsed_by_type.get(cache_key) + if cached is not None: + return cached # type: ignore[no-any-return] + + if not self._is_sse_stream: + await self.read() + + parsed = self._parse(to=to) + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed_by_type[cache_key] = parsed + return parsed + + async def read(self) -> bytes: + """Read and return the binary response content.""" + try: + return await self.http_response.aread() + except httpx.StreamConsumed as exc: + # the default error raised by httpx isn't very + # helpful in our case so we re-raise it with + # a different error message + raise StreamAlreadyConsumed() from exc + + async def text(self) -> str: + """Read and decode the response content into a string.""" + await self.read() + return self.http_response.text + + async def json(self) -> object: + """Read and decode the JSON response content.""" + await self.read() + return self.http_response.json() + + async def close(self) -> None: + """Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + await self.http_response.aclose() + + async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]: + """ + A byte-iterator over the decoded response content. + + This automatically handles gzip, deflate and brotli encoded responses. + """ + async for chunk in self.http_response.aiter_bytes(chunk_size): + yield chunk + + async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]: + """A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + async for chunk in self.http_response.aiter_text(chunk_size): + yield chunk + + async def iter_lines(self) -> AsyncIterator[str]: + """Like `iter_text()` but will only yield chunks for each line""" + async for chunk in self.http_response.aiter_lines(): + yield chunk + + +class BinaryAPIResponse(APIResponse[bytes]): + """Subclass of APIResponse providing helpers for dealing with binary data. + + Note: If you want to stream the response data instead of eagerly reading it + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + + def write_to_file( + self, + file: str | os.PathLike[str], + ) -> None: + """Write the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + + Note: if you want to stream the data to the file instead of writing + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + with open(file, mode="wb") as f: + for data in self.iter_bytes(): + f.write(data) + + +class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]): + """Subclass of APIResponse providing helpers for dealing with binary data. + + Note: If you want to stream the response data instead of eagerly reading it + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + + async def write_to_file( + self, + file: str | os.PathLike[str], + ) -> None: + """Write the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + + Note: if you want to stream the data to the file instead of writing + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.iter_bytes(): + await f.write(data) + + +class StreamedBinaryAPIResponse(APIResponse[bytes]): + def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + """Streams the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + """ + with open(file, mode="wb") as f: + for data in self.iter_bytes(chunk_size): + f.write(data) + + +class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]): + async def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + """Streams the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + """ + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.iter_bytes(chunk_size): + await f.write(data) + + +class MissingStreamClassError(TypeError): + def __init__(self) -> None: + super().__init__( + "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `scalar_api._streaming` for reference", + ) + + +class StreamAlreadyConsumed(ScalarError): + """ + Attempted to read or stream content, but the content has already + been streamed. + + This can happen if you use a method like `.iter_lines()` and then attempt + to read th entire response body afterwards, e.g. + + ```py + response = await client.post(...) + async for line in response.iter_lines(): + ... # do something with `line` + + content = await response.read() + # ^ error + ``` + + If you want this behaviour you'll need to either manually accumulate the response + content or call `await response.read()` before iterating over the stream. + """ + + def __init__(self) -> None: + message = ( + "Attempted to read or stream some content, but the content has " + "already been streamed. " + "This could be due to attempting to stream the response " + "content more than once." + "\n\n" + "You can fix this by manually accumulating the response content while streaming " + "or by calling `.read()` before starting to stream." + ) + super().__init__(message) + + +class ResponseContextManager(Generic[_APIResponseT]): + """Context manager for ensuring that a request is not made + until it is entered and that the response will always be closed + when the context manager exits + """ + + def __init__(self, request_func: Callable[[], _APIResponseT]) -> None: + self._request_func = request_func + self.__response: _APIResponseT | None = None + + def __enter__(self) -> _APIResponseT: + self.__response = self._request_func() + return self.__response + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__response is not None: + self.__response.close() + + +class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]): + """Context manager for ensuring that a request is not made + until it is entered and that the response will always be closed + when the context manager exits + """ + + def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None: + self._api_request = api_request + self.__response: _AsyncAPIResponseT | None = None + + async def __aenter__(self) -> _AsyncAPIResponseT: + self.__response = await self._api_request + return self.__response + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__response is not None: + await self.__response.close() + + +def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support streaming and returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + + kwargs["extra_headers"] = extra_headers + + make_request = functools.partial(func, *args, **kwargs) + + return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request)) + + return wrapped + + +def async_to_streamed_response_wrapper( + func: Callable[P, Awaitable[R]], +) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support streaming and returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + + kwargs["extra_headers"] = extra_headers + + make_request = func(*args, **kwargs) + + return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request)) + + return wrapped + + +def to_custom_streamed_response_wrapper( + func: Callable[P, object], + response_cls: type[_APIResponseT], +) -> Callable[P, ResponseContextManager[_APIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support streaming and returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + make_request = functools.partial(func, *args, **kwargs) + + return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request)) + + return wrapped + + +def async_to_custom_streamed_response_wrapper( + func: Callable[P, Awaitable[object]], + response_cls: type[_AsyncAPIResponseT], +) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support streaming and returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + make_request = func(*args, **kwargs) + + return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request)) + + return wrapped + + +def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]: + """Higher order function that takes one of our bound API methods and wraps it + to support returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + + kwargs["extra_headers"] = extra_headers + + return cast(APIResponse[R], func(*args, **kwargs)) + + return wrapped + + +def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]: + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + + kwargs["extra_headers"] = extra_headers + + return cast(AsyncAPIResponse[R], await func(*args, **kwargs)) + + return wrapped + + +def to_custom_raw_response_wrapper( + func: Callable[P, object], + response_cls: type[_APIResponseT], +) -> Callable[P, _APIResponseT]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + return cast(_APIResponseT, func(*args, **kwargs)) + + return wrapped + + +def async_to_custom_raw_response_wrapper( + func: Callable[P, Awaitable[object]], + response_cls: type[_AsyncAPIResponseT], +) -> Callable[P, Awaitable[_AsyncAPIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]: + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs)) + + return wrapped + + +def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type: + """Given a type like `APIResponse[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyResponse(APIResponse[bytes]): + ... + + extract_response_type(MyResponse) -> bytes + ``` + """ + return extract_type_var_from_base( + typ, + generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)), + index=0, + ) diff --git a/src/_send_queue.py b/src/_send_queue.py new file mode 100644 index 0000000..3140d45 --- /dev/null +++ b/src/_send_queue.py @@ -0,0 +1,90 @@ +# File generated from our OpenAPI spec by Scalar. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import typing +import threading + +from ._exceptions import WebSocketQueueFullError + + +class SendQueue: + """Bounded byte-size queue for outgoing WebSocket messages. + + Messages are stored as pre-serialized strings. The queue enforces a + maximum byte budget so that unbounded buffering cannot occur during + reconnection windows. + """ + + def __init__(self, max_bytes: int = 1_048_576) -> None: + self._queue: list[tuple[str, int]] = [] # (data, byte_length) + self._bytes: int = 0 + self._max_bytes = max_bytes + self._lock = threading.Lock() + + def enqueue(self, data: str) -> None: + """Append *data* to the queue. + + Raises :class:`WebSocketQueueFullError` if the message would + exceed the byte-size limit. + """ + byte_length = len(data.encode("utf-8")) + with self._lock: + if self._bytes + byte_length > self._max_bytes: + raise WebSocketQueueFullError("send queue is full, message discarded") + self._queue.append((data, byte_length)) + self._bytes += byte_length + + def flush_sync(self, send: typing.Callable[[str], object]) -> None: + """Send every queued message via *send*. + + If *send* raises, the failing message and all subsequent messages + are re-queued and the error is re-raised. + """ + with self._lock: + pending = list(self._queue) + self._queue.clear() + self._bytes = 0 + + for i, (data, _byte_length) in enumerate(pending): + try: + send(data) + except Exception: + with self._lock: + remaining = pending[i:] + self._queue = remaining + self._queue + self._bytes = sum(bl for _, bl in self._queue) + raise + + async def flush_async(self, send: typing.Callable[[str], typing.Awaitable[object]]) -> None: + """Async variant of :meth:`flush_sync`.""" + with self._lock: + pending = list(self._queue) + self._queue.clear() + self._bytes = 0 + + for i, (data, _byte_length) in enumerate(pending): + try: + await send(data) + except Exception: + with self._lock: + remaining = pending[i:] + self._queue = remaining + self._queue + self._bytes = sum(bl for _, bl in self._queue) + raise + + def drain(self) -> list[str]: + """Remove and return all queued messages.""" + with self._lock: + items = [data for data, _ in self._queue] + self._queue.clear() + self._bytes = 0 + return items + + def __len__(self) -> int: + with self._lock: + return len(self._queue) + + def __bool__(self) -> bool: + with self._lock: + return len(self._queue) > 0 diff --git a/src/_streaming.py b/src/_streaming.py new file mode 100644 index 0000000..635f694 --- /dev/null +++ b/src/_streaming.py @@ -0,0 +1,470 @@ +# Note: initially copied from https://github.com/florimondmanca/httpx-sse/blob/master/src/httpx_sse/_decoders.py +from __future__ import annotations + +import json +import inspect +from types import TracebackType +from typing import TYPE_CHECKING, Any, Generic, List, Mapping, Optional, Sequence, TypeVar, Union, Iterator, AsyncIterator, cast +from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable + +import httpx + +from ._utils import extract_type_var_from_base + +if TYPE_CHECKING: + from ._client import Scalar, AsyncScalar + from ._models import FinalRequestOptions + + +_T = TypeVar("_T") + + +# Mirrors `StreamingEventHandler` from the IR (`packages/ir-compiler/src/ +# types.ts`). The generated client emits its own `_streaming_handlers` +# list populated from `ctx.config.streaming.onEvent`; the runtime +# dispatch below honors `continue`, `ignore`, `error`, and `yield` +# actions the same way the TypeScript runtime does. When the client has +# no handlers we fall back to the generic "yield every data-bearing +# event" behavior so SDKs without a streaming config still work. +StreamingEventHandler = Mapping[str, Any] + + +def _event_type_matches(event: Optional[str], expected: Any) -> bool: + if expected is None: + return event is None + if isinstance(expected, (list, tuple)): + return (event or "") in expected + return expected is not None and event == expected + + +def _stream_handler_for_sse( + sse: "ServerSentEvent", + handlers: Optional[Sequence[StreamingEventHandler]], +) -> Optional[StreamingEventHandler]: + if not handlers: + return None + for handler in handlers: + if handler.get("kind") == "data": + prefix = handler.get("dataStartsWith") + if isinstance(prefix, str) and sse.data.startswith(prefix): + return handler + for handler in handlers: + kind = handler.get("kind") + if kind == "event" and _event_type_matches(sse.event, handler.get("eventType")): + return handler + if kind == "fallthrough": + return handler + return None + + +def _stream_error_message(sse: "ServerSentEvent", handler: StreamingEventHandler) -> tuple[Any, str]: + body: Any = sse.data + try: + body = sse.json() + except Exception: + body = sse.data + error_property = handler.get("errorProperty") + payload = body + if isinstance(payload, dict) and isinstance(error_property, str) and error_property in payload: + payload = payload[error_property] + return body, f"{payload}" if payload is not None else (sse.data or "Stream error") + + +class Stream(Generic[_T]): + """Provides the core interface to iterate over a synchronous stream response.""" + + response: httpx.Response + _options: Optional[FinalRequestOptions] = None + _decoder: SSEBytesDecoder + + def __init__( + self, + *, + cast_to: type[_T], + response: httpx.Response, + client: Scalar, + options: Optional[FinalRequestOptions] = None, + ) -> None: + self.response = response + self._cast_to = cast_to + self._client = client + self._options = options + self._decoder = client._make_sse_decoder() + self._iterator = self.__stream__() + + def __next__(self) -> _T: + return self._iterator.__next__() + + def __iter__(self) -> Iterator[_T]: + for item in self._iterator: + yield item + + def _iter_events(self) -> Iterator[ServerSentEvent]: + yield from self._decoder.iter_bytes(self.response.iter_bytes()) + + def __stream__(self) -> Iterator[_T]: + cast_to = cast(Any, self._cast_to) + response = self.response + process_data = self._client._process_response_data + iterator = self._iter_events() + handlers = getattr(self._client, "_streaming_handlers", None) + content_type = (response.headers.get("content-type") or "").lower() + is_jsonl = "json" in content_type and "event-stream" not in content_type + + try: + if is_jsonl: + # Newline-delimited JSON (`application/jsonl`, + # `application/x-jsonl`, etc.) has no SSE framing, so the + # `_decoder.iter_bytes` path above would never yield. We + # parse each non-empty line as one event so callers can + # iterate without caring whether the wire format is SSE + # or JSONL. + buffer = b"" + for chunk in response.iter_bytes(): + if not chunk: + continue + buffer += chunk + while b"\n" in buffer: + line, buffer = buffer.split(b"\n", 1) + text = line.decode("utf-8").strip() + if not text: + continue + yield process_data(data=json.loads(text), cast_to=cast_to, response=response) + tail = buffer.decode("utf-8").strip() + if tail: + yield process_data(data=json.loads(tail), cast_to=cast_to, response=response) + return + + for sse in iterator: + # Dispatch every SSE event through the + # config-derived handler list. With no handlers + # configured we fall back to "yield every data-bearing + # event" which matches the TypeScript runtime. + handler = _stream_handler_for_sse(sse, handlers) + action = handler.get("handle") if handler else None + if action in ("continue", "ignore"): + continue + if action == "error" and handler is not None: + body, err_msg = _stream_error_message(sse, handler) + raise self._client._make_status_error(err_msg, body=body, response=self.response) + if handlers and action != "yield": + continue + if not sse.data: + continue + try: + payload = sse.json() + except Exception: + payload = sse.data + yield process_data(data=payload, cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + response.close() + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def close(self) -> None: + """ + Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + self.response.close() + + +class AsyncStream(Generic[_T]): + """Provides the core interface to iterate over an asynchronous stream response.""" + + response: httpx.Response + _options: Optional[FinalRequestOptions] = None + _decoder: SSEDecoder | SSEBytesDecoder + + def __init__( + self, + *, + cast_to: type[_T], + response: httpx.Response, + client: AsyncScalar, + options: Optional[FinalRequestOptions] = None, + ) -> None: + self.response = response + self._cast_to = cast_to + self._client = client + self._options = options + self._decoder = client._make_sse_decoder() + self._iterator = self.__stream__() + + async def __anext__(self) -> _T: + return await self._iterator.__anext__() + + async def __aiter__(self) -> AsyncIterator[_T]: + async for item in self._iterator: + yield item + + async def _iter_events(self) -> AsyncIterator[ServerSentEvent]: + async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()): + yield sse + + async def __stream__(self) -> AsyncIterator[_T]: + cast_to = cast(Any, self._cast_to) + response = self.response + process_data = self._client._process_response_data + iterator = self._iter_events() + handlers = getattr(self._client, "_streaming_handlers", None) + content_type = (response.headers.get("content-type") or "").lower() + is_jsonl = "json" in content_type and "event-stream" not in content_type + + try: + if is_jsonl: + # See the sync sibling for why JSONL gets its own branch. + buffer = b"" + async for chunk in response.aiter_bytes(): + if not chunk: + continue + buffer += chunk + while b"\n" in buffer: + line, buffer = buffer.split(b"\n", 1) + text = line.decode("utf-8").strip() + if not text: + continue + yield process_data(data=json.loads(text), cast_to=cast_to, response=response) + tail = buffer.decode("utf-8").strip() + if tail: + yield process_data(data=json.loads(tail), cast_to=cast_to, response=response) + return + + async for sse in iterator: + handler = _stream_handler_for_sse(sse, handlers) + action = handler.get("handle") if handler else None + if action in ("continue", "ignore"): + continue + if action == "error" and handler is not None: + body, err_msg = _stream_error_message(sse, handler) + raise self._client._make_status_error(err_msg, body=body, response=self.response) + if handlers and action != "yield": + continue + if not sse.data: + continue + try: + payload = sse.json() + except Exception: + payload = sse.data + yield process_data(data=payload, cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + await response.aclose() + + async def __aenter__(self) -> Self: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.close() + + async def close(self) -> None: + """ + Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + await self.response.aclose() + + +class ServerSentEvent: + def __init__( + self, + *, + event: str | None = None, + data: str | None = None, + id: str | None = None, + retry: int | None = None, + ) -> None: + if data is None: + data = "" + + self._id = id + self._data = data + self._event = event or None + self._retry = retry + + @property + def event(self) -> str | None: + return self._event + + @property + def id(self) -> str | None: + return self._id + + @property + def retry(self) -> int | None: + return self._retry + + @property + def data(self) -> str: + return self._data + + def json(self) -> Any: + return json.loads(self.data) + + @override + def __repr__(self) -> str: + return f"ServerSentEvent(event={self.event}, data={self.data}, id={self.id}, retry={self.retry})" + + +class SSEDecoder: + _data: list[str] + _event: str | None + _retry: int | None + _last_event_id: str | None + + def __init__(self) -> None: + self._event = None + self._data = [] + self._last_event_id = None + self._retry = None + + def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + for chunk in self._iter_chunks(iterator): + # Split before decoding so splitlines() only uses \r and \n + for raw_line in chunk.splitlines(): + line = raw_line.decode("utf-8") + sse = self.decode(line) + if sse: + yield sse + + def _iter_chunks(self, iterator: Iterator[bytes]) -> Iterator[bytes]: + """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks""" + data = b"" + for chunk in iterator: + for line in chunk.splitlines(keepends=True): + data += line + if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")): + yield data + data = b"" + if data: + yield data + + async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + async for chunk in self._aiter_chunks(iterator): + # Split before decoding so splitlines() only uses \r and \n + for raw_line in chunk.splitlines(): + line = raw_line.decode("utf-8") + sse = self.decode(line) + if sse: + yield sse + + async def _aiter_chunks(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[bytes]: + """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks""" + data = b"" + async for chunk in iterator: + for line in chunk.splitlines(keepends=True): + data += line + if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")): + yield data + data = b"" + if data: + yield data + + def decode(self, line: str) -> ServerSentEvent | None: + # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501 + + if not line: + if not self._event and not self._data and not self._last_event_id and self._retry is None: + return None + + sse = ServerSentEvent( + event=self._event, + data="\n".join(self._data), + id=self._last_event_id, + retry=self._retry, + ) + + # NOTE: as per the SSE spec, do not reset last_event_id. + self._event = None + self._data = [] + self._retry = None + + return sse + + if line.startswith(":"): + return None + + fieldname, _, value = line.partition(":") + + if value.startswith(" "): + value = value[1:] + + if fieldname == "event": + self._event = value + elif fieldname == "data": + self._data.append(value) + elif fieldname == "id": + if "\0" in value: + pass + else: + self._last_event_id = value + elif fieldname == "retry": + try: + self._retry = int(value) + except (TypeError, ValueError): + pass + else: + pass # Field is ignored. + + return None + + +@runtime_checkable +class SSEBytesDecoder(Protocol): + def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + ... + + def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]: + """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered""" + ... + + +def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]: + """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`""" + origin = get_origin(typ) or typ + return inspect.isclass(origin) and issubclass(origin, (Stream, AsyncStream)) + + +def extract_stream_chunk_type( + stream_cls: type, + *, + failure_message: str | None = None, +) -> type: + """Given a type like `Stream[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyStream(Stream[bytes]): + ... + + extract_stream_chunk_type(MyStream) -> bytes + ``` + """ + from ._base_client import Stream, AsyncStream + + return extract_type_var_from_base( + stream_cls, + index=0, + generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)), + failure_message=failure_message, + ) diff --git a/src/_types.py b/src/_types.py new file mode 100644 index 0000000..047012f --- /dev/null +++ b/src/_types.py @@ -0,0 +1,274 @@ +from __future__ import annotations + +from os import PathLike +from typing import ( + IO, + TYPE_CHECKING, + Any, + Dict, + List, + Type, + Tuple, + Union, + Mapping, + TypeVar, + Callable, + Iterable, + Iterator, + Optional, + Sequence, + AsyncIterable, +) +from typing_extensions import ( + Set, + Literal, + Protocol, + TypeAlias, + TypedDict, + SupportsIndex, + overload, + override, + runtime_checkable, +) + +import httpx +import pydantic +from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport + +if TYPE_CHECKING: + from ._models import BaseModel, SecurityOptions + from ._response import APIResponse, AsyncAPIResponse + +Transport = BaseTransport +AsyncTransport = AsyncBaseTransport +Query = Mapping[str, object] +Body = object +AnyMapping = Mapping[str, object] +ModelT = TypeVar("ModelT", bound=pydantic.BaseModel) +_T = TypeVar("_T") + +ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] +NestedFormat = Literal["dots", "brackets"] + + +# Approximates httpx internal ProxiesTypes and RequestFiles types +# while adding support for `PathLike` instances +ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]] +ProxiesTypes = Union[str, Proxy, ProxiesDict] +if TYPE_CHECKING: + Base64FileInput = Union[IO[bytes], PathLike[str]] + FileContent = Union[IO[bytes], bytes, PathLike[str]] +else: + Base64FileInput = Union[IO[bytes], PathLike] + FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. + + +# Used for sending raw binary data / streaming data in request bodies +# e.g. for file uploads without multipart encoding +BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]] +AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]] + +FileTypes = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], +] +RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]] + +# duplicate of the above but without our custom file support +HttpxFileContent = Union[IO[bytes], bytes] +HttpxFileTypes = Union[ + # file (or bytes) + HttpxFileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], HttpxFileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], HttpxFileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]], +] +HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]] + +# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT +# where ResponseT includes `None`. In order to support directly +# passing `None`, overloads would have to be defined for every +# method that uses `ResponseT` which would lead to an unacceptable +# amount of code duplication and make it unreadable. See _base_client.py +# for example usage. +# +# This unfortunately means that you will either have +# to import this type and pass it explicitly: +# +# from scalar_api import NoneType +# client.get('/foo', cast_to=NoneType) +# +# or build it yourself: +# +# client.get('/foo', cast_to=type(None)) +if TYPE_CHECKING: + NoneType: Type[None] +else: + NoneType = type(None) + + +class RequestOptions(TypedDict, total=False): + headers: Headers + max_retries: int + timeout: float | Timeout | None + params: Query + extra_json: AnyMapping + idempotency_key: str + follow_redirects: bool + security: SecurityOptions + + +# Sentinel class used until PEP 0661 is accepted +class NotGiven: + """ + For parameters with a meaningful None value, we need to distinguish between + the user explicitly passing None, and the user not passing the parameter at + all. + + User code shouldn't need to use not_given directly. + + For example: + + ```py + def create(timeout: Timeout | None | NotGiven = not_given): ... + + + create(timeout=1) # 1s timeout + create(timeout=None) # No timeout + create() # Default timeout behavior + ``` + """ + + def __bool__(self) -> Literal[False]: + return False + + @override + def __repr__(self) -> str: + return "NOT_GIVEN" + + +not_given = NotGiven() +# for backwards compatibility: +NOT_GIVEN = NotGiven() + + +class Omit: + """ + To explicitly omit something from being sent in a request, use `omit`. + + ```py + # as the default `Content-Type` header is `application/json` that will be sent + client.post("/upload/files", files={"file": b"my raw file content"}) + + # you can't explicitly override the header as it has to be dynamically generated + # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983' + client.post(..., headers={"Content-Type": "multipart/form-data"}) + + # instead you can remove the default `application/json` header by passing omit + client.post(..., headers={"Content-Type": omit}) + ``` + """ + + def __bool__(self) -> Literal[False]: + return False + + +omit = Omit() + + +@runtime_checkable +class ModelBuilderProtocol(Protocol): + @classmethod + def build( + cls: type[_T], + *, + response: Response, + data: object, + ) -> _T: ... + + +Headers = Mapping[str, Union[str, Omit]] + + +class HeadersLikeProtocol(Protocol): + def get(self, __key: str) -> str | None: ... + + +HeadersLike = Union[Headers, HeadersLikeProtocol] + +ResponseT = TypeVar( + "ResponseT", + bound=Union[ + object, + str, + None, + "BaseModel", + List[Any], + Dict[str, Any], + Response, + ModelBuilderProtocol, + "APIResponse[Any]", + "AsyncAPIResponse[Any]", + ], +) + +StrBytesIntFloat = Union[str, bytes, int, float] + +# Note: copied from Pydantic +# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79 +IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]] + +PostParser = Callable[[Any], Any] + + +@runtime_checkable +class InheritsGeneric(Protocol): + """Represents a type that has inherited from `Generic` + + The `__orig_bases__` property can be used to determine the resolved + type variable for a given base class. + """ + + __orig_bases__: tuple[_GenericAlias] + + +class _GenericAlias(Protocol): + __origin__: type[object] + + +class HttpxSendArgs(TypedDict, total=False): + auth: httpx.Auth + follow_redirects: bool + + +_T_co = TypeVar("_T_co", covariant=True) + + +if TYPE_CHECKING: + # This works because str.__contains__ does not accept object (either in typeshed or at runtime) + # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + # + # Note: index() and count() methods are intentionally omitted to allow pyright to properly + # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr. + class SequenceNotStr(Protocol[_T_co]): + @overload + def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... + @overload + def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... + def __contains__(self, value: object, /) -> bool: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T_co]: ... + def __reversed__(self) -> Iterator[_T_co]: ... +else: + # just point this to a normal `Sequence` at runtime to avoid having to special case + # deserializing our custom sequence type + SequenceNotStr = Sequence diff --git a/src/_utils/__init__.py b/src/_utils/__init__.py new file mode 100644 index 0000000..1c090e5 --- /dev/null +++ b/src/_utils/__init__.py @@ -0,0 +1,64 @@ +from ._path import path_template as path_template +from ._sync import asyncify as asyncify +from ._proxy import LazyProxy as LazyProxy +from ._utils import ( + flatten as flatten, + is_dict as is_dict, + is_list as is_list, + is_given as is_given, + is_tuple as is_tuple, + json_safe as json_safe, + lru_cache as lru_cache, + is_mapping as is_mapping, + is_tuple_t as is_tuple_t, + is_iterable as is_iterable, + is_sequence as is_sequence, + coerce_float as coerce_float, + is_mapping_t as is_mapping_t, + removeprefix as removeprefix, + removesuffix as removesuffix, + extract_files as extract_files, + is_sequence_t as is_sequence_t, + required_args as required_args, + coerce_boolean as coerce_boolean, + coerce_integer as coerce_integer, + file_from_path as file_from_path, + strip_not_given as strip_not_given, + get_async_library as get_async_library, + maybe_coerce_float as maybe_coerce_float, + get_required_header as get_required_header, + maybe_coerce_boolean as maybe_coerce_boolean, + maybe_coerce_integer as maybe_coerce_integer, +) +from ._compat import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + is_typeddict as is_typeddict, + is_literal_type as is_literal_type, +) +from ._typing import ( + is_list_type as is_list_type, + is_union_type as is_union_type, + extract_type_arg as extract_type_arg, + is_iterable_type as is_iterable_type, + is_required_type as is_required_type, + is_sequence_type as is_sequence_type, + is_annotated_type as is_annotated_type, + is_type_alias_type as is_type_alias_type, + strip_annotated_type as strip_annotated_type, + extract_type_var_from_base as extract_type_var_from_base, +) +from ._streams import consume_sync_iterator as consume_sync_iterator, consume_async_iterator as consume_async_iterator +from ._transform import ( + PropertyInfo as PropertyInfo, + transform as transform, + async_transform as async_transform, + maybe_transform as maybe_transform, + async_maybe_transform as async_maybe_transform, +) +from ._reflection import ( + function_has_argument as function_has_argument, + assert_signatures_in_sync as assert_signatures_in_sync, +) +from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime diff --git a/src/_utils/_compat.py b/src/_utils/_compat.py new file mode 100644 index 0000000..2c70b29 --- /dev/null +++ b/src/_utils/_compat.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import sys +import typing_extensions +from typing import Any, Type, Union, Literal, Optional +from datetime import date, datetime +from typing_extensions import get_args as _get_args, get_origin as _get_origin + +from .._types import StrBytesIntFloat +from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime + +_LITERAL_TYPES = {Literal, typing_extensions.Literal} + + +def get_args(tp: type[Any]) -> tuple[Any, ...]: + return _get_args(tp) + + +def get_origin(tp: type[Any]) -> type[Any] | None: + return _get_origin(tp) + + +def is_union(tp: Optional[Type[Any]]) -> bool: + if sys.version_info < (3, 10): + return tp is Union # type: ignore[comparison-overlap] + else: + import types + + return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap] + + +def is_typeddict(tp: Type[Any]) -> bool: + return typing_extensions.is_typeddict(tp) + + +def is_literal_type(tp: Type[Any]) -> bool: + return get_origin(tp) in _LITERAL_TYPES + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + return _parse_date(value) + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + return _parse_datetime(value) diff --git a/src/_utils/_datetime_parse.py b/src/_utils/_datetime_parse.py new file mode 100644 index 0000000..7cb9d9e --- /dev/null +++ b/src/_utils/_datetime_parse.py @@ -0,0 +1,136 @@ +""" +This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py +without the Pydantic v1 specific errors. +""" + +from __future__ import annotations + +import re +from typing import Dict, Union, Optional +from datetime import date, datetime, timezone, timedelta + +from .._types import StrBytesIntFloat + +date_expr = r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})" +time_expr = ( + r"(?P\d{1,2}):(?P\d{1,2})" + r"(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?" + r"(?PZ|[+-]\d{2}(?::?\d{2})?)?$" +) + +date_re = re.compile(f"{date_expr}$") +datetime_re = re.compile(f"{date_expr}[T ]{time_expr}") + + +EPOCH = datetime(1970, 1, 1) +# if greater than this, the number is in ms, if less than or equal it's in seconds +# (in seconds this is 11th October 2603, in ms it's 20th August 1970) +MS_WATERSHED = int(2e10) +# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9 +MAX_NUMBER = int(3e20) + + +def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]: + if isinstance(value, (int, float)): + return value + try: + return float(value) + except ValueError: + return None + except TypeError: + raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None + + +def _from_unix_seconds(seconds: Union[int, float]) -> datetime: + if seconds > MAX_NUMBER: + return datetime.max + elif seconds < -MAX_NUMBER: + return datetime.min + + while abs(seconds) > MS_WATERSHED: + seconds /= 1000 + dt = EPOCH + timedelta(seconds=seconds) + return dt.replace(tzinfo=timezone.utc) + + +def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]: + if value == "Z": + return timezone.utc + elif value is not None: + offset_mins = int(value[-2:]) if len(value) > 3 else 0 + offset = 60 * int(value[1:3]) + offset_mins + if value[0] == "-": + offset = -offset + return timezone(timedelta(minutes=offset)) + else: + return None + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + """ + Parse a datetime/int/float/string and return a datetime.datetime. + + This function supports time zone offsets. When the input contains one, + the output uses a timezone with a fixed offset from UTC. + + Raise ValueError if the input is well formatted but not a valid datetime. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, datetime): + return value + + number = _get_numeric(value, "datetime") + if number is not None: + return _from_unix_seconds(number) + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + + match = datetime_re.match(value) + if match is None: + raise ValueError("invalid datetime format") + + kw = match.groupdict() + if kw["microsecond"]: + kw["microsecond"] = kw["microsecond"].ljust(6, "0") + + tzinfo = _parse_timezone(kw.pop("tzinfo")) + kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None} + kw_["tzinfo"] = tzinfo + + return datetime(**kw_) # type: ignore + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + """ + Parse a date/int/float/string and return a datetime.date. + + Raise ValueError if the input is well formatted but not a valid date. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, date): + if isinstance(value, datetime): + return value.date() + else: + return value + + number = _get_numeric(value, "date") + if number is not None: + return _from_unix_seconds(number).date() + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + match = date_re.match(value) + if match is None: + raise ValueError("invalid date format") + + kw = {k: int(v) for k, v in match.groupdict().items()} + + try: + return date(**kw) + except ValueError: + raise ValueError("invalid date format") from None diff --git a/src/_utils/_json.py b/src/_utils/_json.py new file mode 100644 index 0000000..6058421 --- /dev/null +++ b/src/_utils/_json.py @@ -0,0 +1,35 @@ +import json +from typing import Any +from datetime import datetime +from typing_extensions import override + +import pydantic + +from .._compat import model_dump + + +def openapi_dumps(obj: Any) -> bytes: + """ + Serialize an object to UTF-8 encoded JSON bytes. + + Extends the standard json.dumps with support for additional types + commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc. + """ + return json.dumps( + obj, + cls=_CustomEncoder, + # Uses the same defaults as httpx's JSON serialization + ensure_ascii=False, + separators=(",", ":"), + allow_nan=False, + ).encode() + + +class _CustomEncoder(json.JSONEncoder): + @override + def default(self, o: Any) -> Any: + if isinstance(o, datetime): + return o.isoformat() + if isinstance(o, pydantic.BaseModel): + return model_dump(o, exclude_unset=True, mode="json", by_alias=True) + return super().default(o) diff --git a/src/_utils/_logs.py b/src/_utils/_logs.py new file mode 100644 index 0000000..bad6b59 --- /dev/null +++ b/src/_utils/_logs.py @@ -0,0 +1,25 @@ +import os +import logging + +logger: logging.Logger = logging.getLogger("scalar_api") +httpx_logger: logging.Logger = logging.getLogger("httpx") + + +def _basic_config() -> None: + # e.g. [2023-10-05 14:12:26 - scalar_api._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK" + logging.basicConfig( + format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + + +def setup_logging() -> None: + env = os.environ.get("SCALAR_LOG") + if env == "debug": + _basic_config() + logger.setLevel(logging.DEBUG) + httpx_logger.setLevel(logging.DEBUG) + elif env == "info": + _basic_config() + logger.setLevel(logging.INFO) + httpx_logger.setLevel(logging.INFO) diff --git a/src/_utils/_path.py b/src/_utils/_path.py new file mode 100644 index 0000000..4d6e1e4 --- /dev/null +++ b/src/_utils/_path.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +import re +from typing import ( + Any, + Mapping, + Callable, +) +from urllib.parse import quote + +# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E). +_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$") + +_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}") + + +def _quote_path_segment_part(value: str) -> str: + """Percent-encode `value` for use in a URI path segment. + + Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 + """ + # quote() already treats unreserved characters (letters, digits, and -._~) + # as safe, so we only need to add sub-delims, ':', and '@'. + # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted. + return quote(value, safe="!$&'()*+,;=:@") + + +def _quote_query_part(value: str) -> str: + """Percent-encode `value` for use in a URI query string. + + Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.4 + """ + return quote(value, safe="!$'()*+,;:@/?") + + +def _quote_fragment_part(value: str) -> str: + """Percent-encode `value` for use in a URI fragment. + + Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.5 + """ + return quote(value, safe="!$&'()*+,;=:@/?") + + +def _interpolate( + template: str, + values: Mapping[str, Any], + quoter: Callable[[str], str], +) -> str: + """Replace {name} placeholders in `template`, quoting each value with `quoter`. + + Placeholder names are looked up in `values`. + + Raises: + KeyError: If a placeholder is not found in `values`. + """ + # re.split with a capturing group returns alternating + # [text, name, text, name, ..., text] elements. + parts = _PLACEHOLDER_RE.split(template) + + for i in range(1, len(parts), 2): + name = parts[i] + if name not in values: + raise KeyError(f"a value for placeholder {{{name}}} was not provided") + val = values[name] + if val is None: + parts[i] = "null" + elif isinstance(val, bool): + parts[i] = "true" if val else "false" + else: + parts[i] = quoter(str(values[name])) + + return "".join(parts) + + +def path_template(template: str, /, **kwargs: Any) -> str: + """Interpolate {name} placeholders in `template` from keyword arguments. + + Args: + template: The template string containing {name} placeholders. + **kwargs: Keyword arguments to interpolate into the template. + + Returns: + The template with placeholders interpolated and percent-encoded. + + Safe characters for percent-encoding are dependent on the URI component. + Placeholders in path and fragment portions are percent-encoded where the `segment` + and `fragment` sets from RFC 3986 respectively are considered safe. + Placeholders in the query portion are percent-encoded where the `query` set from + RFC 3986 §3.3 is considered safe except for = and & characters. + + Raises: + KeyError: If a placeholder is not found in `kwargs`. + ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments). + """ + # Split the template into path, query, and fragment portions. + fragment_template: str | None = None + query_template: str | None = None + + rest = template + if "#" in rest: + rest, fragment_template = rest.split("#", 1) + if "?" in rest: + rest, query_template = rest.split("?", 1) + path_template = rest + + # Interpolate each portion with the appropriate quoting rules. + path_result = _interpolate(path_template, kwargs, _quote_path_segment_part) + + # Reject dot-segments (. and ..) in the final assembled path. The check + # runs after interpolation so that adjacent placeholders or a mix of static + # text and placeholders that together form a dot-segment are caught. + # Also reject percent-encoded dot-segments to protect against incorrectly + # implemented normalization in servers/proxies. + for segment in path_result.split("/"): + if _DOT_SEGMENT_RE.match(segment): + raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed") + + result = path_result + if query_template is not None: + result += "?" + _interpolate(query_template, kwargs, _quote_query_part) + if fragment_template is not None: + result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part) + + return result diff --git a/src/_utils/_proxy.py b/src/_utils/_proxy.py new file mode 100644 index 0000000..0f239a3 --- /dev/null +++ b/src/_utils/_proxy.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Generic, TypeVar, Iterable, cast +from typing_extensions import override + +T = TypeVar("T") + + +class LazyProxy(Generic[T], ABC): + """Implements data methods to pretend that an instance is another instance. + + This includes forwarding attribute access and other methods. + """ + + # Note: we have to special case proxies that themselves return proxies + # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz` + + def __getattr__(self, attr: str) -> object: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return proxied # pyright: ignore + return getattr(proxied, attr) + + @override + def __repr__(self) -> str: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return proxied.__class__.__name__ + return repr(self.__get_proxied__()) + + @override + def __str__(self) -> str: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return proxied.__class__.__name__ + return str(proxied) + + @override + def __dir__(self) -> Iterable[str]: + proxied = self.__get_proxied__() + if isinstance(proxied, LazyProxy): + return [] + return proxied.__dir__() + + @property # type: ignore + @override + def __class__(self) -> type: # pyright: ignore + try: + proxied = self.__get_proxied__() + except Exception: + return type(self) + if issubclass(type(proxied), LazyProxy): + return type(proxied) + return proxied.__class__ + + def __get_proxied__(self) -> T: + return self.__load__() + + def __as_proxied__(self) -> T: + """Helper method that returns the current proxy, typed as the loaded object""" + return cast(T, self) + + @abstractmethod + def __load__(self) -> T: ... diff --git a/src/_utils/_reflection.py b/src/_utils/_reflection.py new file mode 100644 index 0000000..89aa712 --- /dev/null +++ b/src/_utils/_reflection.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import inspect +from typing import Any, Callable + + +def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool: + """Returns whether or not the given function has a specific parameter""" + sig = inspect.signature(func) + return arg_name in sig.parameters + + +def assert_signatures_in_sync( + source_func: Callable[..., Any], + check_func: Callable[..., Any], + *, + exclude_params: set[str] = set(), +) -> None: + """Ensure that the signature of the second function matches the first.""" + + check_sig = inspect.signature(check_func) + source_sig = inspect.signature(source_func) + + errors: list[str] = [] + + for name, source_param in source_sig.parameters.items(): + if name in exclude_params: + continue + + custom_param = check_sig.parameters.get(name) + if not custom_param: + errors.append(f"the `{name}` param is missing") + continue + + if custom_param.annotation != source_param.annotation: + errors.append( + f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(custom_param.annotation)}" + ) + continue + + if errors: + raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors)) diff --git a/src/_utils/_resources_proxy.py b/src/_utils/_resources_proxy.py new file mode 100644 index 0000000..6d86fbe --- /dev/null +++ b/src/_utils/_resources_proxy.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Any +from typing_extensions import override + +from ._proxy import LazyProxy + + +class ResourcesProxy(LazyProxy[Any]): + """A proxy for the `scalar_api.resources` module. + + This is used so that we can lazily import `scalar_api.resources` only when + needed *and* so that users can just import `scalar_api` and reference `scalar_api.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("scalar_api.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() diff --git a/src/_utils/_streams.py b/src/_utils/_streams.py new file mode 100644 index 0000000..f4a0208 --- /dev/null +++ b/src/_utils/_streams.py @@ -0,0 +1,12 @@ +from typing import Any +from typing_extensions import Iterator, AsyncIterator + + +def consume_sync_iterator(iterator: Iterator[Any]) -> None: + for _ in iterator: + ... + + +async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None: + async for _ in iterator: + ... diff --git a/src/_utils/_sync.py b/src/_utils/_sync.py new file mode 100644 index 0000000..f6027c1 --- /dev/null +++ b/src/_utils/_sync.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import asyncio +import functools +from typing import TypeVar, Callable, Awaitable +from typing_extensions import ParamSpec + +import anyio +import sniffio +import anyio.to_thread + +T_Retval = TypeVar("T_Retval") +T_ParamSpec = ParamSpec("T_ParamSpec") + + +async def to_thread( + func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs +) -> T_Retval: + if sniffio.current_async_library() == "asyncio": + return await asyncio.to_thread(func, *args, **kwargs) + + return await anyio.to_thread.run_sync( + functools.partial(func, *args, **kwargs), + ) + + +# inspired by `asyncer`, https://github.com/tiangolo/asyncer +def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: + """ + Take a blocking function and create an async one that receives the same + positional and keyword arguments. + + Usage: + + ```python + def blocking_func(arg1, arg2, kwarg1=None): + # blocking code + return result + + + result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1) + ``` + + ## Arguments + + `function`: a blocking regular callable (e.g. a function) + + ## Return + + An async function that takes the same positional and keyword arguments as the + original one, that when called runs the same original function in a thread worker + and returns the result. + """ + + async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval: + return await to_thread(function, *args, **kwargs) + + return wrapper diff --git a/src/_utils/_transform.py b/src/_utils/_transform.py new file mode 100644 index 0000000..5207549 --- /dev/null +++ b/src/_utils/_transform.py @@ -0,0 +1,457 @@ +from __future__ import annotations + +import io +import base64 +import pathlib +from typing import Any, Mapping, TypeVar, cast +from datetime import date, datetime +from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints + +import anyio +import pydantic + +from ._utils import ( + is_list, + is_given, + lru_cache, + is_mapping, + is_iterable, + is_sequence, +) +from .._files import is_base64_file_input +from ._compat import get_origin, is_typeddict +from ._typing import ( + is_list_type, + is_union_type, + extract_type_arg, + is_iterable_type, + is_required_type, + is_sequence_type, + is_annotated_type, + strip_annotated_type, +) + +_T = TypeVar("_T") + + +# TODO: support for drilling globals() and locals() +# TODO: ensure works correctly with forward references in all cases + + +PropertyFormat = Literal["iso8601", "base64", "custom"] + + +class PropertyInfo: + """Metadata class to be used in Annotated types to provide information about a given type. + + For example: + + class MyParams(TypedDict): + account_holder_name: Annotated[str, PropertyInfo(alias='accountHolderName')] + + This means that {'account_holder_name': 'Robert'} will be transformed to {'accountHolderName': 'Robert'} before being sent to the API. + """ + + alias: str | None + format: PropertyFormat | None + format_template: str | None + discriminator: str | None + + def __init__( + self, + *, + alias: str | None = None, + format: PropertyFormat | None = None, + format_template: str | None = None, + discriminator: str | None = None, + ) -> None: + self.alias = alias + self.format = format + self.format_template = format_template + self.discriminator = discriminator + + @override + def __repr__(self) -> str: + return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')" + + +def maybe_transform( + data: object, + expected_type: object, +) -> Any | None: + """Wrapper over `transform()` that allows `None` to be passed. + + See `transform()` for more details. + """ + if data is None: + return None + return transform(data, expected_type) + + +# Wrapper over _transform_recursive providing fake types +def transform( + data: _T, + expected_type: object, +) -> _T: + """Transform dictionaries based off of type information from the given type, for example: + + ```py + class Params(TypedDict, total=False): + card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]] + + + transformed = transform({"card_id": ""}, Params) + # {'cardID': ''} + ``` + + Any keys / data that does not have type information given will be included as is. + + It should be noted that the transformations that this function does are not represented in the type system. + """ + transformed = _transform_recursive(data, annotation=cast(type, expected_type)) + return cast(_T, transformed) + + +@lru_cache(maxsize=8096) +def _get_annotated_type(type_: type) -> type | None: + """If the given type is an `Annotated` type then it is returned, if not `None` is returned. + + This also unwraps the type when applicable, e.g. `Required[Annotated[T, ...]]` + """ + if is_required_type(type_): + # Unwrap `Required[Annotated[T, ...]]` to `Annotated[T, ...]` + type_ = get_args(type_)[0] + + if is_annotated_type(type_): + return type_ + + return None + + +def _maybe_transform_key(key: str, type_: type) -> str: + """Transform the given `data` based on the annotations provided in `type_`. + + Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata. + """ + annotated_type = _get_annotated_type(type_) + if annotated_type is None: + # no `Annotated` definition for this type, no transformation needed + return key + + # ignore the first argument as it is the actual type + annotations = get_args(annotated_type)[1:] + for annotation in annotations: + if isinstance(annotation, PropertyInfo) and annotation.alias is not None: + return annotation.alias + + return key + + +def _no_transform_needed(annotation: type) -> bool: + return annotation == float or annotation == int + + +def _transform_recursive( + data: object, + *, + annotation: type, + inner_type: type | None = None, +) -> object: + """Transform the given data against the expected type. + + Args: + annotation: The direct type annotation given to the particular piece of data. + This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc + + inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type + is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in + the list can be transformed using the metadata from the container type. + + Defaults to the same value as the `annotation` argument. + """ + from .._compat import model_dump + + if inner_type is None: + inner_type = annotation + + stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type + if is_typeddict(stripped_type) and is_mapping(data): + return _transform_typeddict(data, stripped_type) + + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + + if ( + # List[T] + (is_list_type(stripped_type) and is_list(data)) + # Iterable[T] + or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) + ): + # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually + # intended as an iterable, so we don't transform it. + if isinstance(data, dict): + return cast(object, data) + + inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + + return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] + + if is_union_type(stripped_type): + # For union types we run the transformation against all subtypes to ensure that everything is transformed. + # + # TODO: there may be edge cases where the same normalized field name will transform to two different names + # in different subtypes. + for subtype in get_args(stripped_type): + data = _transform_recursive(data, annotation=annotation, inner_type=subtype) + return data + + if isinstance(data, pydantic.BaseModel): + return model_dump(data, exclude_unset=True, mode="json") + + annotated_type = _get_annotated_type(annotation) + if annotated_type is None: + return data + + # ignore the first argument as it is the actual type + annotations = get_args(annotated_type)[1:] + for annotation in annotations: + if isinstance(annotation, PropertyInfo) and annotation.format is not None: + return _format_data(data, annotation.format, annotation.format_template) + + return data + + +def _format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object: + if isinstance(data, (date, datetime)): + if format_ == "iso8601": + return data.isoformat() + + if format_ == "custom" and format_template is not None: + return data.strftime(format_template) + + if format_ == "base64" and is_base64_file_input(data): + binary: str | bytes | None = None + + if isinstance(data, pathlib.Path): + binary = data.read_bytes() + elif isinstance(data, io.IOBase): + binary = data.read() + + if isinstance(binary, str): # type: ignore[unreachable] + binary = binary.encode() + + if not isinstance(binary, bytes): + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") + + return base64.b64encode(binary).decode("ascii") + + return data + + +def _transform_typeddict( + data: Mapping[str, object], + expected_type: type, +) -> Mapping[str, object]: + result: dict[str, object] = {} + annotations = get_type_hints(expected_type, include_extras=True) + for key, value in data.items(): + if not is_given(value): + # we don't need to include omitted values here as they'll + # be stripped out before the request is sent anyway + continue + + type_ = annotations.get(key) + if type_ is None: + # we do not have a type annotation for this field, leave it as is + result[key] = value + else: + result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_) + return result + + +async def async_maybe_transform( + data: object, + expected_type: object, +) -> Any | None: + """Wrapper over `async_transform()` that allows `None` to be passed. + + See `async_transform()` for more details. + """ + if data is None: + return None + return await async_transform(data, expected_type) + + +async def async_transform( + data: _T, + expected_type: object, +) -> _T: + """Transform dictionaries based off of type information from the given type, for example: + + ```py + class Params(TypedDict, total=False): + card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]] + + + transformed = transform({"card_id": ""}, Params) + # {'cardID': ''} + ``` + + Any keys / data that does not have type information given will be included as is. + + It should be noted that the transformations that this function does are not represented in the type system. + """ + transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type)) + return cast(_T, transformed) + + +async def _async_transform_recursive( + data: object, + *, + annotation: type, + inner_type: type | None = None, +) -> object: + """Transform the given data against the expected type. + + Args: + annotation: The direct type annotation given to the particular piece of data. + This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc + + inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type + is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in + the list can be transformed using the metadata from the container type. + + Defaults to the same value as the `annotation` argument. + """ + from .._compat import model_dump + + if inner_type is None: + inner_type = annotation + + stripped_type = strip_annotated_type(inner_type) + origin = get_origin(stripped_type) or stripped_type + if is_typeddict(stripped_type) and is_mapping(data): + return await _async_transform_typeddict(data, stripped_type) + + if origin == dict and is_mapping(data): + items_type = get_args(stripped_type)[1] + return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()} + + if ( + # List[T] + (is_list_type(stripped_type) and is_list(data)) + # Iterable[T] + or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) + ): + # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually + # intended as an iterable, so we don't transform it. + if isinstance(data, dict): + return cast(object, data) + + inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + + return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] + + if is_union_type(stripped_type): + # For union types we run the transformation against all subtypes to ensure that everything is transformed. + # + # TODO: there may be edge cases where the same normalized field name will transform to two different names + # in different subtypes. + for subtype in get_args(stripped_type): + data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype) + return data + + if isinstance(data, pydantic.BaseModel): + return model_dump(data, exclude_unset=True, mode="json") + + annotated_type = _get_annotated_type(annotation) + if annotated_type is None: + return data + + # ignore the first argument as it is the actual type + annotations = get_args(annotated_type)[1:] + for annotation in annotations: + if isinstance(annotation, PropertyInfo) and annotation.format is not None: + return await _async_format_data(data, annotation.format, annotation.format_template) + + return data + + +async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object: + if isinstance(data, (date, datetime)): + if format_ == "iso8601": + return data.isoformat() + + if format_ == "custom" and format_template is not None: + return data.strftime(format_template) + + if format_ == "base64" and is_base64_file_input(data): + binary: str | bytes | None = None + + if isinstance(data, pathlib.Path): + binary = await anyio.Path(data).read_bytes() + elif isinstance(data, io.IOBase): + binary = data.read() + + if isinstance(binary, str): # type: ignore[unreachable] + binary = binary.encode() + + if not isinstance(binary, bytes): + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") + + return base64.b64encode(binary).decode("ascii") + + return data + + +async def _async_transform_typeddict( + data: Mapping[str, object], + expected_type: type, +) -> Mapping[str, object]: + result: dict[str, object] = {} + annotations = get_type_hints(expected_type, include_extras=True) + for key, value in data.items(): + if not is_given(value): + # we don't need to include omitted values here as they'll + # be stripped out before the request is sent anyway + continue + + type_ = annotations.get(key) + if type_ is None: + # we do not have a type annotation for this field, leave it as is + result[key] = value + else: + result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) + return result + + +@lru_cache(maxsize=8096) +def get_type_hints( + obj: Any, + globalns: dict[str, Any] | None = None, + localns: Mapping[str, Any] | None = None, + include_extras: bool = False, +) -> dict[str, Any]: + return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) diff --git a/src/_utils/_typing.py b/src/_utils/_typing.py new file mode 100644 index 0000000..193109f --- /dev/null +++ b/src/_utils/_typing.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +import sys +import typing +import typing_extensions +from typing import Any, TypeVar, Iterable, cast +from collections import abc as _c_abc +from typing_extensions import ( + TypeIs, + Required, + Annotated, + get_args, + get_origin, +) + +from ._utils import lru_cache +from .._types import InheritsGeneric +from ._compat import is_union as _is_union + + +def is_annotated_type(typ: type) -> bool: + return get_origin(typ) == Annotated + + +def is_list_type(typ: type) -> bool: + return (get_origin(typ) or typ) == list + + +def is_sequence_type(typ: type) -> bool: + origin = get_origin(typ) or typ + return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence + + +def is_iterable_type(typ: type) -> bool: + """If the given type is `typing.Iterable[T]`""" + origin = get_origin(typ) or typ + return origin == Iterable or origin == _c_abc.Iterable + + +def is_union_type(typ: type) -> bool: + return _is_union(get_origin(typ)) + + +def is_required_type(typ: type) -> bool: + return get_origin(typ) == Required + + +def is_typevar(typ: type) -> bool: + # type ignore is required because type checkers + # think this expression will always return False + return type(typ) == TypeVar # type: ignore + + +_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,) +if sys.version_info >= (3, 12): + _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType) + + +def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: + """Return whether the provided argument is an instance of `TypeAliasType`. + + ```python + type Int = int + is_type_alias_type(Int) + # > True + Str = TypeAliasType("Str", str) + is_type_alias_type(Str) + # > True + ``` + """ + return isinstance(tp, _TYPE_ALIAS_TYPES) + + +# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] +@lru_cache(maxsize=8096) +def strip_annotated_type(typ: type) -> type: + if is_required_type(typ) or is_annotated_type(typ): + return strip_annotated_type(cast(type, get_args(typ)[0])) + + return typ + + +def extract_type_arg(typ: type, index: int) -> type: + args = get_args(typ) + try: + return cast(type, args[index]) + except IndexError as err: + raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err + + +def extract_type_var_from_base( + typ: type, + *, + generic_bases: tuple[type, ...], + index: int, + failure_message: str | None = None, +) -> type: + """Given a type like `Foo[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyResponse(Foo[bytes]): + ... + + extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes + ``` + + And where a generic subclass is given: + ```py + _T = TypeVar('_T') + class MyResponse(Foo[_T]): + ... + + extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes + ``` + """ + cls = cast(object, get_origin(typ) or typ) + if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains] + # we're given the class directly + return extract_type_arg(typ, index) + + # if a subclass is given + # --- + # this is needed as __orig_bases__ is not present in the typeshed stubs + # because it is intended to be for internal use only, however there does + # not seem to be a way to resolve generic TypeVars for inherited subclasses + # without using it. + if isinstance(cls, InheritsGeneric): + target_base_class: Any | None = None + for base in cls.__orig_bases__: + if base.__origin__ in generic_bases: + target_base_class = base + break + + if target_base_class is None: + raise RuntimeError( + "Could not find the generic base class;\n" + "This should never happen;\n" + f"Does {cls} inherit from one of {generic_bases} ?" + ) + + extracted = extract_type_arg(target_base_class, index) + if is_typevar(extracted): + # If the extracted type argument is itself a type variable + # then that means the subclass itself is generic, so we have + # to resolve the type argument from the class itself, not + # the base class. + # + # Note: if there is more than 1 type argument, the subclass could + # change the ordering of the type arguments, this is not currently + # supported. + return extract_type_arg(typ, index) + + return extracted + + raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}") diff --git a/src/_utils/_utils.py b/src/_utils/_utils.py new file mode 100644 index 0000000..8b051e2 --- /dev/null +++ b/src/_utils/_utils.py @@ -0,0 +1,433 @@ +from __future__ import annotations + +import os +import re +import inspect +import functools +from typing import ( + Any, + Tuple, + Mapping, + TypeVar, + Callable, + Iterable, + Sequence, + cast, + overload, +) +from pathlib import Path +from datetime import date, datetime +from typing_extensions import TypeGuard, get_args + +import sniffio + +from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike + +_T = TypeVar("_T") +_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) +_MappingT = TypeVar("_MappingT", bound=Mapping[str, object]) +_SequenceT = TypeVar("_SequenceT", bound=Sequence[object]) +CallableT = TypeVar("CallableT", bound=Callable[..., Any]) + + +def flatten(t: Iterable[Iterable[_T]]) -> list[_T]: + return [item for sublist in t for item in sublist] + + +def extract_files( + # TODO: this needs to take Dict but variance issues..... + # create protocol type ? + query: Mapping[str, object], + *, + paths: Sequence[Sequence[str]], + array_format: ArrayFormat = "brackets", +) -> list[tuple[str, FileTypes]]: + """Recursively extract files from the given dictionary based on specified paths. + + A path may look like this ['foo', 'files', '', 'data']. + + ``array_format`` controls how ```` segments contribute to the emitted + field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and + ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``). + + Note: this mutates the given dictionary. + """ + files: list[tuple[str, FileTypes]] = [] + for path in paths: + files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format)) + return files + + +def _array_suffix(array_format: ArrayFormat, array_index: int) -> str: + if array_format == "brackets": + return "[]" + if array_format == "indices": + return f"[{array_index}]" + if array_format == "repeat" or array_format == "comma": + # Both repeat the bare field name for each file part; there is no + # meaningful way to comma-join binary parts. + return "" + raise NotImplementedError( + f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" + ) + + +def _extract_items( + obj: object, + path: Sequence[str], + *, + index: int, + flattened_key: str | None, + array_format: ArrayFormat, +) -> list[tuple[str, FileTypes]]: + try: + key = path[index] + except IndexError: + if not is_given(obj): + # no value was provided - we can safely ignore + return [] + + # cyclical import + from .._files import assert_is_file_content + + # We have exhausted the path, return the entry we found. + assert flattened_key is not None + + if is_list(obj): + files: list[tuple[str, FileTypes]] = [] + for array_index, entry in enumerate(obj): + suffix = _array_suffix(array_format, array_index) + emitted_key = (flattened_key + suffix) if flattened_key else suffix + assert_is_file_content(entry, key=emitted_key) + files.append((emitted_key, cast(FileTypes, entry))) + return files + + assert_is_file_content(obj, key=flattened_key) + return [(flattened_key, cast(FileTypes, obj))] + + index += 1 + if is_dict(obj): + try: + # Remove the field if there are no more dict keys in the path, + # only "" traversal markers or end. + if all(p == "" for p in path[index:]): + item = obj.pop(key) + else: + item = obj[key] + except KeyError: + # Key was not present in the dictionary, this is not indicative of an error + # as the given path may not point to a required field. We also do not want + # to enforce required fields as the API may differ from the spec in some cases. + return [] + if flattened_key is None: + flattened_key = key + else: + flattened_key += f"[{key}]" + return _extract_items( + item, + path, + index=index, + flattened_key=flattened_key, + array_format=array_format, + ) + elif is_list(obj): + if key != "": + return [] + + return flatten( + [ + _extract_items( + item, + path, + index=index, + flattened_key=( + (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index) + ), + array_format=array_format, + ) + for array_index, item in enumerate(obj) + ] + ) + + # Something unexpected was passed, just ignore it. + return [] + + +def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: + return not isinstance(obj, NotGiven) and not isinstance(obj, Omit) + + +# Type safe methods for narrowing types with TypeVars. +# The default narrowing for isinstance(obj, dict) is dict[unknown, unknown], +# however this cause Pyright to rightfully report errors. As we know we don't +# care about the contained types we can safely use `object` in its place. +# +# There are two separate functions defined, `is_*` and `is_*_t` for different use cases. +# `is_*` is for when you're dealing with an unknown input +# `is_*_t` is for when you're narrowing a known union type to a specific subset + + +def is_tuple(obj: object) -> TypeGuard[tuple[object, ...]]: + return isinstance(obj, tuple) + + +def is_tuple_t(obj: _TupleT | object) -> TypeGuard[_TupleT]: + return isinstance(obj, tuple) + + +def is_sequence(obj: object) -> TypeGuard[Sequence[object]]: + return isinstance(obj, Sequence) + + +def is_sequence_t(obj: _SequenceT | object) -> TypeGuard[_SequenceT]: + return isinstance(obj, Sequence) + + +def is_mapping(obj: object) -> TypeGuard[Mapping[str, object]]: + return isinstance(obj, Mapping) + + +def is_mapping_t(obj: _MappingT | object) -> TypeGuard[_MappingT]: + return isinstance(obj, Mapping) + + +def is_dict(obj: object) -> TypeGuard[dict[object, object]]: + return isinstance(obj, dict) + + +def is_list(obj: object) -> TypeGuard[list[object]]: + return isinstance(obj, list) + + +def is_iterable(obj: object) -> TypeGuard[Iterable[object]]: + return isinstance(obj, Iterable) + + +# copied from https://github.com/Rapptz/RoboDanny +def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str: + size = len(seq) + if size == 0: + return "" + + if size == 1: + return seq[0] + + if size == 2: + return f"{seq[0]} {final} {seq[1]}" + + return delim.join(seq[:-1]) + f" {final} {seq[-1]}" + + +def quote(string: str) -> str: + """Add single quotation marks around the given string. Does *not* do any escaping.""" + return f"'{string}'" + + +def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]: + """Decorator to enforce a given set of arguments or variants of arguments are passed to the decorated function. + + Useful for enforcing runtime validation of overloaded functions. + + Example usage: + ```py + @overload + def foo(*, a: str) -> str: ... + + + @overload + def foo(*, b: bool) -> str: ... + + + # This enforces the same constraints that a static type checker would + # i.e. that either a or b must be passed to the function + @required_args(["a"], ["b"]) + def foo(*, a: str | None = None, b: bool | None = None) -> str: ... + ``` + """ + + def inner(func: CallableT) -> CallableT: + params = inspect.signature(func).parameters + positional = [ + name + for name, param in params.items() + if param.kind + in { + param.POSITIONAL_ONLY, + param.POSITIONAL_OR_KEYWORD, + } + ] + + @functools.wraps(func) + def wrapper(*args: object, **kwargs: object) -> object: + given_params: set[str] = set() + for i, _ in enumerate(args): + try: + given_params.add(positional[i]) + except IndexError: + raise TypeError( + f"{func.__name__}() takes {len(positional)} argument(s) but {len(args)} were given" + ) from None + + for key in kwargs.keys(): + given_params.add(key) + + for variant in variants: + matches = all((param in given_params for param in variant)) + if matches: + break + else: # no break + if len(variants) > 1: + variations = human_join( + ["(" + human_join([quote(arg) for arg in variant], final="and") + ")" for variant in variants] + ) + msg = f"Missing required arguments; Expected either {variations} arguments to be given" + else: + assert len(variants) > 0 + + # TODO: this error message is not deterministic + missing = list(set(variants[0]) - given_params) + if len(missing) > 1: + msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}" + else: + msg = f"Missing required argument: {quote(missing[0])}" + raise TypeError(msg) + return func(*args, **kwargs) + + return wrapper # type: ignore + + return inner + + +_K = TypeVar("_K") +_V = TypeVar("_V") + + +@overload +def strip_not_given(obj: None) -> None: ... + + +@overload +def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: ... + + +@overload +def strip_not_given(obj: object) -> object: ... + + +def strip_not_given(obj: object | None) -> object: + """Remove all top-level keys where their values are instances of `NotGiven`""" + if obj is None: + return None + + if not is_mapping(obj): + return obj + + return {key: value for key, value in obj.items() if not isinstance(value, NotGiven)} + + +def coerce_integer(val: str) -> int: + return int(val, base=10) + + +def coerce_float(val: str) -> float: + return float(val) + + +def coerce_boolean(val: str) -> bool: + return val == "true" or val == "1" or val == "on" + + +def maybe_coerce_integer(val: str | None) -> int | None: + if val is None: + return None + return coerce_integer(val) + + +def maybe_coerce_float(val: str | None) -> float | None: + if val is None: + return None + return coerce_float(val) + + +def maybe_coerce_boolean(val: str | None) -> bool | None: + if val is None: + return None + return coerce_boolean(val) + + +def removeprefix(string: str, prefix: str) -> str: + """Remove a prefix from a string. + + Backport of `str.removeprefix` for Python < 3.9 + """ + if string.startswith(prefix): + return string[len(prefix) :] + return string + + +def removesuffix(string: str, suffix: str) -> str: + """Remove a suffix from a string. + + Backport of `str.removesuffix` for Python < 3.9 + """ + if string.endswith(suffix): + return string[: -len(suffix)] + return string + + +def file_from_path(path: str) -> FileTypes: + contents = Path(path).read_bytes() + file_name = os.path.basename(path) + return (file_name, contents) + + +def get_required_header(headers: HeadersLike, header: str) -> str: + lower_header = header.lower() + if is_mapping_t(headers): + # mypy doesn't understand the type narrowing here + for k, v in headers.items(): # type: ignore + if k.lower() == lower_header and isinstance(v, str): + return v + + # to deal with the case where the header looks like Scalar-Event-Id + intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize()) + + for normalized_header in [header, lower_header, header.upper(), intercaps_header]: + value = headers.get(normalized_header) + if value: + return value + + raise ValueError(f"Could not find {header} header") + + +def get_async_library() -> str: + try: + return sniffio.current_async_library() + except Exception: + return "false" + + +def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]: + """A version of functools.lru_cache that retains the type signature + for the wrapped function arguments. + """ + wrapper = functools.lru_cache( # noqa: TID251 + maxsize=maxsize, + ) + return cast(Any, wrapper) # type: ignore[no-any-return] + + +def json_safe(data: object) -> object: + """Translates a mapping / sequence recursively in the same fashion + as `pydantic` v2's `model_dump(mode="json")`. + """ + if is_mapping(data): + return {json_safe(key): json_safe(value) for key, value in data.items()} + + if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)): + return [json_safe(item) for item in data] + + if isinstance(data, (datetime, date)): + return data.isoformat() + + return data diff --git a/src/_version.py b/src/_version.py new file mode 100644 index 0000000..04ad00e --- /dev/null +++ b/src/_version.py @@ -0,0 +1,4 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +__title__ = "scalarApi" +__version__ = "0.2.5" diff --git a/src/py.typed b/src/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/resources/__init__.py b/src/resources/__init__.py new file mode 100644 index 0000000..e5d8992 --- /dev/null +++ b/src/resources/__init__.py @@ -0,0 +1,131 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from .registry import ( + RegistryResource, + AsyncRegistryResource, + RegistryResourceWithRawResponse, + AsyncRegistryResourceWithRawResponse, + RegistryResourceWithStreamingResponse, + AsyncRegistryResourceWithStreamingResponse, +) +from .schemas import ( + SchemasResource, + AsyncSchemasResource, + SchemasResourceWithRawResponse, + AsyncSchemasResourceWithRawResponse, + SchemasResourceWithStreamingResponse, + AsyncSchemasResourceWithStreamingResponse, +) +from .login_portals import ( + LoginPortalsResource, + AsyncLoginPortalsResource, + LoginPortalsResourceWithRawResponse, + AsyncLoginPortalsResourceWithRawResponse, + LoginPortalsResourceWithStreamingResponse, + AsyncLoginPortalsResourceWithStreamingResponse, +) +from .rules import ( + RulesResource, + AsyncRulesResource, + RulesResourceWithRawResponse, + AsyncRulesResourceWithRawResponse, + RulesResourceWithStreamingResponse, + AsyncRulesResourceWithStreamingResponse, +) +from .themes import ( + ThemesResource, + AsyncThemesResource, + ThemesResourceWithRawResponse, + AsyncThemesResourceWithRawResponse, + ThemesResourceWithStreamingResponse, + AsyncThemesResourceWithStreamingResponse, +) +from .teams import ( + TeamsResource, + AsyncTeamsResource, + TeamsResourceWithRawResponse, + AsyncTeamsResourceWithRawResponse, + TeamsResourceWithStreamingResponse, + AsyncTeamsResourceWithStreamingResponse, +) +from .scalar_docs import ( + ScalarDocsResource, + AsyncScalarDocsResource, + ScalarDocsResourceWithRawResponse, + AsyncScalarDocsResourceWithRawResponse, + ScalarDocsResourceWithStreamingResponse, + AsyncScalarDocsResourceWithStreamingResponse, +) +from .namespaces import ( + NamespacesResource, + AsyncNamespacesResource, + NamespacesResourceWithRawResponse, + AsyncNamespacesResourceWithRawResponse, + NamespacesResourceWithStreamingResponse, + AsyncNamespacesResourceWithStreamingResponse, +) +from .authentication import ( + AuthenticationResource, + AsyncAuthenticationResource, + AuthenticationResourceWithRawResponse, + AsyncAuthenticationResourceWithRawResponse, + AuthenticationResourceWithStreamingResponse, + AsyncAuthenticationResourceWithStreamingResponse, +) + +__all__ = [ + "RegistryResource", + "AsyncRegistryResource", + "RegistryResourceWithRawResponse", + "AsyncRegistryResourceWithRawResponse", + "RegistryResourceWithStreamingResponse", + "AsyncRegistryResourceWithStreamingResponse", + "SchemasResource", + "AsyncSchemasResource", + "SchemasResourceWithRawResponse", + "AsyncSchemasResourceWithRawResponse", + "SchemasResourceWithStreamingResponse", + "AsyncSchemasResourceWithStreamingResponse", + "LoginPortalsResource", + "AsyncLoginPortalsResource", + "LoginPortalsResourceWithRawResponse", + "AsyncLoginPortalsResourceWithRawResponse", + "LoginPortalsResourceWithStreamingResponse", + "AsyncLoginPortalsResourceWithStreamingResponse", + "RulesResource", + "AsyncRulesResource", + "RulesResourceWithRawResponse", + "AsyncRulesResourceWithRawResponse", + "RulesResourceWithStreamingResponse", + "AsyncRulesResourceWithStreamingResponse", + "ThemesResource", + "AsyncThemesResource", + "ThemesResourceWithRawResponse", + "AsyncThemesResourceWithRawResponse", + "ThemesResourceWithStreamingResponse", + "AsyncThemesResourceWithStreamingResponse", + "TeamsResource", + "AsyncTeamsResource", + "TeamsResourceWithRawResponse", + "AsyncTeamsResourceWithRawResponse", + "TeamsResourceWithStreamingResponse", + "AsyncTeamsResourceWithStreamingResponse", + "ScalarDocsResource", + "AsyncScalarDocsResource", + "ScalarDocsResourceWithRawResponse", + "AsyncScalarDocsResourceWithRawResponse", + "ScalarDocsResourceWithStreamingResponse", + "AsyncScalarDocsResourceWithStreamingResponse", + "NamespacesResource", + "AsyncNamespacesResource", + "NamespacesResourceWithRawResponse", + "AsyncNamespacesResourceWithRawResponse", + "NamespacesResourceWithStreamingResponse", + "AsyncNamespacesResourceWithStreamingResponse", + "AuthenticationResource", + "AsyncAuthenticationResource", + "AuthenticationResourceWithRawResponse", + "AsyncAuthenticationResourceWithRawResponse", + "AuthenticationResourceWithStreamingResponse", + "AsyncAuthenticationResourceWithStreamingResponse", +] diff --git a/src/resources/authentication.py b/src/resources/authentication.py new file mode 100644 index 0000000..37d5e9e --- /dev/null +++ b/src/resources/authentication.py @@ -0,0 +1,172 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.authentication_exchange_personal_token_response import AuthenticationExchangePersonalTokenResponse +from ..types import authentication_exchange_personal_token_params +from ..types.authentication_list_current_user_response import AuthenticationListCurrentUserResponse + +__all__ = ["AuthenticationResource", "AsyncAuthenticationResource"] + + +class AuthenticationResource(SyncAPIResource): + + @cached_property + def with_raw_response(self) -> AuthenticationResourceWithRawResponse: + return AuthenticationResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AuthenticationResourceWithStreamingResponse: + return AuthenticationResourceWithStreamingResponse(self) + + def exchange_personal_token( + self, + *, + personal_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> AuthenticationExchangePersonalTokenResponse: + """Exchange an API key for an access token.""" + return self._post( + "/v1/auth/exchange", + body=maybe_transform( + {"personal_token": personal_token}, + authentication_exchange_personal_token_params.AuthenticationExchangePersonalTokenParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=AuthenticationExchangePersonalTokenResponse, + ) + + def list_current_user( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthenticationListCurrentUserResponse: + """Get the authenticated user, including their available teams and theme.""" + return self._get( + "/v1/auth/me", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=AuthenticationListCurrentUserResponse, + ) + + +class AsyncAuthenticationResource(AsyncAPIResource): + + @cached_property + def with_raw_response(self) -> AsyncAuthenticationResourceWithRawResponse: + return AsyncAuthenticationResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAuthenticationResourceWithStreamingResponse: + return AsyncAuthenticationResourceWithStreamingResponse(self) + + async def exchange_personal_token( + self, + *, + personal_token: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> AuthenticationExchangePersonalTokenResponse: + """Exchange an API key for an access token.""" + return await self._post( + "/v1/auth/exchange", + body=await async_maybe_transform( + {"personal_token": personal_token}, + authentication_exchange_personal_token_params.AuthenticationExchangePersonalTokenParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=AuthenticationExchangePersonalTokenResponse, + ) + + async def list_current_user( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AuthenticationListCurrentUserResponse: + """Get the authenticated user, including their available teams and theme.""" + return await self._get( + "/v1/auth/me", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=AuthenticationListCurrentUserResponse, + ) + + +class AuthenticationResourceWithRawResponse: + def __init__(self, authentication: AuthenticationResource) -> None: + self._authentication = authentication + + self.exchange_personal_token = to_raw_response_wrapper( + authentication.exchange_personal_token, + ) + self.list_current_user = to_raw_response_wrapper( + authentication.list_current_user, + ) + + +class AsyncAuthenticationResourceWithRawResponse: + def __init__(self, authentication: AsyncAuthenticationResource) -> None: + self._authentication = authentication + + self.exchange_personal_token = async_to_raw_response_wrapper( + authentication.exchange_personal_token, + ) + self.list_current_user = async_to_raw_response_wrapper( + authentication.list_current_user, + ) + + +class AuthenticationResourceWithStreamingResponse: + def __init__(self, authentication: AuthenticationResource) -> None: + self._authentication = authentication + + self.exchange_personal_token = to_streamed_response_wrapper( + authentication.exchange_personal_token, + ) + self.list_current_user = to_streamed_response_wrapper( + authentication.list_current_user, + ) + + +class AsyncAuthenticationResourceWithStreamingResponse: + def __init__(self, authentication: AsyncAuthenticationResource) -> None: + self._authentication = authentication + + self.exchange_personal_token = async_to_streamed_response_wrapper( + authentication.exchange_personal_token, + ) + self.list_current_user = async_to_streamed_response_wrapper( + authentication.list_current_user, + ) diff --git a/src/resources/login_portals.py b/src/resources/login_portals.py new file mode 100644 index 0000000..ff4d9bf --- /dev/null +++ b/src/resources/login_portals.py @@ -0,0 +1,362 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.login_portal_retrieve_response import LoginPortalRetrieveResponse +from ..types.login_portal_update_response import LoginPortalUpdateResponse +from ..types import login_portal_update_params +from ..types.login_portal_delete_response import LoginPortalDeleteResponse +from ..types.login_portal_create_response import LoginPortalCreateResponse +from ..types import login_portal_create_params +from ..types.login_portal_list_response import LoginPortalListResponse + +__all__ = ["LoginPortalsResource", "AsyncLoginPortalsResource"] + + +class LoginPortalsResource(SyncAPIResource): + + @cached_property + def with_raw_response(self) -> LoginPortalsResourceWithRawResponse: + return LoginPortalsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> LoginPortalsResourceWithStreamingResponse: + return LoginPortalsResourceWithStreamingResponse(self) + + def retrieve( + self, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LoginPortalRetrieveResponse: + """Get a login portal by slug.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._get( + path_template("/v1/login-portals/{slug}", **{"slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=LoginPortalRetrieveResponse, + ) + + def update( + self, + slug: str, + *, + title: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> LoginPortalUpdateResponse: + """Update metadata for a login portal.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._patch( + path_template("/v1/login-portals/{slug}", **{"slug": slug}), + body=maybe_transform( + {"title": title}, + login_portal_update_params.LoginPortalUpdateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=LoginPortalUpdateResponse, + ) + + def delete( + self, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> LoginPortalDeleteResponse: + """Delete a login portal.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._delete( + path_template("/v1/login-portals/{slug}", **{"slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=LoginPortalDeleteResponse, + ) + + def create( + self, + *, + title: str, + slug: str, + email: object, + page: object, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> LoginPortalCreateResponse: + """Create a login portal for the current team.""" + return self._post( + "/v1/login-portals", + body=maybe_transform( + { + "title": title, + "slug": slug, + "email": email, + "page": page, + }, + login_portal_create_params.LoginPortalCreateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=LoginPortalCreateResponse, + ) + + def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LoginPortalListResponse: + """List all login portals for the current team.""" + return self._get( + "/v1/login-portals", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=LoginPortalListResponse, + ) + + +class AsyncLoginPortalsResource(AsyncAPIResource): + + @cached_property + def with_raw_response(self) -> AsyncLoginPortalsResourceWithRawResponse: + return AsyncLoginPortalsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncLoginPortalsResourceWithStreamingResponse: + return AsyncLoginPortalsResourceWithStreamingResponse(self) + + async def retrieve( + self, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LoginPortalRetrieveResponse: + """Get a login portal by slug.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._get( + path_template("/v1/login-portals/{slug}", **{"slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=LoginPortalRetrieveResponse, + ) + + async def update( + self, + slug: str, + *, + title: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> LoginPortalUpdateResponse: + """Update metadata for a login portal.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._patch( + path_template("/v1/login-portals/{slug}", **{"slug": slug}), + body=await async_maybe_transform( + {"title": title}, + login_portal_update_params.LoginPortalUpdateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=LoginPortalUpdateResponse, + ) + + async def delete( + self, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> LoginPortalDeleteResponse: + """Delete a login portal.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._delete( + path_template("/v1/login-portals/{slug}", **{"slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=LoginPortalDeleteResponse, + ) + + async def create( + self, + *, + title: str, + slug: str, + email: object, + page: object, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> LoginPortalCreateResponse: + """Create a login portal for the current team.""" + return await self._post( + "/v1/login-portals", + body=await async_maybe_transform( + { + "title": title, + "slug": slug, + "email": email, + "page": page, + }, + login_portal_create_params.LoginPortalCreateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=LoginPortalCreateResponse, + ) + + async def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LoginPortalListResponse: + """List all login portals for the current team.""" + return await self._get( + "/v1/login-portals", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=LoginPortalListResponse, + ) + + +class LoginPortalsResourceWithRawResponse: + def __init__(self, login_portals: LoginPortalsResource) -> None: + self._login_portals = login_portals + + self.retrieve = to_raw_response_wrapper( + login_portals.retrieve, + ) + self.update = to_raw_response_wrapper( + login_portals.update, + ) + self.delete = to_raw_response_wrapper( + login_portals.delete, + ) + self.create = to_raw_response_wrapper( + login_portals.create, + ) + self.list = to_raw_response_wrapper( + login_portals.list, + ) + + +class AsyncLoginPortalsResourceWithRawResponse: + def __init__(self, login_portals: AsyncLoginPortalsResource) -> None: + self._login_portals = login_portals + + self.retrieve = async_to_raw_response_wrapper( + login_portals.retrieve, + ) + self.update = async_to_raw_response_wrapper( + login_portals.update, + ) + self.delete = async_to_raw_response_wrapper( + login_portals.delete, + ) + self.create = async_to_raw_response_wrapper( + login_portals.create, + ) + self.list = async_to_raw_response_wrapper( + login_portals.list, + ) + + +class LoginPortalsResourceWithStreamingResponse: + def __init__(self, login_portals: LoginPortalsResource) -> None: + self._login_portals = login_portals + + self.retrieve = to_streamed_response_wrapper( + login_portals.retrieve, + ) + self.update = to_streamed_response_wrapper( + login_portals.update, + ) + self.delete = to_streamed_response_wrapper( + login_portals.delete, + ) + self.create = to_streamed_response_wrapper( + login_portals.create, + ) + self.list = to_streamed_response_wrapper( + login_portals.list, + ) + + +class AsyncLoginPortalsResourceWithStreamingResponse: + def __init__(self, login_portals: AsyncLoginPortalsResource) -> None: + self._login_portals = login_portals + + self.retrieve = async_to_streamed_response_wrapper( + login_portals.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + login_portals.update, + ) + self.delete = async_to_streamed_response_wrapper( + login_portals.delete, + ) + self.create = async_to_streamed_response_wrapper( + login_portals.create, + ) + self.list = async_to_streamed_response_wrapper( + login_portals.list, + ) diff --git a/src/resources/namespaces.py b/src/resources/namespaces.py new file mode 100644 index 0000000..52f6303 --- /dev/null +++ b/src/resources/namespaces.py @@ -0,0 +1,112 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.namespace_list_response import NamespaceListResponse + +__all__ = ["NamespacesResource", "AsyncNamespacesResource"] + + +class NamespacesResource(SyncAPIResource): + + @cached_property + def with_raw_response(self) -> NamespacesResourceWithRawResponse: + return NamespacesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> NamespacesResourceWithStreamingResponse: + return NamespacesResourceWithStreamingResponse(self) + + def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NamespaceListResponse: + """Get all namespaces for the current team""" + return self._get( + "/v1/namespaces", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=NamespaceListResponse, + ) + + +class AsyncNamespacesResource(AsyncAPIResource): + + @cached_property + def with_raw_response(self) -> AsyncNamespacesResourceWithRawResponse: + return AsyncNamespacesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncNamespacesResourceWithStreamingResponse: + return AsyncNamespacesResourceWithStreamingResponse(self) + + async def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> NamespaceListResponse: + """Get all namespaces for the current team""" + return await self._get( + "/v1/namespaces", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=NamespaceListResponse, + ) + + +class NamespacesResourceWithRawResponse: + def __init__(self, namespaces: NamespacesResource) -> None: + self._namespaces = namespaces + + self.list = to_raw_response_wrapper( + namespaces.list, + ) + + +class AsyncNamespacesResourceWithRawResponse: + def __init__(self, namespaces: AsyncNamespacesResource) -> None: + self._namespaces = namespaces + + self.list = async_to_raw_response_wrapper( + namespaces.list, + ) + + +class NamespacesResourceWithStreamingResponse: + def __init__(self, namespaces: NamespacesResource) -> None: + self._namespaces = namespaces + + self.list = to_streamed_response_wrapper( + namespaces.list, + ) + + +class AsyncNamespacesResourceWithStreamingResponse: + def __init__(self, namespaces: AsyncNamespacesResource) -> None: + self._namespaces = namespaces + + self.list = async_to_streamed_response_wrapper( + namespaces.list, + ) diff --git a/src/resources/registry.py b/src/resources/registry.py new file mode 100644 index 0000000..4aaa648 --- /dev/null +++ b/src/resources/registry.py @@ -0,0 +1,923 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.registry_list_all_api_documents_response import RegistryListAllApiDocumentsResponse +from ..types.registry_list_api_documents_response import RegistryListApiDocumentsResponse +from ..types.registry_create_api_document_response import RegistryCreateApiDocumentResponse +from ..types import registry_create_api_document_params +from ..types.registry_update_api_document_response import RegistryUpdateApiDocumentResponse +from ..types import registry_update_api_document_params +from ..types.registry_delete_api_document_response import RegistryDeleteApiDocumentResponse +from ..types.registry_retrieve_api_document_version_response import RegistryRetrieveApiDocumentVersionResponse +from ..types.registry_update_api_document_version_response import RegistryUpdateApiDocumentVersionResponse +from ..types import registry_update_api_document_version_params +from ..types.registry_delete_api_document_version_response import RegistryDeleteApiDocumentVersionResponse +from ..types.registry_list_api_document_version_metadata_response import RegistryListApiDocumentVersionMetadataResponse +from ..types.registry_create_api_document_version_response import RegistryCreateApiDocumentVersionResponse +from ..types import registry_create_api_document_version_params +from ..types.registry_create_api_document_access_group_response import RegistryCreateApiDocumentAccessGroupResponse +from ..types import registry_create_api_document_access_group_params +from ..types.registry_delete_api_document_access_group_response import RegistryDeleteApiDocumentAccessGroupResponse +from ..types import registry_delete_api_document_access_group_params + +__all__ = ["RegistryResource", "AsyncRegistryResource"] + + +class RegistryResource(SyncAPIResource): + + @cached_property + def with_raw_response(self) -> RegistryResourceWithRawResponse: + return RegistryResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RegistryResourceWithStreamingResponse: + return RegistryResourceWithStreamingResponse(self) + + def list_all_api_documents( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RegistryListAllApiDocumentsResponse: + """List all API documents across every namespace the caller can access.""" + return self._get( + "/v1/apis", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RegistryListAllApiDocumentsResponse, + ) + + def list_api_documents( + self, + namespace: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RegistryListApiDocumentsResponse: + """List API documents in a namespace.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return self._get( + path_template("/v1/apis/{namespace}", **{"namespace": namespace}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RegistryListApiDocumentsResponse, + ) + + def create_api_document( + self, + namespace: str, + *, + title: str, + description: str | Omit = omit, + version: str, + slug: str, + ruleset: str | Omit = omit, + is_private: bool | Omit = omit, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryCreateApiDocumentResponse: + """Create an API document.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return self._post( + path_template("/v1/apis/{namespace}", **{"namespace": namespace}), + body=maybe_transform( + { + "title": title, + "description": description, + "version": version, + "slug": slug, + "ruleset": ruleset, + "is_private": is_private, + "document": document, + }, + registry_create_api_document_params.RegistryCreateApiDocumentParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryCreateApiDocumentResponse, + ) + + def update_api_document( + self, + namespace: str, + slug: str, + *, + title: str | Omit = omit, + description: str | Omit = omit, + is_private: bool | Omit = omit, + ruleset: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryUpdateApiDocumentResponse: + """Update metadata for an API document.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._patch( + path_template("/v1/apis/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + body=maybe_transform( + { + "title": title, + "description": description, + "is_private": is_private, + "ruleset": ruleset, + }, + registry_update_api_document_params.RegistryUpdateApiDocumentParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryUpdateApiDocumentResponse, + ) + + def delete_api_document( + self, + namespace: str, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryDeleteApiDocumentResponse: + """Delete an API document and all versions.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._delete( + path_template("/v1/apis/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryDeleteApiDocumentResponse, + ) + + def retrieve_api_document_version( + self, + namespace: str, + slug: str, + semver: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RegistryRetrieveApiDocumentVersionResponse: + """Get a specific API document version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return self._get( + path_template("/v1/apis/{namespace}/{slug}/version/{semver}", **{"namespace": namespace, "slug": slug, "semver": semver}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RegistryRetrieveApiDocumentVersionResponse, + ) + + def update_api_document_version( + self, + namespace: str, + slug: str, + semver: str, + *, + document: str, + last_known_version_sha: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryUpdateApiDocumentVersionResponse: + """Update the registry file content for an API document version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return self._patch( + path_template("/v1/apis/{namespace}/{slug}/version/{semver}", **{"namespace": namespace, "slug": slug, "semver": semver}), + body=maybe_transform( + { + "document": document, + "last_known_version_sha": last_known_version_sha, + }, + registry_update_api_document_version_params.RegistryUpdateApiDocumentVersionParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryUpdateApiDocumentVersionResponse, + ) + + def delete_api_document_version( + self, + namespace: str, + slug: str, + semver: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryDeleteApiDocumentVersionResponse: + """Delete a specific API document version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return self._delete( + path_template("/v1/apis/{namespace}/{slug}/version/{semver}", **{"namespace": namespace, "slug": slug, "semver": semver}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryDeleteApiDocumentVersionResponse, + ) + + def list_api_document_version_metadata( + self, + namespace: str, + slug: str, + semver: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RegistryListApiDocumentVersionMetadataResponse: + """Get metadata (uid, content shas, version sha, tags) for a specific API document version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return self._get( + path_template("/v1/apis/{namespace}/{slug}/version/{semver}/metadata", **{"namespace": namespace, "slug": slug, "semver": semver}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RegistryListApiDocumentVersionMetadataResponse, + ) + + def create_api_document_version( + self, + namespace: str, + slug: str, + *, + version: str, + document: str, + force: bool | Omit = omit, + last_known_version_sha: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryCreateApiDocumentVersionResponse: + """Create a new API document version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._post( + path_template("/v1/apis/{namespace}/{slug}/version", **{"namespace": namespace, "slug": slug}), + body=maybe_transform( + { + "version": version, + "document": document, + "force": force, + "last_known_version_sha": last_known_version_sha, + }, + registry_create_api_document_version_params.RegistryCreateApiDocumentVersionParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryCreateApiDocumentVersionResponse, + ) + + def create_api_document_access_group( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryCreateApiDocumentAccessGroupResponse: + """Add an access group to an API document.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._post( + path_template("/v1/apis/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=maybe_transform( + {"access_group_slug": access_group_slug}, + registry_create_api_document_access_group_params.RegistryCreateApiDocumentAccessGroupParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryCreateApiDocumentAccessGroupResponse, + ) + + def delete_api_document_access_group( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryDeleteApiDocumentAccessGroupResponse: + """Remove an access group from an API document.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._delete( + path_template("/v1/apis/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=maybe_transform( + {"access_group_slug": access_group_slug}, + registry_delete_api_document_access_group_params.RegistryDeleteApiDocumentAccessGroupParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryDeleteApiDocumentAccessGroupResponse, + ) + + +class AsyncRegistryResource(AsyncAPIResource): + + @cached_property + def with_raw_response(self) -> AsyncRegistryResourceWithRawResponse: + return AsyncRegistryResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRegistryResourceWithStreamingResponse: + return AsyncRegistryResourceWithStreamingResponse(self) + + async def list_all_api_documents( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RegistryListAllApiDocumentsResponse: + """List all API documents across every namespace the caller can access.""" + return await self._get( + "/v1/apis", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RegistryListAllApiDocumentsResponse, + ) + + async def list_api_documents( + self, + namespace: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RegistryListApiDocumentsResponse: + """List API documents in a namespace.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return await self._get( + path_template("/v1/apis/{namespace}", **{"namespace": namespace}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RegistryListApiDocumentsResponse, + ) + + async def create_api_document( + self, + namespace: str, + *, + title: str, + description: str | Omit = omit, + version: str, + slug: str, + ruleset: str | Omit = omit, + is_private: bool | Omit = omit, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryCreateApiDocumentResponse: + """Create an API document.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return await self._post( + path_template("/v1/apis/{namespace}", **{"namespace": namespace}), + body=await async_maybe_transform( + { + "title": title, + "description": description, + "version": version, + "slug": slug, + "ruleset": ruleset, + "is_private": is_private, + "document": document, + }, + registry_create_api_document_params.RegistryCreateApiDocumentParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryCreateApiDocumentResponse, + ) + + async def update_api_document( + self, + namespace: str, + slug: str, + *, + title: str | Omit = omit, + description: str | Omit = omit, + is_private: bool | Omit = omit, + ruleset: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryUpdateApiDocumentResponse: + """Update metadata for an API document.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._patch( + path_template("/v1/apis/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + body=await async_maybe_transform( + { + "title": title, + "description": description, + "is_private": is_private, + "ruleset": ruleset, + }, + registry_update_api_document_params.RegistryUpdateApiDocumentParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryUpdateApiDocumentResponse, + ) + + async def delete_api_document( + self, + namespace: str, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryDeleteApiDocumentResponse: + """Delete an API document and all versions.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._delete( + path_template("/v1/apis/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryDeleteApiDocumentResponse, + ) + + async def retrieve_api_document_version( + self, + namespace: str, + slug: str, + semver: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RegistryRetrieveApiDocumentVersionResponse: + """Get a specific API document version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return await self._get( + path_template("/v1/apis/{namespace}/{slug}/version/{semver}", **{"namespace": namespace, "slug": slug, "semver": semver}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RegistryRetrieveApiDocumentVersionResponse, + ) + + async def update_api_document_version( + self, + namespace: str, + slug: str, + semver: str, + *, + document: str, + last_known_version_sha: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryUpdateApiDocumentVersionResponse: + """Update the registry file content for an API document version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return await self._patch( + path_template("/v1/apis/{namespace}/{slug}/version/{semver}", **{"namespace": namespace, "slug": slug, "semver": semver}), + body=await async_maybe_transform( + { + "document": document, + "last_known_version_sha": last_known_version_sha, + }, + registry_update_api_document_version_params.RegistryUpdateApiDocumentVersionParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryUpdateApiDocumentVersionResponse, + ) + + async def delete_api_document_version( + self, + namespace: str, + slug: str, + semver: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryDeleteApiDocumentVersionResponse: + """Delete a specific API document version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return await self._delete( + path_template("/v1/apis/{namespace}/{slug}/version/{semver}", **{"namespace": namespace, "slug": slug, "semver": semver}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryDeleteApiDocumentVersionResponse, + ) + + async def list_api_document_version_metadata( + self, + namespace: str, + slug: str, + semver: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RegistryListApiDocumentVersionMetadataResponse: + """Get metadata (uid, content shas, version sha, tags) for a specific API document version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return await self._get( + path_template("/v1/apis/{namespace}/{slug}/version/{semver}/metadata", **{"namespace": namespace, "slug": slug, "semver": semver}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RegistryListApiDocumentVersionMetadataResponse, + ) + + async def create_api_document_version( + self, + namespace: str, + slug: str, + *, + version: str, + document: str, + force: bool | Omit = omit, + last_known_version_sha: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryCreateApiDocumentVersionResponse: + """Create a new API document version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._post( + path_template("/v1/apis/{namespace}/{slug}/version", **{"namespace": namespace, "slug": slug}), + body=await async_maybe_transform( + { + "version": version, + "document": document, + "force": force, + "last_known_version_sha": last_known_version_sha, + }, + registry_create_api_document_version_params.RegistryCreateApiDocumentVersionParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryCreateApiDocumentVersionResponse, + ) + + async def create_api_document_access_group( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryCreateApiDocumentAccessGroupResponse: + """Add an access group to an API document.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._post( + path_template("/v1/apis/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=await async_maybe_transform( + {"access_group_slug": access_group_slug}, + registry_create_api_document_access_group_params.RegistryCreateApiDocumentAccessGroupParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryCreateApiDocumentAccessGroupResponse, + ) + + async def delete_api_document_access_group( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RegistryDeleteApiDocumentAccessGroupResponse: + """Remove an access group from an API document.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._delete( + path_template("/v1/apis/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=await async_maybe_transform( + {"access_group_slug": access_group_slug}, + registry_delete_api_document_access_group_params.RegistryDeleteApiDocumentAccessGroupParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RegistryDeleteApiDocumentAccessGroupResponse, + ) + + +class RegistryResourceWithRawResponse: + def __init__(self, registry: RegistryResource) -> None: + self._registry = registry + + self.list_all_api_documents = to_raw_response_wrapper( + registry.list_all_api_documents, + ) + self.list_api_documents = to_raw_response_wrapper( + registry.list_api_documents, + ) + self.create_api_document = to_raw_response_wrapper( + registry.create_api_document, + ) + self.update_api_document = to_raw_response_wrapper( + registry.update_api_document, + ) + self.delete_api_document = to_raw_response_wrapper( + registry.delete_api_document, + ) + self.retrieve_api_document_version = to_raw_response_wrapper( + registry.retrieve_api_document_version, + ) + self.update_api_document_version = to_raw_response_wrapper( + registry.update_api_document_version, + ) + self.delete_api_document_version = to_raw_response_wrapper( + registry.delete_api_document_version, + ) + self.list_api_document_version_metadata = to_raw_response_wrapper( + registry.list_api_document_version_metadata, + ) + self.create_api_document_version = to_raw_response_wrapper( + registry.create_api_document_version, + ) + self.create_api_document_access_group = to_raw_response_wrapper( + registry.create_api_document_access_group, + ) + self.delete_api_document_access_group = to_raw_response_wrapper( + registry.delete_api_document_access_group, + ) + + +class AsyncRegistryResourceWithRawResponse: + def __init__(self, registry: AsyncRegistryResource) -> None: + self._registry = registry + + self.list_all_api_documents = async_to_raw_response_wrapper( + registry.list_all_api_documents, + ) + self.list_api_documents = async_to_raw_response_wrapper( + registry.list_api_documents, + ) + self.create_api_document = async_to_raw_response_wrapper( + registry.create_api_document, + ) + self.update_api_document = async_to_raw_response_wrapper( + registry.update_api_document, + ) + self.delete_api_document = async_to_raw_response_wrapper( + registry.delete_api_document, + ) + self.retrieve_api_document_version = async_to_raw_response_wrapper( + registry.retrieve_api_document_version, + ) + self.update_api_document_version = async_to_raw_response_wrapper( + registry.update_api_document_version, + ) + self.delete_api_document_version = async_to_raw_response_wrapper( + registry.delete_api_document_version, + ) + self.list_api_document_version_metadata = async_to_raw_response_wrapper( + registry.list_api_document_version_metadata, + ) + self.create_api_document_version = async_to_raw_response_wrapper( + registry.create_api_document_version, + ) + self.create_api_document_access_group = async_to_raw_response_wrapper( + registry.create_api_document_access_group, + ) + self.delete_api_document_access_group = async_to_raw_response_wrapper( + registry.delete_api_document_access_group, + ) + + +class RegistryResourceWithStreamingResponse: + def __init__(self, registry: RegistryResource) -> None: + self._registry = registry + + self.list_all_api_documents = to_streamed_response_wrapper( + registry.list_all_api_documents, + ) + self.list_api_documents = to_streamed_response_wrapper( + registry.list_api_documents, + ) + self.create_api_document = to_streamed_response_wrapper( + registry.create_api_document, + ) + self.update_api_document = to_streamed_response_wrapper( + registry.update_api_document, + ) + self.delete_api_document = to_streamed_response_wrapper( + registry.delete_api_document, + ) + self.retrieve_api_document_version = to_streamed_response_wrapper( + registry.retrieve_api_document_version, + ) + self.update_api_document_version = to_streamed_response_wrapper( + registry.update_api_document_version, + ) + self.delete_api_document_version = to_streamed_response_wrapper( + registry.delete_api_document_version, + ) + self.list_api_document_version_metadata = to_streamed_response_wrapper( + registry.list_api_document_version_metadata, + ) + self.create_api_document_version = to_streamed_response_wrapper( + registry.create_api_document_version, + ) + self.create_api_document_access_group = to_streamed_response_wrapper( + registry.create_api_document_access_group, + ) + self.delete_api_document_access_group = to_streamed_response_wrapper( + registry.delete_api_document_access_group, + ) + + +class AsyncRegistryResourceWithStreamingResponse: + def __init__(self, registry: AsyncRegistryResource) -> None: + self._registry = registry + + self.list_all_api_documents = async_to_streamed_response_wrapper( + registry.list_all_api_documents, + ) + self.list_api_documents = async_to_streamed_response_wrapper( + registry.list_api_documents, + ) + self.create_api_document = async_to_streamed_response_wrapper( + registry.create_api_document, + ) + self.update_api_document = async_to_streamed_response_wrapper( + registry.update_api_document, + ) + self.delete_api_document = async_to_streamed_response_wrapper( + registry.delete_api_document, + ) + self.retrieve_api_document_version = async_to_streamed_response_wrapper( + registry.retrieve_api_document_version, + ) + self.update_api_document_version = async_to_streamed_response_wrapper( + registry.update_api_document_version, + ) + self.delete_api_document_version = async_to_streamed_response_wrapper( + registry.delete_api_document_version, + ) + self.list_api_document_version_metadata = async_to_streamed_response_wrapper( + registry.list_api_document_version_metadata, + ) + self.create_api_document_version = async_to_streamed_response_wrapper( + registry.create_api_document_version, + ) + self.create_api_document_access_group = async_to_streamed_response_wrapper( + registry.create_api_document_access_group, + ) + self.delete_api_document_access_group = async_to_streamed_response_wrapper( + registry.delete_api_document_access_group, + ) diff --git a/src/resources/rules.py b/src/resources/rules.py new file mode 100644 index 0000000..fa20ed1 --- /dev/null +++ b/src/resources/rules.py @@ -0,0 +1,552 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.rule_list_rulesets_response import RuleListRulesetsResponse +from ..types.rule_create_ruleset_response import RuleCreateRulesetResponse +from ..types import rule_create_ruleset_params +from ..types.rule_update_ruleset_response import RuleUpdateRulesetResponse +from ..types import rule_update_ruleset_params +from ..types.rule_delete_ruleset_response import RuleDeleteRulesetResponse +from ..types.rule_retrieve_ruleset_document_response import RuleRetrieveRulesetDocumentResponse +from ..types.rule_create_ruleset_access_group_response import RuleCreateRulesetAccessGroupResponse +from ..types import rule_create_ruleset_access_group_params +from ..types.rule_delete_ruleset_access_group_response import RuleDeleteRulesetAccessGroupResponse +from ..types import rule_delete_ruleset_access_group_params + +__all__ = ["RulesResource", "AsyncRulesResource"] + + +class RulesResource(SyncAPIResource): + + @cached_property + def with_raw_response(self) -> RulesResourceWithRawResponse: + return RulesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RulesResourceWithStreamingResponse: + return RulesResourceWithStreamingResponse(self) + + def list_rulesets( + self, + namespace: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleListRulesetsResponse: + """List all rulesets in a namespace.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return self._get( + path_template("/v1/rulesets/{namespace}", **{"namespace": namespace}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RuleListRulesetsResponse, + ) + + def create_ruleset( + self, + namespace: str, + *, + title: str, + description: str | Omit = omit, + slug: str, + is_private: bool | Omit = omit, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RuleCreateRulesetResponse: + """Create a rule in a namespace.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return self._post( + path_template("/v1/rulesets/{namespace}", **{"namespace": namespace}), + body=maybe_transform( + { + "title": title, + "description": description, + "slug": slug, + "is_private": is_private, + "document": document, + }, + rule_create_ruleset_params.RuleCreateRulesetParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RuleCreateRulesetResponse, + ) + + def update_ruleset( + self, + namespace: str, + slug: str, + *, + title: str | Omit = omit, + description: str | Omit = omit, + is_private: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RuleUpdateRulesetResponse: + """Update rule metadata by slug.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._patch( + path_template("/v1/rulesets/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + body=maybe_transform( + { + "title": title, + "description": description, + "is_private": is_private, + }, + rule_update_ruleset_params.RuleUpdateRulesetParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RuleUpdateRulesetResponse, + ) + + def delete_ruleset( + self, + namespace: str, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RuleDeleteRulesetResponse: + """Delete a rule by slug.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._delete( + path_template("/v1/rulesets/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RuleDeleteRulesetResponse, + ) + + def retrieve_ruleset_document( + self, + namespace: str, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleRetrieveRulesetDocumentResponse: + """Get a rule document by slug.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._get( + path_template("/v1/rulesets/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RuleRetrieveRulesetDocumentResponse, + ) + + def create_ruleset_access_group( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RuleCreateRulesetAccessGroupResponse: + """Grant an access group to a rule.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._post( + path_template("/v1/rulesets/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=maybe_transform( + {"access_group_slug": access_group_slug}, + rule_create_ruleset_access_group_params.RuleCreateRulesetAccessGroupParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RuleCreateRulesetAccessGroupResponse, + ) + + def delete_ruleset_access_group( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RuleDeleteRulesetAccessGroupResponse: + """Remove an access group from a rule.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._delete( + path_template("/v1/rulesets/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=maybe_transform( + {"access_group_slug": access_group_slug}, + rule_delete_ruleset_access_group_params.RuleDeleteRulesetAccessGroupParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RuleDeleteRulesetAccessGroupResponse, + ) + + +class AsyncRulesResource(AsyncAPIResource): + + @cached_property + def with_raw_response(self) -> AsyncRulesResourceWithRawResponse: + return AsyncRulesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRulesResourceWithStreamingResponse: + return AsyncRulesResourceWithStreamingResponse(self) + + async def list_rulesets( + self, + namespace: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleListRulesetsResponse: + """List all rulesets in a namespace.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return await self._get( + path_template("/v1/rulesets/{namespace}", **{"namespace": namespace}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RuleListRulesetsResponse, + ) + + async def create_ruleset( + self, + namespace: str, + *, + title: str, + description: str | Omit = omit, + slug: str, + is_private: bool | Omit = omit, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RuleCreateRulesetResponse: + """Create a rule in a namespace.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return await self._post( + path_template("/v1/rulesets/{namespace}", **{"namespace": namespace}), + body=await async_maybe_transform( + { + "title": title, + "description": description, + "slug": slug, + "is_private": is_private, + "document": document, + }, + rule_create_ruleset_params.RuleCreateRulesetParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RuleCreateRulesetResponse, + ) + + async def update_ruleset( + self, + namespace: str, + slug: str, + *, + title: str | Omit = omit, + description: str | Omit = omit, + is_private: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RuleUpdateRulesetResponse: + """Update rule metadata by slug.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._patch( + path_template("/v1/rulesets/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + body=await async_maybe_transform( + { + "title": title, + "description": description, + "is_private": is_private, + }, + rule_update_ruleset_params.RuleUpdateRulesetParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RuleUpdateRulesetResponse, + ) + + async def delete_ruleset( + self, + namespace: str, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RuleDeleteRulesetResponse: + """Delete a rule by slug.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._delete( + path_template("/v1/rulesets/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RuleDeleteRulesetResponse, + ) + + async def retrieve_ruleset_document( + self, + namespace: str, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> RuleRetrieveRulesetDocumentResponse: + """Get a rule document by slug.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._get( + path_template("/v1/rulesets/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=RuleRetrieveRulesetDocumentResponse, + ) + + async def create_ruleset_access_group( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RuleCreateRulesetAccessGroupResponse: + """Grant an access group to a rule.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._post( + path_template("/v1/rulesets/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=await async_maybe_transform( + {"access_group_slug": access_group_slug}, + rule_create_ruleset_access_group_params.RuleCreateRulesetAccessGroupParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RuleCreateRulesetAccessGroupResponse, + ) + + async def delete_ruleset_access_group( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RuleDeleteRulesetAccessGroupResponse: + """Remove an access group from a rule.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._delete( + path_template("/v1/rulesets/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=await async_maybe_transform( + {"access_group_slug": access_group_slug}, + rule_delete_ruleset_access_group_params.RuleDeleteRulesetAccessGroupParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=RuleDeleteRulesetAccessGroupResponse, + ) + + +class RulesResourceWithRawResponse: + def __init__(self, rules: RulesResource) -> None: + self._rules = rules + + self.list_rulesets = to_raw_response_wrapper( + rules.list_rulesets, + ) + self.create_ruleset = to_raw_response_wrapper( + rules.create_ruleset, + ) + self.update_ruleset = to_raw_response_wrapper( + rules.update_ruleset, + ) + self.delete_ruleset = to_raw_response_wrapper( + rules.delete_ruleset, + ) + self.retrieve_ruleset_document = to_raw_response_wrapper( + rules.retrieve_ruleset_document, + ) + self.create_ruleset_access_group = to_raw_response_wrapper( + rules.create_ruleset_access_group, + ) + self.delete_ruleset_access_group = to_raw_response_wrapper( + rules.delete_ruleset_access_group, + ) + + +class AsyncRulesResourceWithRawResponse: + def __init__(self, rules: AsyncRulesResource) -> None: + self._rules = rules + + self.list_rulesets = async_to_raw_response_wrapper( + rules.list_rulesets, + ) + self.create_ruleset = async_to_raw_response_wrapper( + rules.create_ruleset, + ) + self.update_ruleset = async_to_raw_response_wrapper( + rules.update_ruleset, + ) + self.delete_ruleset = async_to_raw_response_wrapper( + rules.delete_ruleset, + ) + self.retrieve_ruleset_document = async_to_raw_response_wrapper( + rules.retrieve_ruleset_document, + ) + self.create_ruleset_access_group = async_to_raw_response_wrapper( + rules.create_ruleset_access_group, + ) + self.delete_ruleset_access_group = async_to_raw_response_wrapper( + rules.delete_ruleset_access_group, + ) + + +class RulesResourceWithStreamingResponse: + def __init__(self, rules: RulesResource) -> None: + self._rules = rules + + self.list_rulesets = to_streamed_response_wrapper( + rules.list_rulesets, + ) + self.create_ruleset = to_streamed_response_wrapper( + rules.create_ruleset, + ) + self.update_ruleset = to_streamed_response_wrapper( + rules.update_ruleset, + ) + self.delete_ruleset = to_streamed_response_wrapper( + rules.delete_ruleset, + ) + self.retrieve_ruleset_document = to_streamed_response_wrapper( + rules.retrieve_ruleset_document, + ) + self.create_ruleset_access_group = to_streamed_response_wrapper( + rules.create_ruleset_access_group, + ) + self.delete_ruleset_access_group = to_streamed_response_wrapper( + rules.delete_ruleset_access_group, + ) + + +class AsyncRulesResourceWithStreamingResponse: + def __init__(self, rules: AsyncRulesResource) -> None: + self._rules = rules + + self.list_rulesets = async_to_streamed_response_wrapper( + rules.list_rulesets, + ) + self.create_ruleset = async_to_streamed_response_wrapper( + rules.create_ruleset, + ) + self.update_ruleset = async_to_streamed_response_wrapper( + rules.update_ruleset, + ) + self.delete_ruleset = async_to_streamed_response_wrapper( + rules.delete_ruleset, + ) + self.retrieve_ruleset_document = async_to_streamed_response_wrapper( + rules.retrieve_ruleset_document, + ) + self.create_ruleset_access_group = async_to_streamed_response_wrapper( + rules.create_ruleset_access_group, + ) + self.delete_ruleset_access_group = async_to_streamed_response_wrapper( + rules.delete_ruleset_access_group, + ) diff --git a/src/resources/scalar_docs.py b/src/resources/scalar_docs.py new file mode 100644 index 0000000..ef55373 --- /dev/null +++ b/src/resources/scalar_docs.py @@ -0,0 +1,249 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from typing import List + +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.scalar_doc_list_guides_response import ScalarDocListGuidesResponse +from ..types.scalar_doc_create_guide_response import ScalarDocCreateGuideResponse +from ..types import scalar_doc_create_guide_params +from ..types.scalar_doc_publish_guide_response import ScalarDocPublishGuideResponse + +__all__ = ["ScalarDocsResource", "AsyncScalarDocsResource"] + + +class ScalarDocsResource(SyncAPIResource): + + @cached_property + def with_raw_response(self) -> ScalarDocsResourceWithRawResponse: + return ScalarDocsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ScalarDocsResourceWithStreamingResponse: + return ScalarDocsResourceWithStreamingResponse(self) + + def list_guides( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ScalarDocListGuidesResponse: + """List all guide projects.""" + return self._get( + "/v1/guides", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=ScalarDocListGuidesResponse, + ) + + def create_guide( + self, + *, + name: str, + slug: str | Omit = omit, + is_private: bool, + allowed_users: List[str], + allowed_domains: List[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ScalarDocCreateGuideResponse: + """Create a guide project.""" + return self._post( + "/v1/guides", + body=maybe_transform( + { + "name": name, + "slug": slug, + "is_private": is_private, + "allowed_users": allowed_users, + "allowed_domains": allowed_domains, + }, + scalar_doc_create_guide_params.ScalarDocCreateGuideParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ScalarDocCreateGuideResponse, + ) + + def publish_guide( + self, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ScalarDocPublishGuideResponse: + """Start a new publish process.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._post( + path_template("/v1/guides/{slug}/publish", **{"slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ScalarDocPublishGuideResponse, + ) + + +class AsyncScalarDocsResource(AsyncAPIResource): + + @cached_property + def with_raw_response(self) -> AsyncScalarDocsResourceWithRawResponse: + return AsyncScalarDocsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncScalarDocsResourceWithStreamingResponse: + return AsyncScalarDocsResourceWithStreamingResponse(self) + + async def list_guides( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ScalarDocListGuidesResponse: + """List all guide projects.""" + return await self._get( + "/v1/guides", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=ScalarDocListGuidesResponse, + ) + + async def create_guide( + self, + *, + name: str, + slug: str | Omit = omit, + is_private: bool, + allowed_users: List[str], + allowed_domains: List[str], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ScalarDocCreateGuideResponse: + """Create a guide project.""" + return await self._post( + "/v1/guides", + body=await async_maybe_transform( + { + "name": name, + "slug": slug, + "is_private": is_private, + "allowed_users": allowed_users, + "allowed_domains": allowed_domains, + }, + scalar_doc_create_guide_params.ScalarDocCreateGuideParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ScalarDocCreateGuideResponse, + ) + + async def publish_guide( + self, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ScalarDocPublishGuideResponse: + """Start a new publish process.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._post( + path_template("/v1/guides/{slug}/publish", **{"slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ScalarDocPublishGuideResponse, + ) + + +class ScalarDocsResourceWithRawResponse: + def __init__(self, scalar_docs: ScalarDocsResource) -> None: + self._scalar_docs = scalar_docs + + self.list_guides = to_raw_response_wrapper( + scalar_docs.list_guides, + ) + self.create_guide = to_raw_response_wrapper( + scalar_docs.create_guide, + ) + self.publish_guide = to_raw_response_wrapper( + scalar_docs.publish_guide, + ) + + +class AsyncScalarDocsResourceWithRawResponse: + def __init__(self, scalar_docs: AsyncScalarDocsResource) -> None: + self._scalar_docs = scalar_docs + + self.list_guides = async_to_raw_response_wrapper( + scalar_docs.list_guides, + ) + self.create_guide = async_to_raw_response_wrapper( + scalar_docs.create_guide, + ) + self.publish_guide = async_to_raw_response_wrapper( + scalar_docs.publish_guide, + ) + + +class ScalarDocsResourceWithStreamingResponse: + def __init__(self, scalar_docs: ScalarDocsResource) -> None: + self._scalar_docs = scalar_docs + + self.list_guides = to_streamed_response_wrapper( + scalar_docs.list_guides, + ) + self.create_guide = to_streamed_response_wrapper( + scalar_docs.create_guide, + ) + self.publish_guide = to_streamed_response_wrapper( + scalar_docs.publish_guide, + ) + + +class AsyncScalarDocsResourceWithStreamingResponse: + def __init__(self, scalar_docs: AsyncScalarDocsResource) -> None: + self._scalar_docs = scalar_docs + + self.list_guides = async_to_streamed_response_wrapper( + scalar_docs.list_guides, + ) + self.create_guide = async_to_streamed_response_wrapper( + scalar_docs.create_guide, + ) + self.publish_guide = async_to_streamed_response_wrapper( + scalar_docs.publish_guide, + ) diff --git a/src/resources/schemas/__init__.py b/src/resources/schemas/__init__.py new file mode 100644 index 0000000..bee0515 --- /dev/null +++ b/src/resources/schemas/__init__.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from .schemas import ( + SchemasResource, + AsyncSchemasResource, + SchemasResourceWithRawResponse, + AsyncSchemasResourceWithRawResponse, + SchemasResourceWithStreamingResponse, + AsyncSchemasResourceWithStreamingResponse, +) + +__all__ = [ + "SchemasResource", + "AsyncSchemasResource", + "SchemasResourceWithRawResponse", + "AsyncSchemasResourceWithRawResponse", + "SchemasResourceWithStreamingResponse", + "AsyncSchemasResourceWithStreamingResponse", +] diff --git a/src/resources/schemas/access_group.py b/src/resources/schemas/access_group.py new file mode 100644 index 0000000..4bd077e --- /dev/null +++ b/src/resources/schemas/access_group.py @@ -0,0 +1,209 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.schemas.access_group_create_schema_response import AccessGroupCreateSchemaResponse +from ...types.schemas import access_group_create_schema_params +from ...types.schemas.access_group_delete_schema_response import AccessGroupDeleteSchemaResponse +from ...types.schemas import access_group_delete_schema_params + +__all__ = ["AccessGroupResource", "AsyncAccessGroupResource"] + + +class AccessGroupResource(SyncAPIResource): + + @cached_property + def with_raw_response(self) -> AccessGroupResourceWithRawResponse: + return AccessGroupResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AccessGroupResourceWithStreamingResponse: + return AccessGroupResourceWithStreamingResponse(self) + + def create_schema( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> AccessGroupCreateSchemaResponse: + """Add an access group to a schema.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._post( + path_template("/v1/schemas/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=maybe_transform( + {"access_group_slug": access_group_slug}, + access_group_create_schema_params.AccessGroupCreateSchemaParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=AccessGroupCreateSchemaResponse, + ) + + def delete_schema( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> AccessGroupDeleteSchemaResponse: + """Remove an access group from a schema.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._delete( + path_template("/v1/schemas/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=maybe_transform( + {"access_group_slug": access_group_slug}, + access_group_delete_schema_params.AccessGroupDeleteSchemaParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=AccessGroupDeleteSchemaResponse, + ) + + +class AsyncAccessGroupResource(AsyncAPIResource): + + @cached_property + def with_raw_response(self) -> AsyncAccessGroupResourceWithRawResponse: + return AsyncAccessGroupResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAccessGroupResourceWithStreamingResponse: + return AsyncAccessGroupResourceWithStreamingResponse(self) + + async def create_schema( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> AccessGroupCreateSchemaResponse: + """Add an access group to a schema.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._post( + path_template("/v1/schemas/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=await async_maybe_transform( + {"access_group_slug": access_group_slug}, + access_group_create_schema_params.AccessGroupCreateSchemaParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=AccessGroupCreateSchemaResponse, + ) + + async def delete_schema( + self, + namespace: str, + slug: str, + *, + access_group_slug: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> AccessGroupDeleteSchemaResponse: + """Remove an access group from a schema.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._delete( + path_template("/v1/schemas/{namespace}/{slug}/access-group", **{"namespace": namespace, "slug": slug}), + body=await async_maybe_transform( + {"access_group_slug": access_group_slug}, + access_group_delete_schema_params.AccessGroupDeleteSchemaParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=AccessGroupDeleteSchemaResponse, + ) + + +class AccessGroupResourceWithRawResponse: + def __init__(self, access_group: AccessGroupResource) -> None: + self._access_group = access_group + + self.create_schema = to_raw_response_wrapper( + access_group.create_schema, + ) + self.delete_schema = to_raw_response_wrapper( + access_group.delete_schema, + ) + + +class AsyncAccessGroupResourceWithRawResponse: + def __init__(self, access_group: AsyncAccessGroupResource) -> None: + self._access_group = access_group + + self.create_schema = async_to_raw_response_wrapper( + access_group.create_schema, + ) + self.delete_schema = async_to_raw_response_wrapper( + access_group.delete_schema, + ) + + +class AccessGroupResourceWithStreamingResponse: + def __init__(self, access_group: AccessGroupResource) -> None: + self._access_group = access_group + + self.create_schema = to_streamed_response_wrapper( + access_group.create_schema, + ) + self.delete_schema = to_streamed_response_wrapper( + access_group.delete_schema, + ) + + +class AsyncAccessGroupResourceWithStreamingResponse: + def __init__(self, access_group: AsyncAccessGroupResource) -> None: + self._access_group = access_group + + self.create_schema = async_to_streamed_response_wrapper( + access_group.create_schema, + ) + self.delete_schema = async_to_streamed_response_wrapper( + access_group.delete_schema, + ) diff --git a/src/resources/schemas/schemas.py b/src/resources/schemas/schemas.py new file mode 100644 index 0000000..ad4b289 --- /dev/null +++ b/src/resources/schemas/schemas.py @@ -0,0 +1,417 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from .version import ( + VersionResource, + AsyncVersionResource, +) +from .access_group import ( + AccessGroupResource, + AsyncAccessGroupResource, +) +from ...types.schema_list_response import SchemaListResponse +from ...types.schema_create_response import SchemaCreateResponse +from ...types import schema_create_params +from ...types.schema_update_response import SchemaUpdateResponse +from ...types import schema_update_params +from ...types.schema_delete_response import SchemaDeleteResponse + +__all__ = ["SchemasResource", "AsyncSchemasResource"] + + +class SchemasResource(SyncAPIResource): + + @cached_property + def version(self) -> VersionResource: + return VersionResource(self._client) + + @cached_property + def access_group(self) -> AccessGroupResource: + return AccessGroupResource(self._client) + + @cached_property + def with_raw_response(self) -> SchemasResourceWithRawResponse: + return SchemasResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SchemasResourceWithStreamingResponse: + return SchemasResourceWithStreamingResponse(self) + + def list( + self, + namespace: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SchemaListResponse: + """List schemas in a namespace.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return self._get( + path_template("/v1/schemas/{namespace}", **{"namespace": namespace}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=SchemaListResponse, + ) + + def create( + self, + namespace: str, + *, + title: str, + description: str | Omit = omit, + version: str, + slug: str, + is_private: bool | Omit = omit, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> SchemaCreateResponse: + """Create a schema in a namespace.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return self._post( + path_template("/v1/schemas/{namespace}", **{"namespace": namespace}), + body=maybe_transform( + { + "title": title, + "description": description, + "version": version, + "slug": slug, + "is_private": is_private, + "document": document, + }, + schema_create_params.SchemaCreateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=SchemaCreateResponse, + ) + + def update( + self, + namespace: str, + slug: str, + *, + title: str | Omit = omit, + description: str | Omit = omit, + is_private: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> SchemaUpdateResponse: + """Update schema metadata.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._patch( + path_template("/v1/schemas/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + body=maybe_transform( + { + "title": title, + "description": description, + "is_private": is_private, + }, + schema_update_params.SchemaUpdateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=SchemaUpdateResponse, + ) + + def delete( + self, + namespace: str, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> SchemaDeleteResponse: + """Delete a schema and all related versions.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._delete( + path_template("/v1/schemas/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=SchemaDeleteResponse, + ) + + +class AsyncSchemasResource(AsyncAPIResource): + + @cached_property + def version(self) -> AsyncVersionResource: + return AsyncVersionResource(self._client) + + @cached_property + def access_group(self) -> AsyncAccessGroupResource: + return AsyncAccessGroupResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncSchemasResourceWithRawResponse: + return AsyncSchemasResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSchemasResourceWithStreamingResponse: + return AsyncSchemasResourceWithStreamingResponse(self) + + async def list( + self, + namespace: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SchemaListResponse: + """List schemas in a namespace.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return await self._get( + path_template("/v1/schemas/{namespace}", **{"namespace": namespace}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=SchemaListResponse, + ) + + async def create( + self, + namespace: str, + *, + title: str, + description: str | Omit = omit, + version: str, + slug: str, + is_private: bool | Omit = omit, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> SchemaCreateResponse: + """Create a schema in a namespace.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + return await self._post( + path_template("/v1/schemas/{namespace}", **{"namespace": namespace}), + body=await async_maybe_transform( + { + "title": title, + "description": description, + "version": version, + "slug": slug, + "is_private": is_private, + "document": document, + }, + schema_create_params.SchemaCreateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=SchemaCreateResponse, + ) + + async def update( + self, + namespace: str, + slug: str, + *, + title: str | Omit = omit, + description: str | Omit = omit, + is_private: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> SchemaUpdateResponse: + """Update schema metadata.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._patch( + path_template("/v1/schemas/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + body=await async_maybe_transform( + { + "title": title, + "description": description, + "is_private": is_private, + }, + schema_update_params.SchemaUpdateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=SchemaUpdateResponse, + ) + + async def delete( + self, + namespace: str, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> SchemaDeleteResponse: + """Delete a schema and all related versions.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._delete( + path_template("/v1/schemas/{namespace}/{slug}", **{"namespace": namespace, "slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=SchemaDeleteResponse, + ) + + +class SchemasResourceWithRawResponse: + def __init__(self, schemas: SchemasResource) -> None: + self._schemas = schemas + + self.list = to_raw_response_wrapper( + schemas.list, + ) + self.create = to_raw_response_wrapper( + schemas.create, + ) + self.update = to_raw_response_wrapper( + schemas.update, + ) + self.delete = to_raw_response_wrapper( + schemas.delete, + ) + + @cached_property + def version(self) -> "VersionResourceWithRawResponse": + from .version import VersionResourceWithRawResponse + return VersionResourceWithRawResponse(self._schemas.version) + + @cached_property + def access_group(self) -> "AccessGroupResourceWithRawResponse": + from .access_group import AccessGroupResourceWithRawResponse + return AccessGroupResourceWithRawResponse(self._schemas.access_group) + + +class AsyncSchemasResourceWithRawResponse: + def __init__(self, schemas: AsyncSchemasResource) -> None: + self._schemas = schemas + + self.list = async_to_raw_response_wrapper( + schemas.list, + ) + self.create = async_to_raw_response_wrapper( + schemas.create, + ) + self.update = async_to_raw_response_wrapper( + schemas.update, + ) + self.delete = async_to_raw_response_wrapper( + schemas.delete, + ) + + @cached_property + def version(self) -> "AsyncVersionResourceWithRawResponse": + from .version import AsyncVersionResourceWithRawResponse + return AsyncVersionResourceWithRawResponse(self._schemas.version) + + @cached_property + def access_group(self) -> "AsyncAccessGroupResourceWithRawResponse": + from .access_group import AsyncAccessGroupResourceWithRawResponse + return AsyncAccessGroupResourceWithRawResponse(self._schemas.access_group) + + +class SchemasResourceWithStreamingResponse: + def __init__(self, schemas: SchemasResource) -> None: + self._schemas = schemas + + self.list = to_streamed_response_wrapper( + schemas.list, + ) + self.create = to_streamed_response_wrapper( + schemas.create, + ) + self.update = to_streamed_response_wrapper( + schemas.update, + ) + self.delete = to_streamed_response_wrapper( + schemas.delete, + ) + + @cached_property + def version(self) -> "VersionResourceWithStreamingResponse": + from .version import VersionResourceWithStreamingResponse + return VersionResourceWithStreamingResponse(self._schemas.version) + + @cached_property + def access_group(self) -> "AccessGroupResourceWithStreamingResponse": + from .access_group import AccessGroupResourceWithStreamingResponse + return AccessGroupResourceWithStreamingResponse(self._schemas.access_group) + + +class AsyncSchemasResourceWithStreamingResponse: + def __init__(self, schemas: AsyncSchemasResource) -> None: + self._schemas = schemas + + self.list = async_to_streamed_response_wrapper( + schemas.list, + ) + self.create = async_to_streamed_response_wrapper( + schemas.create, + ) + self.update = async_to_streamed_response_wrapper( + schemas.update, + ) + self.delete = async_to_streamed_response_wrapper( + schemas.delete, + ) + + @cached_property + def version(self) -> "AsyncVersionResourceWithStreamingResponse": + from .version import AsyncVersionResourceWithStreamingResponse + return AsyncVersionResourceWithStreamingResponse(self._schemas.version) + + @cached_property + def access_group(self) -> "AsyncAccessGroupResourceWithStreamingResponse": + from .access_group import AsyncAccessGroupResourceWithStreamingResponse + return AsyncAccessGroupResourceWithStreamingResponse(self._schemas.access_group) diff --git a/src/resources/schemas/version.py b/src/resources/schemas/version.py new file mode 100644 index 0000000..41a9c0c --- /dev/null +++ b/src/resources/schemas/version.py @@ -0,0 +1,277 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.schemas.version_retrieve_schema_response import VersionRetrieveSchemaResponse +from ...types.schemas.version_delete_schema_response import VersionDeleteSchemaResponse +from ...types.schemas.version_create_schema_response import VersionCreateSchemaResponse +from ...types.schemas import version_create_schema_params + +__all__ = ["VersionResource", "AsyncVersionResource"] + + +class VersionResource(SyncAPIResource): + + @cached_property + def with_raw_response(self) -> VersionResourceWithRawResponse: + return VersionResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VersionResourceWithStreamingResponse: + return VersionResourceWithStreamingResponse(self) + + def retrieve_schema( + self, + namespace: str, + slug: str, + semver: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> VersionRetrieveSchemaResponse: + """Get a specific schema version document.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return self._get( + path_template("/v1/schemas/{namespace}/{slug}/version/{semver}", **{"namespace": namespace, "slug": slug, "semver": semver}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=VersionRetrieveSchemaResponse, + ) + + def delete_schema( + self, + namespace: str, + slug: str, + semver: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> VersionDeleteSchemaResponse: + """Delete a schema version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return self._delete( + path_template("/v1/schemas/{namespace}/{slug}/version/{semver}", **{"namespace": namespace, "slug": slug, "semver": semver}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=VersionDeleteSchemaResponse, + ) + + def create_schema( + self, + namespace: str, + slug: str, + *, + version: str, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> VersionCreateSchemaResponse: + """Create a schema version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._post( + path_template("/v1/schemas/{namespace}/{slug}/version", **{"namespace": namespace, "slug": slug}), + body=maybe_transform( + { + "version": version, + "document": document, + }, + version_create_schema_params.VersionCreateSchemaParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=VersionCreateSchemaResponse, + ) + + +class AsyncVersionResource(AsyncAPIResource): + + @cached_property + def with_raw_response(self) -> AsyncVersionResourceWithRawResponse: + return AsyncVersionResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVersionResourceWithStreamingResponse: + return AsyncVersionResourceWithStreamingResponse(self) + + async def retrieve_schema( + self, + namespace: str, + slug: str, + semver: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> VersionRetrieveSchemaResponse: + """Get a specific schema version document.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return await self._get( + path_template("/v1/schemas/{namespace}/{slug}/version/{semver}", **{"namespace": namespace, "slug": slug, "semver": semver}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=VersionRetrieveSchemaResponse, + ) + + async def delete_schema( + self, + namespace: str, + slug: str, + semver: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> VersionDeleteSchemaResponse: + """Delete a schema version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + if not semver: + raise ValueError(f"Expected a non-empty value for `semver` but received {semver!r}") + return await self._delete( + path_template("/v1/schemas/{namespace}/{slug}/version/{semver}", **{"namespace": namespace, "slug": slug, "semver": semver}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=VersionDeleteSchemaResponse, + ) + + async def create_schema( + self, + namespace: str, + slug: str, + *, + version: str, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> VersionCreateSchemaResponse: + """Create a schema version.""" + if not namespace: + raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._post( + path_template("/v1/schemas/{namespace}/{slug}/version", **{"namespace": namespace, "slug": slug}), + body=await async_maybe_transform( + { + "version": version, + "document": document, + }, + version_create_schema_params.VersionCreateSchemaParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=VersionCreateSchemaResponse, + ) + + +class VersionResourceWithRawResponse: + def __init__(self, version: VersionResource) -> None: + self._version = version + + self.retrieve_schema = to_raw_response_wrapper( + version.retrieve_schema, + ) + self.delete_schema = to_raw_response_wrapper( + version.delete_schema, + ) + self.create_schema = to_raw_response_wrapper( + version.create_schema, + ) + + +class AsyncVersionResourceWithRawResponse: + def __init__(self, version: AsyncVersionResource) -> None: + self._version = version + + self.retrieve_schema = async_to_raw_response_wrapper( + version.retrieve_schema, + ) + self.delete_schema = async_to_raw_response_wrapper( + version.delete_schema, + ) + self.create_schema = async_to_raw_response_wrapper( + version.create_schema, + ) + + +class VersionResourceWithStreamingResponse: + def __init__(self, version: VersionResource) -> None: + self._version = version + + self.retrieve_schema = to_streamed_response_wrapper( + version.retrieve_schema, + ) + self.delete_schema = to_streamed_response_wrapper( + version.delete_schema, + ) + self.create_schema = to_streamed_response_wrapper( + version.create_schema, + ) + + +class AsyncVersionResourceWithStreamingResponse: + def __init__(self, version: AsyncVersionResource) -> None: + self._version = version + + self.retrieve_schema = async_to_streamed_response_wrapper( + version.retrieve_schema, + ) + self.delete_schema = async_to_streamed_response_wrapper( + version.delete_schema, + ) + self.create_schema = async_to_streamed_response_wrapper( + version.create_schema, + ) diff --git a/src/resources/teams.py b/src/resources/teams.py new file mode 100644 index 0000000..4b1a3e8 --- /dev/null +++ b/src/resources/teams.py @@ -0,0 +1,112 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.team_list_response import TeamListResponse + +__all__ = ["TeamsResource", "AsyncTeamsResource"] + + +class TeamsResource(SyncAPIResource): + + @cached_property + def with_raw_response(self) -> TeamsResourceWithRawResponse: + return TeamsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> TeamsResourceWithStreamingResponse: + return TeamsResourceWithStreamingResponse(self) + + def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TeamListResponse: + """List all available teams""" + return self._get( + "/v1/teams", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=TeamListResponse, + ) + + +class AsyncTeamsResource(AsyncAPIResource): + + @cached_property + def with_raw_response(self) -> AsyncTeamsResourceWithRawResponse: + return AsyncTeamsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncTeamsResourceWithStreamingResponse: + return AsyncTeamsResourceWithStreamingResponse(self) + + async def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TeamListResponse: + """List all available teams""" + return await self._get( + "/v1/teams", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=TeamListResponse, + ) + + +class TeamsResourceWithRawResponse: + def __init__(self, teams: TeamsResource) -> None: + self._teams = teams + + self.list = to_raw_response_wrapper( + teams.list, + ) + + +class AsyncTeamsResourceWithRawResponse: + def __init__(self, teams: AsyncTeamsResource) -> None: + self._teams = teams + + self.list = async_to_raw_response_wrapper( + teams.list, + ) + + +class TeamsResourceWithStreamingResponse: + def __init__(self, teams: TeamsResource) -> None: + self._teams = teams + + self.list = to_streamed_response_wrapper( + teams.list, + ) + + +class AsyncTeamsResourceWithStreamingResponse: + def __init__(self, teams: AsyncTeamsResource) -> None: + self._teams = teams + + self.list = async_to_streamed_response_wrapper( + teams.list, + ) diff --git a/src/resources/themes.py b/src/resources/themes.py new file mode 100644 index 0000000..3cefa68 --- /dev/null +++ b/src/resources/themes.py @@ -0,0 +1,436 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.theme_list_response import ThemeListResponse +from ..types.theme_create_response import ThemeCreateResponse +from ..types import theme_create_params +from ..types.theme_update_response import ThemeUpdateResponse +from ..types import theme_update_params +from ..types.theme_replace_document_response import ThemeReplaceDocumentResponse +from ..types import theme_replace_document_params +from ..types.theme_delete_response import ThemeDeleteResponse +from ..types.theme_retrieve_response import ThemeRetrieveResponse + +__all__ = ["ThemesResource", "AsyncThemesResource"] + + +class ThemesResource(SyncAPIResource): + + @cached_property + def with_raw_response(self) -> ThemesResourceWithRawResponse: + return ThemesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ThemesResourceWithStreamingResponse: + return ThemesResourceWithStreamingResponse(self) + + def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ThemeListResponse: + """List all team themes.""" + return self._get( + "/v1/themes", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=ThemeListResponse, + ) + + def create( + self, + *, + name: str, + description: str | Omit = omit, + slug: str, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ThemeCreateResponse: + """Create a team theme.""" + return self._post( + "/v1/themes", + body=maybe_transform( + { + "name": name, + "description": description, + "slug": slug, + "document": document, + }, + theme_create_params.ThemeCreateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ThemeCreateResponse, + ) + + def update( + self, + slug: str, + *, + name: str | Omit = omit, + description: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ThemeUpdateResponse: + """Update theme metadata.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._patch( + path_template("/v1/themes/{slug}", **{"slug": slug}), + body=maybe_transform( + { + "name": name, + "description": description, + }, + theme_update_params.ThemeUpdateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ThemeUpdateResponse, + ) + + def replace_document( + self, + slug: str, + *, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ThemeReplaceDocumentResponse: + """Replace the theme document.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._put( + path_template("/v1/themes/{slug}", **{"slug": slug}), + body=maybe_transform( + {"document": document}, + theme_replace_document_params.ThemeReplaceDocumentParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ThemeReplaceDocumentResponse, + ) + + def delete( + self, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ThemeDeleteResponse: + """Delete a theme by slug.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._delete( + path_template("/v1/themes/{slug}", **{"slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ThemeDeleteResponse, + ) + + def retrieve( + self, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ThemeRetrieveResponse: + """Get the theme document by slug.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return self._get( + path_template("/v1/themes/{slug}", **{"slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=ThemeRetrieveResponse, + ) + + +class AsyncThemesResource(AsyncAPIResource): + + @cached_property + def with_raw_response(self) -> AsyncThemesResourceWithRawResponse: + return AsyncThemesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncThemesResourceWithStreamingResponse: + return AsyncThemesResourceWithStreamingResponse(self) + + async def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ThemeListResponse: + """List all team themes.""" + return await self._get( + "/v1/themes", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=ThemeListResponse, + ) + + async def create( + self, + *, + name: str, + description: str | Omit = omit, + slug: str, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ThemeCreateResponse: + """Create a team theme.""" + return await self._post( + "/v1/themes", + body=await async_maybe_transform( + { + "name": name, + "description": description, + "slug": slug, + "document": document, + }, + theme_create_params.ThemeCreateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ThemeCreateResponse, + ) + + async def update( + self, + slug: str, + *, + name: str | Omit = omit, + description: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ThemeUpdateResponse: + """Update theme metadata.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._patch( + path_template("/v1/themes/{slug}", **{"slug": slug}), + body=await async_maybe_transform( + { + "name": name, + "description": description, + }, + theme_update_params.ThemeUpdateParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ThemeUpdateResponse, + ) + + async def replace_document( + self, + slug: str, + *, + document: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ThemeReplaceDocumentResponse: + """Replace the theme document.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._put( + path_template("/v1/themes/{slug}", **{"slug": slug}), + body=await async_maybe_transform( + {"document": document}, + theme_replace_document_params.ThemeReplaceDocumentParams, + ), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ThemeReplaceDocumentResponse, + ) + + async def delete( + self, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ThemeDeleteResponse: + """Delete a theme by slug.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._delete( + path_template("/v1/themes/{slug}", **{"slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, idempotency_key=idempotency_key), + cast_to=ThemeDeleteResponse, + ) + + async def retrieve( + self, + slug: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ThemeRetrieveResponse: + """Get the theme document by slug.""" + if not slug: + raise ValueError(f"Expected a non-empty value for `slug` but received {slug!r}") + return await self._get( + path_template("/v1/themes/{slug}", **{"slug": slug}), + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout), + cast_to=ThemeRetrieveResponse, + ) + + +class ThemesResourceWithRawResponse: + def __init__(self, themes: ThemesResource) -> None: + self._themes = themes + + self.list = to_raw_response_wrapper( + themes.list, + ) + self.create = to_raw_response_wrapper( + themes.create, + ) + self.update = to_raw_response_wrapper( + themes.update, + ) + self.replace_document = to_raw_response_wrapper( + themes.replace_document, + ) + self.delete = to_raw_response_wrapper( + themes.delete, + ) + self.retrieve = to_raw_response_wrapper( + themes.retrieve, + ) + + +class AsyncThemesResourceWithRawResponse: + def __init__(self, themes: AsyncThemesResource) -> None: + self._themes = themes + + self.list = async_to_raw_response_wrapper( + themes.list, + ) + self.create = async_to_raw_response_wrapper( + themes.create, + ) + self.update = async_to_raw_response_wrapper( + themes.update, + ) + self.replace_document = async_to_raw_response_wrapper( + themes.replace_document, + ) + self.delete = async_to_raw_response_wrapper( + themes.delete, + ) + self.retrieve = async_to_raw_response_wrapper( + themes.retrieve, + ) + + +class ThemesResourceWithStreamingResponse: + def __init__(self, themes: ThemesResource) -> None: + self._themes = themes + + self.list = to_streamed_response_wrapper( + themes.list, + ) + self.create = to_streamed_response_wrapper( + themes.create, + ) + self.update = to_streamed_response_wrapper( + themes.update, + ) + self.replace_document = to_streamed_response_wrapper( + themes.replace_document, + ) + self.delete = to_streamed_response_wrapper( + themes.delete, + ) + self.retrieve = to_streamed_response_wrapper( + themes.retrieve, + ) + + +class AsyncThemesResourceWithStreamingResponse: + def __init__(self, themes: AsyncThemesResource) -> None: + self._themes = themes + + self.list = async_to_streamed_response_wrapper( + themes.list, + ) + self.create = async_to_streamed_response_wrapper( + themes.create, + ) + self.update = async_to_streamed_response_wrapper( + themes.update, + ) + self.replace_document = async_to_streamed_response_wrapper( + themes.replace_document, + ) + self.delete = async_to_streamed_response_wrapper( + themes.delete, + ) + self.retrieve = async_to_streamed_response_wrapper( + themes.retrieve, + ) diff --git a/src/types/_400.py b/src/types/_400.py new file mode 100644 index 0000000..d2a35a0 --- /dev/null +++ b/src/types/_400.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel + +__all__ = ["_400"] + + +class _400(BaseModel): + + message: str + + code: str diff --git a/src/types/_401.py b/src/types/_401.py new file mode 100644 index 0000000..1d7e2e3 --- /dev/null +++ b/src/types/_401.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel + +__all__ = ["_401"] + + +class _401(BaseModel): + + message: str + + code: str diff --git a/src/types/_403.py b/src/types/_403.py new file mode 100644 index 0000000..80cf160 --- /dev/null +++ b/src/types/_403.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel + +__all__ = ["_403"] + + +class _403(BaseModel): + + message: str + + code: str diff --git a/src/types/_404.py b/src/types/_404.py new file mode 100644 index 0000000..40cc635 --- /dev/null +++ b/src/types/_404.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel + +__all__ = ["_404"] + + +class _404(BaseModel): + + message: str + + code: str diff --git a/src/types/_422.py b/src/types/_422.py new file mode 100644 index 0000000..b3191c8 --- /dev/null +++ b/src/types/_422.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel + +__all__ = ["_422"] + + +class _422(BaseModel): + + message: str + + code: str diff --git a/src/types/_500.py b/src/types/_500.py new file mode 100644 index 0000000..0888545 --- /dev/null +++ b/src/types/_500.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel + +__all__ = ["_500"] + + +class _500(BaseModel): + + message: str + + code: str diff --git a/src/types/__init__.py b/src/types/__init__.py new file mode 100644 index 0000000..2f3be9d --- /dev/null +++ b/src/types/__init__.py @@ -0,0 +1,96 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from ._400 import _400 as _400 +from ._401 import _401 as _401 +from ._403 import _403 as _403 +from ._404 import _404 as _404 +from ._422 import _422 as _422 +from ._500 import _500 as _500 +from .api_document import ApiDocument as ApiDocument +from .nanoid import Nanoid as Nanoid +from .version import Version as Version +from .slug import Slug as Slug +from .namespace import Namespace as Namespace +from .managed_doc_version import ManagedDocVersion as ManagedDocVersion +from .method import Method as Method +from .access_group import AccessGroup as AccessGroup +from .schema import Schema as Schema +from .managed_schema_version import ManagedSchemaVersion as ManagedSchemaVersion +from .timestamp import Timestamp as Timestamp +from .uid import Uid as Uid +from .login_portal_email import LoginPortalEmail as LoginPortalEmail +from .login_portal_page import LoginPortalPage as LoginPortalPage +from .login_portal import LoginPortal as LoginPortal +from .rule import Rule as Rule +from .theme import Theme as Theme +from .team import Team as Team +from .team_name import TeamName as TeamName +from .team_image import TeamImage as TeamImage +from .github_project import GithubProject as GithubProject +from .active_deployment import ActiveDeployment as ActiveDeployment +from .github_project_repository import GithubProjectRepository as GithubProjectRepository +from .email import Email as Email +from .team_summary import TeamSummary as TeamSummary +from .user import User as User +from .registry_list_all_api_documents_response import RegistryListAllApiDocumentsResponse as RegistryListAllApiDocumentsResponse +from .registry_list_api_documents_response import RegistryListApiDocumentsResponse as RegistryListApiDocumentsResponse +from .registry_create_api_document_response import RegistryCreateApiDocumentResponse as RegistryCreateApiDocumentResponse +from .registry_create_api_document_params import RegistryCreateApiDocumentParams as RegistryCreateApiDocumentParams +from .registry_update_api_document_response import RegistryUpdateApiDocumentResponse as RegistryUpdateApiDocumentResponse +from .registry_update_api_document_params import RegistryUpdateApiDocumentParams as RegistryUpdateApiDocumentParams +from .registry_delete_api_document_response import RegistryDeleteApiDocumentResponse as RegistryDeleteApiDocumentResponse +from .registry_retrieve_api_document_version_response import RegistryRetrieveApiDocumentVersionResponse as RegistryRetrieveApiDocumentVersionResponse +from .registry_update_api_document_version_response import RegistryUpdateApiDocumentVersionResponse as RegistryUpdateApiDocumentVersionResponse +from .registry_update_api_document_version_params import RegistryUpdateApiDocumentVersionParams as RegistryUpdateApiDocumentVersionParams +from .registry_delete_api_document_version_response import RegistryDeleteApiDocumentVersionResponse as RegistryDeleteApiDocumentVersionResponse +from .registry_list_api_document_version_metadata_response import RegistryListApiDocumentVersionMetadataResponse as RegistryListApiDocumentVersionMetadataResponse +from .registry_create_api_document_version_response import RegistryCreateApiDocumentVersionResponse as RegistryCreateApiDocumentVersionResponse +from .registry_create_api_document_version_params import RegistryCreateApiDocumentVersionParams as RegistryCreateApiDocumentVersionParams +from .registry_create_api_document_access_group_response import RegistryCreateApiDocumentAccessGroupResponse as RegistryCreateApiDocumentAccessGroupResponse +from .registry_create_api_document_access_group_params import RegistryCreateApiDocumentAccessGroupParams as RegistryCreateApiDocumentAccessGroupParams +from .registry_delete_api_document_access_group_response import RegistryDeleteApiDocumentAccessGroupResponse as RegistryDeleteApiDocumentAccessGroupResponse +from .registry_delete_api_document_access_group_params import RegistryDeleteApiDocumentAccessGroupParams as RegistryDeleteApiDocumentAccessGroupParams +from .schema_list_response import SchemaListResponse as SchemaListResponse +from .schema_create_response import SchemaCreateResponse as SchemaCreateResponse +from .schema_create_params import SchemaCreateParams as SchemaCreateParams +from .schema_update_response import SchemaUpdateResponse as SchemaUpdateResponse +from .schema_update_params import SchemaUpdateParams as SchemaUpdateParams +from .schema_delete_response import SchemaDeleteResponse as SchemaDeleteResponse +from .login_portal_retrieve_response import LoginPortalRetrieveResponse as LoginPortalRetrieveResponse +from .login_portal_update_response import LoginPortalUpdateResponse as LoginPortalUpdateResponse +from .login_portal_update_params import LoginPortalUpdateParams as LoginPortalUpdateParams +from .login_portal_delete_response import LoginPortalDeleteResponse as LoginPortalDeleteResponse +from .login_portal_create_response import LoginPortalCreateResponse as LoginPortalCreateResponse +from .login_portal_create_params import LoginPortalCreateParams as LoginPortalCreateParams +from .login_portal_list_response import LoginPortalListResponse as LoginPortalListResponse +from .rule_list_rulesets_response import RuleListRulesetsResponse as RuleListRulesetsResponse +from .rule_create_ruleset_response import RuleCreateRulesetResponse as RuleCreateRulesetResponse +from .rule_create_ruleset_params import RuleCreateRulesetParams as RuleCreateRulesetParams +from .rule_update_ruleset_response import RuleUpdateRulesetResponse as RuleUpdateRulesetResponse +from .rule_update_ruleset_params import RuleUpdateRulesetParams as RuleUpdateRulesetParams +from .rule_delete_ruleset_response import RuleDeleteRulesetResponse as RuleDeleteRulesetResponse +from .rule_retrieve_ruleset_document_response import RuleRetrieveRulesetDocumentResponse as RuleRetrieveRulesetDocumentResponse +from .rule_create_ruleset_access_group_response import RuleCreateRulesetAccessGroupResponse as RuleCreateRulesetAccessGroupResponse +from .rule_create_ruleset_access_group_params import RuleCreateRulesetAccessGroupParams as RuleCreateRulesetAccessGroupParams +from .rule_delete_ruleset_access_group_response import RuleDeleteRulesetAccessGroupResponse as RuleDeleteRulesetAccessGroupResponse +from .rule_delete_ruleset_access_group_params import RuleDeleteRulesetAccessGroupParams as RuleDeleteRulesetAccessGroupParams +from .theme_list_response import ThemeListResponse as ThemeListResponse +from .theme_create_response import ThemeCreateResponse as ThemeCreateResponse +from .theme_create_params import ThemeCreateParams as ThemeCreateParams +from .theme_update_response import ThemeUpdateResponse as ThemeUpdateResponse +from .theme_update_params import ThemeUpdateParams as ThemeUpdateParams +from .theme_replace_document_response import ThemeReplaceDocumentResponse as ThemeReplaceDocumentResponse +from .theme_replace_document_params import ThemeReplaceDocumentParams as ThemeReplaceDocumentParams +from .theme_delete_response import ThemeDeleteResponse as ThemeDeleteResponse +from .theme_retrieve_response import ThemeRetrieveResponse as ThemeRetrieveResponse +from .team_list_response import TeamListResponse as TeamListResponse +from .scalar_doc_list_guides_response import ScalarDocListGuidesResponse as ScalarDocListGuidesResponse +from .scalar_doc_create_guide_response import ScalarDocCreateGuideResponse as ScalarDocCreateGuideResponse +from .scalar_doc_create_guide_params import ScalarDocCreateGuideParams as ScalarDocCreateGuideParams +from .scalar_doc_publish_guide_response import ScalarDocPublishGuideResponse as ScalarDocPublishGuideResponse +from .namespace_list_response import NamespaceListResponse as NamespaceListResponse +from .authentication_exchange_personal_token_response import AuthenticationExchangePersonalTokenResponse as AuthenticationExchangePersonalTokenResponse +from .authentication_exchange_personal_token_params import AuthenticationExchangePersonalTokenParams as AuthenticationExchangePersonalTokenParams +from .authentication_list_current_user_response import AuthenticationListCurrentUserResponse as AuthenticationListCurrentUserResponse diff --git a/src/types/access_group.py b/src/types/access_group.py new file mode 100644 index 0000000..ed3f7dc --- /dev/null +++ b/src/types/access_group.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel +from .slug import Slug + +__all__ = ["AccessGroup"] + + +class AccessGroup(BaseModel): + + accessGroupSlug: Slug diff --git a/src/types/active_deployment.py b/src/types/active_deployment.py new file mode 100644 index 0000000..e01a470 --- /dev/null +++ b/src/types/active_deployment.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel +from .timestamp import Timestamp + +__all__ = ["ActiveDeployment"] + + +class ActiveDeployment(BaseModel): + + uid: str + + domain: str + + publishedAt: Timestamp diff --git a/src/types/api_document.py b/src/types/api_document.py new file mode 100644 index 0000000..70de5b2 --- /dev/null +++ b/src/types/api_document.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List + +from .._models import BaseModel +from .nanoid import Nanoid +from .version import Version +from .slug import Slug +from .namespace import Namespace +from .managed_doc_version import ManagedDocVersion + +__all__ = ["ApiDocument"] + + +class ApiDocument(BaseModel): + + uid: Nanoid + + version: Version + + title: str + + slug: Slug + + description: str + + namespace: Namespace + + isPrivate: bool + + tags: str + + versions: List[ManagedDocVersion] diff --git a/src/types/authentication_exchange_personal_token_params.py b/src/types/authentication_exchange_personal_token_params.py new file mode 100644 index 0000000..1cb5507 --- /dev/null +++ b/src/types/authentication_exchange_personal_token_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from .._utils import PropertyInfo + +__all__ = ["AuthenticationExchangePersonalTokenParams"] + + +class AuthenticationExchangePersonalTokenParams(TypedDict, total=False): + + personal_token: Required[Annotated[str, PropertyInfo(alias="personalToken")]] diff --git a/src/types/authentication_exchange_personal_token_response.py b/src/types/authentication_exchange_personal_token_response.py new file mode 100644 index 0000000..aa3b416 --- /dev/null +++ b/src/types/authentication_exchange_personal_token_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["AuthenticationExchangePersonalTokenResponse"] + +AuthenticationExchangePersonalTokenResponse: TypeAlias = object diff --git a/src/types/authentication_list_current_user_response.py b/src/types/authentication_list_current_user_response.py new file mode 100644 index 0000000..db44af3 --- /dev/null +++ b/src/types/authentication_list_current_user_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias +from .user import User + +__all__ = ["AuthenticationListCurrentUserResponse"] + +AuthenticationListCurrentUserResponse: TypeAlias = User diff --git a/src/types/email.py b/src/types/email.py new file mode 100644 index 0000000..716d068 --- /dev/null +++ b/src/types/email.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["Email"] + +Email: TypeAlias = str diff --git a/src/types/github_project.py b/src/types/github_project.py new file mode 100644 index 0000000..293d8a6 --- /dev/null +++ b/src/types/github_project.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel +from .nanoid import Nanoid +from .timestamp import Timestamp +from .active_deployment import ActiveDeployment +from .slug import Slug +from .github_project_repository import GithubProjectRepository + +__all__ = ["GithubProject"] + + +class GithubProject(BaseModel): + + uid: Nanoid + + createdAt: Timestamp + + updatedAt: Timestamp + + name: str + + activeDeployment: Optional[ActiveDeployment] = None + + lastPublished: Optional[Timestamp] = None + + lastPublishedUid: Optional[str] = None + + loginPortalUid: str + + activeThemeId: str + + typesenseId: Optional[float] = None + + isPrivate: bool + + agentEnabled: bool + + accessGroups: str + + slug: Slug + + publishStatus: str + + publishMessage: str + + repository: Optional[GithubProjectRepository] = None diff --git a/src/types/github_project_repository.py b/src/types/github_project_repository.py new file mode 100644 index 0000000..be96f6f --- /dev/null +++ b/src/types/github_project_repository.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel + +__all__ = ["GithubProjectRepository"] + + +class GithubProjectRepository(BaseModel): + + linkedBy: str + + id: float + + name: str + + configPath: str + + branch: str + + publishOnMerge: bool + + publishPreviews: bool + + prComments: bool + + expired: bool diff --git a/src/types/login_portal.py b/src/types/login_portal.py new file mode 100644 index 0000000..997cb1e --- /dev/null +++ b/src/types/login_portal.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel +from .nanoid import Nanoid +from .slug import Slug + +__all__ = ["LoginPortal"] + + +class LoginPortal(BaseModel): + + uid: Nanoid + + title: str + + slug: Slug diff --git a/src/types/login_portal_create_params.py b/src/types/login_portal_create_params.py new file mode 100644 index 0000000..e127d5e --- /dev/null +++ b/src/types/login_portal_create_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["LoginPortalCreateParams"] + + +class LoginPortalCreateParams(TypedDict, total=False): + + title: Required[str] + + slug: Required[str] + + email: Required[object] + + page: Required[object] diff --git a/src/types/login_portal_create_response.py b/src/types/login_portal_create_response.py new file mode 100644 index 0000000..7c2ae74 --- /dev/null +++ b/src/types/login_portal_create_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias +from .uid import Uid + +__all__ = ["LoginPortalCreateResponse"] + +LoginPortalCreateResponse: TypeAlias = Uid diff --git a/src/types/login_portal_delete_response.py b/src/types/login_portal_delete_response.py new file mode 100644 index 0000000..a7446a7 --- /dev/null +++ b/src/types/login_portal_delete_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["LoginPortalDeleteResponse"] + +LoginPortalDeleteResponse: TypeAlias = None diff --git a/src/types/login_portal_email.py b/src/types/login_portal_email.py new file mode 100644 index 0000000..cd89f91 --- /dev/null +++ b/src/types/login_portal_email.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel + +__all__ = ["LoginPortalEmail"] + + +class LoginPortalEmail(BaseModel): + + logo: str + + logoSize: str + + buttonText: str + + message: str + + title: str + + mainColor: str + + mainBackground: str + + cardColor: str + + cardBackground: str + + buttonColor: str + + buttonBackground: str diff --git a/src/types/login_portal_list_response.py b/src/types/login_portal_list_response.py new file mode 100644 index 0000000..b7ed0ca --- /dev/null +++ b/src/types/login_portal_list_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import TypeAlias +from .login_portal import LoginPortal + +__all__ = ["LoginPortalListResponse"] + +LoginPortalListResponse: TypeAlias = List[LoginPortal] diff --git a/src/types/login_portal_page.py b/src/types/login_portal_page.py new file mode 100644 index 0000000..803f48e --- /dev/null +++ b/src/types/login_portal_page.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel + +__all__ = ["LoginPortalPage"] + + +class LoginPortalPage(BaseModel): + + title: str + + description: str + + head: str + + script: str + + theme: str + + companyName: str + + logo: str + + logoURL: str + + favicon: str + + termsLink: str + + privacyLink: str + + formTitle: str + + formDescription: str + + formImage: str diff --git a/src/types/login_portal_retrieve_response.py b/src/types/login_portal_retrieve_response.py new file mode 100644 index 0000000..9c35326 --- /dev/null +++ b/src/types/login_portal_retrieve_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["LoginPortalRetrieveResponse"] + +LoginPortalRetrieveResponse: TypeAlias = object diff --git a/src/types/login_portal_update_params.py b/src/types/login_portal_update_params.py new file mode 100644 index 0000000..8832ac2 --- /dev/null +++ b/src/types/login_portal_update_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["LoginPortalUpdateParams"] + + +class LoginPortalUpdateParams(TypedDict, total=False): + + title: str diff --git a/src/types/login_portal_update_response.py b/src/types/login_portal_update_response.py new file mode 100644 index 0000000..7a5b913 --- /dev/null +++ b/src/types/login_portal_update_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["LoginPortalUpdateResponse"] + +LoginPortalUpdateResponse: TypeAlias = None diff --git a/src/types/managed_doc_version.py b/src/types/managed_doc_version.py new file mode 100644 index 0000000..28e99b4 --- /dev/null +++ b/src/types/managed_doc_version.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .nanoid import Nanoid +from .version import Version + +__all__ = ["ManagedDocVersion"] + + +class ManagedDocVersion(BaseModel): + + uid: Nanoid + + createdAt: float + + version: Version + + upgraded: bool + + embedStatus: Optional[Literal["complete", "failed"]] = None + + tags: List[str] + + tools: Optional[List[object]] = None + + yamlSha: Optional[str] = None + + jsonSha: Optional[str] = None + + versionSha: Optional[str] = None diff --git a/src/types/managed_schema_version.py b/src/types/managed_schema_version.py new file mode 100644 index 0000000..45cf82f --- /dev/null +++ b/src/types/managed_schema_version.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel +from .nanoid import Nanoid +from .timestamp import Timestamp +from .version import Version + +__all__ = ["ManagedSchemaVersion"] + + +class ManagedSchemaVersion(BaseModel): + + uid: Nanoid + + createdAt: Timestamp + + updatedAt: Timestamp + + version: Version diff --git a/src/types/method.py b/src/types/method.py new file mode 100644 index 0000000..bbfeba4 --- /dev/null +++ b/src/types/method.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypeAlias + +__all__ = ["Method"] + +Method: TypeAlias = Literal["delete", "get", "head", "options", "patch", "post", "put", "trace"] diff --git a/src/types/namespace.py b/src/types/namespace.py new file mode 100644 index 0000000..a6b6c79 --- /dev/null +++ b/src/types/namespace.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["Namespace"] + +Namespace: TypeAlias = str diff --git a/src/types/namespace_list_response.py b/src/types/namespace_list_response.py new file mode 100644 index 0000000..6b2ca47 --- /dev/null +++ b/src/types/namespace_list_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import TypeAlias + +__all__ = ["NamespaceListResponse"] + +NamespaceListResponse: TypeAlias = List[str] diff --git a/src/types/nanoid.py b/src/types/nanoid.py new file mode 100644 index 0000000..73e5bac --- /dev/null +++ b/src/types/nanoid.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["Nanoid"] + +Nanoid: TypeAlias = str diff --git a/src/types/registry_create_api_document_access_group_params.py b/src/types/registry_create_api_document_access_group_params.py new file mode 100644 index 0000000..7be5ced --- /dev/null +++ b/src/types/registry_create_api_document_access_group_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from .._utils import PropertyInfo + +__all__ = ["RegistryCreateApiDocumentAccessGroupParams"] + + +class RegistryCreateApiDocumentAccessGroupParams(TypedDict, total=False): + + access_group_slug: Required[Annotated[str, PropertyInfo(alias="accessGroupSlug")]] diff --git a/src/types/registry_create_api_document_access_group_response.py b/src/types/registry_create_api_document_access_group_response.py new file mode 100644 index 0000000..7a73e57 --- /dev/null +++ b/src/types/registry_create_api_document_access_group_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RegistryCreateApiDocumentAccessGroupResponse"] + +RegistryCreateApiDocumentAccessGroupResponse: TypeAlias = None diff --git a/src/types/registry_create_api_document_params.py b/src/types/registry_create_api_document_params.py new file mode 100644 index 0000000..4dedfa6 --- /dev/null +++ b/src/types/registry_create_api_document_params.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from .._utils import PropertyInfo + +__all__ = ["RegistryCreateApiDocumentParams"] + + +class RegistryCreateApiDocumentParams(TypedDict, total=False): + + title: Required[str] + + description: str + + version: Required[str] + + slug: Required[str] + + ruleset: str + + is_private: Annotated[bool, PropertyInfo(alias="isPrivate")] + + document: Required[str] diff --git a/src/types/registry_create_api_document_response.py b/src/types/registry_create_api_document_response.py new file mode 100644 index 0000000..e13c4dc --- /dev/null +++ b/src/types/registry_create_api_document_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RegistryCreateApiDocumentResponse"] + +RegistryCreateApiDocumentResponse: TypeAlias = object diff --git a/src/types/registry_create_api_document_version_params.py b/src/types/registry_create_api_document_version_params.py new file mode 100644 index 0000000..e135774 --- /dev/null +++ b/src/types/registry_create_api_document_version_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from .._utils import PropertyInfo + +__all__ = ["RegistryCreateApiDocumentVersionParams"] + + +class RegistryCreateApiDocumentVersionParams(TypedDict, total=False): + + version: Required[str] + + document: Required[str] + + force: bool + + last_known_version_sha: Annotated[str, PropertyInfo(alias="lastKnownVersionSha")] diff --git a/src/types/registry_create_api_document_version_response.py b/src/types/registry_create_api_document_version_response.py new file mode 100644 index 0000000..9692d5a --- /dev/null +++ b/src/types/registry_create_api_document_version_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias +from .managed_doc_version import ManagedDocVersion + +__all__ = ["RegistryCreateApiDocumentVersionResponse"] + +RegistryCreateApiDocumentVersionResponse: TypeAlias = ManagedDocVersion diff --git a/src/types/registry_delete_api_document_access_group_params.py b/src/types/registry_delete_api_document_access_group_params.py new file mode 100644 index 0000000..719f324 --- /dev/null +++ b/src/types/registry_delete_api_document_access_group_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from .._utils import PropertyInfo + +__all__ = ["RegistryDeleteApiDocumentAccessGroupParams"] + + +class RegistryDeleteApiDocumentAccessGroupParams(TypedDict, total=False): + + access_group_slug: Required[Annotated[str, PropertyInfo(alias="accessGroupSlug")]] diff --git a/src/types/registry_delete_api_document_access_group_response.py b/src/types/registry_delete_api_document_access_group_response.py new file mode 100644 index 0000000..ad45c5b --- /dev/null +++ b/src/types/registry_delete_api_document_access_group_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RegistryDeleteApiDocumentAccessGroupResponse"] + +RegistryDeleteApiDocumentAccessGroupResponse: TypeAlias = None diff --git a/src/types/registry_delete_api_document_response.py b/src/types/registry_delete_api_document_response.py new file mode 100644 index 0000000..c3e26b4 --- /dev/null +++ b/src/types/registry_delete_api_document_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RegistryDeleteApiDocumentResponse"] + +RegistryDeleteApiDocumentResponse: TypeAlias = None diff --git a/src/types/registry_delete_api_document_version_response.py b/src/types/registry_delete_api_document_version_response.py new file mode 100644 index 0000000..39e1f37 --- /dev/null +++ b/src/types/registry_delete_api_document_version_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RegistryDeleteApiDocumentVersionResponse"] + +RegistryDeleteApiDocumentVersionResponse: TypeAlias = None diff --git a/src/types/registry_list_all_api_documents_response.py b/src/types/registry_list_all_api_documents_response.py new file mode 100644 index 0000000..9552301 --- /dev/null +++ b/src/types/registry_list_all_api_documents_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import TypeAlias +from .api_document import ApiDocument + +__all__ = ["RegistryListAllApiDocumentsResponse"] + +RegistryListAllApiDocumentsResponse: TypeAlias = List[ApiDocument] diff --git a/src/types/registry_list_api_document_version_metadata_response.py b/src/types/registry_list_api_document_version_metadata_response.py new file mode 100644 index 0000000..0ecf442 --- /dev/null +++ b/src/types/registry_list_api_document_version_metadata_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias +from .managed_doc_version import ManagedDocVersion + +__all__ = ["RegistryListApiDocumentVersionMetadataResponse"] + +RegistryListApiDocumentVersionMetadataResponse: TypeAlias = ManagedDocVersion diff --git a/src/types/registry_list_api_documents_response.py b/src/types/registry_list_api_documents_response.py new file mode 100644 index 0000000..85fbbbf --- /dev/null +++ b/src/types/registry_list_api_documents_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import TypeAlias +from .api_document import ApiDocument + +__all__ = ["RegistryListApiDocumentsResponse"] + +RegistryListApiDocumentsResponse: TypeAlias = List[ApiDocument] diff --git a/src/types/registry_retrieve_api_document_version_response.py b/src/types/registry_retrieve_api_document_version_response.py new file mode 100644 index 0000000..5113289 --- /dev/null +++ b/src/types/registry_retrieve_api_document_version_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RegistryRetrieveApiDocumentVersionResponse"] + +RegistryRetrieveApiDocumentVersionResponse: TypeAlias = str diff --git a/src/types/registry_update_api_document_params.py b/src/types/registry_update_api_document_params.py new file mode 100644 index 0000000..a48ede5 --- /dev/null +++ b/src/types/registry_update_api_document_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import TypedDict +from .._utils import PropertyInfo + +__all__ = ["RegistryUpdateApiDocumentParams"] + + +class RegistryUpdateApiDocumentParams(TypedDict, total=False): + + title: str + + description: str + + is_private: Annotated[bool, PropertyInfo(alias="isPrivate")] + + ruleset: str diff --git a/src/types/registry_update_api_document_response.py b/src/types/registry_update_api_document_response.py new file mode 100644 index 0000000..5bfd97e --- /dev/null +++ b/src/types/registry_update_api_document_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RegistryUpdateApiDocumentResponse"] + +RegistryUpdateApiDocumentResponse: TypeAlias = None diff --git a/src/types/registry_update_api_document_version_params.py b/src/types/registry_update_api_document_version_params.py new file mode 100644 index 0000000..b8d3307 --- /dev/null +++ b/src/types/registry_update_api_document_version_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from .._utils import PropertyInfo + +__all__ = ["RegistryUpdateApiDocumentVersionParams"] + + +class RegistryUpdateApiDocumentVersionParams(TypedDict, total=False): + + document: Required[str] + + last_known_version_sha: Annotated[str, PropertyInfo(alias="lastKnownVersionSha")] diff --git a/src/types/registry_update_api_document_version_response.py b/src/types/registry_update_api_document_version_response.py new file mode 100644 index 0000000..1a39b7a --- /dev/null +++ b/src/types/registry_update_api_document_version_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RegistryUpdateApiDocumentVersionResponse"] + +RegistryUpdateApiDocumentVersionResponse: TypeAlias = object diff --git a/src/types/rule.py b/src/types/rule.py new file mode 100644 index 0000000..6f4d2fd --- /dev/null +++ b/src/types/rule.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel +from .nanoid import Nanoid +from .slug import Slug +from .namespace import Namespace + +__all__ = ["Rule"] + + +class Rule(BaseModel): + + uid: Nanoid + + title: str + + description: str + + slug: Slug + + namespace: Namespace + + isPrivate: bool diff --git a/src/types/rule_create_ruleset_access_group_params.py b/src/types/rule_create_ruleset_access_group_params.py new file mode 100644 index 0000000..08d8023 --- /dev/null +++ b/src/types/rule_create_ruleset_access_group_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from .._utils import PropertyInfo + +__all__ = ["RuleCreateRulesetAccessGroupParams"] + + +class RuleCreateRulesetAccessGroupParams(TypedDict, total=False): + + access_group_slug: Required[Annotated[str, PropertyInfo(alias="accessGroupSlug")]] diff --git a/src/types/rule_create_ruleset_access_group_response.py b/src/types/rule_create_ruleset_access_group_response.py new file mode 100644 index 0000000..2a60e6f --- /dev/null +++ b/src/types/rule_create_ruleset_access_group_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RuleCreateRulesetAccessGroupResponse"] + +RuleCreateRulesetAccessGroupResponse: TypeAlias = None diff --git a/src/types/rule_create_ruleset_params.py b/src/types/rule_create_ruleset_params.py new file mode 100644 index 0000000..7e70c5a --- /dev/null +++ b/src/types/rule_create_ruleset_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from .._utils import PropertyInfo + +__all__ = ["RuleCreateRulesetParams"] + + +class RuleCreateRulesetParams(TypedDict, total=False): + + title: Required[str] + + description: str + + slug: Required[str] + + is_private: Annotated[bool, PropertyInfo(alias="isPrivate")] + + document: Required[str] diff --git a/src/types/rule_create_ruleset_response.py b/src/types/rule_create_ruleset_response.py new file mode 100644 index 0000000..d829ec3 --- /dev/null +++ b/src/types/rule_create_ruleset_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias +from .uid import Uid + +__all__ = ["RuleCreateRulesetResponse"] + +RuleCreateRulesetResponse: TypeAlias = Uid diff --git a/src/types/rule_delete_ruleset_access_group_params.py b/src/types/rule_delete_ruleset_access_group_params.py new file mode 100644 index 0000000..502f63c --- /dev/null +++ b/src/types/rule_delete_ruleset_access_group_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from .._utils import PropertyInfo + +__all__ = ["RuleDeleteRulesetAccessGroupParams"] + + +class RuleDeleteRulesetAccessGroupParams(TypedDict, total=False): + + access_group_slug: Required[Annotated[str, PropertyInfo(alias="accessGroupSlug")]] diff --git a/src/types/rule_delete_ruleset_access_group_response.py b/src/types/rule_delete_ruleset_access_group_response.py new file mode 100644 index 0000000..8e6814f --- /dev/null +++ b/src/types/rule_delete_ruleset_access_group_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RuleDeleteRulesetAccessGroupResponse"] + +RuleDeleteRulesetAccessGroupResponse: TypeAlias = None diff --git a/src/types/rule_delete_ruleset_response.py b/src/types/rule_delete_ruleset_response.py new file mode 100644 index 0000000..0f7eacd --- /dev/null +++ b/src/types/rule_delete_ruleset_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RuleDeleteRulesetResponse"] + +RuleDeleteRulesetResponse: TypeAlias = None diff --git a/src/types/rule_list_rulesets_response.py b/src/types/rule_list_rulesets_response.py new file mode 100644 index 0000000..0ab5bac --- /dev/null +++ b/src/types/rule_list_rulesets_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import TypeAlias +from .rule import Rule + +__all__ = ["RuleListRulesetsResponse"] + +RuleListRulesetsResponse: TypeAlias = List[Rule] diff --git a/src/types/rule_retrieve_ruleset_document_response.py b/src/types/rule_retrieve_ruleset_document_response.py new file mode 100644 index 0000000..14fa017 --- /dev/null +++ b/src/types/rule_retrieve_ruleset_document_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RuleRetrieveRulesetDocumentResponse"] + +RuleRetrieveRulesetDocumentResponse: TypeAlias = str diff --git a/src/types/rule_update_ruleset_params.py b/src/types/rule_update_ruleset_params.py new file mode 100644 index 0000000..41b3811 --- /dev/null +++ b/src/types/rule_update_ruleset_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import TypedDict +from .._utils import PropertyInfo + +__all__ = ["RuleUpdateRulesetParams"] + + +class RuleUpdateRulesetParams(TypedDict, total=False): + + namespace: str + + slug: str + + title: str + + description: str + + is_private: Annotated[bool, PropertyInfo(alias="isPrivate")] diff --git a/src/types/rule_update_ruleset_response.py b/src/types/rule_update_ruleset_response.py new file mode 100644 index 0000000..1b2a74d --- /dev/null +++ b/src/types/rule_update_ruleset_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["RuleUpdateRulesetResponse"] + +RuleUpdateRulesetResponse: TypeAlias = None diff --git a/src/types/scalar_doc_create_guide_params.py b/src/types/scalar_doc_create_guide_params.py new file mode 100644 index 0000000..38ca7e5 --- /dev/null +++ b/src/types/scalar_doc_create_guide_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated, List +from typing_extensions import Required, TypedDict +from .._utils import PropertyInfo + +__all__ = ["ScalarDocCreateGuideParams"] + + +class ScalarDocCreateGuideParams(TypedDict, total=False): + + name: Required[str] + + slug: str + + is_private: Required[Annotated[bool, PropertyInfo(alias="isPrivate")]] + + allowed_users: Required[Annotated[List[str], PropertyInfo(alias="allowedUsers")]] + + allowed_domains: Required[Annotated[List[str], PropertyInfo(alias="allowedDomains")]] diff --git a/src/types/scalar_doc_create_guide_response.py b/src/types/scalar_doc_create_guide_response.py new file mode 100644 index 0000000..4899376 --- /dev/null +++ b/src/types/scalar_doc_create_guide_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["ScalarDocCreateGuideResponse"] + +ScalarDocCreateGuideResponse: TypeAlias = object diff --git a/src/types/scalar_doc_list_guides_response.py b/src/types/scalar_doc_list_guides_response.py new file mode 100644 index 0000000..82e4197 --- /dev/null +++ b/src/types/scalar_doc_list_guides_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import TypeAlias +from .github_project import GithubProject + +__all__ = ["ScalarDocListGuidesResponse"] + +ScalarDocListGuidesResponse: TypeAlias = List[GithubProject] diff --git a/src/types/scalar_doc_publish_guide_response.py b/src/types/scalar_doc_publish_guide_response.py new file mode 100644 index 0000000..1befb57 --- /dev/null +++ b/src/types/scalar_doc_publish_guide_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["ScalarDocPublishGuideResponse"] + +ScalarDocPublishGuideResponse: TypeAlias = object diff --git a/src/types/schema.py b/src/types/schema.py new file mode 100644 index 0000000..fc9ca8e --- /dev/null +++ b/src/types/schema.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List + +from .._models import BaseModel +from .nanoid import Nanoid +from .slug import Slug +from .namespace import Namespace +from .managed_schema_version import ManagedSchemaVersion + +__all__ = ["Schema"] + + +class Schema(BaseModel): + + uid: Nanoid + + title: str + + description: str + + slug: Slug + + namespace: Namespace + + isPrivate: bool + + versions: List[ManagedSchemaVersion] diff --git a/src/types/schema_create_params.py b/src/types/schema_create_params.py new file mode 100644 index 0000000..dc95811 --- /dev/null +++ b/src/types/schema_create_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from .._utils import PropertyInfo + +__all__ = ["SchemaCreateParams"] + + +class SchemaCreateParams(TypedDict, total=False): + + title: Required[str] + + description: str + + version: Required[str] + + slug: Required[str] + + is_private: Annotated[bool, PropertyInfo(alias="isPrivate")] + + document: Required[str] diff --git a/src/types/schema_create_response.py b/src/types/schema_create_response.py new file mode 100644 index 0000000..8e7792a --- /dev/null +++ b/src/types/schema_create_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias +from .uid import Uid + +__all__ = ["SchemaCreateResponse"] + +SchemaCreateResponse: TypeAlias = Uid diff --git a/src/types/schema_delete_response.py b/src/types/schema_delete_response.py new file mode 100644 index 0000000..d1b662b --- /dev/null +++ b/src/types/schema_delete_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["SchemaDeleteResponse"] + +SchemaDeleteResponse: TypeAlias = None diff --git a/src/types/schema_list_response.py b/src/types/schema_list_response.py new file mode 100644 index 0000000..93ca662 --- /dev/null +++ b/src/types/schema_list_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import TypeAlias +from .schema import Schema + +__all__ = ["SchemaListResponse"] + +SchemaListResponse: TypeAlias = List[Schema] diff --git a/src/types/schema_update_params.py b/src/types/schema_update_params.py new file mode 100644 index 0000000..e4bac0f --- /dev/null +++ b/src/types/schema_update_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import TypedDict +from .._utils import PropertyInfo + +__all__ = ["SchemaUpdateParams"] + + +class SchemaUpdateParams(TypedDict, total=False): + + title: str + + description: str + + is_private: Annotated[bool, PropertyInfo(alias="isPrivate")] diff --git a/src/types/schema_update_response.py b/src/types/schema_update_response.py new file mode 100644 index 0000000..8d20eeb --- /dev/null +++ b/src/types/schema_update_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["SchemaUpdateResponse"] + +SchemaUpdateResponse: TypeAlias = None diff --git a/src/types/schemas/__init__.py b/src/types/schemas/__init__.py new file mode 100644 index 0000000..640f96a --- /dev/null +++ b/src/types/schemas/__init__.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from .version_retrieve_schema_response import VersionRetrieveSchemaResponse as VersionRetrieveSchemaResponse +from .version_delete_schema_response import VersionDeleteSchemaResponse as VersionDeleteSchemaResponse +from .version_create_schema_response import VersionCreateSchemaResponse as VersionCreateSchemaResponse +from .version_create_schema_params import VersionCreateSchemaParams as VersionCreateSchemaParams +from .access_group_create_schema_response import AccessGroupCreateSchemaResponse as AccessGroupCreateSchemaResponse +from .access_group_create_schema_params import AccessGroupCreateSchemaParams as AccessGroupCreateSchemaParams +from .access_group_delete_schema_response import AccessGroupDeleteSchemaResponse as AccessGroupDeleteSchemaResponse +from .access_group_delete_schema_params import AccessGroupDeleteSchemaParams as AccessGroupDeleteSchemaParams diff --git a/src/types/schemas/access_group_create_schema_params.py b/src/types/schemas/access_group_create_schema_params.py new file mode 100644 index 0000000..034b841 --- /dev/null +++ b/src/types/schemas/access_group_create_schema_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from ..._utils import PropertyInfo + +__all__ = ["AccessGroupCreateSchemaParams"] + + +class AccessGroupCreateSchemaParams(TypedDict, total=False): + + access_group_slug: Required[Annotated[str, PropertyInfo(alias="accessGroupSlug")]] diff --git a/src/types/schemas/access_group_create_schema_response.py b/src/types/schemas/access_group_create_schema_response.py new file mode 100644 index 0000000..4a7bbc5 --- /dev/null +++ b/src/types/schemas/access_group_create_schema_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["AccessGroupCreateSchemaResponse"] + +AccessGroupCreateSchemaResponse: TypeAlias = None diff --git a/src/types/schemas/access_group_delete_schema_params.py b/src/types/schemas/access_group_delete_schema_params.py new file mode 100644 index 0000000..c17eb49 --- /dev/null +++ b/src/types/schemas/access_group_delete_schema_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import Annotated +from typing_extensions import Required, TypedDict +from ..._utils import PropertyInfo + +__all__ = ["AccessGroupDeleteSchemaParams"] + + +class AccessGroupDeleteSchemaParams(TypedDict, total=False): + + access_group_slug: Required[Annotated[str, PropertyInfo(alias="accessGroupSlug")]] diff --git a/src/types/schemas/access_group_delete_schema_response.py b/src/types/schemas/access_group_delete_schema_response.py new file mode 100644 index 0000000..2f95a9f --- /dev/null +++ b/src/types/schemas/access_group_delete_schema_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["AccessGroupDeleteSchemaResponse"] + +AccessGroupDeleteSchemaResponse: TypeAlias = None diff --git a/src/types/schemas/version_create_schema_params.py b/src/types/schemas/version_create_schema_params.py new file mode 100644 index 0000000..95a659e --- /dev/null +++ b/src/types/schemas/version_create_schema_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["VersionCreateSchemaParams"] + + +class VersionCreateSchemaParams(TypedDict, total=False): + + version: Required[str] + + document: Required[str] diff --git a/src/types/schemas/version_create_schema_response.py b/src/types/schemas/version_create_schema_response.py new file mode 100644 index 0000000..88e7d40 --- /dev/null +++ b/src/types/schemas/version_create_schema_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias +from ..uid import Uid + +__all__ = ["VersionCreateSchemaResponse"] + +VersionCreateSchemaResponse: TypeAlias = Uid diff --git a/src/types/schemas/version_delete_schema_response.py b/src/types/schemas/version_delete_schema_response.py new file mode 100644 index 0000000..25078d4 --- /dev/null +++ b/src/types/schemas/version_delete_schema_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["VersionDeleteSchemaResponse"] + +VersionDeleteSchemaResponse: TypeAlias = None diff --git a/src/types/schemas/version_retrieve_schema_response.py b/src/types/schemas/version_retrieve_schema_response.py new file mode 100644 index 0000000..154cea5 --- /dev/null +++ b/src/types/schemas/version_retrieve_schema_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["VersionRetrieveSchemaResponse"] + +VersionRetrieveSchemaResponse: TypeAlias = str diff --git a/src/types/slug.py b/src/types/slug.py new file mode 100644 index 0000000..3925e4d --- /dev/null +++ b/src/types/slug.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["Slug"] + +Slug: TypeAlias = str diff --git a/src/types/team.py b/src/types/team.py new file mode 100644 index 0000000..cd07803 --- /dev/null +++ b/src/types/team.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel +from .nanoid import Nanoid +from .team_name import TeamName +from .team_image import TeamImage +from .slug import Slug + +__all__ = ["Team"] + + +class Team(BaseModel): + + uid: Nanoid + + name: TeamName + + imageUri: Optional[TeamImage] = None + + slug: Slug + + theme: str diff --git a/src/types/team_image.py b/src/types/team_image.py new file mode 100644 index 0000000..5d2e625 --- /dev/null +++ b/src/types/team_image.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["TeamImage"] + +TeamImage: TypeAlias = str diff --git a/src/types/team_list_response.py b/src/types/team_list_response.py new file mode 100644 index 0000000..39698a7 --- /dev/null +++ b/src/types/team_list_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import TypeAlias +from .team import Team + +__all__ = ["TeamListResponse"] + +TeamListResponse: TypeAlias = List[Team] diff --git a/src/types/team_name.py b/src/types/team_name.py new file mode 100644 index 0000000..2bf4ed8 --- /dev/null +++ b/src/types/team_name.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["TeamName"] + +TeamName: TypeAlias = str diff --git a/src/types/team_summary.py b/src/types/team_summary.py new file mode 100644 index 0000000..bc4d716 --- /dev/null +++ b/src/types/team_summary.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import Optional + +from .._models import BaseModel +from .nanoid import Nanoid +from .team_name import TeamName +from .team_image import TeamImage + +__all__ = ["TeamSummary"] + + +class TeamSummary(BaseModel): + + uid: Nanoid + + name: TeamName + + imageUri: Optional[TeamImage] = None diff --git a/src/types/theme.py b/src/types/theme.py new file mode 100644 index 0000000..24ba21d --- /dev/null +++ b/src/types/theme.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel +from .nanoid import Nanoid +from .slug import Slug + +__all__ = ["Theme"] + + +class Theme(BaseModel): + + uid: Nanoid + + name: str + + description: str + + slug: Slug diff --git a/src/types/theme_create_params.py b/src/types/theme_create_params.py new file mode 100644 index 0000000..9e1c513 --- /dev/null +++ b/src/types/theme_create_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ThemeCreateParams"] + + +class ThemeCreateParams(TypedDict, total=False): + + name: Required[str] + + description: str + + slug: Required[str] + + document: Required[str] diff --git a/src/types/theme_create_response.py b/src/types/theme_create_response.py new file mode 100644 index 0000000..fd27bad --- /dev/null +++ b/src/types/theme_create_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias +from .uid import Uid + +__all__ = ["ThemeCreateResponse"] + +ThemeCreateResponse: TypeAlias = Uid diff --git a/src/types/theme_delete_response.py b/src/types/theme_delete_response.py new file mode 100644 index 0000000..87e53d2 --- /dev/null +++ b/src/types/theme_delete_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["ThemeDeleteResponse"] + +ThemeDeleteResponse: TypeAlias = None diff --git a/src/types/theme_list_response.py b/src/types/theme_list_response.py new file mode 100644 index 0000000..7f5b941 --- /dev/null +++ b/src/types/theme_list_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import TypeAlias +from .theme import Theme + +__all__ = ["ThemeListResponse"] + +ThemeListResponse: TypeAlias = List[Theme] diff --git a/src/types/theme_replace_document_params.py b/src/types/theme_replace_document_params.py new file mode 100644 index 0000000..8f41750 --- /dev/null +++ b/src/types/theme_replace_document_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ThemeReplaceDocumentParams"] + + +class ThemeReplaceDocumentParams(TypedDict, total=False): + + document: Required[str] diff --git a/src/types/theme_replace_document_response.py b/src/types/theme_replace_document_response.py new file mode 100644 index 0000000..26395b2 --- /dev/null +++ b/src/types/theme_replace_document_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["ThemeReplaceDocumentResponse"] + +ThemeReplaceDocumentResponse: TypeAlias = None diff --git a/src/types/theme_retrieve_response.py b/src/types/theme_retrieve_response.py new file mode 100644 index 0000000..c4af558 --- /dev/null +++ b/src/types/theme_retrieve_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["ThemeRetrieveResponse"] + +ThemeRetrieveResponse: TypeAlias = str diff --git a/src/types/theme_update_params.py b/src/types/theme_update_params.py new file mode 100644 index 0000000..d7b438b --- /dev/null +++ b/src/types/theme_update_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ThemeUpdateParams"] + + +class ThemeUpdateParams(TypedDict, total=False): + + name: str + + description: str diff --git a/src/types/theme_update_response.py b/src/types/theme_update_response.py new file mode 100644 index 0000000..bde2d8e --- /dev/null +++ b/src/types/theme_update_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["ThemeUpdateResponse"] + +ThemeUpdateResponse: TypeAlias = None diff --git a/src/types/timestamp.py b/src/types/timestamp.py new file mode 100644 index 0000000..1185561 --- /dev/null +++ b/src/types/timestamp.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["Timestamp"] + +Timestamp: TypeAlias = int diff --git a/src/types/uid.py b/src/types/uid.py new file mode 100644 index 0000000..2be173b --- /dev/null +++ b/src/types/uid.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + + +from .._models import BaseModel +from .nanoid import Nanoid + +__all__ = ["Uid"] + + +class Uid(BaseModel): + + uid: Nanoid diff --git a/src/types/user.py b/src/types/user.py new file mode 100644 index 0000000..80a0a74 --- /dev/null +++ b/src/types/user.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from typing import List, Optional + +from .._models import BaseModel +from .nanoid import Nanoid +from .timestamp import Timestamp +from .email import Email +from .team_summary import TeamSummary + +__all__ = ["User"] + + +class User(BaseModel): + + uid: Nanoid + + createdAt: Timestamp + + updatedAt: Timestamp + + email: Email + + theme: Optional[str] = None + + activeTeamId: Optional[str] = None + + hasGithub: bool + + teams: List[TeamSummary] diff --git a/src/types/version.py b/src/types/version.py new file mode 100644 index 0000000..d8c8fff --- /dev/null +++ b/src/types/version.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +from __future__ import annotations + +from typing_extensions import TypeAlias + +__all__ = ["Version"] + +Version: TypeAlias = str diff --git a/tests/smoke-test.py b/tests/smoke-test.py new file mode 100644 index 0000000..b65b23a --- /dev/null +++ b/tests/smoke-test.py @@ -0,0 +1,732 @@ +# File generated from our OpenAPI spec by Scalar. See README.md for details. + +# Smoke test: calls every generated operation once to confirm the SDK can reach each endpoint. +# Run it from this repo with `python tests/smoke-test.py`. The generator also runs this file +# against a mock server and reads the JSON report produced via SCALAR_SMOKE_REPORT. +from __future__ import annotations + +import json +import os +import sys +import time +import traceback +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from typing import Any, Callable, TypedDict + +from scalar_api import Scalar + +# The shared smoke-test runner injects base URL and credentials through the same +# environment variables the generated client reads in normal use. +client = Scalar(max_retries=0, timeout=30) + + +class SmokeResult(TypedDict, total=False): + operation: str + method: str + path: str + status: str + durationMs: int + error: str + + +class SmokeCase(TypedDict): + operation: str + method: str + path: str + run: Callable[[], Any] + + +def _smoke_case_0() -> None: + registry = client.registry.list_all_api_documents() + +def _smoke_case_1() -> None: + registry = client.registry.list_api_documents( + namespace="namespace", + ) + +def _smoke_case_2() -> None: + registry = client.registry.create_api_document( + namespace="namespace", + title="", + version="", + slug="", + document="", + idempotency_key="", + ) + +def _smoke_case_3() -> None: + registry = client.registry.update_api_document( + namespace="namespace", + slug="slug", + idempotency_key="", + ) + +def _smoke_case_4() -> None: + registry = client.registry.delete_api_document( + namespace="namespace", + slug="slug", + idempotency_key="", + ) + +def _smoke_case_5() -> None: + registry = client.registry.retrieve_api_document_version( + namespace="namespace", + slug="slug", + semver="semver", + ) + +def _smoke_case_6() -> None: + registry = client.registry.update_api_document_version( + namespace="namespace", + slug="slug", + semver="semver", + document="", + idempotency_key="", + ) + +def _smoke_case_7() -> None: + registry = client.registry.delete_api_document_version( + namespace="namespace", + slug="slug", + semver="semver", + idempotency_key="", + ) + +def _smoke_case_8() -> None: + registry = client.registry.list_api_document_version_metadata( + namespace="namespace", + slug="slug", + semver="semver", + ) + +def _smoke_case_9() -> None: + registry = client.registry.create_api_document_version( + namespace="namespace", + slug="slug", + version="", + document="", + idempotency_key="", + ) + +def _smoke_case_10() -> None: + registry = client.registry.create_api_document_access_group( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", + ) + +def _smoke_case_11() -> None: + registry = client.registry.delete_api_document_access_group( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", + ) + +def _smoke_case_12() -> None: + schema = client.schemas.list( + namespace="namespace", + ) + +def _smoke_case_13() -> None: + schema = client.schemas.create( + namespace="namespace", + title="", + version="", + slug="", + document="", + idempotency_key="", + ) + +def _smoke_case_14() -> None: + schema = client.schemas.update( + namespace="namespace", + slug="slug", + idempotency_key="", + ) + +def _smoke_case_15() -> None: + schema = client.schemas.delete( + namespace="namespace", + slug="slug", + idempotency_key="", + ) + +def _smoke_case_16() -> None: + version = client.schemas.version.retrieve_schema( + namespace="namespace", + slug="slug", + semver="semver", + ) + +def _smoke_case_17() -> None: + version = client.schemas.version.delete_schema( + namespace="namespace", + slug="slug", + semver="semver", + idempotency_key="", + ) + +def _smoke_case_18() -> None: + version = client.schemas.version.create_schema( + namespace="namespace", + slug="slug", + version="", + document="", + idempotency_key="", + ) + +def _smoke_case_19() -> None: + access_group = client.schemas.access_group.create_schema( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", + ) + +def _smoke_case_20() -> None: + access_group = client.schemas.access_group.delete_schema( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", + ) + +def _smoke_case_21() -> None: + login_portal = client.login_portals.retrieve( + slug="slug", + ) + +def _smoke_case_22() -> None: + login_portal = client.login_portals.update( + slug="slug", + idempotency_key="", + ) + +def _smoke_case_23() -> None: + login_portal = client.login_portals.delete( + slug="slug", + idempotency_key="", + ) + +def _smoke_case_24() -> None: + login_portal = client.login_portals.create( + title="", + slug="", + email={"logo": "", "logo_size": "100", "button_text": "Login", "message": "Click to access private documentation hosted by scalar.com", "title": "Private Docs", "main_color": "#2a2f45", "main_background": "#f6f6f6", "card_color": "2a2f45", "card_background": "#fff", "button_color": "#fff", "button_background": "#0f0f0f"}, + page={"title": "Scalar Private Docs", "description": "Login to access your documentation", "head": "", "script": "", "theme": "", "company_name": "", "logo": "", "logo_url": "", "favicon": "", "terms_link": "", "privacy_link": "", "form_title": "Scalar Private Docs", "form_description": "Login to access your documentation", "form_image": ""}, + idempotency_key="", + ) + +def _smoke_case_25() -> None: + login_portal = client.login_portals.list() + +def _smoke_case_26() -> None: + rule = client.rules.list_rulesets( + namespace="namespace", + ) + +def _smoke_case_27() -> None: + rule = client.rules.create_ruleset( + namespace="namespace", + title="", + slug="", + document="", + idempotency_key="", + ) + +def _smoke_case_28() -> None: + rule = client.rules.update_ruleset( + namespace="namespace", + slug="slug", + idempotency_key="", + ) + +def _smoke_case_29() -> None: + rule = client.rules.delete_ruleset( + namespace="namespace", + slug="slug", + idempotency_key="", + ) + +def _smoke_case_30() -> None: + rule = client.rules.retrieve_ruleset_document( + namespace="namespace", + slug="slug", + ) + +def _smoke_case_31() -> None: + rule = client.rules.create_ruleset_access_group( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", + ) + +def _smoke_case_32() -> None: + rule = client.rules.delete_ruleset_access_group( + namespace="namespace", + slug="slug", + access_group_slug="", + idempotency_key="", + ) + +def _smoke_case_33() -> None: + theme = client.themes.list() + +def _smoke_case_34() -> None: + theme = client.themes.create( + name="", + slug="", + document="", + idempotency_key="", + ) + +def _smoke_case_35() -> None: + theme = client.themes.update( + slug="slug", + idempotency_key="", + ) + +def _smoke_case_36() -> None: + theme = client.themes.replace_document( + slug="slug", + document="", + idempotency_key="", + ) + +def _smoke_case_37() -> None: + theme = client.themes.delete( + slug="slug", + idempotency_key="", + ) + +def _smoke_case_38() -> None: + theme = client.themes.retrieve( + slug="slug", + ) + +def _smoke_case_39() -> None: + team = client.teams.list() + +def _smoke_case_40() -> None: + scalar_doc = client.scalar_docs.list_guides() + +def _smoke_case_41() -> None: + scalar_doc = client.scalar_docs.create_guide( + name="", + is_private=False, + allowed_users=[], + allowed_domains=[], + idempotency_key="", + ) + +def _smoke_case_42() -> None: + scalar_doc = client.scalar_docs.publish_guide( + slug="slug", + idempotency_key="", + ) + +def _smoke_case_43() -> None: + namespace = client.namespaces.list() + +def _smoke_case_44() -> None: + authentication = client.authentication.exchange_personal_token( + personal_token="", + idempotency_key="", + ) + +def _smoke_case_45() -> None: + authentication = client.authentication.list_current_user() + + +cases: list[SmokeCase] = [ + { + "operation": "listAllApiDocuments", + "method": "GET", + "path": "/v1/apis", + "run": _smoke_case_0, + }, + + { + "operation": "listApiDocuments", + "method": "GET", + "path": "/v1/apis/{namespace}", + "run": _smoke_case_1, + }, + + { + "operation": "createApiDocument", + "method": "POST", + "path": "/v1/apis/{namespace}", + "run": _smoke_case_2, + }, + + { + "operation": "updateApiDocument", + "method": "PATCH", + "path": "/v1/apis/{namespace}/{slug}", + "run": _smoke_case_3, + }, + + { + "operation": "deleteApiDocument", + "method": "DELETE", + "path": "/v1/apis/{namespace}/{slug}", + "run": _smoke_case_4, + }, + + { + "operation": "retrieveApiDocumentVersion", + "method": "GET", + "path": "/v1/apis/{namespace}/{slug}/version/{semver}", + "run": _smoke_case_5, + }, + + { + "operation": "updateApiDocumentVersion", + "method": "PATCH", + "path": "/v1/apis/{namespace}/{slug}/version/{semver}", + "run": _smoke_case_6, + }, + + { + "operation": "deleteApiDocumentVersion", + "method": "DELETE", + "path": "/v1/apis/{namespace}/{slug}/version/{semver}", + "run": _smoke_case_7, + }, + + { + "operation": "listApiDocumentVersionMetadata", + "method": "GET", + "path": "/v1/apis/{namespace}/{slug}/version/{semver}/metadata", + "run": _smoke_case_8, + }, + + { + "operation": "createApiDocumentVersion", + "method": "POST", + "path": "/v1/apis/{namespace}/{slug}/version", + "run": _smoke_case_9, + }, + + { + "operation": "createApiDocumentAccessGroup", + "method": "POST", + "path": "/v1/apis/{namespace}/{slug}/access-group", + "run": _smoke_case_10, + }, + + { + "operation": "deleteApiDocumentAccessGroup", + "method": "DELETE", + "path": "/v1/apis/{namespace}/{slug}/access-group", + "run": _smoke_case_11, + }, + + { + "operation": "list", + "method": "GET", + "path": "/v1/schemas/{namespace}", + "run": _smoke_case_12, + }, + + { + "operation": "create", + "method": "POST", + "path": "/v1/schemas/{namespace}", + "run": _smoke_case_13, + }, + + { + "operation": "update", + "method": "PATCH", + "path": "/v1/schemas/{namespace}/{slug}", + "run": _smoke_case_14, + }, + + { + "operation": "delete", + "method": "DELETE", + "path": "/v1/schemas/{namespace}/{slug}", + "run": _smoke_case_15, + }, + + { + "operation": "retrieveSchema", + "method": "GET", + "path": "/v1/schemas/{namespace}/{slug}/version/{semver}", + "run": _smoke_case_16, + }, + + { + "operation": "deleteSchema", + "method": "DELETE", + "path": "/v1/schemas/{namespace}/{slug}/version/{semver}", + "run": _smoke_case_17, + }, + + { + "operation": "createSchema", + "method": "POST", + "path": "/v1/schemas/{namespace}/{slug}/version", + "run": _smoke_case_18, + }, + + { + "operation": "createSchema", + "method": "POST", + "path": "/v1/schemas/{namespace}/{slug}/access-group", + "run": _smoke_case_19, + }, + + { + "operation": "deleteSchema", + "method": "DELETE", + "path": "/v1/schemas/{namespace}/{slug}/access-group", + "run": _smoke_case_20, + }, + + { + "operation": "retrieve", + "method": "GET", + "path": "/v1/login-portals/{slug}", + "run": _smoke_case_21, + }, + + { + "operation": "update", + "method": "PATCH", + "path": "/v1/login-portals/{slug}", + "run": _smoke_case_22, + }, + + { + "operation": "delete", + "method": "DELETE", + "path": "/v1/login-portals/{slug}", + "run": _smoke_case_23, + }, + + { + "operation": "create", + "method": "POST", + "path": "/v1/login-portals", + "run": _smoke_case_24, + }, + + { + "operation": "list", + "method": "GET", + "path": "/v1/login-portals", + "run": _smoke_case_25, + }, + + { + "operation": "listRulesets", + "method": "GET", + "path": "/v1/rulesets/{namespace}", + "run": _smoke_case_26, + }, + + { + "operation": "createRuleset", + "method": "POST", + "path": "/v1/rulesets/{namespace}", + "run": _smoke_case_27, + }, + + { + "operation": "updateRuleset", + "method": "PATCH", + "path": "/v1/rulesets/{namespace}/{slug}", + "run": _smoke_case_28, + }, + + { + "operation": "deleteRuleset", + "method": "DELETE", + "path": "/v1/rulesets/{namespace}/{slug}", + "run": _smoke_case_29, + }, + + { + "operation": "retrieveRulesetDocument", + "method": "GET", + "path": "/v1/rulesets/{namespace}/{slug}", + "run": _smoke_case_30, + }, + + { + "operation": "createRulesetAccessGroup", + "method": "POST", + "path": "/v1/rulesets/{namespace}/{slug}/access-group", + "run": _smoke_case_31, + }, + + { + "operation": "deleteRulesetAccessGroup", + "method": "DELETE", + "path": "/v1/rulesets/{namespace}/{slug}/access-group", + "run": _smoke_case_32, + }, + + { + "operation": "list", + "method": "GET", + "path": "/v1/themes", + "run": _smoke_case_33, + }, + + { + "operation": "create", + "method": "POST", + "path": "/v1/themes", + "run": _smoke_case_34, + }, + + { + "operation": "update", + "method": "PATCH", + "path": "/v1/themes/{slug}", + "run": _smoke_case_35, + }, + + { + "operation": "replaceDocument", + "method": "PUT", + "path": "/v1/themes/{slug}", + "run": _smoke_case_36, + }, + + { + "operation": "delete", + "method": "DELETE", + "path": "/v1/themes/{slug}", + "run": _smoke_case_37, + }, + + { + "operation": "retrieve", + "method": "GET", + "path": "/v1/themes/{slug}", + "run": _smoke_case_38, + }, + + { + "operation": "list", + "method": "GET", + "path": "/v1/teams", + "run": _smoke_case_39, + }, + + { + "operation": "listGuides", + "method": "GET", + "path": "/v1/guides", + "run": _smoke_case_40, + }, + + { + "operation": "createGuide", + "method": "POST", + "path": "/v1/guides", + "run": _smoke_case_41, + }, + + { + "operation": "publishGuide", + "method": "POST", + "path": "/v1/guides/{slug}/publish", + "run": _smoke_case_42, + }, + + { + "operation": "list", + "method": "GET", + "path": "/v1/namespaces", + "run": _smoke_case_43, + }, + + { + "operation": "exchangePersonalToken", + "method": "POST", + "path": "/v1/auth/exchange", + "run": _smoke_case_44, + }, + + { + "operation": "listCurrentUser", + "method": "GET", + "path": "/v1/auth/me", + "run": _smoke_case_45, + }, + +] + +def _selected_cases() -> list[SmokeCase]: + filter_value = os.environ.get("SCALAR_SMOKE_FILTER") + needles = [needle.strip() for needle in filter_value.split(",") if needle.strip()] if filter_value else [] + if not needles: + return cases + return [ + case + for case in cases + if any(needle in case["operation"] or needle in case["path"] for needle in needles) + ] + + +def _run_case(case: SmokeCase) -> SmokeResult: + started_at = time.monotonic() + try: + case["run"]() + return { + "operation": case["operation"], + "method": case["method"], + "path": case["path"], + "status": "passed", + "durationMs": int((time.monotonic() - started_at) * 1000), + } + except Exception: + return { + "operation": case["operation"], + "method": case["method"], + "path": case["path"], + "status": "failed", + "durationMs": int((time.monotonic() - started_at) * 1000), + "error": traceback.format_exc(), + } + + +def main() -> None: + selected = _selected_cases() + if selected: + with ThreadPoolExecutor(max_workers=len(selected)) as executor: + results = list(executor.map(_run_case, selected)) + else: + results = [] + failed = [result for result in results if result["status"] == "failed"] + + report_path = os.environ.get("SCALAR_SMOKE_REPORT") + if report_path: + Path(report_path).write_text(json.dumps({"total": len(results), "failed": len(failed), "results": results}), encoding="utf-8") + else: + for result in results: + if result["status"] == "passed": + print(f"PASS {result['operation']} ({result['method']} {result['path']}) {result['durationMs']}ms") + else: + print(f"FAIL {result['operation']} ({result['method']} {result['path']})\n{result.get('error', '')}", file=sys.stderr) + if not results: + print("No code samples ran (empty SDK or a SCALAR_SMOKE_FILTER that matched nothing).", file=sys.stderr) + else: + print(f"\n{len(results) - len(failed)}/{len(results)} samples passed") + + if failed or not results: + sys.exit(1) + + +if __name__ == "__main__": + main()