def open(self, correlation_id): """ Opens the component. :param correlation_id: (optional) transaction id to trace execution through call chain. """ if self._references is not None: raise InvalidStateException(correlation_id, "ALREADY_OPENED", "Container was already opened") try: self._logger.trace(correlation_id, "Starting container.") # Create references with configured components self._references = ContainerReferences() self._init_references(self._references) self._references.put_from_config(self._config) self.set_references(self._references) # Get custom description if available info_descriptor = Descriptor("*", "context-info", "*", "*", "*") self._info = self._references.get_one_optional(info_descriptor) # Reference and open components self._references.open(correlation_id) # Get reference to logger self._logger = CompositeLogger(self._references) self._logger.info(correlation_id, "Container " + self._info.name + " started.") except Exception as ex: self._logger.error(correlation_id, ex, "Failed to start container") traceback.print_exc() raise ex
def __init__(self): """ Creates a new instance of the client. """ self._logger = CompositeLogger() self._counters = CompositeCounters() self._dependency_resolver = DependencyResolver() self._dependency_resolver.put('controller', 'none')
def __init__(self): self._default_config = ConfigParams.from_tuples( "base_route", None, "dependencies.endpoint", "*:endpoint:http:*:1.0") # self._registered = False self._dependency_resolver = DependencyResolver() self._logger = CompositeLogger() self._counters = CompositeCounters()
def __init__(self, name=None): """ Creates a new instance of the message queue. :param name: (optional) a queue name """ self._lock = threading.Lock() self._logger = CompositeLogger() self._counters = CompositeCounters() self._connection_resolver = ConnectionResolver() self._credential_resolver = CredentialResolver() self._name = name
def __init__(self, loader = None, saver = None): """ Creates a new instance of the persistence. :param loader: (optional) a loader to load items from external datasource. :param saver: (optional) a saver to save items to external datasource. """ self._lock = threading.Lock() self._logger = CompositeLogger() self._items = [] self._loader = loader self._saver = saver
def __init__(self): """ Creates a new instance of the performance counters. """ super(PrometheusCounters, self).__init__() self.__logger = CompositeLogger() self.__connection_resolver = HttpConnectionResolver() self.__opened = False self.__source: str = None self.__instance: str = None self.__push_enabled: bool = None self.__client: Any = None self.__request_route: str = None
def __init__(self): """ Creates a new instance of the client. """ self._connection_resolver = HttpConnectionResolver() self._default_config = ConfigParams.from_tuples( "connection.protocol", "http", "connection.host", "0.0.0.0", "connection.port", 3000, "options.timeout", 10000, "options.request_max_size", 1024 * 1024, "options.connect_timeout", 10000, "options.retries", 3, "options.debug", True) self._logger = CompositeLogger() self._counters = CompositeCounters() self._options = ConfigParams() self._headers = {}
def __init__(self): """ Creates HttpEndpoint """ self._default_config = ConfigParams.from_tuples( "connection.protocol", "http", "connection.host", "0.0.0.0", "connection.port", 3000, "credential.ssl_key_file", None, "credential.ssl_crt_file", None, "credential.ssl_ca_file", None, "options.maintenance_enabled", False, "options.request_max_size", 1024 * 1024, "options.file_max_size", 200 * 1024 * 1024, "connection.connect_timeout", 60000, "connection.debug", True) self._connection_resolver = HttpConnectionResolver() self._logger = CompositeLogger() self._counters = CompositeCounters() self._registrations = []
def __init__(self, table_name: str = None, schema_name: str = None): """ Creates a new instance of the persistence component. :param table_name: (optional) a table name. :param schema_name: (optional) a schema name. """ self.__config: ConfigParams = None self.__references: IReferences = None self.__opened: bool = None self.__local_connection: bool = None self.__schema_statements = [] # The dependency resolver. self._dependency_resolver: DependencyResolver = DependencyResolver(self._default_config) # The logger. self._logger: CompositeLogger = CompositeLogger() # The MySQL connection component. self._connection: MySqlConnection = None # The MySQL connection pool object. self._client: Any = None # The MySQL database name. self._database_name: str = None # The MySQL table object. self._table_name: str = None # Max number of objects in data pages self._max_page_size: int = 100 self._table_name = table_name self._schema_name = schema_name
def __init__(self): """ Creates a new instance of the client. """ # The HTTP client. self._client: Any = None # The remote service uri which is calculated on open. self._uri: str = None # The invocation timeout in milliseconds. self._timeout = 1000 # The connection resolver. self._connection_resolver: HttpConnectionResolver = HttpConnectionResolver( ) # The logger. self._logger: CompositeLogger = CompositeLogger() # The performance counters. self._counters: CompositeCounters = CompositeCounters() # The tracer. self._tracer: CompositeTracer = CompositeTracer() # The configuration options. self._options: ConfigParams = ConfigParams() # The base route. self._base_route: str = None # The number of retries. self._retries = 1 # The default headers to be added to every request. self._headers: dict = {} # The connection timeout in milliseconds. self._connect_timeout = 1000 self._correlation_id_location: str = "query"
def __init__(self): # The dependency resolver. self._dependency_resolver: DependencyResolver = DependencyResolver( self._default_config) # The logger. self._logger: CompositeLogger = CompositeLogger() # The performance counters. self._counters: CompositeCounters = CompositeCounters() self._debug = False # The base route. self._base_route: str = None # The HTTP endpoint that exposes this service. self._endpoint: HttpEndpoint = None # The tracer. self._tracer: CompositeTracer = CompositeTracer() self._config: ConfigParams = None self._swagger_service: ISwaggerService = None self._swagger_enabled = False self._swagger_route = 'swagger' self.__local_endpoint: bool = None self.__references: IReferences = None self.__opened: bool = None
def setup_method(self, method): refs = References.from_tuples( Descriptor('pip-services-commons', 'logger', 'console', 'default', '1.0'), ConsoleLogger(), Descriptor('pip-services-commons', 'logger', 'null', 'default', '1.0'), NullLogger()) self.log = CompositeLogger(refs) self.fixture = LoggerFixture(self.log)
def setup_method(self, method): refs = References.from_tuples( Descriptor("pip-services", "logger", "null", "default", "1.0"), NullLogger(), Descriptor("pip-services", "logger", "console", "default", "1.0"), ConsoleLogger()) self.log = CompositeLogger(refs) self.fixture = LoggerFixture(self.log)
def __init__(self, name=None, description=None): """ Creates a new instance of the container. :param name: (optional) a container name (accessible via ContextInfo) :param description: (optional) a container description (accessible via ContextInfo) """ self._logger = NullLogger() self._info = ContextInfo(name, description) self._factories = DefaultContainerFactory()
class DummyController(IReferenceable, IReconfigurable, IOpenable, INotifiable): __timer: FixedRateTimer = None __logger: CompositeLogger = None __message: str = None __counter: int = None def __init__(self): self.__message = "Hello World!" self.__logger = CompositeLogger() self.__timer = FixedRateTimer(self, 1000, 1000) self.__counter = 0 @property def message(self) -> str: return self.__message @message.setter def message(self, value: str): self.__message = value @property def counter(self) -> int: return self.__counter @counter.setter def counter(self, value: int): self.__counter = value def configure(self, config: ConfigParams): self.__message = config.get_as_string_with_default("message", self.__message) def set_references(self, references: IReferences): self.__logger.set_references(references) def is_open(self) -> bool: return self.__timer.is_started() def open(self, correlation_id: Optional[str]): self.__timer.start() self.__logger.trace(correlation_id, "Dummy controller opened") def close(self, correlation_id: Optional[str]): self.__timer.stop() self.__logger.trace(correlation_id, "Dummy controller closed") def notify(self, correlation_id: Optional[str], args: Parameters): self.counter += 1 self.__logger.info(correlation_id, "%d - %s", self.counter, self.message)
def __init__(self, loader: ILoader = None, saver: ISaver = None): """ Creates a new instance of the persistence. :param loader: (optional) a loader to load items from external datasource. :param saver: (optional) a saver to save items to external datasource. """ self._lock: threading.Lock = threading.Lock() self._logger: CompositeLogger = CompositeLogger() self._items: List[Any] = [] self._loader: ILoader = loader self._saver: ISaver = saver self._opened: bool = False self._max_page_size = 100
def __init__(self): self.__default_config = ConfigParams.from_tuples( "options.connect_timeout", 0, "options.idle_timeout", 10000, "options.max_pool_size", 3) # The logger. self._logger: CompositeLogger = CompositeLogger() # The connection resolver. self._connection_resolver: MySqlConnectionResolver = MySqlConnectionResolver( ) # The configuration options. self._options: ConfigParams = ConfigParams() # The MySQL connection pool object. self._connection: Any = None # The MySQL database name. self._database_name: str = None
def __init__(self): """ Creates a new instance of the client. """ # The controller reference. self._controller: Any = None # The open flag. self._opened: bool = True # The logger. self._logger: CompositeLogger = CompositeLogger() # The tracer. self._tracer: CompositeTracer = CompositeTracer() # The performance counters self._counters: CompositeCounters = CompositeCounters() # The dependency resolver to get controller reference. self._dependency_resolver: DependencyResolver = DependencyResolver() self._dependency_resolver.put('controller', 'none')
def __init__(self): """ Creates HttpEndpoint """ self.__service = None self.__server = None self.__maintenance_enabled: bool = False self.__file_max_size = 200 * 1024 * 1024 self.__protocol_upgrade_enabled: bool = False self.__uri: str = None self.__connection_resolver: HttpConnectionResolver = HttpConnectionResolver( ) self.__logger: CompositeLogger = CompositeLogger() self.__counters: CompositeCounters = CompositeCounters() self.__registrations: List[IRegisterable] = [] self.__allowed_headers: List[str] = ["correlation_id"] self.__allowed_origins: List[str] = []
def __init__(self, name: str = None, capabilities: MessagingCapabilities = None): """ Creates a new instance of the message queue. :param name: (optional) a queue name :param capabilities: (optional) a capabilities of this message queue """ self._lock: threading.Lock = threading.Lock() self._event = threading.Event() self._capabilities: MessagingCapabilities = None self._logger: CompositeLogger = CompositeLogger() self._counters: CompositeCounters = CompositeCounters() self._connection_resolver: ConnectionResolver = ConnectionResolver() self._credential_resolver: CredentialResolver = CredentialResolver() self._name: str = name self._capabilities = capabilities or \ MessagingCapabilities(False, False, False, False, False, False, False, False, False)
class FacadeRoutes(ABC, IConfigurable, IReferenceable): _logger = CompositeLogger() _counters = CompositeCounters() _dependencyResolver = DependencyResolver() _service: IFacadeService def __init__(self): self._dependencyResolver.put( "service", Descriptor("pip-services-facade", "service", "*", "*", "*")) def configure(self, config): self._dependencyResolver.configure(config) def set_references(self, references): self._logger.set_references(references) self._counters.set_references(references) self._dependencyResolver.set_references(references) self._service = self._dependencyResolver.get_one_required('service') self._register() def instrument(self, correlation_id, method, route): self._logger.debug(correlation_id, "Calling {} {}", method, route) self._counters.increment_one(route + "." + method + ".calls") def get_correlation_id(self, req): return req.query.get("correlation_id") def register_route(self, method, route, action): def action_curl(req=None, res=None): correlation_id = self.get_correlation_id(req) self.instrument(correlation_id, method, route) action(req, res) self._service.register_route(method, route, action_curl) @abstractmethod def _register(self): pass
def __init__(self, collection: str = None): """ Creates a new instance of the persistence component. :param collection: (optional) a collection name. """ self._lock: threading.Lock = threading.Lock() self._connection_resolver: MongoDbConnectionResolver = MongoDbConnectionResolver( ) self._options: ConfigParams = ConfigParams() # The logger. self._logger: CompositeLogger = CompositeLogger() # The dependency resolver. self._dependency_resolver = DependencyResolver(self.__default_config) # The MongoDB database name. self._database_name: str = None # The MongoDb database object. self._db: Any = None # The MongoDb collection object. self._collection: Collection = None # The MongoDB connection object. self._client: Any = None # The MongoDB connection component. self._connection: MongoDbConnection = None self._max_page_size = 100 # The MongoDB colleciton object. self._collection_name: str = collection self.__config: ConfigParams = None self.__references: IReferences = None self.__opened = False self.__local_connection = False self.__indexes: List[MongoDbIndex] = []
class FacadeService(IConfigurable, IReferenceable, IFacadeService): _root_path = '' _partition = SessionMiddleware(bottle.Bottle(catchall=True, autojson=True)).app _dependency_resolver = DependencyResolver() _logger = CompositeLogger() def configure(self, config): self._root_path = config.get_as_string_with_default('root_path', self._root_path) if len(self._root_path) > 0 and not (self._root_path.startswith('/')): self._root_path = '/' + self._root_path def set_references(self, references): self._dependency_resolver.set_references(references) self._logger.set_references(references) def get_root_path(self): return self._root_path def register_middleware(self, action): self._partition.add_hook('before_request', action) def register_middleware_for_path(self, path, action): self._partition.add_hook('before_request', lambda: action() if not ( path is not None and path != '' and bottle.request.url.startswith(path)) else None) def register_route(self, method, route, action): if method == 'del': method = 'delete' self._logger.debug(None, 'Registering route {} {}', method, self.get_root_path() + route) self._partition.route(route, method, action) def register_route_with_auth(self, method, route, action, authorize): if method == 'del': method = 'delete' self._logger.debug(None, 'Registering route {} {}', method, self.get_root_path() + route) self._partition.route(route, method, action, authorize=authorize)
def __init__(self): self.__message = "Hello World!" self.__logger = CompositeLogger() self.__timer = FixedRateTimer(self, 1000, 1000) self.__counter = 0
class FacadeOperations(ABC, IConfigurable, IReferenceable): _logger = CompositeLogger() _counters = CompositeCounters() _dependencyResolver = DependencyResolver() def configure(self, config): self._dependencyResolver.configure(config) def set_references(self, references): self._logger.set_references(references) self._counters.set_references(references) self._dependencyResolver.set_references(references) def _get_correlation_id(self, req): return req.query.get("correlation_id") def _get_filter_params(self, req): key_value_req = {} for key, value in req.query.items: if key not in ['skip', 'take', 'total']: key_value_req[key] = value filter = FilterParams.from_value(key_value_req) return filter def _get_paging_params(self, req): key_value_req = {} for key, value in req.query.items: if key in ['skip', 'take', 'total']: key_value_req[key] = value filter = FilterParams.from_value(key_value_req) return filter def _send_result(self, res): return HttpResponseSender.send_result(res) def _send_empty_result(self, res): return HttpResponseSender.send_empty_result(res) def _send_created_result(self, res): return HttpResponseSender.send_created_result(res) def _send_deleted_result(self, res): return HttpResponseSender.send_deleted_result(res) def _send_error(self, err): HttpResponseSender.send_error(err) def _send_bad_request(self, req, err, message): correlation_id = self._get_correlation_id(req) error = BadRequestException(correlation_id, 'BAD_REQUEST', message) self._send_error(error) def _send_unauthorized(self, req, message): correlation_id = self._get_correlation_id(req) error = UnauthorizedException(correlation_id, 'UNAUTHORIZED', message) self._send_error(error) def _send_not_found(self, req, message): correlation_id = self._get_correlation_id(req) error = NotFoundException(correlation_id, 'NOT_FOUND', message) self._send_error(error) def _send_conflict(self, req, message): correlation_id = self._get_correlation_id(req) error = ConflictException(correlation_id, 'CONFLICT', message) self._send_error(error) def _send_session_expired(self, req, message): correlation_id = self._get_correlation_id(req) error = UnknownException(correlation_id, 'SESSION_EXPIRED', message) error.status = 440 self._send_error(error) def _send_internal_error(self, req, message): correlation_id = self._get_correlation_id(req) error = UnknownException(correlation_id, 'INTERNAL', message) self._send_error(error) def _send_server_unavailable(self, req, message): correlation_id = self._get_correlation_id(req) error = ConflictException(correlation_id, 'SERVER_UNAVAILABLE', message) error.status = 503 self._send_error(error)
class Container(IConfigurable, IReferenceable, IUnreferenceable, IOpenable): """ Inversion of control (IoC) container that creates components and manages their lifecycle. The container is driven by configuration, that usually stored in JSON or YAML file. The configuration contains a list of components identified by type or locator, followed by component configuration. On container start it performs the following actions: - Creates components using their types or calls registered factories to create components using their locators - Configures components that implement IConfigurable interface and passes them their configuration parameters - Sets references to components that implement IReferenceable interface and passes them references of all components in the container - Opens components that implement IOpenable interface On container stop actions are performed in reversed order: - Closes components that implement ICloseable interface - Unsets references in components that implement IUnreferenceable interface - Destroys components in the container. The component configuration can be parameterized by dynamic values. That allows specialized containers to inject parameters from command line or from environment variables. The container automatically creates a ContextInfo component that carries detail information about the container and makes it available for other components. ### Configuration parameters ### - name: the context (container or process) name - description: human-readable description of the context - properties: entire section of additional descriptive properties - ... Example: .. code-block:: yaml ======= config.yaml ======== - descriptor: mygroup:mycomponent1:default:default:1.0 param1: 123 param2: ABC - type: mycomponent2,mypackage param1: 321 param2: XYZ ============================ .. code-block:: python container = Container() container.add_factory(MyComponentFactory()) parameters = ConfigParams.from_value(os.env) container.read_config_from_file("123", "./config/config.yml", parameters) container.open("123") print "Container is opened" # process... container.close("123") print "Container is closed" """ def __init__(self, name: str = None, description: str = None): """ Creates a new instance of the container. :param name: (optional) a container name (accessible via ContextInfo) :param description: (optional) a container description (accessible via ContextInfo) """ self._config: ContainerConfig = None self._references: ContainerReferences = None self._logger: ILogger = NullLogger() self._info: ContextInfo = ContextInfo(name, description) self._factories: DefaultContainerFactory = DefaultContainerFactory() def configure(self, config: ConfigParams): """ Configures component by passing configuration parameters. :param config: configuration parameters to be set. """ self._config = ContainerConfig.from_config(config) def read_config_from_file(self, correlation_id: Optional[str], path: str, parameters: ConfigParams): """ Reads container configuration from JSON or YAML file and parameterizes it with given values. :param correlation_id: (optional) transaction id to trace execution through call chain. :param path: a path to configuration file :param parameters: values to parameters the configuration or null to skip parameterization. """ self._config = ContainerConfigReader.read_from_file( correlation_id, path, parameters) self._logger.trace(correlation_id, self._config.__str__()) def set_references(self, references: IReferences): """ Sets references to dependent components. :param references: references to locate the component dependencies. """ pass def unset_references(self): """ Unsets (clears) previously set references to dependent components. """ pass def __init_references(self, references: IReferences): # Override in base classes existingInfo = references.get_one_optional( DefaultInfoFactory.ContextInfoDescriptor) if existingInfo is None: references.put(DefaultInfoFactory.ContextInfoDescriptor, self._info) else: self._info = existingInfo references.put( Descriptor("pip-services", "factory", "container", "default", "1.0"), self._factories) def add_factory(self, factory: IFactory): """ Adds a factory to the container. The factory is used to create components added to the container by their locators (descriptors). :param factory: a component factory to be added. """ self._factories.add(factory) def is_open(self) -> bool: """ Checks if the component is opened. :return: true if the component has been opened and false otherwise. """ return self._references is not None def open(self, correlation_id: Optional[str]): """ Opens the component. :param correlation_id: (optional) transaction id to trace execution through call chain. """ if self._references is not None: raise InvalidStateException(correlation_id, "ALREADY_OPENED", "Container was already opened") try: self._logger.trace(correlation_id, "Starting container.") # Create references with configured components self._references = ContainerReferences() self.__init_references(self._references) self._references.put_from_config(self._config) self.set_references(self._references) # Get custom description if available info_descriptor = Descriptor("*", "context-info", "*", "*", "*") self._info = self._references.get_one_optional(info_descriptor) # Reference and open components self._references.open(correlation_id) # Get component to logger self._logger = CompositeLogger(self._references) self._logger.info(correlation_id, "Container " + self._info.name + " started.") except Exception as ex: self._logger.fatal(correlation_id, ex, "Failed to start container") self.close(None) traceback.print_exc() raise ex def close(self, correlation_id: Optional[str]): """ Closes component and frees used resources. :param correlation_id: (optional) transaction id to trace execution through call chain. """ if self._references is None: return try: self._logger.trace(correlation_id, "Stopping " + self._info.name + " container") # Unset references for child container self.unset_references() # Close and deference components self._references.close(correlation_id) self._references = None self._logger.info(correlation_id, "Container " + self._info.name + " stopped") except Exception as ex: self._logger.error(correlation_id, ex, "Failed to stop container") raise ex
class DirectClient(IConfigurable, IReferenceable, IOpenable): """ Abstract client that calls controller directly in the same memory space. It is used when multiple microservices are deployed in a single container (monolyth) and communication between them can be done by direct calls rather then through the network. ### Configuration parameters ### - dependencies: - controller: override controller descriptor ### References ### - *:logger:*:*:1.0 (optional) ILogger components to pass log messages - *:counters:*:*:1.0 (optional) ICounters components to pass collected measurements - *:controller:*:*:1.0 controller to call business methods Example: class MyDirectClient(DirectClient, IMyClient): def __init__(self): super(MyDirectClient, self).__init__() self._dependencyResolver.put('controller', Descriptor("mygroup", "controller", "*", "*", "*")) ... def get_data(self, correlation_id, id): timing = self.instrument(correlationId, 'myclient.get_data') result = self._controller.get_data(correlationId, id) timing.end_timing() return result client = MyDirectClient() client.set_references(References.from_tuples(Descriptor("mygroup","controller","default","default","1.0"), controller)) data = client.get_data("123", "1") ... """ _controller = None _opened = True _logger = None _counters = None _dependency_resolver = None def __init__(self): """ Creates a new instance of the client. """ self._logger = CompositeLogger() self._counters = CompositeCounters() self._dependency_resolver = DependencyResolver() self._dependency_resolver.put('controller', 'none') def configure(self, config): """ Configures component by passing configuration parameters. :param config: configuration parameters to be set. """ self._dependency_resolver.configure(config) def set_references(self, references): """ Sets references to dependent components. :param references: references to locate the component dependencies. """ self._logger.set_references(references) self._counters.set_references(references) self._dependency_resolver.set_references(references) self._controller = self._dependency_resolver.get_one_required('controller') def _instrument(self, correlation_id, name): """ Adds instrumentation to log calls and measure call time. It returns a Timing object that is used to end the time measurement. :param correlation_id: (optional) transaction id to trace execution through call chain. :param name: a method name. :return: Timing object to end the time measurement. """ self._logger.trace(correlation_id, f"Executing {name} method") return self._counters.begin_timing(f"{name} .call_time") def _instrument_error(self, correlation_id, name, err, result, callback): """ Adds instrumentation to error handling. :param correlation_id: (optional) transaction id to trace execution through call chain. :param name: a method name. :param err: an occured error :param result: (optional) an execution result :param callback: (optional) an execution callback """ if err is not None: self._logger.error(correlation_id, err, f'Failed to call {name} method') self._counters.increment_one(f"{name}.call_errors") if callback: callback(err, result) def is_opened(self): """ Checks if the component is opened. :return: true if the component has been opened and false otherwise. """ return self._opened def open(self, correlation_id): """ Opens the component. :param correlation_id: (optional) transaction id to trace execution through call chain. """ if self._opened: return if self._controller is None: raise ConnectionException(correlation_id, 'NO_CONTROLLER', 'Controller references is missing') self._opened = True self._logger.info(correlation_id, 'Opened direct client') def close(self, correlation_id): """ Closes component and frees used resources. :param correlation_id: (optional) transaction id to trace execution through call chain. """ if self._opened: self._logger.info(correlation_id, 'Closed direct client') self._opened = False
class RestService(IOpenable, IConfigurable, IReferenceable, IUnreferenceable, IRegisterable): """ Abstract service that receives remove calls via HTTP/REST protocol. ### Configuration parameters ### - base_route: base route for remote URI - dependencies: - endpoint: override for HTTP Endpoint dependency - controller: override for Controller dependency - connection(s): - discovery_key: (optional) a key to retrieve the connection from IDiscovery - protocol: connection protocol: http or https - host: host name or IP address - port: port number - uri: resource URI or connection string with all parameters in it ### References ### - *:logger:*:*:1.0 (optional) ILogger components to pass log messages - *:counters:*:*:1.0 (optional) ICounters components to pass collected measurements - *:discovery:*:*:1.0 (optional) IDiscovery services to resolve connection - *:endpoint:http:*:1.0 (optional) HttpEndpoint reference Example: class MyRestService(RestService): _controller = None ... def __init__(self): super(MyRestService, self).__init__() self._dependencyResolver.put("controller", Descriptor("mygroup","controller","*","*","1.0")) def set_references(self, references): super(MyRestService, self).set_references(references) self._controller = self._dependencyResolver.get_required("controller") def register(): ... service = MyRestService() service.configure(ConfigParams.from_tuples("connection.protocol", "http", "connection.host", "localhost", "connection.port", 8080)) service.set_references(References.from_tuples(Descriptor("mygroup","controller","default","default","1.0"), controller)) service.open("123") """ _default_config = None _debug = False _dependency_resolver = None _logger = None _counters = None # _registered = None _local_endpoint = None _config = None _references = None _base_route = None _endpoint = None _opened = None def __init__(self): self._default_config = ConfigParams.from_tuples( "base_route", None, "dependencies.endpoint", "*:endpoint:http:*:1.0") # self._registered = False self._dependency_resolver = DependencyResolver() self._logger = CompositeLogger() self._counters = CompositeCounters() def _instrument(self, correlation_id, name): """ Adds instrumentation to log calls and measure call time. It returns a Timing object that is used to end the time measurement. :param correlation_id: (optional) transaction id to trace execution through call chain. :param name: a method name. """ self._logger.trace(correlation_id, "Executing " + name + " method") return self._counters.begin_timing(name + ".exec_time") def set_references(self, references): """ Sets references to dependent components. :param references: references to locate the component dependencies. """ self._references = references self._logger.set_references(references) self._counters.set_references(references) self._dependency_resolver.set_references(references) self._endpoint = self._dependency_resolver.get_one_optional('endpoint') if self._endpoint is None: self._endpoint = self.create_endpoint() self._local_endpoint = True else: self._local_endpoint = False self._endpoint.register(self) def configure(self, config): """ Configures component by passing configuration parameters. :param config: configuration parameters to be set. """ config = config.set_defaults(self._default_config) self._config = config self._dependency_resolver.configure(config) self._base_route = config.get_as_string_with_default( "base_route", self._base_route) def unset_references(self): """ Unsets (clears) previously set references to dependent components. """ if not (self._endpoint is None): self._endpoint.unregister(self) self._endpoint = None def create_endpoint(self): endpoint = HttpEndpoint() if not (self._config is None): endpoint.configure(self._config) if not (self._references is None): endpoint.set_references(self._references) return endpoint def _instrument(self, correlation_id, name): self._logger.trace(correlation_id, f"Executing {name} method") self._counters.increment_one(f"{name}.exec_count") return self._counters.begin_timing(f"{name}.exec_time") def _instrument_error(self, correlation_id, name, error, result, callback): if not (error is None): self._logger.error(correlation_id, error, f"Failed to execute {name} method") self._counters.increment_one(f"{name}.exec_error") if not (callback is None): callback(error, result) def is_opened(self): """ Checks if the component is opened. :return: true if the component has been opened and false otherwise. """ return self._opened def open(self, correlation_id): """ Opens the component. :param correlation_id: (optional) transaction id to trace execution through call chain. """ if self.is_opened(): return if self._endpoint is None: self._endpoint = self.create_endpoint() self._endpoint.register(self) self._local_endpoint = True if self._local_endpoint: self._endpoint.open(correlation_id) self._opened = True # # register route # if self._registered != True: # self.add_route() # self._registered = True def close(self, correlation_id): """ Closes component and frees used resources. :param correlation_id: (optional) transaction id to trace execution through call chain. """ if not self._opened: return if self._endpoint is None: raise InvalidStateException(correlation_id, "NO_ENDPOINT", "HTTP endpoint is missing") if self._local_endpoint: self._endpoint.close(correlation_id) self._opened = False def send_result(self, result): """ Creates a callback function that sends result as JSON object. That callack function call be called directly or passed as a parameter to business logic components. If object is not null it returns 200 status code. For null results it returns 204 status code. If error occur it sends ErrorDescription with approproate status code. :param result: a body object to result. :return: execution result. """ return HttpResponseSender.send_result(result) def send_created_result(self, result): """ Creates a callback function that sends newly created object as JSON. That callack function call be called directly or passed as a parameter to business logic components. If object is not null it returns 201 status code. For null results it returns 204 status code. If error occur it sends ErrorDescription with approproate status code. :param result: a body object to result. :return: execution result. """ return HttpResponseSender.send_created_result(result) def send_deleted_result(self): """ Creates a callback function that sends newly created object as JSON. That callack function call be called directly or passed as a parameter to business logic components. If object is not null it returns 200 status code. For null results it returns 204 status code. If error occur it sends ErrorDescription with approproate status code. :return: execution result. """ return HttpResponseSender.send_deleted_result() def send_error(self, error): """ Sends error serialized as ErrorDescription object and appropriate HTTP status code. If status code is not defined, it uses 500 status code. :param error: an error object to be sent. """ return HttpResponseSender.send_error(error) def fix_route(self, route) -> str: if (route is not None and len(route) > 0): if (route[0] != '/'): route = f'/{route}' return route return '' def register_route(self, method, route, schema, handler): """ Registers an action in this objects REST server (service) by the given method and route. :param method: the HTTP method of the route. :param route: the route to register in this object's REST server (service). :param schema: the schema to use for parameter validation. :param handler: the action to perform at the given route. """ if self._endpoint is None: return route = f"{self.fix_route(self._base_route)}{self.fix_route(route)}" # if not (self._base_route is None) and len(self._base_route) > 0: # base_route = self._base_route # if base_route[0] != '/': # base_route = '/' + base_route # if route[0] != '/': # base_route = base_route + '/' # route = base_route + route self._endpoint.register_route(method, route, schema, handler) def register(self): pass def get_data(self): data = bottle.request.json if isinstance(data, str): return json.loads(bottle.request.json) elif bottle.request.json: return bottle.request.json else: return None def get_request_param(self): if (not (bottle is None)) and (not (bottle.request is None)) and ( not (bottle.request.params is None)): return bottle.request.params else: return {} def _append_base_route(self, route): route = route or '' if self._base_route is not None and len(self._base_route) > 0: base_route = self._base_route if base_route[0] != '/': base_route = '/' + base_route route = base_route + route return route def register_route_with_auth(self, method, route, schema, authorize, action): """ Registers a route with authorization in HTTP endpoint. :param method: HTTP method: "get", "head", "post", "put", "delete" :param route: a command route. Base route will be added to this route :param schema: a validation schema to validate received parameters. :param authorize: an authorization interceptor :param action: an action function that is called when operation is invoked. """ if self._endpoint is None: return route = self._append_base_route(self.fix_route(route)) self._endpoint.register_route_with_auth(method, route, schema, authorize, method) def register_interceptor(self, route, action): """ Registers a middleware for a given route in HTTP endpoint. :param route: a command route. Base route will be added to this route :param action: an action function that is called when middleware is invoked. """ if self._endpoint is None: return route = self._append_base_route(self.fix_route(route)) self._endpoint.register_interceptor(route, action)
class RestClient(IOpenable, IConfigurable, IReferenceable): """ Abstract client that calls remove endpoints using HTTP/REST protocol. ### Configuration parameters ### - base_route: base route for remote URI - connection(s): - discovery_key: (optional) a key to retrieve the connection from IDiscovery - protocol: connection protocol: http or https - host: host name or IP address - port: port number - uri: resource URI or connection string with all parameters in it - options: - retries: number of retries (default: 3) - connect_timeout: connection timeout in milliseconds (default: 10 sec) - timeout: invocation timeout in milliseconds (default: 10 sec) ### References ### - *:logger:*:*:1.0 (optional) ILogger components to pass log messages - *:counters:*:*:1.0 (optional) ICounters components to pass collected measurements - *:discovery:*:*:1.0 (optional) IDiscovery services to resolve connection Example: class MyRestClient(RestClient, IMyClient): def get_data(self, correlation_id, id): timing = self.instrument(correlationId, 'myclient.get_data') result = self._controller.get_data(correlationId, id) timing.end_timing() return result ... client = MyRestClient() client.configure(ConfigParams.fromTuples("connection.protocol", "http", "connection.host", "localhost", "connection.port", 8080)) data = client.getData("123", "1") ... """ _default_config = None _client = None _uri = None _timeout = 1000 _connection_resolver = None _logger = None _counters = None _options = None _base_route = None _retries = 1 _headers = None _connect_timeout = 1000 def __init__(self): """ Creates a new instance of the client. """ self._connection_resolver = HttpConnectionResolver() self._default_config = ConfigParams.from_tuples( "connection.protocol", "http", "connection.host", "0.0.0.0", "connection.port", 3000, "options.timeout", 10000, "options.request_max_size", 1024 * 1024, "options.connect_timeout", 10000, "options.retries", 3, "options.debug", True) self._logger = CompositeLogger() self._counters = CompositeCounters() self._options = ConfigParams() self._headers = {} def set_references(self, references): """ Sets references to dependent components. :param references: references to locate the component dependencies. """ self._logger.set_references(references) self._counters.set_references(references) self._connection_resolver.set_references(references) def configure(self, config): """ Configures component by passing configuration parameters. :param config: configuration parameters to be set. """ config = config.set_defaults(self._default_config) self._connection_resolver.configure(config) self._options.override(config.get_section("options")) self._retries = config.get_as_integer_with_default( "options.retries", self._retries) self._connect_timeout = config.get_as_integer_with_default( "options.connect_timeout", self._connect_timeout) self._timeout = config.get_as_integer_with_default( "options.timeout", self._timeout) self._base_route = config.get_as_string_with_default( "base_route", self._base_route) def _instrument(self, correlation_id, name): """ Adds instrumentation to log calls and measure call time. It returns a Timing object that is used to end the time measurement. :param correlation_id: (optional) transaction id to trace execution through call chain. :param name: a method name. :return: Timing object to end the time measurement. """ TYPE_NAME = self.__class__.__name__ or 'unknown-target' self._logger.trace(correlation_id, f"Calling {name} method {TYPE_NAME}") self._counters.increment_one(f"{TYPE_NAME}.{name}.call_count") return self._counters.begin_timing(f"{TYPE_NAME}.{name}.call_count") def _instrument_error(self, correlation_id, name, err, result=None, callback=None): """ Adds instrumentation to error handling. :param correlation_id: (optional) transaction id to trace execution through call chain. :param name: a method name. :param err: an occured error :param result: (optional) an execution result :param callback: (optional) an execution callback """ if err is not None: TYPE_NAME = self.__class__.__name__ or 'unknown-target' self._logger.error(correlation_id, err, f"Failed to call {name} method of {TYPE_NAME}") self._counters.increment_one(f"{name}.call_errors") if callback: callback(err, result) def is_opened(self): """ Checks if the component is opened. :return: true if the component has been opened and false otherwise. """ return self._client is not None def open(self, correlation_id): """ Opens the component. :param correlation_id: (optional) transaction id to trace execution through call chain. """ if self.is_opened(): return connection = self._connection_resolver.resolve(correlation_id) self._uri = connection.get_uri() self._client = requests self._logger.debug(correlation_id, "Connected via REST to " + self._uri) def close(self, correlation_id): """ Closes component and frees used resources. :param correlation_id: (optional) transaction id to trace execution through call chain. """ if self._client is not None: self._logger.debug(correlation_id, "Disconnected from " + self._uri) self._client = None self._uri = None def _to_json(self, obj): if obj is None: return None if isinstance(obj, set): obj = list(obj) if isinstance(obj, list): result = [] for item in obj: item = self._to_json(item) result.append(item) return result if isinstance(obj, dict): result = {} for (k, v) in obj.items(): v = self._to_json(v) result[k] = v return result if hasattr(obj, 'to_json'): return obj.to_json() if hasattr(obj, '__dict__'): return self._to_json(obj.__dict__) return obj def fix_route(self, route) -> str: if route is not None and len(route) > 0: if route[0] != '/': route = f'/{route}' return route return '' def createRequestRoute(self, route): builder = '' if self._uri is not None and len(self._uri) > 0: builder = self._uri builder += self.fix_route(self._base_route) if route[0] != '/': builder += '/' builder += route return builder def add_correlation_id(self, correlation_id=None, params=None): params = params or {} if not (correlation_id is None): params['correlation_id'] = correlation_id return params def add_filter_params(self, params=None, filters=None): params = params or {} if not (filters is None): params.update(filters) return params def add_paging_params(self, params=None, paging=None): params = params or {} if not (paging is None): if not (paging['total'] is None): params['total'] = paging['total'] if not (paging['skip'] is None): params['skip'] = paging['skip'] if not (paging['take'] is None): params['take'] = paging['take'] # params.update(paging) return params def call(self, method, route, correlation_id=None, params=None, data=None): """ Calls a remote method via HTTP/REST protocol. :param method: HTTP method: "get", "head", "post", "put", "delete" :param route: a command route. Base route will be added to this route :param correlation_id: (optional) transaction id to trace execution through call chain. :param params: (optional) query parameters. :param data: (optional) body object. :return: result object """ method = method.upper() route = self.createRequestRoute(route) params = self.add_correlation_id(correlation_id=correlation_id, params=params) response = None result = None try: # Call the service data = self._to_json(data) response = requests.request(method, route, params=params, json=data, timeout=self._timeout) except Exception as ex: error = InvocationException(correlation_id, 'REST_ERROR', 'REST operation failed: ' + str(ex)).wrap(ex) raise error if response.status_code == 404 or response.status_code == 204: return None try: # Retrieve JSON data result = response.json() except: # Data is not in JSON if response.status_code < 400: raise UnknownException(correlation_id, 'FORMAT_ERROR', 'Failed to deserialize JSON data: ' + response.text) \ .with_details('response', response.text) else: raise UnknownException(correlation_id, 'UNKNOWN', 'Unknown error occured: ' + response.text) \ .with_details('response', response.text) # Return result if response.status_code < 400: return result # Raise error # Todo: We need to implement proper from_value method error = ErrorDescription.from_json(result) error.status = response.status_code raise ApplicationExceptionFactory.create(error)
class MessageQueue(IConfigurable, IReferenceable, IMessageQueue): """ Abstract message queue. Abstract message queue that is used as a basis for specific message queue implementations. ### Configuration parameters ### - name: name of the message queue - connection(s): - discovery_key: key to retrieve parameters from discovery service - protocol: connection protocol like http, https, tcp, udp - host: host name or IP address - port: port number - uri: resource URI or connection string with all parameters in it - credential(s): - store_key: key to retrieve parameters from credential store - username: user name - password: user password - access_id: application access id - access_key: application secret key ### References ### - *:logger:*:*:1.0 (optional) ILogger components to pass log messages - *:counters:*:*:1.0 (optional) ICounters components to pass collected measurements - *:discovery:*:*:1.0 (optional) IDiscovery components to discover connection(s) - *:credential-store:*:*:1.0 (optional) ICredentialStore componetns to lookup credential(s) """ _name = None _capabilities = None _lock = None _logger = None _counters = None _credential_resolver = None _connection_resolver = None def __init__(self, name=None): """ Creates a new instance of the message queue. :param name: (optional) a queue name """ self._lock = threading.Lock() self._logger = CompositeLogger() self._counters = CompositeCounters() self._connection_resolver = ConnectionResolver() self._credential_resolver = CredentialResolver() self._name = name def configure(self, config): """ Configures component by passing configuration parameters. :param config: configuration parameters to be set. """ self._name = NameResolver.resolve(config) self._logger.configure(config) self._credential_resolver.configure(config) self._connection_resolver.configure(config) def set_references(self, references): """ Sets references to dependent components. :param references: references to locate the component dependencies. """ self._logger.set_references(references) self._counters.set_references(references) self._credential_resolver.set_references(references) self._connection_resolver.set_references(references) def open(self, correlation_id): """ Opens the component. :param correlation_id: (optional) transaction id to trace execution through call chain. """ connection = self._connection_resolver.resolve(correlation_id) credential = self._credential_resolver.lookup(correlation_id) self._open_with_params(correlation_id, connection, credential) def _open_with_params(self, correlation_id, connection, credential): """ Opens the component with given connection and credential parameters. :param correlation_id: (optional) transaction id to trace execution through call chain. :param connection: connection parameters :param credential: credential parameters """ raise NotImplementedError('Abstract method that shall be overriden') def get_name(self): """ Gets the queue name :return: the queue name. """ return self._name if self._name != None else "undefined" def get_capabilities(self): """ Gets the queue capabilities :return: the queue's capabilities object. """ return self._capabilities def send_as_object(self, correlation_id, message_type, message): """ Sends an object into the queue. Before sending the object is converted into JSON string and wrapped in a [[MessageEnvelop]]. :param correlation_id: (optional) transaction id to trace execution through call chain. :param message_type: a message type :param message: an object value to be sent """ envelop = MessageEnvelop(correlation_id, message_type, message) self.send(correlation_id, envelop) def begin_listen(self, correlation_id, receiver): """ Listens for incoming messages without blocking the current thread. :param correlation_id: (optional) transaction id to trace execution through call chain. :param receiver: a receiver to receive incoming messages. """ # Start listening on a parallel tread thread = threading.Thread(target=self.listen, args=(correlation_id, receiver)) thread.daemon = True thread.start() def __str__(self): """ Gets a string representation of the object. :return: a string representation of the object. """ return "[" + self.get_name() + "]"