Compare commits

..

7 Commits

Author SHA1 Message Date
7ee7964440 update makefile
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2021-12-14 18:51:57 -08:00
789f0b2a3b fix the client
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2021-12-14 18:39:22 -08:00
62b021d566 bump the version
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2021-12-14 18:38:16 -08:00
27266e32ec update
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2021-12-14 18:29:47 -08:00
7b445af622 Update README.md 2021-12-14 18:19:40 -08:00
4366d70775 fix
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2021-12-08 14:47:38 -08:00
5eb7401824 fix readme
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2021-12-08 14:46:08 -08:00
14 changed files with 69 additions and 115 deletions

View File

@ -5,6 +5,10 @@ ifeq ($(INTERACTIVE), 1)
DOCKER_FLAGS += -t DOCKER_FLAGS += -t
endif 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 .PHONY: generate
generate: docker-image generate: docker-image
docker run --rm -i $(DOCKER_FLAGS) \ docker run --rm -i $(DOCKER_FLAGS) \
@ -19,6 +23,10 @@ generate: docker-image
docker-image: docker-image:
docker build -t $(DOCKER_IMAGE_NAME) . 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 .PHONY: help
help: help:

View File

@ -5,6 +5,9 @@ The Python API client for KittyCAD.
This is generated from This is generated from
[openapi-generators/openapi-python-client](https://github.com/openapi-generators/openapi-python-client). [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 ## Generating
You can trigger a build with the GitHub action to generate the client. This will You can trigger a build with the GitHub action to generate the client. This will

View File

@ -2,86 +2,42 @@
A client library for accessing KittyCAD A client library for accessing KittyCAD
## Usage ## Usage
First, create a client: First, create an authenticated 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:
```python ```python
from kittycad import AuthenticatedClient 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: Now call your endpoint and use your models:
```python ```python
from kittycad.models import MyDataModel from kittycad.models import AuthSession
from kittycad.api.my_tag import get_my_data_model from kittycad.api.meta import meta_debug_session
from kittycad.types import Response 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) # 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: Or do the same thing with an async version:
```python ```python
from kittycad.models import MyDataModel from kittycad.models import AuthSession
from kittycad.api.my_tag import get_my_data_model from kittycad.api.meta import meta_debug_session
from kittycad.types import Response from kittycad.types import Response
my_data: MyDataModel = await get_my_data_model.asyncio(client=client) session: AuthSession = await meta_debug_session.asyncio(client=client)
response: Response[MyDataModel] = await get_my_data_model.asyncio_detailed(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>`

View File

@ -50,6 +50,10 @@ def _parse_response(*, response: httpx.Response) -> Optional[Union[Any, FileConv
response_406 = None response_406 = None
return response_406 return response_406
if response.status_code == 500:
response_500 = None
return response_500
return None return None

View File

@ -54,6 +54,10 @@ def _parse_response(*, response: httpx.Response) -> Optional[Union[Any, FileConv
response_406 = None response_406 = None
return response_406 return response_406
if response.status_code == 500:
response_500 = None
return response_500
return None return None

View File

@ -3,7 +3,7 @@ from typing import Any, Dict, Optional
import httpx import httpx
from ...client import Client from ...client import Client
from ...models.ping_response_200 import PingResponse200 from ...models.message import Message
from ...types import Response 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: if response.status_code == 200:
response_200 = PingResponse200.from_dict(response.json()) response_200 = Message.from_dict(response.json())
return response_200 return response_200
return None return None
def _build_response(*, response: httpx.Response) -> Response[PingResponse200]: def _build_response(*, response: httpx.Response) -> Response[Message]:
return Response( return Response(
status_code=response.status_code, status_code=response.status_code,
content=response.content, content=response.content,
@ -44,7 +44,7 @@ def _build_response(*, response: httpx.Response) -> Response[PingResponse200]:
def sync_detailed( def sync_detailed(
*, *,
client: Client, client: Client,
) -> Response[PingResponse200]: ) -> Response[Message]:
kwargs = _get_kwargs( kwargs = _get_kwargs(
client=client, client=client,
) )
@ -60,7 +60,7 @@ def sync_detailed(
def sync( def sync(
*, *,
client: Client, client: Client,
) -> Optional[PingResponse200]: ) -> Optional[Message]:
"""Simple ping to the server.""" """Simple ping to the server."""
return sync_detailed( return sync_detailed(
@ -71,7 +71,7 @@ def sync(
async def asyncio_detailed( async def asyncio_detailed(
*, *,
client: Client, client: Client,
) -> Response[PingResponse200]: ) -> Response[Message]:
kwargs = _get_kwargs( kwargs = _get_kwargs(
client=client, client=client,
) )
@ -85,7 +85,7 @@ async def asyncio_detailed(
async def asyncio( async def asyncio(
*, *,
client: Client, client: Client,
) -> Optional[PingResponse200]: ) -> Optional[Message]:
"""Simple ping to the server.""" """Simple ping to the server."""
return ( return (

View File

@ -1,4 +1,3 @@
import os
import ssl import ssl
from typing import Dict, Union from typing import Dict, Union
@ -47,13 +46,3 @@ class AuthenticatedClient(Client):
def get_headers(self) -> Dict[str, str]: def get_headers(self) -> Dict[str, str]:
"""Get headers to be used in authenticated endpoints""" """Get headers to be used in authenticated endpoints"""
return {"Authorization": f"Bearer {self.token}", **self.headers} 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}

View File

@ -1,11 +1,10 @@
""" Contains all the data models used in inputs/outputs """ """ Contains all the data models used in inputs/outputs """
from .auth_session import AuthSession from .auth_session import AuthSession
from .environment import Environment
from .error_message import ErrorMessage from .error_message import ErrorMessage
from .file_conversion import FileConversion from .file_conversion import FileConversion
from .file_conversion_status import FileConversionStatus from .file_conversion_status import FileConversionStatus
from .instance_metadata import InstanceMetadata from .instance_metadata import InstanceMetadata
from .instance_metadata_environment import InstanceMetadataEnvironment from .message import Message
from .ping_response_200 import PingResponse200
from .ping_response_200_message import PingResponse200Message
from .valid_file_types import ValidFileTypes from .valid_file_types import ValidFileTypes

View File

@ -14,6 +14,7 @@ class AuthSession:
""" """ """ """
created_at: Union[Unset, datetime.datetime] = UNSET created_at: Union[Unset, datetime.datetime] = UNSET
email: Union[Unset, str] = UNSET
id: Union[Unset, str] = UNSET id: Union[Unset, str] = UNSET
ip_address: Union[Unset, str] = UNSET ip_address: Union[Unset, str] = UNSET
is_valid: Union[Unset, bool] = False is_valid: Union[Unset, bool] = False
@ -26,6 +27,7 @@ class AuthSession:
if not isinstance(self.created_at, Unset): if not isinstance(self.created_at, Unset):
created_at = self.created_at.isoformat() created_at = self.created_at.isoformat()
email = self.email
id = self.id id = self.id
ip_address = self.ip_address ip_address = self.ip_address
is_valid = self.is_valid is_valid = self.is_valid
@ -37,6 +39,8 @@ class AuthSession:
field_dict.update({}) field_dict.update({})
if created_at is not UNSET: if created_at is not UNSET:
field_dict["created_at"] = created_at field_dict["created_at"] = created_at
if email is not UNSET:
field_dict["email"] = email
if id is not UNSET: if id is not UNSET:
field_dict["id"] = id field_dict["id"] = id
if ip_address is not UNSET: if ip_address is not UNSET:
@ -60,6 +64,8 @@ class AuthSession:
else: else:
created_at = isoparse(_created_at) created_at = isoparse(_created_at)
email = d.pop("email", UNSET)
id = d.pop("id", UNSET) id = d.pop("id", UNSET)
ip_address = d.pop("ip_address", UNSET) ip_address = d.pop("ip_address", UNSET)
@ -72,6 +78,7 @@ class AuthSession:
auth_session = cls( auth_session = cls(
created_at=created_at, created_at=created_at,
email=email,
id=id, id=id,
ip_address=ip_address, ip_address=ip_address,
is_valid=is_valid, is_valid=is_valid,

View File

@ -1,7 +1,7 @@
from enum import Enum from enum import Enum
class InstanceMetadataEnvironment(str, Enum): class Environment(str, Enum):
DEVELOPMENT = "DEVELOPMENT" DEVELOPMENT = "DEVELOPMENT"
PREVIEW = "PREVIEW" PREVIEW = "PREVIEW"
PRODUCTION = "PRODUCTION" PRODUCTION = "PRODUCTION"

View File

@ -2,7 +2,7 @@ from typing import Any, Dict, List, Type, TypeVar, Union
import attr import attr
from ..models.instance_metadata_environment import InstanceMetadataEnvironment from ..models.environment import Environment
from ..types import UNSET, Unset from ..types import UNSET, Unset
T = TypeVar("T", bound="InstanceMetadata") T = TypeVar("T", bound="InstanceMetadata")
@ -14,7 +14,7 @@ class InstanceMetadata:
cpu_platform: Union[Unset, str] = UNSET cpu_platform: Union[Unset, str] = UNSET
description: 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 git_hash: Union[Unset, str] = UNSET
hostname: Union[Unset, str] = UNSET hostname: Union[Unset, str] = UNSET
id: Union[Unset, str] = UNSET id: Union[Unset, str] = UNSET
@ -77,11 +77,11 @@ class InstanceMetadata:
description = d.pop("description", UNSET) description = d.pop("description", UNSET)
_environment = d.pop("environment", UNSET) _environment = d.pop("environment", UNSET)
environment: Union[Unset, InstanceMetadataEnvironment] environment: Union[Unset, Environment]
if isinstance(_environment, Unset): if isinstance(_environment, Unset):
environment = UNSET environment = UNSET
else: else:
environment = InstanceMetadataEnvironment(_environment) environment = Environment(_environment)
git_hash = d.pop("git_hash", UNSET) git_hash = d.pop("git_hash", UNSET)

View File

@ -2,23 +2,20 @@ from typing import Any, Dict, List, Type, TypeVar, Union
import attr import attr
from ..models.ping_response_200_message import PingResponse200Message
from ..types import UNSET, Unset from ..types import UNSET, Unset
T = TypeVar("T", bound="PingResponse200") T = TypeVar("T", bound="Message")
@attr.s(auto_attribs=True) @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) additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
message: Union[Unset, str] = UNSET message = self.message
if not isinstance(self.message, Unset):
message = self.message.value
field_dict: Dict[str, Any] = {} field_dict: Dict[str, Any] = {}
field_dict.update(self.additional_properties) field_dict.update(self.additional_properties)
@ -31,19 +28,14 @@ class PingResponse200:
@classmethod @classmethod
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
d = src_dict.copy() d = src_dict.copy()
_message = d.pop("message", UNSET) message = d.pop("message", UNSET)
message: Union[Unset, PingResponse200Message]
if isinstance(_message, Unset):
message = UNSET
else:
message = PingResponse200Message(_message)
ping_response_200 = cls( message = cls(
message=message, message=message,
) )
ping_response_200.additional_properties = d message.additional_properties = d
return ping_response_200 return message
@property @property
def additional_keys(self) -> List[str]: def additional_keys(self) -> List[str]:

View File

@ -1,8 +0,0 @@
from enum import Enum
class PingResponse200Message(str, Enum):
PONG = "pong"
def __str__(self) -> str:
return str(self.value)

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "kittycad" name = "kittycad"
version = "0.0.1" version = "0.0.3"
description = "A client library for accessing KittyCAD" description = "A client library for accessing KittyCAD"
authors = [] authors = []