") - if self.swagger: - self.write(f"
Visit here to login and use my swagger doc
") - self.finish() - - - - - - - -__all__ = [ - SystemHostHandler.__name__, - UsersHandler.__name__, - AppSettingsHandler.__name__, - AppSettingHandler.__name__, - LoginHandler.__name__, - LogoutHandler.__name__, - WhoAmIHandler.__name__, - PagesHandler.__name__, - PageHandler.__name__, - SubscribersHandler.__name__, - SwaggerUIHandler.__name__, - MainHandler.__name__ -] \ No newline at end of file diff --git a/hololinked/system_host/models.py b/hololinked/system_host/models.py deleted file mode 100644 index 14d7948..0000000 --- a/hololinked/system_host/models.py +++ /dev/null @@ -1,86 +0,0 @@ -import typing -from dataclasses import asdict, field - -from sqlalchemy import Integer, String, JSON, ARRAY, Boolean, BLOB -from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase, MappedAsDataclass - -from ..server.constants import JSONSerializable - - -class HololinkedHostTableBase(DeclarativeBase): - pass - -class Pages(HololinkedHostTableBase, MappedAsDataclass): - __tablename__ = "pages" - - name : Mapped[str] = mapped_column(String(1024), primary_key=True, nullable=False) - URL : Mapped[str] = mapped_column(String(1024), unique=True, nullable=False) - description : Mapped[str] = mapped_column(String(16384)) - json_specfication : Mapped[typing.Dict[str, typing.Any]] = mapped_column(JSON, nullable=True) - - def json(self): - return { - "name" : self.name, - "URL" : self.URL, - "description" : self.description, - "json_specification" : self.json_specfication - } - -class AppSettings(HololinkedHostTableBase, MappedAsDataclass): - __tablename__ = "appsettings" - - field : Mapped[str] = mapped_column(String(8192), primary_key=True) - value : Mapped[typing.Dict[str, typing.Any]] = mapped_column(JSON) - - def json(self): - return asdict(self) - -class LoginCredentials(HololinkedHostTableBase, MappedAsDataclass): - __tablename__ = "login_credentials" - - email : Mapped[str] = mapped_column(String(1024), primary_key=True) - password : Mapped[str] = mapped_column(String(1024), unique=True) - -class Server(HololinkedHostTableBase, MappedAsDataclass): - __tablename__ = "http_servers" - - hostname : Mapped[str] = mapped_column(String, primary_key=True) - type : Mapped[str] = mapped_column(String) - port : Mapped[int] = mapped_column(Integer) - IPAddress : Mapped[str] = mapped_column(String) - https : Mapped[bool] = mapped_column(Boolean) - - def json(self): - return { - "hostname" : self.hostname, - "type" : self.type, - "port" : self.port, - "IPAddress" : self.IPAddress, - "https" : self.https - } - - - -class HololinkedHostInMemoryTableBase(DeclarativeBase): - pass - -class UserSession(HololinkedHostInMemoryTableBase, MappedAsDataclass): - __tablename__ = "user_sessions" - - email : Mapped[str] = mapped_column(String) - session_key : Mapped[BLOB] = mapped_column(BLOB, primary_key=True) - origin : Mapped[str] = mapped_column(String) - user_agent : Mapped[str] = mapped_column(String) - remote_IP : Mapped[str] = mapped_column(String) - - - -__all__ = [ - HololinkedHostTableBase.__name__, - HololinkedHostInMemoryTableBase.__name__, - Pages.__name__, - AppSettings.__name__, - LoginCredentials.__name__, - Server.__name__, - UserSession.__name__ -] \ No newline at end of file diff --git a/hololinked/system_host/server.py b/hololinked/system_host/server.py deleted file mode 100644 index f229abb..0000000 --- a/hololinked/system_host/server.py +++ /dev/null @@ -1,146 +0,0 @@ -import secrets -import os -import base64 -import socket -import json -import asyncio -import ssl -import typing -import getpass -from argon2 import PasswordHasher - -from sqlalchemy import create_engine -from sqlalchemy.orm import Session, sessionmaker -from sqlalchemy.ext import asyncio as asyncio_ext -from sqlalchemy_utils import database_exists, create_database, drop_database -from tornado.web import Application, StaticFileHandler, RequestHandler -from tornado.httpserver import HTTPServer as TornadoHTTP1Server -from tornado import ioloop - -from ..server.serializers import JSONSerializer -from ..server.database import BaseDB -from ..server.config import global_config -from .models import * -from .handlers import * - - -def create_system_host(db_config_file : typing.Optional[str] = None, ssl_context : typing.Optional[ssl.SSLContext] = None, - handlers : typing.List[typing.Tuple[str, RequestHandler, dict]] = [], **server_settings) -> TornadoHTTP1Server: - """ - global function for creating system hosting server using a database configuration file, SSL context & certain - server settings. Currently supports only one server per process due to usage of some global variables. - """ - disk_DB_URL = BaseDB.create_postgres_URL(db_config_file, database='hololinked-host', use_dialect=False) - if not database_exists(disk_DB_URL): - try: - create_database(disk_DB_URL) - sync_disk_db_engine = create_engine(disk_DB_URL) - HololinkedHostTableBase.metadata.create_all(sync_disk_db_engine) - create_tables(sync_disk_db_engine) - create_credentials(sync_disk_db_engine) - except Exception as ex: - if disk_DB_URL.startswith("sqlite"): - os.remove(disk_DB_URL.split('/')[-1]) - else: - drop_database(disk_DB_URL) - raise ex from None - finally: - sync_disk_db_engine.dispose() - - disk_DB_URL = BaseDB.create_postgres_URL(db_config_file, database='hololinked-host', use_dialect=True) - disk_engine = asyncio_ext.create_async_engine(disk_DB_URL, echo=True) - disk_session = sessionmaker(disk_engine, expire_on_commit=True, - class_=asyncio_ext.AsyncSession) # type: asyncio_ext.AsyncSession - - mem_DB_URL = BaseDB.create_sqlite_URL(in_memory=True) - mem_engine = create_engine(mem_DB_URL, echo=True) - mem_session = sessionmaker(mem_engine, expire_on_commit=True, - class_=Session) # type: Session - HololinkedHostInMemoryTableBase.metadata.create_all(mem_engine) - - CORS = server_settings.pop("CORS", []) - if not isinstance(CORS, (str, list)): - raise TypeError("CORS should be a list of strings or a string") - if isinstance(CORS, str): - CORS = [CORS] - kwargs = dict( - CORS=CORS, - disk_session=disk_session, - mem_session=mem_session - ) - - system_host_compatible_handlers = [] - for handler in handlers: - system_host_compatible_handlers.append((handler[0], handler[1], kwargs)) - - app = Application([ - (r"/", MainHandler, dict(IP="https://localhost:8080", swagger=True, **kwargs)), - (r"/users", UsersHandler, kwargs), - (r"/pages", PagesHandler, kwargs), - (r"/pages/(.*)", PageHandler, kwargs), - (r"/app-settings", AppSettingsHandler, kwargs), - (r"/app-settings/(.*)", AppSettingHandler, kwargs), - (r"/subscribers", SubscribersHandler, kwargs), - # (r"/remote-objects", RemoteObjectsHandler), - (r"/login", LoginHandler, kwargs), - (r"/logout", LogoutHandler, kwargs), - (r"/swagger-ui", SwaggerUIHandler, kwargs), - *system_host_compatible_handlers, - (r"/(.*)", StaticFileHandler, dict(path=os.path.join(os.path.dirname(__file__), - f"assets{os.sep}hololinked-server-swagger-api{os.sep}system-host-api")) - ), - ], - cookie_secret=base64.b64encode(os.urandom(32)).decode('utf-8'), - **server_settings) - - return TornadoHTTP1Server(app, ssl_options=ssl_context) - - -def start_tornado_server(server : TornadoHTTP1Server, port : int = 8080): - server.listen(port) - event_loop = ioloop.IOLoop.current() - print("starting server") - event_loop.start() - - -def create_tables(engine): - with Session(engine) as session, session.begin(): - file = open(f"{os.path.dirname(os.path.abspath(__file__))}{os.sep}assets{os.sep}default_host_settings.json", 'r') - default_settings = JSONSerializer.generic_load(file) - for name, settings in default_settings.items(): - session.add(AppSettings( - field = name, - value = settings - )) - session.commit() - - -def create_credentials(sync_engine): - """ - create name and password for a new user in a database - """ - - print("Requested primary host seems to use a new database. Give username and password (not for database server, but for client logins from hololinked-portal) : ") - email = input("email-id (not collected anywhere else excepted your own database) : ") - while True: - password = getpass.getpass("password : ") - password_confirm = getpass.getpass("repeat-password : ") - if password != password_confirm: - print("password & repeat password not the same. Try again.") - continue - with Session(sync_engine) as session, session.begin(): - ph = PasswordHasher(time_cost=global_config.PWD_HASHER_TIME_COST) - session.add(LoginCredentials(email=email, password=ph.hash(password))) - session.commit() - return - raise RuntimeError("password not created, aborting database creation.") - - -def delete_database(db_config_file): - # config_file = str(Path(os.path.dirname(__file__)).parent) + "\\assets\\db_config.json" - URL = BaseDB.create_URL(db_config_file, database="hololinked-host", use_dialect=False) - drop_database(URL) - - - -__all__ = ['create_system_host'] \ No newline at end of file diff --git a/hololinked/webdashboard/__init__.py b/hololinked/webdashboard/__init__.py deleted file mode 100644 index 2bd519c..0000000 --- a/hololinked/webdashboard/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .baseprops import dataGrid -from .basecomponents import * -from .serializer import * -from .axios import * -from .statemachine import * -from .app import * -from .actions import * diff --git a/hololinked/webdashboard/actions.py b/hololinked/webdashboard/actions.py deleted file mode 100644 index 73ac20d..0000000 --- a/hololinked/webdashboard/actions.py +++ /dev/null @@ -1,139 +0,0 @@ -from typing import List, Union, Any, Dict - -from ..param.parameters import String, ClassSelector, TypedList -from .baseprops import ComponentName, StringProp, StubProp, ActionID -from .utils import unique_id -from .constants import url_regex -from .valuestub import NumberStub, ObjectStub, BooleanStub - - - -class BaseAction: - - actionType = ComponentName(default=__qualname__) - id = ActionID() - - def __init__(self) -> None: - self.id = unique_id(prefix='actionid_') - - def json(self): - return dict(type=self.actionType, id=self.id) - - -class ActionProp(ClassSelector): - - def __init__(self, default=None, **kwargs): - super().__init__(class_=BaseAction, default=default, - per_instance_descriptor=True, deepcopy_default=True, allow_None=True, - **kwargs) - -class ActionListProp(TypedList): - - def __init__(self, default : Union[List, None] = None, **params): - super().__init__(default, item_type=BaseAction, **params) - - -class ComponentOutputProp(ClassSelector): - - def __init__(self): - super().__init__(default=None, allow_None=True, constant=True, class_=ComponentOutput) - - -class setLocation(BaseAction): - - actionType = ComponentName(default="setLocation") - path = StringProp(default='/') - - def json(self): - return dict(**super().json(), path=self.path) - - -class setGlobalLocation(setLocation): - - actionType = ComponentName(default='setGlobalLocation') - - -class EventSource(BaseAction): - - actionType = ComponentName(default='SSE') - url = StringProp(default = None, allow_None=True, regex=url_regex ) - response = StubProp(doc="""response value of the SSE (symbolic JSON specification based pointer - the actual value is in the browser). - Index it to access specific fields within the response.""" ) - readyState = StubProp(doc="0 - connecting, 1 - open, 2 - closed, use it for comparisons") - withCredentials = StubProp(doc="") - onerror = ActionListProp(doc="actions to execute when event source produces error") - onopen = ActionListProp(doc="actions to execute when event source is subscribed and connected") - - def __init__(self, URL : str) -> None: - super().__init__() - self.response = ObjectStub(self.id) - self.readyState = NumberStub(self.id) - self.withCredentials = BooleanStub(self.id) - self.URL = URL - - def json(self): - return dict(**super().json(), URL=self.URL) - - def close(self): - return Cancel(self.id) - - -class Cancel(BaseAction): - - actionType = ComponentName(default='Cancel') - - def __init__(self, id_or_action : str) -> None: - super().__init__() - if isinstance(id_or_action, BaseAction): - self.cancel_id = id_or_action.id - else: - self.cancel_id = id_or_action - - def json(self) -> Dict[str, Any]: - return dict(**super().json(), cancelID=self.cancel_id) - - -class SSEVideoSource(EventSource): - - actionType = ComponentName( 'SSEVideo' ) - - -class SetState(BaseAction): - - actionType = ComponentName(default='setSimpleFSMState') - componentID = String (default=None, allow_None=True) - state = String (default=None, allow_None=True) - - def __init__(self, componentID : str, state : str) -> None: - super().__init__() - self.componentID = componentID - self.state = state - - def json(self) -> Dict[str, Any]: - return dict(**super().json(), - componentID=self.componentID, - state=self.state - ) - -def setState(componentID : str, state : str) -> SetState: - return SetState(componentID, state) - - - -class ComponentOutput(BaseAction): - - actionType = ComponentName(default='componentOutput') - - def __init__(self, outputID : str): - super().__init__() - self.outputID = outputID - - def json(self) -> Dict[str, Any]: - return dict(**super().json(), - outputID=self.outputID, - ) - - -__all__ = ['setGlobalLocation', 'setLocation', 'setGlobalLocation', 'EventSource', - 'Cancel', 'setState', 'SSEVideoSource'] - diff --git a/hololinked/webdashboard/app.py b/hololinked/webdashboard/app.py deleted file mode 100644 index 6571af4..0000000 --- a/hololinked/webdashboard/app.py +++ /dev/null @@ -1,49 +0,0 @@ -import json -import typing -from pprint import pprint -from typing import Dict, Any, Union - -from .baseprops import BooleanProp, ComponentName, TypedListProp, TypedList -from .basecomponents import BaseComponentWithChildren, Page - - - -class ReactApp(BaseComponentWithChildren): - """ - Main React App - kwargs - URL : give the URL to access all the components - """ - componentName = ComponentName(default="__App__") - children : typing.List[Page] = TypedListProp(item_type=Page, - doc="pages in the app, add multiple pages to quickly switch between UIs") - showUtilitySpeedDial : bool = BooleanProp(default=True, allow_None=False) - remoteObjects : list = TypedList(default=None, allow_None=True, item_type=str, - doc="add rmeote object URLs to display connection status") # type: ignore - - def __init__(self): - super().__init__(id='__App__') - - def save_json(self, file_name : str, indent : int = 4, tree : Union[str, None] = None) -> None: - if tree is None: - with open(file_name, 'w') as file: - json.dump(self.json(), file, ensure_ascii=False, allow_nan=True) - else: - with open(file_name, 'w') as file: - json.dump(self.get_component(self.json(), tree), file, ensure_ascii=False, allow_nan=True) - - def print_json(self, tree : Union[str, None] = None) -> None: - if tree is not None: - pprint(self.get_component(self.json(), tree)) - else: - pprint(self.json()) - - def get_component(self, children : Dict[str, Any], tree : str): - for component_json in children.values(): - if component_json["tree"] == tree: - return component_json - raise AttributeError("No component with tree {} found.".format(tree)) - - - -__all__ = ['ReactApp'] \ No newline at end of file diff --git a/hololinked/webdashboard/axios.py b/hololinked/webdashboard/axios.py deleted file mode 100644 index 15f6856..0000000 --- a/hololinked/webdashboard/axios.py +++ /dev/null @@ -1,193 +0,0 @@ -from typing import Union -from collections import namedtuple - -from ..param.parameters import TypedList, ClassSelector, TypedDict -from .baseprops import StringProp, SelectorProp, IntegerProp, BooleanProp, ObjectProp, StubProp, ComponentName -from .valuestub import ObjectStub -from .actions import BaseAction, Cancel -from .utils import unique_id - - - -interceptor = namedtuple('interceptor', 'request response') - -class InterceptorContainer: - - def use(self, callable : BaseAction) -> None: - self.callable = callable - - - -class AxiosRequestConfig(BaseAction): - """ - url : str - method : ['GET', 'POST', 'PUT'] - baseurl : str - headers : dict - params : dict - data : dict - timeout : int > 0 - withCredentials : bool - auth : dict - responseType : ['arraybuffer', 'document', 'json', 'text', 'stream', 'blob'] - xsrfCookieName : str - xsrfHeaderName : str - maxContentLength : int - maxBodyLength : int - maxRedirects : int - """ - actionType = ComponentName ( default = "SingleHTTPRequest") - url = StringProp ( default = None, allow_None = True, - doc = """URL to make request. Enter full URL or just the path without the server. - Server can also be specified in baseURL. Please dont specify server in both URL and baseURL""" ) - method = SelectorProp ( objects = ['get', 'post', 'put', 'delete', 'options'], default = 'post', - doc = "HTTP method of the request" ) - baseurl = StringProp ( default = None, allow_None = True, - doc = "Server or path to prepend to url" ) - # headers = DictProp ( default = {'X-Requested-With': 'XMLAxiosRequestConfigRequest'}, key_type = str, allow_None = True ) - # params = DictProp ( default = None, allow_None = True ) - data = ObjectProp ( default = None, allow_None = True ) - timeout = IntegerProp ( default = 0, bounds = (0,None), allow_None = False ) - # withCredentials = BooleanProp ( default = False, allow_None = False ) - # # auth = DictProp ( default = None, allow_None = True ) - # responseType = SelectorProp ( objects = ['arraybuffer', 'document', 'json', 'text', 'stream', 'blob'], - # default = 'json', allow_None = True ) - # xsrfCookieName = StringProp ( default = 'XSRF-TOKEN' , allow_None = True ) - # xsrfHeaderName = StringProp ( default = 'X-XSRF-TOKEN', allow_None = True ) - # maxContentLength = IntegerProp ( default = 2000, bounds = (0,None), allow_None = False ) - # maxBodyLength = IntegerProp ( default = 2000, bounds = (0,None), allow_None = False ) - # maxRedirects = IntegerProp ( default = 21, bounds = (0,None), allow_None = False ) - # repr = StringProp ( default = "Axios Request Configuration", readonly = True, allow_None = False ) - # intervalAfterTimeout = BooleanProp ( default = False, allow_None = False ) - response = StubProp ( doc = """response value of the request (symbolic JSON specification based pointer - the actual value is in the browser). - Index it to access specific fields within the response.""" ) - onStatus = TypedDict ( default = None, allow_None = True, key_type = int, item_type = BaseAction ) - interceptor = ClassSelector (class_=namedtuple, allow_None=True, constant=True, default=None ) - - - def request_json(self): - return { - 'url' : self.url, - 'method' : self.method, - 'baseurl' : self.baseurl, - 'data' : self.data - } - - def json(self): - return dict(**super().json(), config=self.request_json()) - - def __init__(self, **kwargs) -> None: - super().__init__() - self.response = ObjectStub(self.id) - # self.interceptor = interceptor( - # request = InterceptorContainer(), - # response = InterceptorContainer() - # ) - for key, value in kwargs.items(): - setattr(self, key, value) - - def cancel(self): - return Cancel(self.id) - - -def makeRequest(**params) -> AxiosRequestConfig: - """ - url : str - method : ['GET', 'POST', 'PUT'] - baseurl : str - headers : dict - params : dict - data : dict - timeout : int > 0 - withCredentials : bool - auth : dict - responseType : ['arraybuffer', 'document', 'json', 'text', 'stream', 'blob'] - xsrfCookieName : str - xsrfHeaderName : str - maxContentLength : int - maxBodyLength : int - maxRedirects : int - """ - return AxiosRequestConfig(**params) - - -class QueuedHTTPRequests(BaseAction): - - actionType = ComponentName(default="QueuedHTTPRequests") - requests = TypedList(default=None, allow_None=True, item_type=AxiosRequestConfig, - doc="Request objects that will be fired one after the other, order within the list is respected.") - ignoreFailedRequests = BooleanProp(default=True, - doc="If False, if one request fails, remain requests are dropped. If true, failed requests are ignored.") - - def json(self): - return dict(**super().json(), requests=self.requests, ignoreFailedRequests=True) - - def __init__(self, *args : AxiosRequestConfig, ignore_failed_requests : bool = True) -> None: - super().__init__() - self.requests = list(args) - self.ignoreFailedRequests = ignore_failed_requests - - def cancel(self) -> Cancel: - return Cancel(self.id) - - -class ParallelHTTPRequests(BaseAction): - - actionType = ComponentName(default="ParallelHTTPRequests") - requests = TypedList(default=None, allow_None=True, item_type=AxiosRequestConfig, - doc="Request objects that will be fired one after the other. Order within the list is not important.") - - def json(self): - return dict(**super().json(), requests=self.requests, ignoreFailedRequests=True) - - def __init__(self, *args : AxiosRequestConfig) -> None: - super().__init__() - self.requests = list(args) - - -def makeRequests(*args : AxiosRequestConfig, mode : str = 'serial', ignore_failed_requests : bool = True): - if mode == 'serial': - return QueuedHTTPRequests(*args, ignore_failed_requests=ignore_failed_requests) - elif mode == 'parallel': - return ParallelHTTPRequests(*args) - else: - raise ValueError("Only two modes are supported - serial or parallel. Given value : {}".format(mode)) - - - -class RepeatedRequests(BaseAction): - - actionType = ComponentName(default="RepeatedRequests") - requests = ClassSelector(class_=(AxiosRequestConfig, QueuedHTTPRequests, ParallelHTTPRequests), default = None, - allow_None=True) - interval = IntegerProp(default=None, allow_None=True) - - def __init__(self, requests : Union[AxiosRequestConfig, QueuedHTTPRequests, ParallelHTTPRequests], interval : int = 60000) -> None: - super().__init__() - self.requests = requests - # self.id = requests.id - self.interval = interval - - def json(self): - return dict(super().json(), requests=self.requests, interval=self.interval) - - -def repeatRequest(requests, interval = 60000): - return ( - RepeatedRequests( - requests, - interval = interval - ) - ) - - - -class RequestProp(ClassSelector): - - def __init__(self, default = None, **params): - super().__init__(class_=(AxiosRequestConfig, QueuedHTTPRequests, ParallelHTTPRequests, RepeatedRequests), default=default, - deepcopy_default=False, allow_None=True, **params) - - -__all__ = ['makeRequest', 'AxiosRequestConfig', 'QueuedHTTPRequests', 'ParallelHTTPRequests', 'RepeatedRequests', - 'makeRequests', 'repeatRequest' ] \ No newline at end of file diff --git a/hololinked/webdashboard/basecomponents.py b/hololinked/webdashboard/basecomponents.py deleted file mode 100644 index 3aa5ebf..0000000 --- a/hololinked/webdashboard/basecomponents.py +++ /dev/null @@ -1,238 +0,0 @@ -import typing - -from ..param.parameters import TypedList as TypedListProp, String, ClassSelector -from ..param.exceptions import raise_ValueError -from ..param import edit_constant, Parameterized, Parameter - -from .baseprops import (TypedListProp, NodeProp, StringProp, HTMLid, ComponentName, SelectorProp, - StubProp, dataGrid, ObjectProp, IntegerProp, BooleanProp, NumberProp, - TupleSelectorProp) -from .actions import ComponentOutput, ComponentOutputProp -from .exceptions import * -from .statemachine import StateMachineProp -from .valuestub import ValueStub -from .utils import unique_id - - - - -UICOMPONENTS = 'UIcomponents' -ACTIONS = 'actions' -NODES = 'nodes' - -class ReactBaseComponent(Parameterized): - """ - Validator class that serves as the information container for all components in the React frontend. The - parameters of this class are serialized to JSON, deserialized at frontend and applied onto components as props, - i.e. this class is a JSON specfication generator. - - Attributes: - - id : The html id of the component, allowed characters are < - tree : read-only, used for generating unique key prop for components, it denotes the nesting of the component based on id. - repr : read-only, used for raising meaningful Exceptions - stateMachine : specify state machine for front end components - """ - componentName = ComponentName(default='ReactBaseComponent') - id = HTMLid(default=None) - tree = String(default=None, allow_None=True, constant=True) - stateMachine = StateMachineProp(default=None, allow_None=True) - # outputID = ActionID() - - def __init__(self, id : str, **params): - # id separated in kwargs to force the user to give it - self.id = id # & also be set as the first parameter before all other parameters - super().__init__(**params) - parameters = self.parameters.descriptors - with edit_constant(parameters['tree']): - self.tree = id - try: - """ - Multiple action_result stubs are possible however, only one internal action stub is possible. - For example, a button's action may be to store the number of clicks, make request etc., however - only number of clicks can be an internal action although any number of other stubs may access this value. - """ - self.action_id = unique_id('actionid_') - # if self.outputID is None: - # with edit_constant(parameters['outputID']): - # self.outputID = action_id - for param in parameters.values(): - if isinstance(param, StubProp): - stub = param.__get__(self, type(self)) - stub.create_base_info(self.action_id) - for param in parameters.values(): - if isinstance(param, ComponentOutputProp): - output_action = ComponentOutput(outputID=self.action_id) - param.__set__(self, output_action) - stub.create_base_info(output_action.id) - except Exception as ex: - raise RuntimeError(f"stub information improperly configured for component {self.componentName} : received exception : {str(ex)}") - - def validate(self): - """ - use this function in a child component to perform type checking and assignment which is appropriate at JSON creation time i.e. - the user has sufficient space to manipulate parts of the props of the component until JSON is created. - Dont forget to call super().secondStagePropCheck as its mandatory to ensure state machine of the - """ - if self.id is None: - raise_ValueError("ID cannot be None for {}".format(self.componentName), self) - - def __str__(self): - if self.id is not None: - return "{} with HTML id : {}".format(self.componentName, self.id) - else: - return self.componentName - - def json(self, JSON : typing.Dict[str, typing.Any] = {}) -> typing.Dict[str, typing.Any]: - self.validate() - JSON[UICOMPONENTS].update({self.id : self.parameters.serialize(mode='FrontendJSONSpec')}) - JSON[ACTIONS].update(JSON[UICOMPONENTS][self.id].pop(ACTIONS, {})) - return JSON - - - -class BaseComponentWithChildren(ReactBaseComponent): - - children = TypedListProp(doc="children of the component", - item_type=(ReactBaseComponent, ValueStub, str)) - - def addComponent(self, *args : ReactBaseComponent) -> None: - for component in args: - if component.componentName == 'ReactApp': - raise TypeError("ReactApp can never be child of any component") - if self.children is None: - self.children = [component] - else: - self.children.append(component) - - def json(self, JSON: typing.Dict[str, typing.Any] = {UICOMPONENTS : {}, ACTIONS : {}}) -> typing.Dict[str, typing.Any]: - super().json(JSON) - if self.children != None and len(self.children) > 0: - for child in self.children: - if isinstance(child, ReactBaseComponent): - with edit_constant(child.parameters.descriptors['tree']): - if child.id is None: - raise_PropError(AttributeError("object {} id is None, cannot create JSON".format(child)), - self, 'children') - child.tree = self.tree + '/' + child.id - child.json(JSON) - return JSON - - -class ReactGridLayout(BaseComponentWithChildren): - - componentName = ComponentName(default="ContextfulRGL") - width = IntegerProp(default=300, - doc='This allows setting the initial width on the server side. \ - This is required unless using the HOC