start of refactor

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2022-02-27 18:39:04 -08:00
parent e7aaaab78d
commit 2a3cec9aac
10 changed files with 817 additions and 72 deletions

0
.dockerignore Normal file
View File

View File

@ -0,0 +1,69 @@
on:
push:
tags:
- v*
pull_request:
paths:
- .github/workflows/update-spec-for-docs.yml
workflow_dispatch:
name: update spec for docs
concurrency:
group: docs-${{ github.ref }}
cancel-in-progress: true
jobs:
update-spec:
name: update-spec
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '1.x'
- name: make generate
shell: bash
run: |
make generate
# Ensure no files changed.
- name: Ensure no files changed
shell: bash
run: |
if [[ `git status --porcelain` ]]; then
echo "Files changed, exiting";
exit 1;
else
# No changes
echo "No files changed, proceeding";
fi
# Checkout the docs repo since we will want to update the files there.
- uses: actions/checkout@v2
with:
repository: 'kittycad/docs'
path: 'docs'
token: ${{secrets.PAT_GITHUB}}
- name: move spec to docs
shell: bash
run: |
rm docs/spec.json || true
cp spec.json docs/spec.json
- name: commit the changes in the docs repo
shell: bash
run: |
export VERSION=$(cat VERSION.txt);
cd docs
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add .
git commit -am "YOYO NEW SPEC DOCS ${VERSION}!" || exit 0
git fetch origin
git rebase origin/main || exit 0
export NEW_BRANCH="update-spec-${VERSION}"
git checkout -b "$NEW_BRANCH"
git push -f origin "$NEW_BRANCH"
gh pr create --title "Update lang spec docs for ${VERSION}" \
--body "Updating the generated docs for go lang" \
--head "$NEW_BRANCH" \
--base main || true
env:
GITHUB_TOKEN: ${{secrets.PAT_GITHUB}}

View File

@ -1,7 +1,13 @@
FROM python:latest
RUN pip install \
openapi-python-client
poetry
WORKDIR /usr/src/
COPY . /usr/src/
RUN poetry install
# Set the default command to bash.
CMD ["bash"]

View File

@ -10,14 +10,21 @@ endif
VERSION := $(shell toml get $(CURDIR)/pyproject.toml tool.poetry.version | jq -r .)
.PHONY: generate
generate: docker-image
generate: docker-image ## Generate the api client.
docker run --rm -i $(DOCKER_FLAGS) \
--name python-generator \
-v $(CURDIR):/usr/kittycad \
--workdir /usr \
$(DOCKER_IMAGE_NAME) openapi-python-client update \
--url https://api.kittycad.io \
--config /usr/kittycad/config.yml
-v $(CURDIR):/usr/src \
--workdir /usr/src \
$(DOCKER_IMAGE_NAME) poetry run python generate/generate.py
.PHONY: shell
shell: docker-image ## Pop into a shell in the docker image.
docker run --rm -i $(DOCKER_FLAGS) \
--name python-generator-shell \
-v $(CURDIR):/usr/src \
--workdir /usr/src \
$(DOCKER_IMAGE_NAME) /bin/bash
.PHONY: docker-image
docker-image:

View File

