Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
7ee7964440 | |||
789f0b2a3b | |||
62b021d566 | |||
27266e32ec | |||
7b445af622 | |||
4366d70775 | |||
5eb7401824 |
8
Makefile
8
Makefile
@ -5,6 +5,10 @@ ifeq ($(INTERACTIVE), 1)
|
||||
DOCKER_FLAGS += -t
|
||||
endif
|
||||
|
||||
# For this to work, you need to install toml-cli: https://github.com/gnprice/toml-cli
|
||||
# `cargo install toml-cli`
|
||||
VERSION := $(shell toml get $(CURDIR)/kittycad/pyproject.toml tool.poetry.version | jq -r .)
|
||||
|
||||
.PHONY: generate
|
||||
generate: docker-image
|
||||
docker run --rm -i $(DOCKER_FLAGS) \
|
||||
@ -19,6 +23,10 @@ generate: docker-image
|
||||
docker-image:
|
||||
docker build -t $(DOCKER_IMAGE_NAME) .
|
||||
|
||||
.PHONY: tag
|
||||
tag: ## Create a new git tag to prepare to build a release.
|
||||
git tag -sa "v$(VERSION)" -m "v$(VERSION)"
|
||||
@echo "Run git push origin v$(VERSION) to push your new tag to GitHub and trigger a release."
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
|
@ -5,6 +5,9 @@ The Python API client for KittyCAD.
|
||||
This is generated from
|
||||
[openapi-generators/openapi-python-client](https://github.com/openapi-generators/openapi-python-client).
|
||||
|
||||
- [PyPI](https://pypi.org/project/kittycad/)
|
||||
- [KittyCAD API Docs](https://docs.kittycad.io/?lang=python)
|
||||
|
||||
## Generating
|
||||
|
||||
You can trigger a build with the GitHub action to generate the client. This will
|
||||
|
@ -2,86 +2,42 @@
|
||||
A client library for accessing KittyCAD
|
||||
|
||||
## Usage
|
||||
First, create a client:
|
||||
|
||||
```python
|
||||
from kittycad import Client
|
||||
|
||||
client = Client(base_url="https://api.example.com")
|
||||
```
|
||||
|
||||
If the endpoints you're going to hit require authentication, use `AuthenticatedClient` instead:
|
||||
First, create an authenticated client:
|
||||
|
||||
```python
|
||||
from kittycad import AuthenticatedClient
|
||||
|
||||
client = AuthenticatedClient(base_url="https://api.example.com", token="SuperSecretToken")
|
||||
client = AuthenticatedClient(token="your_token")
|
||||
```
|
||||
|
||||
If you want to use the environment variable `KITTYCAD_API_TOKEN` to do
|
||||
authentication and not pass one to the client, do the following:
|
||||
|
||||
```python
|
||||
from kittycad import AuthenticatedClientFromEnv
|
||||
|
||||
client = AuthenticatedClientFromEnv()
|
||||
```
|
||||
|
||||
Now call your endpoint and use your models:
|
||||
|
||||
```python
|
||||
from kittycad.models import MyDataModel
|
||||
from kittycad.api.my_tag import get_my_data_model
|
||||
from kittycad.models import AuthSession
|
||||
from kittycad.api.meta import meta_debug_session
|
||||
from kittycad.types import Response
|
||||
|
||||
my_data: MyDataModel = get_my_data_model.sync(client=client)
|
||||
session: AuthSession = meta_debug_session.sync(client=client)
|
||||
# or if you need more info (e.g. status_code)
|
||||
response: Response[MyDataModel] = get_my_data_model.sync_detailed(client=client)
|
||||
response: Response[AuthSession] = meta_debug_session.sync_detailed(client=client)
|
||||
```
|
||||
|
||||
Or do the same thing with an async version:
|
||||
|
||||
```python
|
||||
from kittycad.models import MyDataModel
|
||||
from kittycad.api.my_tag import get_my_data_model
|
||||
from kittycad.models import AuthSession
|
||||
from kittycad.api.meta import meta_debug_session
|
||||
from kittycad.types import Response
|
||||
|
||||
my_data: MyDataModel = await get_my_data_model.asyncio(client=client)
|
||||
response: Response[MyDataModel] = await get_my_data_model.asyncio_detailed(client=client)
|
||||
session: AuthSession = await meta_debug_session.asyncio(client=client)
|
||||
response: Response[AuthSession] = await meta_debug_session.asyncio_detailed(client=client)
|
||||
```
|
||||
|
||||
By default, when you're calling an HTTPS API it will attempt to verify that SSL is working correctly. Using certificate verification is highly recommended most of the time, but sometimes you may need to authenticate to a server (especially an internal server) using a custom certificate bundle.
|
||||
|
||||
```python
|
||||
client = AuthenticatedClient(
|
||||
base_url="https://internal_api.example.com",
|
||||
token="SuperSecretToken",
|
||||
verify_ssl="/path/to/certificate_bundle.pem",
|
||||
)
|
||||
```
|
||||
|
||||
You can also disable certificate validation altogether, but beware that **this is a security risk**.
|
||||
|
||||
```python
|
||||
client = AuthenticatedClient(
|
||||
base_url="https://internal_api.example.com",
|
||||
token="SuperSecretToken",
|
||||
verify_ssl=False
|
||||
)
|
||||
```
|
||||
|
||||
Things to know:
|
||||
1. Every path/method combo becomes a Python module with four functions:
|
||||
1. `sync`: Blocking request that returns parsed data (if successful) or `None`
|
||||
1. `sync_detailed`: Blocking request that always returns a `Request`, optionally with `parsed` set if the request was successful.
|
||||
1. `asyncio`: Like `sync` but the async instead of blocking
|
||||
1. `asyncio_detailed`: Like `sync_detailed` by async instead of blocking
|
||||
|
||||
1. All path/query params, and bodies become method arguments.
|
||||
1. If your endpoint had any tags on it, the first tag will be used as a module name for the function (my_tag above)
|
||||
1. Any endpoint which did not have a tag will be in `kittycad.api.default`
|
||||
|
||||
## Building / publishing this Client
|
||||
This project uses [Poetry](https://python-poetry.org/) to manage dependencies and packaging. Here are the basics:
|
||||
1. Update the metadata in pyproject.toml (e.g. authors, version)
|
||||
1. If you're using a private repository, configure it with Poetry
|
||||
1. `poetry config repositories.<your-repository-name> <url-to-your-repository>`
|
||||
1. `poetry config http-basic.<your-repository-name> <username> <password>`
|
||||
1. Publish the client with `poetry publish --build -r <your-repository-name>` or, if for public PyPI, just `poetry publish --build`
|
||||
|
||||
If you want to install this client into another project without publishing it (e.g. for development) then:
|
||||
1. If that project **is using Poetry**, you can simply do `poetry add <path-to-this-client>` from that project
|
||||
1. If that project is not using Poetry:
|
||||
1. Build a wheel with `poetry build -f wheel`
|
||||
1. Install that wheel from the other project `pip install <path-to-wheel>`
|
@ -50,6 +50,10 @@ def _parse_response(*, response: httpx.Response) -> Optional[Union[Any, FileConv
|
||||
response_406 = None
|
||||
|
||||
return response_406
|
||||
if response.status_code == 500:
|
||||
response_500 = None
|
||||
|
||||
return response_500
|
||||
return None
|
||||
|
||||
|
||||
|
@ -54,6 +54,10 @@ def _parse_response(*, response: httpx.Response) -> Optional[Union[Any, FileConv
|
||||
response_406 = None
|
||||
|
||||
return response_406
|
||||
if response.status_code == 500:
|
||||
response_500 = None
|
||||
|
||||
return response_500
|
||||
return None
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ from typing import Any, Dict, Optional
|
||||
import httpx
|
||||
|
||||
from ...client import Client
|
||||
from ...models.ping_response_200 import PingResponse200
|
||||
from ...models.message import Message
|
||||
from ...types import Response
|
||||
|
||||
|
||||
@ -24,15 +24,15 @@ def _get_kwargs(
|
||||
}
|
||||
|
||||
|
||||
def _parse_response(*, response: httpx.Response) -> Optional[PingResponse200]:
|
||||
def _parse_response(*, response: httpx.Response) -> Optional[Message]:
|
||||
if response.status_code == 200:
|
||||
response_200 = PingResponse200.from_dict(response.json())
|
||||
response_200 = Message.from_dict(response.json())
|
||||
|
||||
return response_200
|
||||
return None
|
||||
|
||||
|
||||
def _build_response(*, response: httpx.Response) -> Response[PingResponse200]:
|
||||
def _build_response(*, response: httpx.Response) -> Response[Message]:
|
||||
return Response(
|
||||
status_code=response.status_code,
|
||||
content=response.content,
|
||||
@ -44,7 +44,7 @@ def _build_response(*, response: httpx.Response) -> Response[PingResponse200]:
|
||||
def sync_detailed(
|
||||
*,
|
||||
client: Client,
|
||||
) -> Response[PingResponse200]:
|
||||
) -> Response[Message]:
|
||||
kwargs = _get_kwargs(
|
||||
client=client,
|
||||
)
|
||||
@ -60,7 +60,7 @@ def sync_detailed(
|
||||
def sync(
|
||||
*,
|
||||
client: Client,
|
||||
) -> Optional[PingResponse200]:
|
||||
) -> Optional[Message]:
|
||||
"""Simple ping to the server."""
|
||||
|
||||
return sync_detailed(
|
||||
@ -71,7 +71,7 @@ def sync(
|
||||
async def asyncio_detailed(
|
||||
*,
|
||||
client: Client,
|
||||
) -> Response[PingResponse200]:
|
||||
) -> Response[Message]:
|
||||
kwargs = _get_kwargs(
|
||||
client=client,
|
||||
)
|
||||
@ -85,7 +85,7 @@ async def asyncio_detailed(
|
||||
async def asyncio(
|
||||
*,
|
||||
client: Client,
|
||||
) -> Optional[PingResponse200]:
|
||||
) -> Optional[Message]:
|
||||
"""Simple ping to the server."""
|
||||
|
||||
return (
|
||||
|
@ -1,4 +1,3 @@
|
||||
import os
|
||||
import ssl
|
||||
from typing import Dict, Union
|
||||
|
||||
@ -47,13 +46,3 @@ class AuthenticatedClient(Client):
|
||||
def get_headers(self) -> Dict[str, str]:
|
||||
"""Get headers to be used in authenticated endpoints"""
|
||||
return {"Authorization": f"Bearer {self.token}", **self.headers}
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class AuthenticatedClientFromEnv(Client):
|
||||
"""A Client which has been authenticated for use on secured endpoints that uses the KITTYCAD_API_TOKEN environment variable for the authentication token."""
|
||||
|
||||
token: str = attr.ib(default=os.getenv('KITTYCAD_API_TOKEN'))
|
||||
|
||||
def get_headers(self) -> Dict[str, str]:
|
||||
"""Get headers to be used in authenticated endpoints"""
|
||||
return {"Authorization": f"Bearer {self.token}", **self.headers}
|
||||
|
@ -1,11 +1,10 @@
|
||||
""" Contains all the data models used in inputs/outputs """
|
||||
|
||||
from .auth_session import AuthSession
|
||||
from .environment import Environment
|
||||
from .error_message import ErrorMessage
|
||||
from .file_conversion import FileConversion
|
||||
from .file_conversion_status import FileConversionStatus
|
||||
from .instance_metadata import InstanceMetadata
|
||||
from .instance_metadata_environment import InstanceMetadataEnvironment
|
||||
from .ping_response_200 import PingResponse200
|
||||
from .ping_response_200_message import PingResponse200Message
|
||||
from .message import Message
|
||||
from .valid_file_types import ValidFileTypes
|
||||
|
@ -14,6 +14,7 @@ class AuthSession:
|
||||
""" """
|
||||
|
||||
created_at: Union[Unset, datetime.datetime] = UNSET
|
||||
email: Union[Unset, str] = UNSET
|
||||
id: Union[Unset, str] = UNSET
|
||||
ip_address: Union[Unset, str] = UNSET
|
||||
is_valid: Union[Unset, bool] = False
|
||||
@ -26,6 +27,7 @@ class AuthSession:
|
||||
if not isinstance(self.created_at, Unset):
|
||||
created_at = self.created_at.isoformat()
|
||||
|
||||
email = self.email
|
||||
id = self.id
|
||||
ip_address = self.ip_address
|
||||
is_valid = self.is_valid
|
||||
@ -37,6 +39,8 @@ class AuthSession:
|
||||
field_dict.update({})
|
||||
if created_at is not UNSET:
|
||||
field_dict["created_at"] = created_at
|
||||
if email is not UNSET:
|
||||
field_dict["email"] = email
|
||||
if id is not UNSET:
|
||||
field_dict["id"] = id
|
||||
if ip_address is not UNSET:
|
||||
@ -60,6 +64,8 @@ class AuthSession:
|
||||
else:
|
||||
created_at = isoparse(_created_at)
|
||||
|
||||
email = d.pop("email", UNSET)
|
||||
|
||||
id = d.pop("id", UNSET)
|
||||
|
||||
ip_address = d.pop("ip_address", UNSET)
|
||||
@ -72,6 +78,7 @@ class AuthSession:
|
||||
|
||||
auth_session = cls(
|
||||
created_at=created_at,
|
||||
email=email,
|
||||
id=id,
|
||||
ip_address=ip_address,
|
||||
is_valid=is_valid,
|
||||
|
@ -1,7 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class InstanceMetadataEnvironment(str, Enum):
|
||||
class Environment(str, Enum):
|
||||
DEVELOPMENT = "DEVELOPMENT"
|
||||
PREVIEW = "PREVIEW"
|
||||
PRODUCTION = "PRODUCTION"
|
@ -2,7 +2,7 @@ from typing import Any, Dict, List, Type, TypeVar, Union
|
||||
|
||||
import attr
|
||||
|
||||
from ..models.instance_metadata_environment import InstanceMetadataEnvironment
|
||||
from ..models.environment import Environment
|
||||
from ..types import UNSET, Unset
|
||||
|
||||
T = TypeVar("T", bound="InstanceMetadata")
|
||||
@ -14,7 +14,7 @@ class InstanceMetadata:
|
||||
|
||||
cpu_platform: Union[Unset, str] = UNSET
|
||||
description: Union[Unset, str] = UNSET
|
||||
environment: Union[Unset, InstanceMetadataEnvironment] = UNSET
|
||||
environment: Union[Unset, Environment] = UNSET
|
||||
git_hash: Union[Unset, str] = UNSET
|
||||
hostname: Union[Unset, str] = UNSET
|
||||
id: Union[Unset, str] = UNSET
|
||||
@ -77,11 +77,11 @@ class InstanceMetadata:
|
||||
description = d.pop("description", UNSET)
|
||||
|
||||
_environment = d.pop("environment", UNSET)
|
||||
environment: Union[Unset, InstanceMetadataEnvironment]
|
||||
environment: Union[Unset, Environment]
|
||||
if isinstance(_environment, Unset):
|
||||
environment = UNSET
|
||||
else:
|
||||
environment = InstanceMetadataEnvironment(_environment)
|
||||
environment = Environment(_environment)
|
||||
|
||||
git_hash = d.pop("git_hash", UNSET)
|
||||
|
||||
|
@ -2,23 +2,20 @@ from typing import Any, Dict, List, Type, TypeVar, Union
|
||||
|
||||
import attr
|
||||
|
||||
from ..models.ping_response_200_message import PingResponse200Message
|
||||
from ..types import UNSET, Unset
|
||||
|
||||
T = TypeVar("T", bound="PingResponse200")
|
||||
T = TypeVar("T", bound="Message")
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class PingResponse200:
|
||||
class Message:
|
||||
""" """
|
||||
|
||||
message: Union[Unset, PingResponse200Message] = UNSET
|
||||
message: Union[Unset, str] = UNSET
|
||||
additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
message: Union[Unset, str] = UNSET
|
||||
if not isinstance(self.message, Unset):
|
||||
message = self.message.value
|
||||
message = self.message
|
||||
|
||||
field_dict: Dict[str, Any] = {}
|
||||
field_dict.update(self.additional_properties)
|
||||
@ -31,19 +28,14 @@ class PingResponse200:
|
||||
@classmethod
|
||||
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
|
||||
d = src_dict.copy()
|
||||
_message = d.pop("message", UNSET)
|
||||
message: Union[Unset, PingResponse200Message]
|
||||
if isinstance(_message, Unset):
|
||||
message = UNSET
|
||||
else:
|
||||
message = PingResponse200Message(_message)
|
||||
message = d.pop("message", UNSET)
|
||||
|
||||
ping_response_200 = cls(
|
||||
message = cls(
|
||||
message=message,
|
||||
)
|
||||
|
||||
ping_response_200.additional_properties = d
|
||||
return ping_response_200
|
||||
message.additional_properties = d
|
||||
return message
|
||||
|
||||
@property
|
||||
def additional_keys(self) -> List[str]:
|
@ -1,8 +0,0 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class PingResponse200Message(str, Enum):
|
||||
PONG = "pong"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.value)
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "kittycad"
|
||||
version = "0.0.1"
|
||||
version = "0.0.3"
|
||||
description = "A client library for accessing KittyCAD"
|
||||
|
||||
authors = []
|
||||
|
Reference in New Issue
Block a user