Ausgabe der neuen DB Einträge
This commit is contained in:
parent
bad48e1627
commit
cfbbb9ee3d
2399 changed files with 843193 additions and 43 deletions
440
venv/lib/python3.9/site-packages/stone/ir/api.py
Normal file
440
venv/lib/python3.9/site-packages/stone/ir/api.py
Normal file
|
|
@ -0,0 +1,440 @@
|
|||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from collections import OrderedDict
|
||||
# See <https://github.com/PyCQA/pylint/issues/73>
|
||||
from distutils.version import StrictVersion
|
||||
import six
|
||||
|
||||
from .data_types import (
|
||||
doc_unwrap,
|
||||
is_alias,
|
||||
is_composite_type,
|
||||
is_list_type,
|
||||
is_nullable_type,
|
||||
)
|
||||
|
||||
_MYPY = False
|
||||
if _MYPY:
|
||||
import typing # pylint: disable=import-error,useless-suppression
|
||||
|
||||
from .data_types import ( # noqa: F401 # pylint: disable=unused-import
|
||||
Alias,
|
||||
Annotation,
|
||||
AnnotationType,
|
||||
DataType,
|
||||
List as DataTypeList,
|
||||
Nullable,
|
||||
Struct,
|
||||
UserDefined,
|
||||
)
|
||||
|
||||
from stone.frontend.ast import AstRouteDef # noqa: F401 # pylint: disable=unused-import
|
||||
|
||||
# TODO: This can be changed back to a single declaration with a
|
||||
# unicode literal after <https://github.com/python/mypy/pull/2516>
|
||||
# makes it into a PyPi release
|
||||
if six.PY3:
|
||||
NamespaceDict = typing.Dict[typing.Text, 'ApiNamespace']
|
||||
else:
|
||||
NamespaceDict = typing.Dict[typing.Text, b'ApiNamespace']
|
||||
|
||||
|
||||
class Api(object):
|
||||
"""
|
||||
A full description of an API's namespaces, data types, and routes.
|
||||
"""
|
||||
def __init__(self, version):
|
||||
# type: (str) -> None
|
||||
self.version = StrictVersion(version)
|
||||
self.namespaces = OrderedDict() # type: NamespaceDict
|
||||
self.route_schema = None # type: typing.Optional[Struct]
|
||||
|
||||
def ensure_namespace(self, name):
|
||||
# type: (str) -> ApiNamespace
|
||||
"""
|
||||
Only creates a namespace if it hasn't yet been defined.
|
||||
|
||||
:param str name: Name of the namespace.
|
||||
|
||||
:return ApiNamespace:
|
||||
"""
|
||||
if name not in self.namespaces:
|
||||
self.namespaces[name] = ApiNamespace(name)
|
||||
return self.namespaces[name]
|
||||
|
||||
def normalize(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Alphabetizes namespaces and routes to make spec parsing order mostly
|
||||
irrelevant.
|
||||
"""
|
||||
ordered_namespaces = OrderedDict() # type: NamespaceDict
|
||||
# self.namespaces is currently ordered by declaration order.
|
||||
for namespace_name in sorted(self.namespaces.keys()):
|
||||
ordered_namespaces[namespace_name] = self.namespaces[namespace_name]
|
||||
self.namespaces = ordered_namespaces
|
||||
|
||||
for namespace in self.namespaces.values():
|
||||
namespace.normalize()
|
||||
|
||||
def add_route_schema(self, route_schema):
|
||||
# type: (Struct) -> None
|
||||
assert self.route_schema is None
|
||||
self.route_schema = route_schema
|
||||
|
||||
|
||||
class _ImportReason(object):
|
||||
"""
|
||||
Tracks the reason a namespace was imported.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
self.alias = False
|
||||
self.data_type = False
|
||||
self.annotation = False
|
||||
self.annotation_type = False
|
||||
|
||||
|
||||
class ApiNamespace(object):
|
||||
"""
|
||||
Represents a category of API endpoints and their associated data types.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
# type: (typing.Text) -> None
|
||||
self.name = name
|
||||
self.doc = None # type: typing.Optional[six.text_type]
|
||||
self.routes = [] # type: typing.List[ApiRoute]
|
||||
# TODO (peichao): route_by_name is deprecated by routes_by_name and should be removed.
|
||||
self.route_by_name = {} # type: typing.Dict[typing.Text, ApiRoute]
|
||||
self.routes_by_name = {} # type: typing.Dict[typing.Text, ApiRoutesByVersion]
|
||||
self.data_types = [] # type: typing.List[UserDefined]
|
||||
self.data_type_by_name = {} # type: typing.Dict[str, UserDefined]
|
||||
self.aliases = [] # type: typing.List[Alias]
|
||||
self.alias_by_name = {} # type: typing.Dict[str, Alias]
|
||||
self.annotations = [] # type: typing.List[Annotation]
|
||||
self.annotation_by_name = {} # type: typing.Dict[str, Annotation]
|
||||
self.annotation_types = [] # type: typing.List[AnnotationType]
|
||||
self.annotation_type_by_name = {} # type: typing.Dict[str, AnnotationType]
|
||||
self._imported_namespaces = {} # type: typing.Dict[ApiNamespace, _ImportReason]
|
||||
|
||||
def add_doc(self, docstring):
|
||||
# type: (six.text_type) -> None
|
||||
"""Adds a docstring for this namespace.
|
||||
|
||||
The input docstring is normalized to have no leading whitespace and
|
||||
no trailing whitespace except for a newline at the end.
|
||||
|
||||
If a docstring already exists, the new normalized docstring is appended
|
||||
to the end of the existing one with two newlines separating them.
|
||||
"""
|
||||
assert isinstance(docstring, six.text_type), type(docstring)
|
||||
normalized_docstring = doc_unwrap(docstring) + '\n'
|
||||
if self.doc is None:
|
||||
self.doc = normalized_docstring
|
||||
else:
|
||||
self.doc += normalized_docstring
|
||||
|
||||
def add_route(self, route):
|
||||
# type: (ApiRoute) -> None
|
||||
self.routes.append(route)
|
||||
if route.version == 1:
|
||||
self.route_by_name[route.name] = route
|
||||
if route.name not in self.routes_by_name:
|
||||
self.routes_by_name[route.name] = ApiRoutesByVersion()
|
||||
self.routes_by_name[route.name].at_version[route.version] = route
|
||||
|
||||
def add_data_type(self, data_type):
|
||||
# type: (UserDefined) -> None
|
||||
self.data_types.append(data_type)
|
||||
self.data_type_by_name[data_type.name] = data_type
|
||||
|
||||
def add_alias(self, alias):
|
||||
# type: (Alias) -> None
|
||||
self.aliases.append(alias)
|
||||
self.alias_by_name[alias.name] = alias
|
||||
|
||||
def add_annotation(self, annotation):
|
||||
# type: (Annotation) -> None
|
||||
self.annotations.append(annotation)
|
||||
self.annotation_by_name[annotation.name] = annotation
|
||||
|
||||
def add_annotation_type(self, annotation_type):
|
||||
# type: (AnnotationType) -> None
|
||||
self.annotation_types.append(annotation_type)
|
||||
self.annotation_type_by_name[annotation_type.name] = annotation_type
|
||||
|
||||
def add_imported_namespace(self,
|
||||
namespace,
|
||||
imported_alias=False,
|
||||
imported_data_type=False,
|
||||
imported_annotation=False,
|
||||
imported_annotation_type=False):
|
||||
# type: (ApiNamespace, bool, bool, bool, bool) -> None
|
||||
"""
|
||||
Keeps track of namespaces that this namespace imports.
|
||||
|
||||
Args:
|
||||
namespace (Namespace): The imported namespace.
|
||||
imported_alias (bool): Set if this namespace references an alias
|
||||
in the imported namespace.
|
||||
imported_data_type (bool): Set if this namespace references a
|
||||
data type in the imported namespace.
|
||||
imported_annotation (bool): Set if this namespace references a
|
||||
annotation in the imported namespace.
|
||||
imported_annotation_type (bool): Set if this namespace references an
|
||||
annotation in the imported namespace, possibly indirectly (by
|
||||
referencing an annotation elsewhere that has this type).
|
||||
"""
|
||||
assert self.name != namespace.name, \
|
||||
'Namespace cannot import itself.'
|
||||
reason = self._imported_namespaces.setdefault(namespace, _ImportReason())
|
||||
if imported_alias:
|
||||
reason.alias = True
|
||||
if imported_data_type:
|
||||
reason.data_type = True
|
||||
if imported_annotation:
|
||||
reason.annotation = True
|
||||
if imported_annotation_type:
|
||||
reason.annotation_type = True
|
||||
|
||||
def linearize_data_types(self):
|
||||
# type: () -> typing.List[UserDefined]
|
||||
"""
|
||||
Returns a list of all data types used in the namespace. Because the
|
||||
inheritance of data types can be modeled as a DAG, the list will be a
|
||||
linearization of the DAG. It's ideal to generate data types in this
|
||||
order so that composite types that reference other composite types are
|
||||
defined in the correct order.
|
||||
"""
|
||||
linearized_data_types = []
|
||||
seen_data_types = set() # type: typing.Set[UserDefined]
|
||||
|
||||
def add_data_type(data_type):
|
||||
# type: (UserDefined) -> None
|
||||
if data_type in seen_data_types:
|
||||
return
|
||||
elif data_type.namespace != self:
|
||||
# We're only concerned with types defined in this namespace.
|
||||
return
|
||||
if is_composite_type(data_type) and data_type.parent_type:
|
||||
add_data_type(data_type.parent_type)
|
||||
linearized_data_types.append(data_type)
|
||||
seen_data_types.add(data_type)
|
||||
|
||||
for data_type in self.data_types:
|
||||
add_data_type(data_type)
|
||||
|
||||
return linearized_data_types
|
||||
|
||||
def linearize_aliases(self):
|
||||
# type: () -> typing.List[Alias]
|
||||
"""
|
||||
Returns a list of all aliases used in the namespace. The aliases are
|
||||
ordered to ensure that if they reference other aliases those aliases
|
||||
come earlier in the list.
|
||||
"""
|
||||
linearized_aliases = []
|
||||
seen_aliases = set() # type: typing.Set[Alias]
|
||||
|
||||
def add_alias(alias):
|
||||
# type: (Alias) -> None
|
||||
if alias in seen_aliases:
|
||||
return
|
||||
elif alias.namespace != self:
|
||||
return
|
||||
if is_alias(alias.data_type):
|
||||
add_alias(alias.data_type)
|
||||
linearized_aliases.append(alias)
|
||||
seen_aliases.add(alias)
|
||||
|
||||
for alias in self.aliases:
|
||||
add_alias(alias)
|
||||
|
||||
return linearized_aliases
|
||||
|
||||
def get_route_io_data_types(self):
|
||||
# type: () -> typing.List[UserDefined]
|
||||
"""
|
||||
Returns a list of all user-defined data types that are referenced as
|
||||
either an argument, result, or error of a route. If a List or Nullable
|
||||
data type is referenced, then the contained data type is returned
|
||||
assuming it's a user-defined type.
|
||||
"""
|
||||
data_types = set() # type: typing.Set[UserDefined]
|
||||
for route in self.routes:
|
||||
data_types |= self.get_route_io_data_types_for_route(route)
|
||||
return sorted(data_types, key=lambda dt: dt.name)
|
||||
|
||||
def get_route_io_data_types_for_route(self, route):
|
||||
# type: (ApiRoute) -> typing.Set[UserDefined]
|
||||
"""
|
||||
Given a route, returns a set of its argument/result/error datatypes.
|
||||
"""
|
||||
data_types = set() # type: typing.Set[UserDefined]
|
||||
for dtype in (route.arg_data_type, route.result_data_type, route.error_data_type):
|
||||
while is_list_type(dtype) or is_nullable_type(dtype):
|
||||
data_list_type = dtype # type: typing.Any
|
||||
dtype = data_list_type.data_type
|
||||
if is_composite_type(dtype) or is_alias(dtype):
|
||||
data_user_type = dtype # type: typing.Any
|
||||
data_types.add(data_user_type)
|
||||
return data_types
|
||||
|
||||
def get_imported_namespaces(self,
|
||||
must_have_imported_data_type=False,
|
||||
consider_annotations=False,
|
||||
consider_annotation_types=False):
|
||||
# type: (bool, bool, bool) -> typing.List[ApiNamespace]
|
||||
"""
|
||||
Returns a list of Namespace objects. A namespace is a member of this
|
||||
list if it is imported by the current namespace and a data type is
|
||||
referenced from it. Namespaces are in ASCII order by name.
|
||||
|
||||
Args:
|
||||
must_have_imported_data_type (bool): If true, result does not
|
||||
include namespaces that were not imported for data types.
|
||||
consider_annotations (bool): If false, result does not include
|
||||
namespaces that were only imported for annotations
|
||||
consider_annotation_types (bool): If false, result does not
|
||||
include namespaces that were only imported for annotation types.
|
||||
|
||||
Returns:
|
||||
List[Namespace]: A list of imported namespaces.
|
||||
"""
|
||||
imported_namespaces = []
|
||||
for imported_namespace, reason in self._imported_namespaces.items():
|
||||
if must_have_imported_data_type and not reason.data_type:
|
||||
continue
|
||||
if (not consider_annotations) and not (
|
||||
reason.data_type or reason.alias or reason.annotation_type
|
||||
):
|
||||
continue
|
||||
if (not consider_annotation_types) and not (
|
||||
reason.data_type or reason.alias or reason.annotation
|
||||
):
|
||||
continue
|
||||
|
||||
imported_namespaces.append(imported_namespace)
|
||||
imported_namespaces.sort(key=lambda n: n.name)
|
||||
return imported_namespaces
|
||||
|
||||
def get_namespaces_imported_by_route_io(self):
|
||||
# type: () -> typing.List[ApiNamespace]
|
||||
"""
|
||||
Returns a list of Namespace objects. A namespace is a member of this
|
||||
list if it is imported by the current namespace and has a data type
|
||||
from it referenced as an argument, result, or error of a route.
|
||||
Namespaces are in ASCII order by name.
|
||||
"""
|
||||
namespace_data_types = sorted(self.get_route_io_data_types(),
|
||||
key=lambda dt: dt.name)
|
||||
referenced_namespaces = set()
|
||||
for data_type in namespace_data_types:
|
||||
if data_type.namespace != self:
|
||||
referenced_namespaces.add(data_type.namespace)
|
||||
return sorted(referenced_namespaces, key=lambda n: n.name)
|
||||
|
||||
def normalize(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Alphabetizes routes to make route declaration order irrelevant.
|
||||
"""
|
||||
self.routes.sort(key=lambda route: route.name)
|
||||
self.data_types.sort(key=lambda data_type: data_type.name)
|
||||
self.aliases.sort(key=lambda alias: alias.name)
|
||||
self.annotations.sort(key=lambda annotation: annotation.name)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return str('ApiNamespace({!r})').format(self.name)
|
||||
|
||||
|
||||
class ApiRoute(object):
|
||||
"""
|
||||
Represents an API endpoint.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
name,
|
||||
version,
|
||||
ast_node):
|
||||
# type: (typing.Text, int, typing.Optional[AstRouteDef]) -> None
|
||||
"""
|
||||
:param str name: Designated name of the endpoint.
|
||||
:param int version: Designated version of the endpoint.
|
||||
:param ast_node: Raw route definition from the parser.
|
||||
"""
|
||||
self.name = name
|
||||
self.version = version
|
||||
self._ast_node = ast_node
|
||||
|
||||
# These attributes are set later by set_attributes()
|
||||
self.deprecated = None # type: typing.Optional[DeprecationInfo]
|
||||
self.raw_doc = None # type: typing.Optional[typing.Text]
|
||||
self.doc = None # type: typing.Optional[typing.Text]
|
||||
self.arg_data_type = None # type: typing.Optional[DataType]
|
||||
self.result_data_type = None # type: typing.Optional[DataType]
|
||||
self.error_data_type = None # type: typing.Optional[DataType]
|
||||
self.attrs = None # type: typing.Optional[typing.Mapping[typing.Text, typing.Any]]
|
||||
|
||||
def set_attributes(self, deprecated, doc, arg_data_type, result_data_type,
|
||||
error_data_type, attrs):
|
||||
"""
|
||||
Converts a forward reference definition of a route into a full
|
||||
definition.
|
||||
|
||||
:param DeprecationInfo deprecated: Set if this route is deprecated.
|
||||
:param str doc: Description of the endpoint.
|
||||
:type arg_data_type: :class:`stone.data_type.DataType`
|
||||
:type result_data_type: :class:`stone.data_type.DataType`
|
||||
:type error_data_type: :class:`stone.data_type.DataType`
|
||||
:param dict attrs: Map of string keys to values that are either int,
|
||||
float, bool, str, or None. These are the route attributes assigned
|
||||
in the spec.
|
||||
"""
|
||||
self.deprecated = deprecated
|
||||
self.raw_doc = doc
|
||||
self.doc = doc_unwrap(doc)
|
||||
self.arg_data_type = arg_data_type
|
||||
self.result_data_type = result_data_type
|
||||
self.error_data_type = error_data_type
|
||||
self.attrs = attrs
|
||||
|
||||
def name_with_version(self):
|
||||
"""
|
||||
Get user-friendly representation of the route.
|
||||
|
||||
:return: Route name with version suffix. The version suffix is omitted for version 1.
|
||||
"""
|
||||
if self.version == 1:
|
||||
return self.name
|
||||
else:
|
||||
return '{}:{}'.format(self.name, self.version)
|
||||
|
||||
def __repr__(self):
|
||||
return 'ApiRoute({})'.format(self.name_with_version())
|
||||
|
||||
|
||||
class DeprecationInfo(object):
|
||||
|
||||
def __init__(self, by=None):
|
||||
# type: (typing.Optional[ApiRoute]) -> None
|
||||
"""
|
||||
:param ApiRoute by: The route that replaces this deprecated one.
|
||||
"""
|
||||
assert by is None or isinstance(by, ApiRoute), repr(by)
|
||||
self.by = by
|
||||
|
||||
class ApiRoutesByVersion(object):
|
||||
"""
|
||||
Represents routes of different versions for a common name.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
:param at_version: The dict mapping a version number to a route.
|
||||
"""
|
||||
self.at_version = {} # type: typing.Dict[int, ApiRoute]
|
||||
Loading…
Add table
Add a link
Reference in a new issue