@ -2,9 +2,6 @@
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/)
- [Python docs](https://python.api.docs.kittycad.io/)
- [KittyCAD API Docs](https://docs.kittycad.io/?lang=python)
@ -26,44 +23,3 @@ $ make generate
Please do not change the code directly since it is generated. PRs that change
the code directly will be automatically closed by a bot.
## Usage
First, create an authenticated client:
```python
from kittycad import AuthenticatedClient
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 AuthSession
from kittycad.api.meta import meta_debug_session
from kittycad.types import Response
session: AuthSession = meta_debug_session.sync(client=client)
# or if you need more info (e.g. status_code)
response: Response[AuthSession] = meta_debug_session.sync_detailed(client=client)
```
Or do the same thing with an async version:
```python
from kittycad.models import AuthSession
from kittycad.api.meta import meta_debug_session
from kittycad.types import Response
session: AuthSession = await meta_debug_session.asyncio(client=client)
response: Response[AuthSession] = await meta_debug_session.asyncio_detailed(client=client)
```

38
generate/generate.py Executable file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env python3
from openapi_parser.parser.loader import OpenApiParser
import os
package_name = 'kittycad'
def main():
cwd = os.getcwd()
path = os.path.join(cwd, 'spec.json')
print("opening spec file: ", path)
parser = OpenApiParser.open(path)
# Ignore the security definitions.
parser.load_metadata()
parser.load_schemas()
parser.load_path_items()
# Generate the types.
generateTypes(cwd, parser)
print([parser])
def generateTypes(cwd: str, parser: OpenApiParser):
# Make sure we have the directory.
path = os.path.join(cwd, 'kittycad', 'models')
os.makedirs(path, exist_ok=True)
# Generate the types.
data = parser.data
schemas = data['components']['schemas']
for key in schemas:
schema = schemas[key]
print(key)
if (__name__ == '__main__'):
exit_code = main()
exit(exit_code)

View File

@ -7,8 +7,9 @@ import attr
@attr.s(auto_attribs=True)
class Client:
"""A class for keeping track of data related to the API"""
"""A Client which has been authenticated for use on secured endpoints of the KittyCAD API."""
token: str = attr.ib(kw_only=True)
base_url: str = attr.ib(default="https://api.kittycad.io")
cookies: Dict[str, str] = attr.ib(factory=dict, kw_only=True)
headers: Dict[str, str] = attr.ib(factory=dict, kw_only=True)
@ -17,7 +18,7 @@ class Client:
def get_headers(self) -> Dict[str, str]:
"""Get headers to be used in all endpoints"""
return {**self.headers}
return {"Authorization": f"Bearer {self.token}", **self.headers}
def with_headers(self, headers: Dict[str, str]) -> "Client":
"""Get a new client matching this one with additional headers"""
@ -39,17 +40,7 @@ class Client:
@attr.s(auto_attribs=True)
class AuthenticatedClient(Client):
"""A Client which has been authenticated for use on secured endpoints"""
token: str = attr.ib(kw_only=True)
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):
class ClientFromEnv(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'))

View File

@ -9,7 +9,7 @@ from .api.meta import meta_debug_session, meta_debug_instance, ping
def test_get_session():
# Create our client.
client = AuthenticatedClientFromEnv()
client = ClientFromEnv()
# Get the session.
session: AuthSession = meta_debug_session.sync(client=client)
@ -21,7 +21,7 @@ def test_get_session():
@pytest.mark.asyncio
async def test_get_session_async():
# Create our client.
client = AuthenticatedClientFromEnv()
client = ClientFromEnv()
# Get the session.
session: AuthSession = await meta_debug_session.asyncio(client=client)
@ -32,7 +32,7 @@ async def test_get_session_async():
def test_get_instance():
# Create our client.
client = AuthenticatedClientFromEnv()
client = ClientFromEnv()
# Get the instance.
instance: InstanceMetadata = meta_debug_instance.sync(client=client)
@ -44,7 +44,7 @@ def test_get_instance():
@pytest.mark.asyncio
async def test_get_instance_async():
# Create our client.
client = AuthenticatedClientFromEnv()
client = ClientFromEnv()
# Get the instance.
instance: InstanceMetadata = await meta_debug_instance.asyncio(client=client)
@ -55,7 +55,7 @@ async def test_get_instance_async():
def test_ping():
# Create our client.
client = AuthenticatedClientFromEnv()
client = ClientFromEnv()
# Get the message.
message: Message = ping.sync(client=client)
@ -67,7 +67,7 @@ def test_ping():
@pytest.mark.asyncio
async def test_ping_async():
# Create our client.
client = AuthenticatedClientFromEnv()
client = ClientFromEnv()
# Get the message.
message: Message = await ping.asyncio(client=client)
@ -78,7 +78,7 @@ async def test_ping_async():
def test_file_convert_stl():
# Create our client.
client = AuthenticatedClientFromEnv()
client = ClientFromEnv()
dir_path = os.path.dirname(os.path.realpath(__file__))
file = open(os.path.join(dir_path, "../assets/testing.stl"), "rb")
@ -95,7 +95,7 @@ def test_file_convert_stl():
@pytest.mark.asyncio
async def test_file_convert_stl_async():
# Create our client.
client = AuthenticatedClientFromEnv()
client = ClientFromEnv()
dir_path = os.path.dirname(os.path.realpath(__file__))
file = open(os.path.join(dir_path, "../assets/testing.stl"), "rb")

View File

@ -29,6 +29,7 @@ sphinx-rtd-theme = "^1.0.0"
sphinx-automodapi = "^0.13"
pytest-cov = "^3.0.0"
pytest-asyncio = "^0.16.0"
openapi-parser = "^0.2.6"
[build-system]
requires = ["poetry>=1.0"]

677
spec.json Normal file
View File

@ -0,0 +1,677 @@
{
"components": {
"responses": {
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorMessage"
}
}
},
"description": "Bad Request"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorMessage"
}
}
},
"description": "Unauthorized"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorMessage"
}
}
},
"description": "Forbidden"
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorMessage"
}
}
},
"description": "Not Found"
},
"406": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorMessage"
}
}
},
"description": "Not Acceptable"
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorMessage"
}
}
},
"description": "Internal Server Error"
}
},
"schemas": {
"AuthSession": {
"properties": {
"created_at": {
"description": "The date and time the session/request was created.",
"format": "date-time",
"type": "string"
},
"email": {
"description": "The user's email address.",
"format": "email",
"type": "string"
},
"id": {
"description": "The unique identifier of the session.",
"type": "string"
},
"image": {
"description": "The virtual machine image the instance used as a base.",
"type": "string"
},
"ip_address": {
"description": "The IP address the request originated from.",
"format": "ip",
"type": "string"
},
"is_valid": {
"description": "If the token is valid.",
"type": "boolean"
},
"token": {
"description": "The token the user provided for the request.",
"type": "string"
},
"user_id": {
"description": "The unique identifier of the user.",
"type": "string"
}
},
"type": "object"
},
"ErrorMessage": {
"properties": {
"code": {
"description": "Status code",
"maximum": 2147483647,
"minimum": -2147483648,
"type": "integer"
},
"message": {
"description": "Verbose message",
"type": "string"
},
"status": {
"description": "Short status text",
"type": "string"
}
},
"type": "object"
},
"FileConversion": {
"properties": {
"completed_at": {
"description": "The date and time the file conversion was completed.",
"format": "date-time",
"type": "string"
},
"created_at": {
"description": "The date and time the file conversion was created.",
"format": "date-time",
"type": "string"
},
"id": {
"description": "The unique identifier of the file conversion.",
"type": "string"
},
"output": {
"description": "The converted file, base64 encoded. If the conversion failed, this field will show any errors.",
"type": "string"
},
"output_format": {
"$ref": "#/components/schemas/ValidOutputFileFormat"
},
"src_format": {
"$ref": "#/components/schemas/ValidSourceFileFormat"
},
"started_at": {
"description": "The date and time the file conversion was completed.",
"format": "date-time",
"type": "string"
},
"status": {
"$ref": "#/components/schemas/FileConversionStatus"
}
},
"type": "object"
},
"FileConversionStatus": {
"enum": [
"Queued",
"Uploaded",
"In Progress",
"Completed",
"Failed"
],
"type": "string"
},
"GPUDevice": {
"properties": {
"id": {
"description": "The unique identifier of the device.",
"format": "int64",
"type": "integer"
},
"memory_bus_width": {
"description": "The memory bus width of the device.",
"format": "int64",
"type": "integer"
},
"memory_clock_rate": {
"description": "The memory clock rate of the device.",
"format": "int64",
"type": "integer"
},
"name": {
"description": "The name of the device.",
"type": "string"
},
"peak_memory_bandwidth": {
"description": "The peak memory bandwidth of the device.",
"format": "int64",
"type": "integer"
}
},
"type": "object"
},
"Instance": {
"properties": {
"cpu_platform": {
"description": "The CPU platform of the server instance.",
"type": "string"
},
"description": {
"description": "The description of the server instance.",
"type": "string"
},
"environment": {
"$ref": "#/components/schemas/ServerEnv"
},
"git_hash": {
"description": "The git commit hash that the server binary was built from.",
"type": "string"
},
"hostname": {
"description": "The hostname of the server instance.",
"type": "string"
},
"id": {
"description": "The unique identifier of the server instance.",
"type": "string"
},
"image": {
"description": "The virtual machine image the instance used as a base.",
"type": "string"
},
"ip_address": {
"description": "The IP address of the server instance.",
"format": "ip",
"type": "string"
},
"machine_type": {
"description": "The machine type of the server instance.",
"type": "string"
},
"name": {
"description": "The name of the server instance.",
"type": "string"
},
"zone": {
"description": "The zone the server instance is deployed in.",
"type": "string"
}
},
"type": "object"
},
"PongEnum": {
"enum": [
"pong"
],
"type": "string"
},
"PongMessage": {
"properties": {
"message": {
"$ref": "#/components/schemas/PongEnum"
}
},
"type": "object"
},
"ServerEnv": {
"enum": [
"production",
"development",
"preview"
],
"type": "string"
},
"ValidOutputFileFormat": {
"enum": [
"stl",
"obj",
"dae",
"step",
"fbx",
"fbxb"
],
"type": "string"
},
"ValidSourceFileFormat": {
"enum": [
"stl",
"obj",
"dae",
"step",
"fbx"
],
"type": "string"
}
},
"securitySchemes": {
"bearerAuth": {
"description": "Default HTTP Basic Authorization",
"scheme": "bearer",
"type": "http"
}
}
},
"externalDocs": {
"description": "KittyCAD API Documentation",
"url": "https://docs.kittycad.io"
},
"info": {
"contact": {
"email": "support@kittycad.io",
"name": "KittyCAD Support",
"url": "https://kittycad.io"
},
"description": "The KittyCAD API",
"license": {
"name": "Apache License, Version 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0"
},
"termsOfService": "https://kittycad.io/terms-and-conditions",
"title": "KittyCAD API",
"version": "0.0.2-6f1ca7e",
"x-go": {
"client": "// Create a client with your token and host.\nclient, err := kittycad.NewClient(\"$TOKEN\", \"your apps user agent\")\nif err != nil {\n panic(err)\n}\n\n// - OR -\n\n// Create a new client with your token and host parsed from the environment\n// variables: KITTYCAD_API_TOKEN.\nclient, err := kittycad.NewClientFromEnv(\"your apps user agent\")\nif err != nil {\n panic(err)\n}",
"install": "go get github.com/kittycad/kittycad.go"
}
},
"openapi": "3.0.0",
"paths": {
"/_internal/async/conversions/stop": {
"post": {
"description": "Stop all async conversions that are currently running. This endpoint can only be used by specific KittyCAD employees.",
"operationId": "stopAsyncConversions",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FileConversion"
}
}
},
"description": "OK"
},
"401": {
"$ref": "#/components/responses/401"
},
"403": {
"$ref": "#/components/responses/403"
},
"404": {
"$ref": "#/components/responses/404"
}
},
"security": [
{
"bearerAuth": []
}
],
"summary": "Stop all async conversions",
"tags": [
"internal"
],
"x-go": {
"example": "// StopAsyncConversions: Stop all async conversions\n//\n// Stop all async conversions that are currently running. This endpoint can only be used by specific KittyCAD employees.\nfileConversion, err := client.Internal.StopAsyncConversions()",
"libDocsLink": "https://pkg.go.dev/github.com/kittycad/kittycad.go/#InternalService.StopAsyncConversions"
}
}
},
"/_internal/gpu/devices": {
"get": {
"description": "Get information about GPU devices on this server. This is primarily used for debugging. This endpoint can only be used by specific KittyCAD employees.",
"operationId": "gpuDevices",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/GPUDevice"
},
"type": "array"
}
}
},
"description": "Returns the GPU devices if successful."
},
"401": {
"$ref": "#/components/responses/401"
},
"403": {
"$ref": "#/components/responses/403"
}
},
"security": [
{
"bearerAuth": []
}
],
"summary": "Get GPU devices",
"tags": [
"internal"
],
"x-go": {
"example": "// GPUDevices: Get GPU devices\n//\n// Get information about GPU devices on this server. This is primarily used for debugging. This endpoint can only be used by specific KittyCAD employees.\nGPUDevice, err := client.Internal.GPUDevices()",
"libDocsLink": "https://pkg.go.dev/github.com/kittycad/kittycad.go/#InternalService.GPUDevices"
}
}
},
"/_meta/debug/instance": {
"get": {
"description": "Get information about this specific API server instance. This is primarily used for debugging.",
"operationId": "instanceMetadata",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Instance"
}
}
},
"description": "Returns the instance metadata if successful."
},
"401": {
"$ref": "#/components/responses/401"
},
"403": {
"$ref": "#/components/responses/403"
}
},
"security": [
{
"bearerAuth": []
}
],
"summary": "Get instance metadata",
"tags": [
"meta"
],
"x-go": {
"example": "// InstanceMetadata: Get instance metadata\n//\n// Get information about this specific API server instance. This is primarily used for debugging.\ninstance, err := client.Meta.InstanceMetadata()",
"libDocsLink": "https://pkg.go.dev/github.com/kittycad/kittycad.go/#MetaService.InstanceMetadata"
}
}
},
"/_meta/debug/session": {
"get": {
"description": "Get information about your API request session. This is primarily used for debugging.",
"operationId": "authSession",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AuthSession"
}
}
},
"description": "Returns the authorized user's authentication session if successful."
},
"401": {
"$ref": "#/components/responses/401"
},
"403": {
"$ref": "#/components/responses/403"
}
},
"security": [
{
"bearerAuth": []
}
],
"summary": "Get auth session",
"tags": [
"meta"
],
"x-go": {
"example": "// AuthSession: Get auth session\n//\n// Get information about your API request session. This is primarily used for debugging.\nauthSession, err := client.Meta.AuthSession()",
"libDocsLink": "https://pkg.go.dev/github.com/kittycad/kittycad.go/#MetaService.AuthSession"
}
}
},
"/file/conversion/{id}": {
"get": {
"description": "Get the status and output of an async file conversion.",
"operationId": "fileConversionStatus",
"parameters": [
{
"description": "The id of the file conversion.",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FileConversion"
}
}
},
"description": "Returns the status of the file conversion. If completed, the contents of the converted file will be returned as a base64 encoded string."
},
"401": {
"$ref": "#/components/responses/401"
},
"403": {
"$ref": "#/components/responses/403"
},
"404": {
"$ref": "#/components/responses/404"
},
"406": {
"$ref": "#/components/responses/406"
},
"500": {
"$ref": "#/components/responses/500"
}
},
"security": [
{
"bearerAuth": []
}
],
"summary": "Get a file conversion",
"tags": [
"file",
"beta"
],
"x-go": {
"example": "// ConversionStatus: Get a file conversion\n//\n// Get the status and output of an async file conversion.\n//\n// Parameters:\n//\t- `id`: The id of the file conversion.\nfileConversion, err := client.File.ConversionStatus(id)",
"libDocsLink": "https://pkg.go.dev/github.com/kittycad/kittycad.go/#FileService.ConversionStatus"
}
}
},
"/file/conversion/{sourceFormat}/{outputFormat}": {
"post": {
"description": "Convert a CAD file from one format to another. If the file being converted is larger than 30MB, it will be performed asynchronously.",
"operationId": "postFileConversion",
"parameters": [
{
"description": "The format of the file to convert.",
"in": "path",
"name": "sourceFormat",
"required": true,
"schema": {
"$ref": "#/components/schemas/ValidSourceFileFormat"
}
},
{
"description": "The format the file should be converted to.",
"in": "path",
"name": "outputFormat",
"required": true,
"schema": {
"$ref": "#/components/schemas/ValidOutputFileFormat"
}
}
],
"requestBody": {
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FileConversion"
}
}
},
"description": "Returns the contents of the converted file, base64 encoded, if successful. The contents will be base64 encoded."
},
"202": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FileConversion"
}
}
},
"description": "The file conversion is being performed asynchronously. You can use the `id` returned from the request to get status information about the async conversion."
},
"400": {
"$ref": "#/components/responses/400"
},
"401": {
"$ref": "#/components/responses/401"
},
"403": {
"$ref": "#/components/responses/403"
},
"406": {
"$ref": "#/components/responses/406"
},
"500": {
"$ref": "#/components/responses/500"
}
},
"security": [
{
"bearerAuth": []
}
],
"summary": "Convert CAD file",
"tags": [
"file",
"beta"
],
"x-go": {
"example": "// PostConversion: Convert CAD file\n//\n// Convert a CAD file from one format to another. If the file being converted is larger than 30MB, it will be performed asynchronously.\n//\n// Parameters:\n//\t- `outputFormat`: The format the file should be converted to.\n//\t- `sourceFormat`: The format of the file to convert.\nfileConversion, err := client.File.PostConversion(sourceFormat, outputFormat)",
"libDocsLink": "https://pkg.go.dev/github.com/kittycad/kittycad.go/#FileService.PostConversion"
}
}
},
"/ping": {
"get": {
"description": "Simple ping to the server.",
"operationId": "ping",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PongMessage"
}
}
},
"description": "Returns the message \"pong\" if successful."
}
},
"summary": "Ping",
"tags": [
"meta"
],
"x-go": {
"example": "// Ping: Ping\n//\n// Simple ping to the server.\npongMessage, err := client.Meta.Ping()",
"libDocsLink": "https://pkg.go.dev/github.com/kittycad/kittycad.go/#MetaService.Ping"
}
}
}
},
"servers": [
{
"description": "Production",
"url": "https://api.kittycad.io"
}
],
"tags": [
{
"description": "CAD file operations.",
"name": "file"
},
{
"description": "Meta information about servers, instances, and sessions.",
"name": "meta"
},
{
"description": "Beta API endpoints.",
"name": "beta"
},
{
"description": "Internal API endpoints.",
"name": "internal"
}
]
}