feat: initial stub generator package
commit
65d135a6d1
@ -0,0 +1,49 @@
|
||||
"""Types definitions that are common in capnproto schemas."""
|
||||
from types import ModuleType
|
||||
from typing import Dict, Tuple
|
||||
|
||||
CAPNP_TYPE_TO_PYTHON = {
|
||||
"void": "None",
|
||||
"bool": "bool",
|
||||
"int8": "int",
|
||||
"int16": "int",
|
||||
"int32": "int",
|
||||
"int64": "int",
|
||||
"uint8": "int",
|
||||
"uint16": "int",
|
||||
"uint32": "int",
|
||||
"uint64": "int",
|
||||
"float32": "float",
|
||||
"float64": "float",
|
||||
"text": "str",
|
||||
"data": "bytes",
|
||||
}
|
||||
|
||||
class CapnpFieldType:
|
||||
"""Types of capnproto fields."""
|
||||
|
||||
GROUP = "group"
|
||||
SLOT = "slot"
|
||||
|
||||
|
||||
class CapnpSlotType:
|
||||
"""Types of capnproto slots.
|
||||
|
||||
If CapnpFieldType is 'slot', this defines the type of that slot.
|
||||
"""
|
||||
|
||||
ANY_POINTER = "anyPointer"
|
||||
STRUCT = "struct"
|
||||
ENUM = "enum"
|
||||
LIST = "list"
|
||||
|
||||
|
||||
class CapnpElementType:
|
||||
"""Types of capnproto elements."""
|
||||
|
||||
ENUM = "enum"
|
||||
STRUCT = "struct"
|
||||
CONST = "const"
|
||||
|
||||
|
||||
ModuleRegistryType = Dict[int, Tuple[str, ModuleType]]
|
@ -0,0 +1,112 @@
|
||||
"""Command-line interface for generating type hints for *.capnp schemas.
|
||||
|
||||
Notes:
|
||||
- This generator requires pycapnp >= 1.0.0.
|
||||
- Capnp interfaces (RPC) are not yet supported.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os.path
|
||||
from typing import Sequence
|
||||
|
||||
from capnp_stub_generator.generator import run
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _add_output_argument(parser: argparse.ArgumentParser):
|
||||
"""Add an output argument to a parser.
|
||||
|
||||
Args:
|
||||
parser (argparse.ArgumentParser): The parser to add the argument to.
|
||||
"""
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output-dir",
|
||||
dest="output_dir",
|
||||
default=None,
|
||||
help="override directory where to write the .pyi file",
|
||||
)
|
||||
|
||||
|
||||
def _add_recursive_argument(parser: argparse.ArgumentParser):
|
||||
"""Add a recursive argument to a parser.
|
||||
|
||||
Args:
|
||||
parser (argparse.ArgumentParser): The parser to add the argument to.
|
||||
"""
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--recursive",
|
||||
dest="recursive",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="recursively search for *.capnp files with a given glob expression.",
|
||||
)
|
||||
|
||||
|
||||
def setup_parser() -> argparse.ArgumentParser:
|
||||
"""Setup for the parser.
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: The parser after setup.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description="Generate type hints for capnp schema files.")
|
||||
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--paths",
|
||||
type=str,
|
||||
nargs="+",
|
||||
default=["**/*.capnp"],
|
||||
help="path or glob expressions that match *.capnp files.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--excludes",
|
||||
type=str,
|
||||
nargs="+",
|
||||
default=["**/c-capnproto/**/*.capnp"],
|
||||
help="path or glob expressions to exclude from matches.",
|
||||
)
|
||||
|
||||
_add_output_argument(parser)
|
||||
_add_recursive_argument(parser)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
"""Entry point of the stub generator.
|
||||
|
||||
Args:
|
||||
argv (Sequence[str] | None, optional): Run arguments. Defaults to None.
|
||||
|
||||
Returns:
|
||||
int: Error code.
|
||||
"""
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
root_directory = os.getcwd()
|
||||
logging.info("Working from root directory: %s", root_directory)
|
||||
|
||||
parser = setup_parser()
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
run(args, root_directory)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(
|
||||
[
|
||||
"-p",
|
||||
"python/libraries/mars/mars/interfaces/**/*.capnp",
|
||||
"-r",
|
||||
]
|
||||
)
|
@ -0,0 +1,85 @@
|
||||
"""Top-level module for stub generation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import logging
|
||||
import os.path
|
||||
from types import ModuleType
|
||||
from typing import Set
|
||||
|
||||
import black
|
||||
import capnp
|
||||
import isort
|
||||
from capnp_stub_generator.capnp_types import ModuleRegistryType
|
||||
from capnp_stub_generator.helper import replace_capnp_suffix
|
||||
from capnp_stub_generator.writer import Writer
|
||||
|
||||
capnp.remove_import_hook()
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PYI_SUFFIX = ".pyi"
|
||||
PY_SUFFIX = ".py"
|
||||
|
||||
|
||||
def generate_stubs(module: ModuleType, module_registry: ModuleRegistryType, output_file_path: str):
|
||||
"""Entry-point for generating *.pyi stubs from a module definition.
|
||||
|
||||
Args:
|
||||
module (ModuleType): The module to generate stubs for.
|
||||
module_registry (ModuleRegistryType): A registry of all detected modules.
|
||||
output_file_path (str): The name of the output stub files, without file extension.
|
||||
"""
|
||||
writer = Writer(module, module_registry)
|
||||
writer.generate_recursive()
|
||||
|
||||
for outputs, suffix in zip((writer.dumps_pyi(), writer.dumps_py()), (PYI_SUFFIX, PY_SUFFIX)):
|
||||
sorted_imports = isort.code(outputs, config=isort.Config(profile="black"))
|
||||
formatted_output = black.format_str(sorted_imports, mode=black.Mode(is_pyi=True, line_length=79))
|
||||
|
||||
with open(output_file_path + suffix, "w", encoding="utf8") as output_file:
|
||||
output_file.write(formatted_output)
|
||||
|
||||
logger.info("Wrote stubs to '%s(%s/%s)'.", output_file_path, PYI_SUFFIX, PY_SUFFIX)
|
||||
|
||||
|
||||
def run(args: argparse.Namespace, root_directory: str):
|
||||
"""Run the stub generator on a set of paths that point to *.capnp schemas.
|
||||
|
||||
Uses `generate_stubs` on each input file.
|
||||
|
||||
Args:
|
||||
args (argparse.Namespace): The arguments that were passed when calling the stub generator.
|
||||
root_directory (str): The directory, from which the generator is executed.
|
||||
"""
|
||||
paths: str = args.paths
|
||||
excludes: str = args.excludes
|
||||
|
||||
excluded_paths: Set[str] = set()
|
||||
for exclude in excludes:
|
||||
exclude_directory = os.path.join(root_directory, exclude)
|
||||
excluded_paths = excluded_paths.union(glob.glob(exclude_directory, recursive=args.recursive))
|
||||
|
||||
search_paths: Set[str] = set()
|
||||
for path in paths:
|
||||
search_directory = os.path.join(root_directory, path)
|
||||
search_paths = search_paths.union(glob.glob(search_directory, recursive=args.recursive))
|
||||
|
||||
# The `valid_paths` contain the automatically detected search paths, except for specifically excluded paths.
|
||||
valid_paths = search_paths - excluded_paths
|
||||
|
||||
parser = capnp.SchemaParser()
|
||||
module_registry: ModuleRegistryType = {}
|
||||
|
||||
for path in valid_paths:
|
||||
module = parser.load(path)
|
||||
module_registry[module.schema.node.id] = (path, module)
|
||||
|
||||
for path, module in module_registry.values():
|
||||
output_directory = os.path.dirname(path)
|
||||
output_file_name = replace_capnp_suffix(os.path.basename(path))
|
||||
|
||||
generate_stubs(module, module_registry, os.path.join(output_directory, output_file_name))
|
@ -0,0 +1,19 @@
|
||||
"""Helper functionality that is used in other modules of this package."""
|
||||
|
||||
|
||||
def replace_capnp_suffix(original: str) -> str:
|
||||
"""If found, replaces the .capnp suffix in a string with _capnp.
|
||||
|
||||
For example, `some_module.capnp` becomes `some_module_capnp`.
|
||||
|
||||
Args:
|
||||
original (str): The string to replace the suffix in.
|
||||
|
||||
Returns:
|
||||
str: The string with the replaced suffix.
|
||||
"""
|
||||
if original.endswith(".capnp"):
|
||||
return original.replace(".capnp", "_capnp")
|
||||
|
||||
else:
|
||||
return original
|
@ -0,0 +1,810 @@
|
||||
"""Generate type hints for *.capnp schemas.
|
||||
|
||||
Note: This generator requires pycapnp >= 1.0.0.
|
||||
|
||||
Note: capnp interfaces (RPC) are not yet supported.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import keyword
|
||||
import logging
|
||||
import os.path
|
||||
import pathlib
|
||||
from types import ModuleType
|
||||
from typing import Any, Literal, Set
|
||||
|
||||
import capnp
|
||||
from capnp_stub_generator.capnp_types import (
|
||||
CAPNP_TYPE_TO_PYTHON,
|
||||
CapnpElementType,
|
||||
CapnpFieldType,
|
||||
CapnpSlotType,
|
||||
ModuleRegistryType,
|
||||
)
|
||||
from capnp_stub_generator.helper import replace_capnp_suffix
|
||||
|
||||
capnp.remove_import_hook()
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
INDENT_SPACES = 4
|
||||
|
||||
|
||||
class NoParentError(Exception):
|
||||
"""Raised, when the parent of a scope is not available."""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Scope:
|
||||
"""A scope within the output .pyi file.
|
||||
|
||||
Scopes contain text and are indented by a certain amount. They often have parents, within which they are located.
|
||||
|
||||
Args:
|
||||
name (str): The name of the scope. Use an empty name for the root scope ("").
|
||||
id (int): A numerical identifier of the scope.
|
||||
parent (Scope | None): The direct parent scope of this scope, if there is any.
|
||||
return scope (Scope | None): The scope to which to return, when closing this one.
|
||||
lines (list[str]): The list of text lines in this scope.
|
||||
"""
|
||||
|
||||
name: str
|
||||
id: int
|
||||
parent: Scope | None
|
||||
return_scope: Scope | None
|
||||
lines: list[str] = dataclasses.field(default_factory=list)
|
||||
|
||||
def __post_init__(self):
|
||||
"""Assures that, if this is the root scope, its name is empty."""
|
||||
assert (self.is_root) == (self.name == "")
|
||||
|
||||
@property
|
||||
def parents(self) -> list[Scope]:
|
||||
"""A list of all parent scopes of this scope, starting from the first parent.
|
||||
|
||||
If the returned list is empty, this scope has no parents. The first parent in the list has no further
|
||||
parents, it is the root scope.
|
||||
"""
|
||||
parents: list[Scope] = []
|
||||
scope: Scope | None = self.parent
|
||||
|
||||
while scope is not None:
|
||||
parents.append(scope)
|
||||
scope = scope.parent
|
||||
|
||||
parents.reverse()
|
||||
|
||||
return parents
|
||||
|
||||
@property
|
||||
def trace(self) -> list[Scope]:
|
||||
"""A list of all scopes that lead to this scope, starting from the first parent.
|
||||
|
||||
The first parent has no further parents.
|
||||
"""
|
||||
return self.parents + [self]
|
||||
|
||||
@property
|
||||
def root(self) -> Scope:
|
||||
"""Get the root scope that has no further parents."""
|
||||
if not self.parents:
|
||||
return self
|
||||
|
||||
else:
|
||||
return self.parents[0]
|
||||
|
||||
@property
|
||||
def is_root(self) -> bool:
|
||||
"""Determine, whether this is the root scope."""
|
||||
return self.root == self
|
||||
|
||||
@property
|
||||
def indent_spaces(self) -> int:
|
||||
"""The number of spaces by which this scope is indented."""
|
||||
return len(self.parents) * INDENT_SPACES
|
||||
|
||||
def add_line(self, line: str = ""):
|
||||
"""Add a line to this scope, taking into account the current indent spaces.
|
||||
|
||||
Args:
|
||||
line (str): The line to add. Optional, defaults to "".
|
||||
"""
|
||||
if not line:
|
||||
self.lines.append("")
|
||||
|
||||
else:
|
||||
self.lines.append(" " * self.indent_spaces + line)
|
||||
|
||||
def trace_as_str(self, delimiter: Literal[".", "_"] = ".") -> str:
|
||||
"""A string representation of this scope's relative trace.
|
||||
|
||||
Follow the trace of the scope, and connect parent scopes with a delimiter.
|
||||
The root scope is not included in this trace string.
|
||||
|
||||
Args:
|
||||
delimiter (Literal[".", "_"]): The delimiter to join the scope names with.
|
||||
"""
|
||||
return delimiter.join((scope.name for scope in self.trace if not scope.is_root))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""A string representation of this scope.
|
||||
|
||||
Follow the path of scopes, and connect parent scopes with '.'.
|
||||
"""
|
||||
return self.trace_as_str(".")
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class CapnpType:
|
||||
"""Represents a type that is extracted from a .capnp schema.
|
||||
|
||||
Args:
|
||||
schema (Any):
|
||||
name (str):
|
||||
scope (Scope):
|
||||
generic_params (list[str]):
|
||||
"""
|
||||
|
||||
schema: Any
|
||||
name: str
|
||||
scope: Scope
|
||||
generic_params: list[str] = dataclasses.field(default_factory=list)
|
||||
|
||||
|
||||
class Writer:
|
||||
"""A class that handles writing the stub file, based on a provided module definition."""
|
||||
|
||||
VALID_TYPING_IMPORTS = Literal["Generic", "TypeVar", "List", "Literal", "Union", "overload"]
|
||||
|
||||
def __init__(self, module: ModuleType, module_registry: ModuleRegistryType):
|
||||
"""Initialize the stub writer with a module definition.
|
||||
|
||||
Args:
|
||||
module (ModuleType): The module definition to parse and write a stub for.
|
||||
module_registry (ModuleRegistryType): The module registry, for finding dependencies between loaded modules.
|
||||
"""
|
||||
self.scope = Scope(name="", id=module.schema.node.id, parent=None, return_scope=None)
|
||||
self.scopes_by_id: dict[int, Scope] = {self.scope.id: self.scope}
|
||||
|
||||
self._module = module
|
||||
self._module_registry = module_registry
|
||||
|
||||
if self._module.__file__:
|
||||
self._module_path = pathlib.Path(self._module.__file__)
|
||||
|
||||
else:
|
||||
raise ValueError("The module has no file path attached to it.")
|
||||
|
||||
self._imports: Set[str] = set()
|
||||
self._add_import("from __future__ import annotations")
|
||||
|
||||
self._typing_imports: Set[Writer.VALID_TYPING_IMPORTS] = set()
|
||||
|
||||
self.type_vars: set[str] = set()
|
||||
self.type_map: dict[int, CapnpType] = {}
|
||||
|
||||
self.docstring = f'"""This is an automatically generated stub for `{self._module_path.name}`."""'
|
||||
|
||||
def _add_typing_import(self, module_name: Writer.VALID_TYPING_IMPORTS):
|
||||
"""Add an import for a module from the 'typing' package.
|
||||
|
||||
E.g., when using
|
||||
add_typing_import("List")
|
||||
add_typing_import("Union")
|
||||
|
||||
this generates an import line `from typing import List, Union`.
|
||||
|
||||
Args:
|
||||
module_name (Writer.VALID_TYPING_IMPORTS): The module to import from `typing`.
|
||||
"""
|
||||
self._typing_imports.add(module_name)
|
||||
|
||||
def _add_import(self, import_line: str):
|
||||
"""Add a full import line.
|
||||
|
||||
E.g. 'import numpy as np'.
|
||||
|
||||
Args:
|
||||
import_line (str): The import line to add.
|
||||
"""
|
||||
self._imports.add(import_line)
|
||||
|
||||
def _add_enum_import(self):
|
||||
"""Adds an import for the `Enum` class."""
|
||||
self._add_import("from enum import Enum")
|
||||
|
||||
@property
|
||||
def base_module_name(self) -> str:
|
||||
"""The base name of this writer's target module."""
|
||||
return self._module.schema.node.displayName
|
||||
|
||||
@property
|
||||
def imports(self) -> list[str]:
|
||||
"""Get the full list of import strings that were added to the writer, including typing imports.
|
||||
|
||||
Returns:
|
||||
list[str]: The list of imports that were previously added.
|
||||
"""
|
||||
import_lines: list[str] = []
|
||||
|
||||
for imp in self._imports:
|
||||
import_lines.append(imp)
|
||||
|
||||
if self._typing_imports:
|
||||
import_lines.append("from typing import " + ", ".join(sorted(self._typing_imports)))
|
||||
|
||||
return import_lines
|
||||
|
||||
@staticmethod
|
||||
def get_display_name(schema: Any) -> str:
|
||||
"""Extract the display name from the schema.
|
||||
|
||||
Args:
|
||||
schema (Any): The schema to get the display name from.
|
||||
|
||||
Returns:
|
||||
str: The display name of the schema.
|
||||
"""
|
||||
return schema.node.displayName[schema.node.displayNamePrefixLength :]
|
||||
|
||||
def gen_const(self, schema: Any) -> None:
|
||||
"""Generate a `const` object.
|
||||
|
||||
Args:
|
||||
schema (Any): The schema to generate the `const` object out of.
|
||||
"""
|
||||
assert schema.node.which() == CapnpElementType.CONST
|
||||
|
||||
name = self.get_display_name(schema)
|
||||
python_type = CAPNP_TYPE_TO_PYTHON[schema.node.const.type.which()]
|
||||
self.scope.add_line(f"{name}: {python_type}")
|
||||
|
||||
def gen_enum(self, schema: Any) -> CapnpType | None:
|
||||
"""Generate an `enum` object.
|
||||
|
||||
Args:
|
||||
schema (Any): The schema to generate the `enum` object out of.
|
||||
"""
|
||||
assert schema.node.which() == CapnpElementType.ENUM
|
||||
|
||||
imported = self.register_import(schema)
|
||||
|
||||
if imported is not None:
|
||||
return imported
|
||||
|
||||
name = self.get_display_name(schema)
|
||||
self._add_enum_import()
|
||||
|
||||
self.new_scope(name, schema.node, f"class {name}(str, Enum):")
|
||||
self.register_type(schema.node.id, schema, name)
|
||||
|
||||
for enumerant in schema.node.enum.enumerants:
|
||||
value = enumerant.name
|
||||
name = enumerant.name
|
||||
|
||||
if enumerant.name in keyword.kwlist:
|
||||
# Avoid naming collisions with Python keywords.
|
||||
name += "_"
|
||||
|
||||
self.scope.add_line(f'{name}: str = "{value}"')
|
||||
|
||||
self.return_from_scope()
|
||||
|
||||
return None
|
||||
|
||||
def gen_generic(self, schema: Any) -> list[str]:
|
||||
"""Generate a `generic` type variable.
|
||||
|
||||
Args:
|
||||
schema (Any): The schema to generate the `generic` object out of.
|
||||
|
||||
Returns:
|
||||
list[str]: The list of registered generic type variables.
|
||||
"""
|
||||
self._add_typing_import("TypeVar")
|
||||
self._add_typing_import("Generic")
|
||||
|
||||
generic_params: list[str] = [param.name for param in schema.node.parameters]
|
||||
referenced_params: list[str] = []
|
||||
|
||||
for field, _ in zip(schema.node.struct.fields, schema.as_struct().fields_list):
|
||||
if field.slot.type.which() == "anyPointer" and field.slot.type.anyPointer.which() == "parameter":
|
||||
param = field.slot.type.anyPointer.parameter
|
||||
|
||||
t = self.get_type_by_id(param.scopeId)
|
||||
|
||||
if t is not None:
|
||||
param_source = t.schema
|
||||
source_params: list[str] = [param.name for param in param_source.node.parameters]
|
||||
referenced_params.append(source_params[param.parameterIndex])
|
||||
|
||||
return [self.register_type_var(param) for param in generic_params + referenced_params]
|
||||
|
||||
def gen_slot(
|
||||
self,
|
||||
schema: Any,
|
||||
field: Any,
|
||||
raw_field: Any,
|
||||
registered_type: CapnpType,
|
||||
contructor_kwargs: list[str],
|
||||
init_choices: list[tuple[str, str]],
|
||||
) -> None:
|
||||
"""Generate a slot of a type that is yet to be determined.
|
||||
|
||||
Args:
|
||||
schema (Any): The schema to extract the slot from.
|
||||
field (Any): FIXME
|
||||
raw_field (Any): FIXME
|
||||
registered_type (Type): FIXME
|
||||
contructor_kwargs (list[str]): FIXME
|
||||
init_choices (list[tuple[str, str]]): FIXME
|
||||
"""
|
||||
|
||||
def gen_list_slot():
|
||||
"""Generate a slot, which contains a `list`."""
|
||||
list_slot_type: CapnpElementType = field.slot.type.list.elementType.which()
|
||||
|
||||
if list_slot_type == CapnpElementType.STRUCT:
|
||||
if not self.is_type_id_known(field.slot.type.list.elementType.struct.typeId):
|
||||
self.generate_nested(raw_field.schema.elementType)
|
||||
|
||||
elif list_slot_type == CapnpElementType.ENUM:
|
||||
if not self.is_type_id_known(field.slot.type.list.elementType.enum.typeId):
|
||||
self.generate_nested(raw_field.schema.elementType)
|
||||
|
||||
type_name = self.get_type_name(field.slot.type.list.elementType)
|
||||
field_py_code = f"{field.name}: List[{type_name}]"
|
||||
self.scope.add_line(field_py_code)
|
||||
contructor_kwargs.append(field_py_code)
|
||||
self._add_typing_import("List")
|
||||
|
||||
def gen_python_type_slot():
|
||||
"""Generate a slot, which contains a regular Python type."""
|
||||
python_type_name: str = CAPNP_TYPE_TO_PYTHON[field_slot_type]
|
||||
|
||||
field_py_code = f"{field.name}: {python_type_name}"
|
||||
self.scope.add_line(field_py_code)
|
||||
contructor_kwargs.append(field_py_code)
|
||||
|
||||
def gen_enum_slot():
|
||||
"""Generate a slot, which contains an `enum`."""
|
||||
if not self.is_type_id_known(field.slot.type.enum.typeId):
|
||||
try:
|
||||
self.generate_nested(raw_field.schema)
|
||||
|
||||
except NoParentError:
|
||||
pass
|
||||
|
||||
type_name = self.get_type_name(field.slot.type)
|
||||
field_py_code = f"{field.name}: {type_name}"
|
||||
self.scope.add_line(field_py_code)
|
||||
contructor_kwargs.append(field_py_code)
|
||||
|
||||
def gen_struct_slot():
|
||||
"""Generate a slot, which contains a `struct`."""
|
||||
elem_type = raw_field.schema
|
||||
|
||||
if not self.is_type_id_known(elem_type.node.id):
|
||||
self.gen_struct(elem_type)
|
||||
|
||||
type_name = self.get_type_name(field.slot.type)
|
||||
field_py_code = f"{field.name}: {type_name}"
|
||||
self.scope.add_line(field_py_code)
|
||||
contructor_kwargs.append(field_py_code)
|
||||
init_choices.append((field.name, type_name))
|
||||
|
||||
def gen_any_pointer_slot():
|
||||
"""Generate a slot, which contains an `any_pointer` object."""
|
||||
param = field.slot.type.anyPointer.parameter
|
||||
type_name = registered_type.generic_params[param.parameterIndex]
|
||||
field_py_code = f"{field.name}: {type_name}"
|
||||
self.scope.add_line(field_py_code)
|
||||
contructor_kwargs.append(field_py_code)
|
||||
|
||||
field_slot_type = field.slot.type.which()
|
||||
|
||||
if field_slot_type == CapnpSlotType.LIST:
|
||||
gen_list_slot()
|
||||
|
||||
elif field_slot_type in CAPNP_TYPE_TO_PYTHON:
|
||||
gen_python_type_slot()
|
||||
|
||||
elif field_slot_type == CapnpSlotType.ENUM:
|
||||
gen_enum_slot()
|
||||
|
||||
elif field_slot_type == CapnpSlotType.STRUCT:
|
||||
gen_struct_slot()
|
||||
|
||||
elif field_slot_type == CapnpSlotType.ANY_POINTER:
|
||||
gen_any_pointer_slot()
|
||||
|
||||
else:
|
||||
raise AssertionError(f"{schema.node.displayName}: {field.name}: " f"{field_slot_type}")
|
||||
|
||||
def gen_struct(self, schema: Any, type_name: str = "") -> CapnpType:
|
||||
"""Generate a `struct` object.
|
||||
|
||||
Args:
|
||||
schema (Any): The schema to generate the `struct` object out of.
|
||||
type_name (str, optional): A type name to override the display name of the struct. Defaults to "".
|
||||
|
||||
Returns:
|
||||
Type: The `struct`-type module that was generated.
|
||||
"""
|
||||
assert schema.node.which() == CapnpElementType.STRUCT
|
||||
|
||||
imported = self.register_import(schema)
|
||||
|
||||
if imported is not None:
|
||||
return imported
|
||||
|
||||
if not type_name:
|
||||
type_name = self.get_display_name(schema)
|
||||
|
||||
registered_params: list[str] = []
|
||||
if schema.node.isGeneric:
|
||||
registered_params = self.gen_generic()
|
||||
|
||||
if registered_params:
|
||||
scope_decl_line = f"class {type_name}(Generic[{', '.join(registered_params)}]):"
|
||||
|
||||
else:
|
||||
scope_decl_line = f"class {type_name}:"
|
||||
|
||||
self.new_scope(type_name, schema.node, scope_decl_line)
|
||||
|
||||
registered_type: CapnpType = self.register_type(schema.node.id, schema, name=type_name)
|
||||
registered_type.generic_params = registered_params
|
||||
type_name = registered_type.name
|
||||
definition_has_body = False
|
||||
|
||||
init_choices: list[tuple[str, str]] = []
|
||||
contructor_kwargs: list[str] = []
|
||||
|
||||
for field, raw_field in zip(schema.node.struct.fields, schema.as_struct().fields_list):
|
||||
field_type = field.which()
|
||||
|
||||
if field_type == CapnpFieldType.SLOT:
|
||||
definition_has_body = True
|
||||
self.gen_slot(schema, field, raw_field, registered_type, contructor_kwargs, init_choices)
|
||||
|
||||
elif field_type == CapnpFieldType.GROUP:
|
||||
group_name = field.name[0].upper() + field.name[1:]
|
||||
|
||||
assert group_name != field.name
|
||||
|
||||
raw_schema = raw_field.schema
|
||||
group_name = self.gen_struct(raw_schema, type_name=group_name).name
|
||||
field_py_code = f"{field.name}: {group_name}"
|
||||
self.scope.add_line(field_py_code)
|
||||
contructor_kwargs.append(field_py_code)
|
||||
definition_has_body = True
|
||||
init_choices.append((field.name, group_name))
|
||||
|
||||
else:
|
||||
raise AssertionError(f"{schema.node.displayName}: {field.name}: " f"{field.which()}")
|
||||
|
||||
if not registered_type.scope.is_root:
|
||||
scoped_name = f"{registered_type.scope}.{type_name}"
|
||||
|
||||
else:
|
||||
scoped_name = type_name
|
||||
|
||||
self.scope.add_line("@staticmethod")
|
||||
self.scope.add_line(f"def from_bytes(data: bytes) -> {scoped_name}: ...")
|
||||
self.scope.add_line("def to_bytes(self) -> bytes: ...")
|
||||
definition_has_body = True
|
||||
|
||||
if schema.node.struct.discriminantCount:
|
||||
literals = ", ".join(
|
||||
f'Literal["{field.name}"]' for field in schema.node.struct.fields if field.discriminantValue != 65535
|
||||
)
|
||||
self._add_typing_import("Literal")
|
||||
self._add_typing_import("Union")
|
||||
self.scope.add_line(f"def which(self) -> Union[{literals}]: ...")
|
||||
definition_has_body = True
|
||||
|
||||
if contructor_kwargs:
|
||||
kwargs = ", ".join(f"{kwarg} = ..." for kwarg in contructor_kwargs)
|
||||
self.scope.add_line(f"def __init__(self, *, {kwargs}) -> None: ...")
|
||||
definition_has_body = True
|
||||
|
||||
if len(init_choices) > 1:
|
||||
self._add_typing_import("Literal")
|
||||
self._add_typing_import("overload")
|
||||
|
||||
for field_name, field_type in init_choices:
|
||||
|
||||
self.scope.add_line("@overload")
|
||||
self.scope.add_line(f'def init(self, name: Literal["{field_name}"])' f" -> {field_type}: ...")
|
||||
|
||||
elif len(init_choices) == 1:
|
||||
self._add_typing_import("Literal")
|
||||
field_name, field_type = init_choices[0]
|
||||
self.scope.add_line(f'def init(self, name: Literal["{field_name}"])' f" -> {field_type}: ...")
|
||||
|
||||
if not definition_has_body:
|
||||
self.scope.add_line("pass")
|
||||
|
||||
self.return_from_scope()
|
||||
|
||||
return registered_type
|
||||
|
||||
def generate_nested(self, schema: Any) -> None:
|
||||
"""Generate the type for a nested schema.
|
||||
|
||||
Args:
|
||||
schema (Any): The schema to generate types for.
|
||||
|
||||
Raises:
|
||||
AssertionError: If the schema belongs to an unknown type.
|
||||
"""
|
||||
if schema.node.id in self.type_map:
|
||||
return # already generated type hints for this type
|
||||
|
||||
node_type = schema.node.which()
|
||||
|
||||
if node_type == "const":
|
||||
self.gen_const(schema)
|
||||
|
||||
elif node_type == "struct":
|
||||
self.gen_struct(schema)
|
||||
|
||||
elif node_type == "enum":
|
||||
self.gen_enum(schema)
|
||||
|
||||
elif node_type == "interface":
|
||||
logger.warning("Skipping interface: not implemented")
|
||||
|
||||
else:
|
||||
raise AssertionError(node_type)
|
||||
|
||||
def generate_recursive(self):
|
||||
"""Generate types for all nested nodes, recursively."""
|
||||
for node in self._module.schema.node.nestedNodes:
|
||||
self.generate_nested(self._module.schema.get_nested(node.name))
|
||||
|
||||
def register_import(self, schema) -> CapnpType | None:
|
||||
"""Determine, whether a schema is imported from the base module.
|
||||
|
||||
If so, the type definition that the schema contains, is added to the type registry.
|
||||
|
||||
Args:
|
||||
schema (Any): The schema to check.
|
||||
|
||||
Returns:
|
||||
Type | None: The type of the import, if the schema is imported,
|
||||
or None if the schema defines the base module itself.
|
||||
"""
|
||||
module_name, definition_name = schema.node.displayName.split(":")
|
||||
|
||||
if module_name == self.base_module_name:
|
||||
# This is the base module, not an import.
|
||||
return None
|
||||
|
||||
common_path: str
|
||||
matching_path: pathlib.Path | None = None
|
||||
|
||||
# Find the path of the parent module, from which this schema is imported.
|
||||
for path, module in self._module_registry.values():
|
||||
for node in module.schema.node.nestedNodes:
|
||||
if node.id == schema.node.id:
|
||||
matching_path = pathlib.Path(path)
|
||||
break
|
||||
|
||||
# Since this is an import, there must be a parent module.
|
||||
assert matching_path is not None
|
||||
|
||||
# Find the relative path to go from the parent module, to this imported module.
|
||||
common_path = os.path.commonpath([self._module_path, matching_path])
|
||||
|
||||
relative_module_path = self._module_path.relative_to(common_path)
|
||||
relative_import_path = matching_path.relative_to(common_path)
|
||||
|
||||
# Shape the relative path to a relative Python import statement.
|
||||
python_import_path = "." * len(relative_module_path.parents) + replace_capnp_suffix(
|
||||
".".join(relative_import_path.parts)
|
||||
)
|
||||
self._add_import(f"from {python_import_path} import {definition_name}")
|
||||
|
||||
return self.register_type(schema.node.id, schema, name=definition_name, scope=self.scope.root)
|
||||
|
||||
def register_type_var(self, name: str) -> str:
|
||||
"""Find and register the full name of a type variable, which includes its scopes.
|
||||
|
||||
Args:
|
||||
name (str): The type name to register.
|
||||
|
||||
Returns:
|
||||
str: The full name in the format scope0_scope1_..._scopeN_name, including the type name to register.
|
||||
"""
|
||||
full_name: str = self.scope.trace_as_str("_") + f"_{name}"
|
||||
|
||||
self.type_vars.add(full_name)
|
||||
return full_name
|
||||
|
||||
def register_type(self, type_id: int, schema: Any, name: str = "", scope: Scope | None = None) -> CapnpType:
|
||||
"""Register a new type in the writer's registry of types.
|
||||
|
||||
Args:
|
||||
type_id (int): The identification number of the type.
|
||||
schema (Any): The schema that defines the type.
|
||||
name (str, optional): An name to specify, if overriding the type name. Defaults to "".
|
||||
scope (Scope | None, optional): The scope in which the type is defined. Defaults to None.
|
||||
|
||||
Returns:
|
||||
Type: The registered type.
|
||||
"""
|
||||
if not name:
|
||||
name = self.get_display_name(schema)
|
||||
|
||||
if scope is None:
|
||||
scope = self.scope.parent
|
||||
|
||||
if scope is None:
|
||||
raise ValueError(f"No valid scope was found for registering the type '{name}'.")
|
||||
|
||||
self.type_map[type_id] = retval = CapnpType(schema=schema, name=name, scope=scope)
|
||||
return retval
|
||||
|
||||
def is_type_id_known(self, type_id: int) -> bool:
|
||||
"""Check, whether a type ID was previously registered.
|
||||
|
||||
Args:
|
||||
type_id (int): The type ID to check.
|
||||
|
||||
Returns:
|
||||
bool: True, if the type ID is known, False otherwise.
|
||||
"""
|
||||
return type_id in self.type_map
|
||||
|
||||
def get_type_by_id(self, type_id: int) -> CapnpType:
|
||||
"""Look up a type in the type registry, by means of its ID.
|
||||
|
||||
Args:
|
||||
type_id (int): The identification number of the type.
|
||||
|
||||
Raises:
|
||||
KeyError: If the type ID was not found in the registry.
|
||||
|
||||
Returns:
|
||||
Type: The type, if it exists.
|
||||
"""
|
||||
if self.is_type_id_known(type_id):
|
||||
return self.type_map[type_id]
|
||||
|
||||
else:
|
||||
raise KeyError(f"The type ID '{type_id} was not found in the type registry.'")
|
||||
|
||||
def new_scope(self, name: str, node: Any, scope_heading: str) -> None:
|
||||
"""Creates a new scope below the scope of the provided node.
|
||||
|
||||
Args:
|
||||
name (str): The name of the new scope.
|
||||
node (Any): The node whose scope is the parent scope of the new scope.
|
||||
scope_heading (str): The line of code that starts this new scope.
|
||||
"""
|
||||
try:
|
||||
parent_scope = self.scopes_by_id[node.scopeId]
|
||||
|
||||
except KeyError as e:
|
||||
raise NoParentError(f"The scope with name '{name}' has no parent.") from e
|
||||
|
||||
# Add the heading of the scope to the parent scope.
|
||||
parent_scope.add_line(scope_heading)
|
||||
|
||||
# Then, make a new scope that is one indent level deeper.
|
||||
child_scope = Scope(name=name, id=node.id, parent=parent_scope, return_scope=self.scope)
|
||||
|
||||
self.scope = child_scope
|
||||
self.scopes_by_id[node.id] = child_scope
|
||||
|
||||
def return_from_scope(self):
|
||||
"""Return from the current scope."""
|
||||
# Cannot return from the root scope, as it is the highest of all scopes.
|
||||
assert not self.scope.is_root
|
||||
|
||||
scope = self.scope
|
||||
scope.parent.lines += scope.lines
|
||||
self.scope = scope.return_scope
|
||||
|
||||
def get_type_name(self, type_reader: capnp._DynamicStructReader) -> str:
|
||||
"""Extract the type name from a type reader.
|
||||
|
||||
The output type name is prepended by the scope name, if there is a parent scope.
|
||||
|
||||
Args:
|
||||
type_reader (capnp._DynamicStructReader): The type reader to get the type name from.
|
||||
|
||||
Returns:
|
||||
str: The extracted type name.
|
||||
"""
|
||||
try:
|
||||
return CAPNP_TYPE_TO_PYTHON[type_reader.which()]
|
||||
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
type_reader_type = type_reader.which()
|
||||
|
||||
if type_reader_type == "struct":
|
||||
element_type = self.get_type_by_id(type_reader.struct.typeId)
|
||||
type_name = element_type.name
|
||||
generic_params = []
|
||||
|
||||
for brand_scope in type_reader.struct.brand.scopes:
|
||||
brand_scope_type = brand_scope.which()
|
||||
|
||||
if brand_scope_type == "inherit":
|
||||
parent_scope = self.get_type_by_id(brand_scope.scopeId)
|
||||
generic_params.extend(parent_scope.generic_params)
|
||||
|
||||
elif brand_scope_type == "bind":
|
||||
for bind in brand_scope.bind:
|
||||
generic_params.append(self.get_type_name(bind.type))
|
||||
|
||||
else:
|
||||
raise TypeError(f"Unknown brand scope '{brand_scope_type}'.")
|
||||
|
||||
if generic_params:
|
||||
type_name += f"[{', '.join(generic_params)}]"
|
||||
|
||||
elif type_reader_type == "enum":
|
||||
element_type = self.get_type_by_id(type_reader.enum.typeId)
|
||||
type_name = element_type.name
|
||||
|
||||
else:
|
||||
raise TypeError(f"Unknown type reader type '{type_reader_type}'.")
|
||||
|
||||
if not element_type.scope.is_root:
|
||||
return f"{element_type.scope}.{type_name}"
|
||||
|
||||
else:
|
||||
return type_name
|
||||
|
||||
def dumps_pyi(self) -> str:
|
||||
"""Generates string output for the *.pyi stub file that provides type hinting.
|
||||
|
||||
Returns:
|
||||
str: The output string.
|
||||
"""
|
||||
assert self.scope.is_root
|
||||
|
||||
out = []
|
||||
out.append(self.docstring)
|
||||
out.extend(self.imports)
|
||||
out.append("")
|
||||
|
||||
if self.type_vars:
|
||||
for name in sorted(self.type_vars):
|
||||
out.append(f'{name} = TypeVar("{name}")')
|
||||
out.append("")
|
||||
|
||||
out.extend(self.scope.lines)
|
||||
return "\n".join(out)
|
||||
|
||||
def dumps_py(self) -> str:
|
||||
"""Generates string output for the *.py stub file that handles the import of capnproto schemas.
|
||||
|
||||
Returns:
|
||||
str: The output string.
|
||||
"""
|
||||
assert self.scope.is_root
|
||||
|
||||
out = []
|
||||
out.append(self.docstring)
|
||||
out.append("import os")
|
||||
out.append("import capnp")
|
||||
out.append("capnp.remove_import_hook()")
|
||||
out.append("here = os.path.dirname(os.path.abspath(__file__))")
|
||||
|
||||
out.append(f'module_file = os.path.abspath(os.path.join(here, "{self.base_module_name}"))')
|
||||
|
||||
for scope in self.scopes_by_id.values():
|
||||
if scope.parent is not None and scope.parent.is_root:
|
||||
out.append(f"{scope.name} = capnp.load(module_file).{scope.name}")
|
||||
|
||||
return "\n".join(out)
|
@ -0,0 +1,330 @@
|
||||
[[package]]
|
||||
name = "atomicwrites"
|
||||
version = "1.4.0"
|
||||
description = "Atomic file writes."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "21.4.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
|
||||
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
|
||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "22.6.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
|
||||
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.7.4)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.5"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.10.1"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1,<4.0"
|
||||
|
||||
[package.extras]
|
||||
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
|
||||
requirements_deprecated_finder = ["pipreqs", "pip-api"]
|
||||
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||
plugins = ["setuptools"]
|
||||
|
||||
[[package]]
|
||||
name = "more-itertools"
|
||||
version = "8.13.0"
|
||||
description = "More routines for operating on iterables, beyond itertools"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "21.3"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.9.0"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.5.2"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "0.13.1"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "py"
|
||||
version = "1.11.0"
|
||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pycapnp"
|
||||
version = "1.1.1"
|
||||
description = "A cython wrapping of the C++ Cap'n Proto library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.0.9"
|
||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.8"
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["railroad-diagrams", "jinja2"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "5.4.3"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.dependencies]
|
||||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
||||
attrs = ">=17.4.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
more-itertools = ">=4.0.0"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<1.0"
|
||||
py = ">=1.5.0"
|
||||
wcwidth = "*"
|
||||
|
||||
[package.extras]
|
||||
checkqa-mypy = ["mypy (==v0.761)"]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.2.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.5"
|
||||
description = "Measures the displayed width of unicode strings in a terminal"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "cb72b821776054307543e305bf211ea56ad6e97d83b9b443ec5087ddef10fa98"
|
||||
|
||||
[metadata.files]
|
||||
atomicwrites = [
|
||||
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
|
||||
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
|
||||
]
|
||||
attrs = [
|
||||
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
|
||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"},
|
||||
{file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"},
|
||||
{file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"},
|
||||
{file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"},
|
||||
{file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"},
|
||||
{file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"},
|
||||
{file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"},
|
||||
{file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"},
|
||||
{file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"},
|
||||
{file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"},
|
||||
{file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"},
|
||||
{file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"},
|
||||
{file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"},
|
||||
{file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"},
|
||||
{file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"},
|
||||
{file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"},
|
||||
{file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"},
|
||||
{file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"},
|
||||
{file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"},
|
||||
{file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"},
|
||||
{file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"},
|
||||
{file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"},
|
||||
{file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||
]
|
||||
isort = [
|
||||
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
|
||||
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
|
||||
]
|
||||
more-itertools = [
|
||||
{file = "more-itertools-8.13.0.tar.gz", hash = "sha256:a42901a0a5b169d925f6f217cd5a190e32ef54360905b9c39ee7db5313bfec0f"},
|
||||
{file = "more_itertools-8.13.0-py3-none-any.whl", hash = "sha256:c5122bffc5f104d37c1626b8615b511f3427aa5389b94d61e5ef8236bfbc3ddb"},
|
||||
]
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||
]
|
||||
pathspec = [
|
||||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
||||
]
|
||||
py = [
|
||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
|
||||
]
|
||||
pycapnp = [
|
||||
{file = "pycapnp-1.1.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:48169ebf71b032c83348320160abef3992a9b17e7a588b372bb256426cad7564"},
|
||||
{file = "pycapnp-1.1.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1138be597238ca1a5837fd9c81fe10743c8826750e6d2b112d43d3c551bc474f"},
|
||||
{file = "pycapnp-1.1.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:313dbdf28d0f93e22df5db1ba1f0b2bf568e66f55487794ab431175fa2235d57"},
|
||||
{file = "pycapnp-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0627a8f981c7ad509197e4f8b4be613cc36f1c287e3f8917b77c04c50ac8b31a"},
|
||||
{file = "pycapnp-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:97bbd1106fa8eb46a0a6acb1ab81c8dff2ab67b0e1cb679465f368784a85d894"},
|
||||
{file = "pycapnp-1.1.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e4808d524f252d8883db0f0a72f00512bdc137159272ae9436a222b7f3266ef0"},
|
||||
{file = "pycapnp-1.1.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:99903494567d1bec4e3eed45f8ddb67c46552ea10dc148ab12f489b02a605c2a"},
|
||||
{file = "pycapnp-1.1.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2dc3f390f4fd46ad84d1dee6f135770fa538b4964cd2d5430b96806958e86f15"},
|
||||
{file = "pycapnp-1.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:295f9c364d662a603f3454dc9a8f294a55cf7cf0f1d527032e4d933fcacb0574"},
|
||||
{file = "pycapnp-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f5d97c4e0ab7c84308153ee23200d75b9253b2e8617b7b9f838a1c114d448ce3"},
|
||||
{file = "pycapnp-1.1.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:97257ab9e33c223e2c04541c5066c09f91575c5ba7205af94c518461f7222e1a"},
|
||||
{file = "pycapnp-1.1.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a5ce83e1c1e492a58031f55a86f21402e067a12b7ffeb4747b3503eff87fb16e"},
|
||||
{file = "pycapnp-1.1.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4d824455d6e624ced4af5eaea37d88b731de078bf250f3158728ed4f8435e1bb"},
|
||||
{file = "pycapnp-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afbd0705d22d3179d8c3ae0bc37c4fe1c9e084f5266a1930fba6ee690cfe6703"},
|
||||
{file = "pycapnp-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:d4664c5b982a89ffc936e55963c333b68919d6a008f96f6989fff17c379a047c"},
|
||||
{file = "pycapnp-1.1.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:72d5259060f54698ca2cd1c72f57ec301a7505191c14efa3a58b536935afc011"},
|
||||
{file = "pycapnp-1.1.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:78b5b23c796ec956e438f04b787e31b4b059ce65ec225f4476ba7ff662d9aea8"},
|
||||
{file = "pycapnp-1.1.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0bac4b4af672229dcf40aa16108f6363e4dfc38ef424954a19be797eccd6da25"},
|
||||
{file = "pycapnp-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33165fa2bafe1916b182b123e1edb1d5288a2f4872e070bf3cfe8965c87add6d"},
|
||||
{file = "pycapnp-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:5652f9730f7fa695df20b7f5613a31d27bbb7827392a93b8ef32ab60504ef007"},
|
||||
{file = "pycapnp-1.1.1.tar.gz", hash = "sha256:45e77810624b9d2b37cbdc4d1854ff9984b3dea20d1b3f7dd4a65403263a5aea"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
||||
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
|
||||
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
|
||||
]
|
||||
tomli = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
|
||||
{file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
|
||||
]
|
||||
wcwidth = [
|
||||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
||||
]
|
@ -0,0 +1,21 @@
|
||||
[tool.poetry]
|
||||
name = "capnp-stub-generator"
|
||||
version = "1.0.0"
|
||||
description = ""
|
||||
authors = ["Adrian Figueroa <adrian.figueroa@metirionic.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
black = "^22.6.0"
|
||||
isort = "^5.10.1"
|
||||
pycapnp = "^1.1.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^5.2"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
capnp-stub-generator = "capnp_stub_generator.cli:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
Loading…
Reference in New Issue