198 lines
5.8 KiB
Python
198 lines
5.8 KiB
Python
import abc
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
|
|
import attr
|
|
|
|
from typing import (
|
|
Any,
|
|
Callable,
|
|
cast,
|
|
List,
|
|
Mapping,
|
|
Optional,
|
|
Sequence,
|
|
Type,
|
|
TypeVar,
|
|
)
|
|
|
|
from . import _reference_map
|
|
from ._compat import importlib_resources
|
|
from ._kernel import Kernel
|
|
from .python import _ClassPropertyMeta
|
|
|
|
|
|
# Yea, a global here is kind of gross, however, there's not really a better way of
|
|
# handling this. Fundamentally this is a global value, since we can only reasonably
|
|
# have a single kernel active at any one time in a real program.
|
|
kernel = Kernel()
|
|
|
|
|
|
@attr.s(auto_attribs=True, frozen=True, slots=True)
|
|
class JSIIAssembly:
|
|
name: str
|
|
version: str
|
|
module: str
|
|
filename: str
|
|
|
|
@classmethod
|
|
def load(cls, *args, _kernel=kernel, **kwargs) -> "JSIIAssembly":
|
|
# Our object here really just acts as a record for our JSIIAssembly, it doesn't
|
|
# offer any functionality itself, besides this class method that will trigger
|
|
# the loading of the given assembly in the JSII Kernel.
|
|
assembly = cls(*args, **kwargs)
|
|
|
|
# Actually load the assembly into the kernel, we're using the
|
|
# importlib.resources API here instead of manually constructing the path, in
|
|
# the hopes that this will make JSII modules able to be used with zipimport
|
|
# instead of only on the FS.
|
|
with importlib_resources.as_file(
|
|
importlib_resources.files(f"{assembly.module}._jsii").joinpath(
|
|
assembly.filename
|
|
)
|
|
) as assembly_path:
|
|
_kernel.load(assembly.name, assembly.version, os.fspath(assembly_path))
|
|
|
|
# Give our record of the assembly back to the caller.
|
|
return assembly
|
|
|
|
@classmethod
|
|
def invokeBinScript(
|
|
cls,
|
|
pkgname: str,
|
|
script: str,
|
|
args: Optional[Sequence[str]] = None,
|
|
_kernel=kernel,
|
|
) -> int:
|
|
if args is None:
|
|
args = []
|
|
|
|
response = _kernel.getBinScriptCommand(pkgname, script, args)
|
|
|
|
result = subprocess.run(
|
|
" ".join([response.command, *response.args]),
|
|
encoding="utf-8",
|
|
shell=True,
|
|
env=response.env,
|
|
)
|
|
return result.returncode
|
|
|
|
|
|
M = TypeVar("M")
|
|
|
|
|
|
class JSIIMeta(_ClassPropertyMeta, type):
|
|
def __new__(
|
|
cls: Type["JSIIMeta"],
|
|
name: str,
|
|
bases: tuple,
|
|
attrs: dict,
|
|
*,
|
|
jsii_type: Optional[str] = None,
|
|
) -> "JSIIMeta":
|
|
# We want to ensure that subclasses of a JSII class do not require setting the
|
|
# jsii_type keyword argument. They should be able to subclass it as normal.
|
|
# Since their parent class will have the __jsii_type__ variable defined, they
|
|
# will as well anyways.
|
|
if jsii_type is not None:
|
|
attrs["__jsii_type__"] = jsii_type
|
|
# The declared type should NOT be inherited by subclasses. This way we can identify whether
|
|
# an MRO entry corresponds to a possible overrides contributor or not.
|
|
attrs["__jsii_declared_type__"] = jsii_type
|
|
|
|
obj = super().__new__(cls, name, bases, attrs)
|
|
|
|
# Now that we've created the class, we'll need to register it with our reference
|
|
# mapper. We only do this for types that are actually jsii types, and not any
|
|
# subclasses of them.
|
|
if jsii_type is not None:
|
|
_reference_map.register_type(obj)
|
|
|
|
return cast("JSIIMeta", obj)
|
|
|
|
def __call__(cls: Type[M], *args: Any, **kwargs) -> M:
|
|
# There is no way to constrain the metaclass of a `Type[M]` hint today, so we have to
|
|
# perform a `cast` trick here in order for MyPy to accept this code as valid... The implicit
|
|
# arguments to `super()` otherwise are `super(__class__, cls)`, which results in an error.
|
|
inst = super(JSIIMeta, cast(JSIIMeta, cls)).__call__(*args, **kwargs)
|
|
|
|
# Register this instance with our reference map.
|
|
_reference_map.register_reference(inst)
|
|
|
|
return inst
|
|
|
|
|
|
class JSIIAbstractClass(abc.ABCMeta, JSIIMeta):
|
|
pass
|
|
|
|
|
|
F = TypeVar("F", bound=Callable[..., Any])
|
|
T = TypeVar("T", bound=Type[Any])
|
|
|
|
|
|
def enum(*, jsii_type: str) -> Callable[[T], T]:
|
|
def deco(cls):
|
|
cls.__jsii_type__ = jsii_type
|
|
_reference_map.register_enum(cls)
|
|
return cls
|
|
|
|
return deco
|
|
|
|
|
|
def data_type(
|
|
*,
|
|
jsii_type: str,
|
|
jsii_struct_bases: List[Type[Any]],
|
|
name_mapping: Mapping[str, str],
|
|
) -> Callable[[T], T]:
|
|
def deco(cls):
|
|
cls.__jsii_type__ = jsii_type
|
|
cls.__jsii_struct_bases__ = jsii_struct_bases
|
|
cls.__jsii_name_mapping__ = name_mapping
|
|
_reference_map.register_data_type(cls)
|
|
return cls
|
|
|
|
return deco
|
|
|
|
|
|
def member(*, jsii_name: str) -> Callable[[F], F]:
|
|
def deco(fn):
|
|
fn.__jsii_name__ = jsii_name
|
|
return fn
|
|
|
|
return deco
|
|
|
|
|
|
def implements(*interfaces: Type[Any]) -> Callable[[T], T]:
|
|
def deco(cls):
|
|
cls.__jsii_type__ = getattr(cls, "__jsii_type__", None)
|
|
cls.__jsii_ifaces__ = getattr(cls, "__jsii_ifaces__", []) + list(interfaces)
|
|
cls.__jsii_proxy_class__ = getattr(cls, "__jsii_proxy_class__", lambda: None)
|
|
|
|
# https://github.com/agronholm/typeguard/issues/479
|
|
cls.__protocol_attrs__ = getattr(cls, "__protocol_attrs__", [])
|
|
return cls
|
|
|
|
return deco
|
|
|
|
|
|
def interface(*, jsii_type: str) -> Callable[[T], T]:
|
|
def deco(iface):
|
|
iface.__jsii_type__ = jsii_type
|
|
_reference_map.register_interface(iface)
|
|
return iface
|
|
|
|
return deco
|
|
|
|
|
|
def proxy_for(abstract_class: Type[Any]) -> Type[Any]:
|
|
if not hasattr(abstract_class, "__jsii_proxy_class__"):
|
|
raise TypeError(f"{abstract_class} is not a JSII Abstract class.")
|
|
|
|
return cast(Any, abstract_class).__jsii_proxy_class__()
|
|
|
|
|
|
def python_jsii_mapping(cls: Type[Any]) -> Optional[Mapping[str, str]]:
|
|
return getattr(cls, "__jsii_name_mapping__", None)
|