def get_walker(fns: t.List[t.Callable[..., t.Any]]) -> Walker: from metashape.runtime import get_walker as _get_walker from metashape.analyze.config import Config from egoist.internal._fnspec import fnspec dq = deque(fns) seen: t.Set[t.Type[t.Any]] = set() for fn in fns: spec = fnspec(fn) for typ in spec.argspec.annotations.values(): if typ in seen: continue seen.add(typ) dq.append(typ) seen = set() # clear classes: t.List[t.Type[t.Any]] = [] while dq: typ = dq.pop() if typ in seen: continue seen.add(typ) if isinstance(typ, type): classes.append(typ) for sub_type in _get_flatten_args(typ): dq.append(sub_type) return _get_walker(classes, config=Config(option=Config.Option(strict=False)))
def parse(fn: t.Callable[..., t.Any]) -> t.Tuple[str, t.List[str], Metadata]: spec = fnspec(fn) depends = [ primitive(name, metadata={"component_type": typ}) if typ.__module__ == "builtins" else typ.__name__ for name, typ, _ in spec.arguments ] return_type = spec.return_type if not hasattr(return_type, "__origin__"): component_type = return_type else: assert return_type.__origin__ == tuple, return_type.__origin__ component_type, *_ = t.get_args(spec.return_type) metadata: Metadata = { "return_type": return_type, "component_type": component_type, "fnspec": spec, } return { "name": component_type.__name__, "depends": depends, "metadata": metadata, }
def emit(self, emit_fn: t.Callable[..., t.Any]) -> Symbol: global m spec = fnspec(emit_fn) methodname = goname(spec.name) # todo: type -> gotype args = [m.symbol(name) for name, _, _, in spec.arguments] return_type = "string" with m.method( f"{self.typename} *{self.receiver}", methodname, *[f"{x} string" for x in args], returns=return_type, ): emit_fn(m.symbol(self.receiver), *args) return m.symbol(methodname)
def emit(w: Walker, routes: t.List[t.Tuple[t.Callable[..., t.Any], Metadata]], *, title: str = "egoist", version: str = "0.0.0") -> t.Dict[str, t.Any]: from metashape.outputs.openapi.emit import scan root = { "openapi": "3.0.2", "info": { "title": title, "version": version }, "paths": defaultdict(dict), "components": { "schemas": {} }, } paths = root["paths"] # TODO: lazy ctx = scan(w) r = Resolver(ctx) root.update(ctx.result.result) h = Handler(root, resolver=r) for fn, metadata in routes: spec = fnspec(fn) path = metadata["path"] method = metadata["method"] d = paths[path][method] = {} d["summary"] = metadata.get("summary") or r.resolve_doc(spec) if "description" in metadata: d["description"] = metadata["description"] if "tags" in metadata: d["tags"] = metadata["tags"] d.update(r.resolve_request_body(spec)) responses = d["responses"] = {} responses.update(h.handle_successful_response(typ=spec.return_type)) responses.update(h.handle_validation_error_response()) return root
def _emit(receiver: str) -> Symbol: global m spec = fnspec(emit_fn) methodname = goname(spec.name) self = m.symbol(receiver.lstrip("* ")[0].lower()) # todo: type -> gotype args = [m.symbol(name) for name, _, _, in spec.arguments] return_type = "string" with m.method( f"{self} *{receiver}", methodname, *[f"{x} string" for x in args], returns=return_type, ): emit_fn(self, *args) return m.symbol(methodname)
def parse(fn: t.Callable[..., t.Any]) -> AddNodeParamsDict: spec = fnspec(fn) depends: t.List[t.Union[str, _Seed]] = [] levels: t.Dict[str, int] = {} for name, typ, _ in spec.arguments: # parameters? if typ.__module__ == "builtins": depends.append(primitive(name, metadata={"component_type": typ})) continue typ, level = _unwrap_pointer_type(typ) depends.append(typ.__name__) levels[name] = level return_type = spec.return_type return_level = 0 if not hasattr(return_type, "__origin__"): component_type = return_type elif return_type.__origin__ == tuple: component_type, *_ = typing_get_args(return_type) component_type, return_level = _unwrap_pointer_type(component_type) elif return_type.__origin__ == GoPointer: component_type, return_level = _unwrap_pointer_type(return_type) else: import inspect raise RuntimeError( f"unexpected return-type. {inspect.signature(spec.body)}") levels["return"] = return_level metadata: Metadata = { "return_type": return_type, "component_type": component_type, "fnspec": spec, "levels": levels, } return { "name": component_type.__name__, "depends": depends, "metadata": metadata, }
def fnspec(self) -> Fnspec: from egoist.internal._fnspec import fnspec return fnspec(self.fn)
import typing as t import typing_extensions as tx from egoist.internal._fnspec import fnspec from dataclasses import dataclass T = t.TypeVar("T") class Query(t.Generic[T]): @classmethod def __emit__(cls, name: str) -> None: print("emit", name) @dataclass class Name: name: str s = Query[str] print(s) print(type(s)) print(vars(tx.Annotated[Query[str], Name("s")])) def hello(name: tx.Annotated[Query[str], Name("s")]) -> None: pass print(fnspec(hello).arguments[0][1].__metadata__[0].name)
from egoist.internal._fnspec import fnspec def hello(name: str): pass print(fnspec(hello))
import typing as t import typing_extensions as tx from egoist.internal._fnspec import fnspec class Query: pass def hello(name: tx.Annotated[str, Query]): pass print(fnspec(hello)) print(fnspec(hello).arguments[0][1].__metadata__) print(t.get_args(fnspec(hello).arguments[0][1]))
def emit( api: runtime.API, *, title: str = "my-api", version: str = "0.0.0", license: t.Optional[str] = None, # e.g. "mit" servers: t.Optional[t.List[str]] = None, default_error_response: t.Optional[runtime.Response] = None, autotags: bool = False, ) -> t.Dict[str, t.Any]: from metashape.outputs.openapi.emit import scan from metashape.marker import mark root = { "openapi": "3.0.2", "info": { "title": title, "version": version }, } if license is not None: root["info"]["license"] = {"name": license} if servers is not None: root["servers"] = [{"url": url} for url in servers] root["paths"] = defaultdict(dict) # TODO: lazy routes = list(api.routes) stack = api._stack w = get_walker([fn for fn, _ in routes]) if default_error_response is not None: mark(default_error_response.result) # mark w.append(default_error_response.result) ctx = scan(w) root.update(ctx.result) # inject #/components/schemas refs = ctx.state.refs resolver = Resolver(refs=refs) tags_map = {} default_error_response_dict: t.Dict[str, t.Any] = None if default_error_response is not None: default_error_response._asdict = lambda x: resolver.resolve_schema( default_error_response.result) # xxx default_error_response_dict = resolver.resolve_response( default_error_response, description=default_error_response.description) # default_part = DefaultPartInjector() for fn, metadata in routes: path, path_type_map = resolver.resolve_path(metadata["path"]) method = metadata["method"] d = root["paths"][path][method] = {} spec = fnspec(fn) param_names = [name for name, _, _ in spec.parameters] + ["return_"] if path_type_map is not None: param_names.extend(path_type_map.keys()) stack.push(param_names) c = stack.current args = [] kwargs = {} parameter_args = [] for name, typ, kind in spec.parameters: origin = runtime.Body first_arg = typ if hasattr(typ, "__origin__") and hasattr(typ.__origin__, "__emit__"): origin = typ.__origin__ # for Query first_arg = typ.__args__[0] p = origin.__emit__(name, first_arg, d) if issubclass(origin, runtime.Body): p._asdict = partial(resolver.resolve_request_body, first_arg) setattr(c, name, p) if kind.startswith("arg"): args.append(p) else: kwargs[name] = p parameter_args.append(p) spec.body(*args, **kwargs) parameters = [] if path_type_map is not None: for name, type_schema in path_type_map.items(): p = getattr(c, name) p.schema = type_schema parameters.append(p.asdict()) if parameter_args: for p in parameter_args: if p.schema is None: p.schema = resolver.resolve_schema(p._type) parameters.append(p.asdict()) # basic d["operationId"] = spec.name if "summary" in metadata: d["summary"] = metadata["summary"] else: d["summary"] = resolver.resolve_summary(spec) # if "description" in metadata: # d["description"] = metadata["description"] # else: # d["description"] = resolver.resolve_description(spec) if "tags" in metadata: d["tags"] = metadata["tags"] elif autotags: modname = spec.body.__module__ tags = tags_map.get(modname) if tags is None: mod = sys.modules.get(modname) tags = tags_map[modname] = getattr(mod, "__TAGS__", None) or [modname] d["tags"] = tags # parameters if parameters: d["parameters"] = parameters # responses responses = d["responses"] = {} # 200 default_status = 200 default_asdict = resolver.resolve_response if spec.return_type is not None: return_type = spec.return_type # for tx.Annotated[T, DefaultStatus(N)] if hasattr(return_type, "__metadata__"): for m in return_type.__metadata__: if hasattr(m, "code"): # DefaultStatus default_status = m.code return_type = t.get_args(return_type)[0] responses[str(default_status)] = default_asdict( return_type, description=c.return_.description, extra_data=c.return_.extra_data, ) if default_error_response_dict is not None: responses["default"] = default_error_response_dict # # 422 # default_part.inject_error_part(root) # responses["422"] = { # "description": "Validation Error", # "content": { # "application/json": { # "schema": {"$ref": "#/components/schemas/HTTPValidationError"} # } # }, # } stack.pop() return root
def emit( routes: t.List[t.Tuple[t.Callable[..., t.Any], Metadata]], *, title: str = "egoist", version: str = "0.0.0" ) -> t.Dict[str, t.Any]: from metashape.outputs.openapi.emit import scan root = { "openapi": "3.0.2", "info": {"title": title, "version": version}, "paths": defaultdict(dict), } # TODO: lazy w = get_walker([fn for fn, _ in routes]) ctx = scan(w) root.update(ctx.result.result) # inject #/components/schemas refs = ctx.state.refs resolver = Resolver(refs=refs) default_part = DefaultPartInjector() for fn, metadata in routes: path = metadata["path"] method = metadata["method"] d = root["paths"][path][method] = {} spec = fnspec(fn) d["summary"] = metadata.get("summary") or resolver.resolve_doc(spec) if "description" in metadata: d["description"] = metadata["description"] if "tags" in metadata: d["tags"] = metadata["tags"] if spec.arguments: typ = [typ for _, typ, _ in spec.arguments][0] d.update(resolver.resolve_request_body(typ)) # responses responses = d["responses"] = {} # 200 value = {} if spec.return_type is not None: value = resolver.resolve_schema(spec.return_type) responses["200"] = { "description": "Successful Response", "content": {"application/json": value}, } # 422 default_part.inject_error_part(root) responses["422"] = { "description": "Validation Error", "content": { "application/json": { "schema": {"$ref": "#/components/schemas/HTTPValidationError"} } }, } return root