#py-codegen
Minimal Python Telepact example that shows both the generated client and the generated server bindings wired into the runtime library.
Browse the files:
api/greet.telepact.yaml- Telepact schemagen/gen_types.py- committed generated bindingsserver.py- generated server handler wired into Telepacttest_example.py- generated client wired into TelepactMakefile- local run target, including codegen
Run it:
make run#Source Files
#Makefile
#|
#| Copyright The Telepact Authors
#|
#| Licensed under the Apache License, Version 2.0 (the "License");
#| you may not use this file except in compliance with the License.
#| You may obtain a copy of the License at
#|
#| https://www.apache.org/licenses/LICENSE-2.0
#|
#| Unless required by applicable law or agreed to in writing, software
#| distributed under the License is distributed on an "AS IS" BASIS,
#| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#| See the License for the specific language governing permissions and
#| limitations under the License.
#|
SHELL := /bin/bash
PYTHON := .venv/bin/python
.PHONY: run dep codegen clean
run: codegen
@set -euo pipefail; rm -rf .venv; uv venv --python python3.11 .venv; uv pip install --python $(PYTHON) pytest ../../lib/py/dist/*.tar.gz; $(PYTHON) -m pytest -q
dep:
@$(MAKE) -C ../../lib/py
codegen: dep
@set -euo pipefail; rm -rf ../../sdk/cli/telepact_cli/telepact; cp -r ../../lib/py/telepact ../../sdk/cli/telepact_cli/telepact; rm -rf gen; uv run --project ../../sdk/cli telepact codegen --schema-dir api --lang py --out gen; printf '\n' > gen/.license-header-ignore
clean:
@rm -rf .venv#api/
#api/greet.telepact.yaml
#|
#| Copyright The Telepact Authors
#|
#| Licensed under the Apache License, Version 2.0 (the "License");
#| you may not use this file except in compliance with the License.
#| You may obtain a copy of the License at
#|
#| https://www.apache.org/licenses/LICENSE-2.0
#|
#| Unless required by applicable law or agreed to in writing, software
#| distributed under the License is distributed on an "AS IS" BASIS,
#| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#| See the License for the specific language governing permissions and
#| limitations under the License.
#|
- fn.greet:
subject: "string"
->:
- Ok_:
message: "string"#gen/
#gen/init.py
#gen/gen_types.py
# Code generated by the Telepact CLI. DO NOT EDIT BY HAND.
import keyword
from typing import Awaitable, Callable, TypeVar, Union, Literal, Generic, NoReturn, Never
from dataclasses import dataclass
from typing import cast, ForwardRef
import telepact.Message
import telepact.Client
import telepact.TypedMessage
from typing import Tuple, NamedTuple
from enum import Enum
T = TypeVar('T')
U = TypeVar('U')
def util_let(value: T, f: Callable[[T], U]) -> U:
return f(value)
class Undefined(Enum):
Inst = "undefined"
class TaggedValue_(Generic[T, U]):
tag: T
value: U
def __init__(self, tag: T, value: U) -> None:
self.tag = tag
self.value = value
class UntypedTaggedValue_:
tag: str
value: dict[str, object]
def __init__(self, tag: str, value: dict[str, object]) -> None:
self.tag = tag
self.value = value
class greet:
class Input:
pseudo_json: dict[str, object]
def __init__(self, pseudo_json: dict[str, object]) -> None:
self.pseudo_json = pseudo_json
@staticmethod
def from_(
subject: 'str',
) -> 'greet.Input':
input: dict[str, object] = {}
input["subject"] = subject
return greet.Input({'fn.greet': input})
def subject(self) -> 'str':
return cast(str, self.pseudo_json["fn.greet"]["subject"])
class Output:
pseudo_json: dict[str, object]
def __init__(self, pseudo_json: dict[str, object]) -> None:
self.pseudo_json = pseudo_json
@staticmethod
def from_Ok_(
message: 'str',
) -> 'greet.Output':
return greet.Output({
"Ok_": greet.Output.Ok_.from_(
message=message,
).pseudo_json
})
def get_tagged_value(self) -> Union[
TaggedValue_[Literal["Ok_"], 'greet.Output.Ok_'],
TaggedValue_[Literal["NoMatch_"], UntypedTaggedValue_]]:
tag = next(iter(self.pseudo_json.keys()))
if tag == "Ok_":
return TaggedValue_(cast(Literal["Ok_"], "Ok_"), greet.Output.Ok_(cast(dict[str, object], self.pseudo_json["Ok_"])))
else:
return TaggedValue_(cast(Literal["NoMatch_"], "NoMatch_"), UntypedTaggedValue_(tag, cast(dict[str, object], self.pseudo_json[tag])))
class Ok_:
pseudo_json: dict[str, object]
def __init__(self, pseudo_json: dict[str, object]) -> None:
self.pseudo_json = pseudo_json
@staticmethod
def from_(
message: 'str',
) -> 'greet.Output.Ok_':
input: dict[str, object] = {}
input["message"] = message
return greet.Output.Ok_(input)
def message(self) -> 'str':
return cast(str, self.pseudo_json["message"])
class Select_:
pseudo_json: dict[str, object]
def ok_message(self) -> 'greet.Select_':
result_union = cast(dict[str, object], self.pseudo_json.get("->", {}))
these_fields = cast(list[object], result_union.get("Ok_", []))
if 'message' not in these_fields:
these_fields.append('message')
result_union["Ok_"] = these_fields
self.pseudo_json["->"] = result_union
return self
class Select_:
pseudo_json: dict[str, object] = {}
def __init__(self, pseudo_json: dict[str, object]) -> None:
self.pseudo_json = pseudo_json
@staticmethod
def greet(select: 'greet.Select_') -> 'Select_':
return Select_(select.pseudo_json)
class TypedClient:
client: telepact.Client
def __init__(self, client: telepact.Client):
self.client = client
async def greet(self, headers: dict[str, object], input: greet.Input) -> telepact.TypedMessage[greet.Output]:
message = await self.client.request(telepact.Message(headers, input.pseudo_json))
return telepact.TypedMessage(message.headers, greet.Output(message.body))
class TypedServerHandler:
async def greet(self, headers: dict[str, object], input: greet.Input) -> telepact.TypedMessage[greet.Output]:
raise NotImplementedError()
def function_routes(self) -> dict[str, Callable[[str, telepact.Message], Awaitable[telepact.Message]]]:
return {
"fn.greet": self._greet_route,
}
async def _greet_route(self, function_name: str, request_message: telepact.Message) -> telepact.Message:
argument = request_message.body[function_name]
response_headers, greet_output = await self.greet(request_message.headers, greet.Input({"fn.greet": argument}))
return telepact.Message(response_headers, greet_output.pseudo_json)
async def handler(self, message: telepact.Message) -> telepact.Message:
function_name = next(iter(message.body.keys()))
function_route = self.function_routes().get(function_name)
if function_route is None:
raise Exception("Unknown function: " + function_name)
return await function_route(function_name, message)#server.py
#|
#| Copyright The Telepact Authors
#|
#| Licensed under the Apache License, Version 2.0 (the "License");
#| you may not use this file except in compliance with the License.
#| You may obtain a copy of the License at
#|
#| https://www.apache.org/licenses/LICENSE-2.0
#|
#| Unless required by applicable law or agreed to in writing, software
#| distributed under the License is distributed on an "AS IS" BASIS,
#| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#| See the License for the specific language governing permissions and
#| limitations under the License.
#|
import asyncio
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from telepact import FunctionRouter, Server, TelepactSchema, TypedMessage
from gen.gen_types import TypedServerHandler, greet
schema = TelepactSchema.from_directory('api')
options = Server.Options()
options.auth_required = False
class GreetingHandler(TypedServerHandler):
async def greet(self, headers: dict[str, object], input: greet.Input) -> TypedMessage[greet.Output]:
return TypedMessage(
{},
greet.Output.from_Ok_(message=f'Hello {input.subject()} from generated code!'),
)
function_router = FunctionRouter(GreetingHandler().function_routes())
telepact_server = Server(schema, function_router, options)
def create_http_server(host: str = '127.0.0.1', port: int = 0) -> ThreadingHTTPServer:
class RequestHandler(BaseHTTPRequestHandler):
def do_POST(self) -> None:
if self.path != '/api/telepact':
self.send_response(404)
self.end_headers()
return
content_length = int(self.headers.get('Content-Length', '0'))
request_bytes = self.rfile.read(content_length)
response = asyncio.run(telepact_server.process(request_bytes))
content_type = 'application/octet-stream' if '@bin_' in response.headers else 'application/json'
self.send_response(200)
self.send_header('Content-Type', content_type)
self.end_headers()
self.wfile.write(response.bytes)
def log_message(self, format: str, *args: object) -> None:
return
return ThreadingHTTPServer((host, port), RequestHandler)#test_example.py
#|
#| Copyright The Telepact Authors
#|
#| Licensed under the Apache License, Version 2.0 (the "License");
#| you may not use this file except in compliance with the License.
#| You may obtain a copy of the License at
#|
#| https://www.apache.org/licenses/LICENSE-2.0
#|
#| Unless required by applicable law or agreed to in writing, software
#| distributed under the License is distributed on an "AS IS" BASIS,
#| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#| See the License for the specific language governing permissions and
#| limitations under the License.
#|
from __future__ import annotations
import asyncio
import threading
import urllib.request
from telepact import Client, Message, Serializer
from gen.gen_types import TypedClient, greet
from server import create_http_server
def run_server(server) -> threading.Thread:
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
return thread
def stop_server(server, thread: threading.Thread) -> None:
server.shutdown()
server.server_close()
thread.join(timeout=5)
def post_bytes(url: str, request_bytes: bytes) -> bytes:
request = urllib.request.Request(
url,
data=request_bytes,
headers={'Content-Type': 'application/json'},
method='POST',
)
with urllib.request.urlopen(request) as response:
return response.read()
async def run_example() -> None:
server = create_http_server()
thread = run_server(server)
try:
url = f'http://127.0.0.1:{server.server_address[1]}/api/telepact'
async def adapter(message: Message, serializer: Serializer) -> Message:
request_bytes = serializer.serialize(message)
response_bytes = await asyncio.to_thread(post_bytes, url, request_bytes)
return serializer.deserialize(response_bytes)
client = TypedClient(Client(adapter, Client.Options()))
response = await client.greet(
{},
greet.Input.from_(subject='Telepact'),
)
ok = response.body.get_tagged_value()
assert ok.tag == 'Ok_'
assert ok.value.message() == 'Hello Telepact from generated code!'
assert response.headers == {}
finally:
stop_server(server, thread)
def test_codegen_example_runs_end_to_end() -> None:
asyncio.run(run_example())