Module licenseware.app_builder.app_builder
AppBuilder
is reponsible for creating the Api from the X_namespace
packages and X_route
modules. Authenticates the App
and sends the registration information to registry-service.
Notice that history report route/path is provided but is not implemented that's because a report must be defined with aggregated data from AnalysisStats mongo collection.
Expand source code
"""
`AppBuilder` is reponsible for creating the Api from the `X_namespace` packages and `X_route` modules. Authenticates the `App` and sends the registration information to registry-service.
Notice that history report route/path is provided but is not implemented that's because a report must be defined with aggregated data from AnalysisStats mongo collection.
"""
from typing import List, Callable, Dict
from dataclasses import dataclass
from flask import Flask
from flask_cors import CORS
from flask_restx import Api, Namespace, Resource
from marshmallow.schema import Schema
from licenseware.uploader_builder.uploader_builder import UploaderBuilder
from licenseware.report_builder.report_builder import ReportBuilder
from licenseware.report_components.base_report_component import BaseReportComponent
from licenseware.feature_builder import FeatureBuilder
from licenseware.common.constants.envs import envs
from licenseware.registry_service import register_all
from licenseware.tenants import get_activated_tenants, get_tenants_with_data
from licenseware.utils.logger import log
from licenseware.auth import Authenticator
from licenseware.utils.dramatiq_redis_broker import broker
from licenseware.utils.miscellaneous import swagger_authorization_header
from licenseware.editable_table import EditableTable
from licenseware.schema_namespace import SchemaNamespace
from licenseware.decorators.xss_decorator import xss_security
from licenseware.mongodata import create_collection
from .refresh_registration_route import add_refresh_registration_route
from .editable_tables_route import add_editable_tables_route
from .tenant_registration_route import add_tenant_registration_route
from .app_activation_route import add_app_activation_route
from .app_registration_route import add_app_registration_route
from .terms_and_conditions_route import add_terms_and_conditions_route
from .features_route import add_features_route
from .endpoint_builder_namespace import endpoint_builder_namespace
from .uploads_namespace import uploads_namespace
from .uploads_namespace import (
get_filenames_validation_namespace,
get_filestream_validation_namespace,
get_status_namespace,
get_quota_namespace,
)
from .reports_namespace import reports_namespace
from .reports_namespace import (
get_report_register_namespace,
get_report_metadata_namespace,
get_report_components_namespace,
get_report_image_preview_namespace,
get_report_image_preview_dark_namespace,
)
from .report_components_namespace import report_components_namespace
from .report_components_namespace import get_report_individual_components_namespace
from .features_namespace import features_namespace
from .features_namespace import get_features_namespace
from .data_sync_namespace import data_sync_namespace
from .data_sync_namespace import get_data_sync_namespace
# from .decrypt_namespace import decrypt_namespace
# from .decrypt_namespace import get_decrypt_namespace
from .download_as_route import add_download_as_route
# TODO there are some paths in both envs and base paths identify them and remove redundant data
@dataclass
class base_paths:
app_activation_path: str = "/activate_app"
register_app_path: str = "/register_app"
refresh_registration_path: str = "/refresh_registration"
editable_tables_path: str = "/editable_tables"
history_report_path: str = "/reports/history_report"
tenant_registration_path: str = "/register_tenant"
terms_and_conditions_path: str = "/terms_and_conditions"
features_path: str = "/features"
data_sync_path: str = "/data-sync"
class AppBuilder:
def __init__(
self,
name: str,
description: str,
flags: list = None,
app_meta: dict = None,
features: List[FeatureBuilder] = None,
editable_tables: List[EditableTable] = None,
editable_tables_schemas: List[Schema] = None,
data_sync_schema: Schema = None,
broker_funcs: Dict[str, List[Callable]] = None,
doc_authorizations: dict = swagger_authorization_header,
api_decorators: list = None,
icon: str = "default.png",
**options,
):
self.name = name
self.app_id = envs.APP_ID
if envs.DEPLOYMENT_SUFFIX is not None:
self.name = self.name + envs.DEPLOYMENT_SUFFIX
self.app_id = self.app_id + envs.DEPLOYMENT_SUFFIX
self.description = description
self.flags = flags or []
self.app_meta = app_meta
self.features = features or []
self.data_sync_schema = data_sync_schema
self.editable_tables = editable_tables or []
self.editable_tables_schemas = editable_tables_schemas or []
self.broker_funcs = broker_funcs
self.icon = icon
self.app_activation_path = base_paths.app_activation_path
self.register_app_path = base_paths.register_app_path
self.refresh_registration_path = base_paths.refresh_registration_path
self.editable_tables_path = base_paths.editable_tables_path
self.history_report_path = base_paths.history_report_path
self.tenant_registration_path = base_paths.tenant_registration_path
self.terms_and_conditions_path = base_paths.terms_and_conditions_path
self.features_path = base_paths.features_path
self.data_sync_path = base_paths.data_sync_path
self.app_activation_url = envs.BASE_URL + self.app_activation_path
self.refresh_registration_url = envs.BASE_URL + self.refresh_registration_path
self.editable_tables_url = envs.BASE_URL + self.editable_tables_path
self.history_report_url = envs.BASE_URL + self.history_report_path
self.tenant_registration_url = envs.BASE_URL + self.tenant_registration_path
self.terms_and_conditions_url = envs.BASE_URL + self.terms_and_conditions_path
self.features_url = envs.BASE_URL + self.features_path
self.data_sync_url = envs.BASE_URL + self.data_sync_path
self.authorizations = doc_authorizations
# Add xss security
self.decorators = [xss_security] if api_decorators is None else api_decorators + [xss_security]
# parameters with default values provided can be added stright to __init__
# otherwise added them to options until apps are actualized
self.options = options
# TODO version needs to be added to all urls + '/v' + self.version
self.prefix = envs.APP_PATH
self.app = None
self.api = None
self.ns = None
self.reports: List[ReportBuilder] = []
self.report_components: List[BaseReportComponent] = []
self.uploaders: List[UploaderBuilder] = []
self.custom_namespaces: List[Namespace] = []
self.appvars = vars(self)
def init_app(self, app: Flask, register: bool = False):
# This hides flask_restx `X-fields` from swagger headers
app.config["RESTX_MASK_SWAGGER"] = False
@app.after_request
def after_request(response):
response.headers.set("Access-Control-Allow-Origin", "*")
response.headers.set(
"Access-Control-Allow-Headers",
"Content-Type,Authorization,TenantId,Tenantid",
)
response.headers.set(
"Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS"
)
response.headers.set("Access-Control-Allow-Credentials", "true")
# https://flask.palletsprojects.com/en/2.1.x/security/#security-csp
# XSS
response.headers['X-Content-Type-Options'] = 'nosniff'
return response
# TODO CORS protection
# CORS(app)
self.app = app
if not self.uploaders:
log.warning("No uploaders provided")
if not self.reports:
log.warning("No reports provided")
self.authenticate_app()
self.init_api()
self.init_routes()
self.init_namespaces()
self.init_broker()
if register:
self.register_app()
return self.app
def init_broker(self, app: Flask = None):
self.broker = broker.init_app(app or self.app)
return self.broker
def authenticate_app(self):
if envs.DESKTOP_ENVIRONMENT: return
response, status_code = Authenticator.connect(
max_retries="infinite", wait_seconds=2
)
if status_code != 200:
raise Exception("App failed to authenticate!")
log.info("Authentification succeded!")
return response, status_code
def init_api(self):
self.api = Api(
app=self.app,
title=self.name,
description=self.description,
prefix=self.prefix,
default=self.name,
default_label=self.description,
decorators=self.decorators,
authorizations=self.authorizations,
security=list(self.authorizations.keys()),
doc=self.prefix + "/docs",
)
def init_routes(self):
# Here we are adding the routes available for each app
# Api must be passed from route function back to this context
api_funcs = [
add_refresh_registration_route,
add_editable_tables_route,
add_tenant_registration_route,
add_app_activation_route,
add_app_registration_route,
add_terms_and_conditions_route,
add_download_as_route,
add_features_route,
]
for func in api_funcs:
self.api = func(self.api, self.appvars)
# Another way is to group routes in namespaces
# This way the url prefix is specified only in the namespace
self.add_uploads_routes()
self.add_reports_routes()
self.add_report_components_routes()
self.add_editables_routes()
self.add_features_routes()
self.add_data_sync_routes()
#self.add_decrypt_routes()
def add_uploads_routes(self):
ns_funcs = [
get_filenames_validation_namespace,
get_filestream_validation_namespace,
get_status_namespace,
get_quota_namespace,
]
for func in ns_funcs:
self.register_namespace(
func(ns=uploads_namespace, uploaders=self.uploaders)
)
def add_reports_routes(self):
ns_funcs = [
get_report_register_namespace,
get_report_metadata_namespace,
get_report_components_namespace,
get_report_image_preview_namespace,
get_report_image_preview_dark_namespace,
]
for func in ns_funcs:
self.register_namespace(func(ns=reports_namespace, reports=self.reports))
def add_report_components_routes(self):
ns_funcs = [get_report_individual_components_namespace]
for func in ns_funcs:
self.register_namespace(
func(
ns=report_components_namespace,
report_components=self.report_components,
)
)
def add_editables_routes(self):
for editable in self.editable_tables:
if editable.namespace:
self.register_namespace(editable.namespace)
else:
ns = SchemaNamespace(schema=editable.schema).initialize()
self.register_namespace(ns)
def add_features_routes(self):
ns_funcs = [get_features_namespace]
for func in ns_funcs:
self.register_namespace(func(ns=features_namespace, features=self.features))
def add_data_sync_routes(self):
if self.data_sync_schema is None:
return
ns_funcs = [get_data_sync_namespace]
for func in ns_funcs:
self.register_namespace(
func(ns=data_sync_namespace, data_sync_schema=self.data_sync_schema)
)
# def add_decrypt_routes(self):
# ns_funcs = [get_decrypt_namespace]
# for func in ns_funcs:
# self.register_namespace(
# func(ns=decrypt_namespace)
# )
def get_serialized_app_dict(self):
app_dict = {
k: v
for k, v in self.appvars.items()
if k
in [
"app_id",
"name",
"description",
"flags",
"icon",
"refresh_registration_url",
"app_activation_url",
"editable_tables_url",
"history_report_url",
"tenant_registration_url",
"terms_and_conditions_url",
"features_url",
"app_meta",
]
}
app_dict["features"] = [f.get_details() for f in self.features]
app_dict["tenants_with_app_activated"] = get_activated_tenants()
app_dict["tenants_with_data_available"] = get_tenants_with_data()
return app_dict
def get_serialized_reports(self):
reports = [
{
k: v
for k, v in vars(r).items()
if k
in [
"app_id",
"report_id",
"name",
"description",
"flags",
"url",
"preview_image_url",
"preview_image_dark_url",
"report_components",
"connected_apps",
"filters",
"registrable",
]
}
for r in self.reports
]
return reports
def get_serialized_uploaders(self):
uploaders = [
{
k: v
for k, v in vars(r).items()
if k
in [
"app_id",
"uploader_id",
"name",
"description",
"accepted_file_types",
"flags",
"status",
"icon",
"upload_url",
"upload_validation_url",
"quota_validation_url",
"status_check_url",
"validation_parameters",
"encryption_parameters",
"query_params_on_upload",
]
}
for r in self.uploaders
]
return uploaders
def get_serialized_report_components(self):
report_components = [
{
k: v
for k, v in vars(r).items()
if k
in [
"app_id",
"component_id",
"url",
"order",
"style_attributes",
"attributes",
"title",
"type",
"filters",
]
}
for r in self.report_components
]
return report_components
def register_app(self):
"""
Sending registration payloads to registry-service
"""
# Converting from objects to dictionaries
app_dict = self.get_serialized_app_dict()
reports = self.get_serialized_reports()
uploaders = self.get_serialized_uploaders()
report_components = self.get_serialized_report_components()
payload = {
"data": dict(
apps=[app_dict],
reports=reports,
uploaders=uploaders,
report_components=report_components,
)
}
register_all.send(payload)
return payload
def register_feature(self, feature_instance: FeatureBuilder):
for feature in self.features:
if feature.feature_id == feature_instance.feature_id:
raise Exception(
f"Feature id '{feature_instance.feature_id}' was already declared"
)
self.features.append(feature_instance)
def register_uploader(self, uploader_instance: UploaderBuilder):
for uploader in self.uploaders:
if uploader.uploader_id == uploader_instance.uploader_id:
raise Exception(
f"Uploader id '{uploader_instance.uploader_id}' was already declared"
)
self.uploaders.append(uploader_instance)
def register_report(self, report_instance: ReportBuilder):
for report in self.reports:
if report.report_id == report_instance.report_id:
raise Exception(
f"Report id '{report_instance.report_id}' was already declared"
)
self.reports.append(report_instance)
def register_report_component(self, report_component_instance: BaseReportComponent):
for rep_component in self.report_components:
if rep_component.component_id == report_component_instance.component_id:
raise Exception(
f"Report component_id: '{report_component_instance.component_id}' was already declared"
)
metadata = report_component_instance.get_registration_payload()
report_component_instance.order = (
metadata["order"] or len(self.report_components) + 1
)
report_component_instance.type = metadata.pop("component_type")
report_component_instance.style_attributes = metadata["style_attributes"]
report_component_instance.attributes = metadata["attributes"]
report_component_instance.filters = metadata["filters"]
self.report_components.append(report_component_instance)
def register_endpoint(self, endpoint_instance):
ns = endpoint_instance.build_namespace(endpoint_builder_namespace)
self.register_namespace(ns)
def register_editable_table(self, editable_table_instance: EditableTable):
for registered_table in self.editable_tables:
registered_table: EditableTable
if (
registered_table.schema.__name__
== editable_table_instance.schema.__name__
):
raise Exception(
f"Editable table '{registered_table.schema.__name__}' already registered"
)
self.editable_tables.append(editable_table_instance)
self.editable_tables_schemas.append(editable_table_instance.schema)
def register_namespace(self, ns: Namespace, path: str = None):
"""Used for custom restx namespaces"""
self.custom_namespaces.append((ns, path))
def init_namespaces(self):
for namespace in self.custom_namespaces:
self.api.add_namespace(*namespace)
def add_resource(self, resource: Resource, path: str):
self.api.add_resource(resource, path)
def add_collection(self, collection_name: str, db_name: str=None, timeseries_config: dict=None):
create_collection(
collection_name=collection_name,
db_name=db_name,
timeseries_config=timeseries_config
)
Classes
class AppBuilder (name: str, description: str, flags: list = None, app_meta: dict = None, features: List[FeatureBuilder] = None, editable_tables: List[EditableTable] = None, editable_tables_schemas: List[marshmallow.schema.Schema] = None, data_sync_schema: marshmallow.schema.Schema = None, broker_funcs: Dict[str, List[Callable]] = None, doc_authorizations: dict = {'Tenantid': {'type': 'apiKey', 'in': 'header', 'name': 'Tenantid'}, 'Authorization': {'type': 'apiKey', 'in': 'header', 'name': 'Authorization'}}, api_decorators: list = None, icon: str = 'default.png', **options)
-
Expand source code
class AppBuilder: def __init__( self, name: str, description: str, flags: list = None, app_meta: dict = None, features: List[FeatureBuilder] = None, editable_tables: List[EditableTable] = None, editable_tables_schemas: List[Schema] = None, data_sync_schema: Schema = None, broker_funcs: Dict[str, List[Callable]] = None, doc_authorizations: dict = swagger_authorization_header, api_decorators: list = None, icon: str = "default.png", **options, ): self.name = name self.app_id = envs.APP_ID if envs.DEPLOYMENT_SUFFIX is not None: self.name = self.name + envs.DEPLOYMENT_SUFFIX self.app_id = self.app_id + envs.DEPLOYMENT_SUFFIX self.description = description self.flags = flags or [] self.app_meta = app_meta self.features = features or [] self.data_sync_schema = data_sync_schema self.editable_tables = editable_tables or [] self.editable_tables_schemas = editable_tables_schemas or [] self.broker_funcs = broker_funcs self.icon = icon self.app_activation_path = base_paths.app_activation_path self.register_app_path = base_paths.register_app_path self.refresh_registration_path = base_paths.refresh_registration_path self.editable_tables_path = base_paths.editable_tables_path self.history_report_path = base_paths.history_report_path self.tenant_registration_path = base_paths.tenant_registration_path self.terms_and_conditions_path = base_paths.terms_and_conditions_path self.features_path = base_paths.features_path self.data_sync_path = base_paths.data_sync_path self.app_activation_url = envs.BASE_URL + self.app_activation_path self.refresh_registration_url = envs.BASE_URL + self.refresh_registration_path self.editable_tables_url = envs.BASE_URL + self.editable_tables_path self.history_report_url = envs.BASE_URL + self.history_report_path self.tenant_registration_url = envs.BASE_URL + self.tenant_registration_path self.terms_and_conditions_url = envs.BASE_URL + self.terms_and_conditions_path self.features_url = envs.BASE_URL + self.features_path self.data_sync_url = envs.BASE_URL + self.data_sync_path self.authorizations = doc_authorizations # Add xss security self.decorators = [xss_security] if api_decorators is None else api_decorators + [xss_security] # parameters with default values provided can be added stright to __init__ # otherwise added them to options until apps are actualized self.options = options # TODO version needs to be added to all urls + '/v' + self.version self.prefix = envs.APP_PATH self.app = None self.api = None self.ns = None self.reports: List[ReportBuilder] = [] self.report_components: List[BaseReportComponent] = [] self.uploaders: List[UploaderBuilder] = [] self.custom_namespaces: List[Namespace] = [] self.appvars = vars(self) def init_app(self, app: Flask, register: bool = False): # This hides flask_restx `X-fields` from swagger headers app.config["RESTX_MASK_SWAGGER"] = False @app.after_request def after_request(response): response.headers.set("Access-Control-Allow-Origin", "*") response.headers.set( "Access-Control-Allow-Headers", "Content-Type,Authorization,TenantId,Tenantid", ) response.headers.set( "Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS" ) response.headers.set("Access-Control-Allow-Credentials", "true") # https://flask.palletsprojects.com/en/2.1.x/security/#security-csp # XSS response.headers['X-Content-Type-Options'] = 'nosniff' return response # TODO CORS protection # CORS(app) self.app = app if not self.uploaders: log.warning("No uploaders provided") if not self.reports: log.warning("No reports provided") self.authenticate_app() self.init_api() self.init_routes() self.init_namespaces() self.init_broker() if register: self.register_app() return self.app def init_broker(self, app: Flask = None): self.broker = broker.init_app(app or self.app) return self.broker def authenticate_app(self): if envs.DESKTOP_ENVIRONMENT: return response, status_code = Authenticator.connect( max_retries="infinite", wait_seconds=2 ) if status_code != 200: raise Exception("App failed to authenticate!") log.info("Authentification succeded!") return response, status_code def init_api(self): self.api = Api( app=self.app, title=self.name, description=self.description, prefix=self.prefix, default=self.name, default_label=self.description, decorators=self.decorators, authorizations=self.authorizations, security=list(self.authorizations.keys()), doc=self.prefix + "/docs", ) def init_routes(self): # Here we are adding the routes available for each app # Api must be passed from route function back to this context api_funcs = [ add_refresh_registration_route, add_editable_tables_route, add_tenant_registration_route, add_app_activation_route, add_app_registration_route, add_terms_and_conditions_route, add_download_as_route, add_features_route, ] for func in api_funcs: self.api = func(self.api, self.appvars) # Another way is to group routes in namespaces # This way the url prefix is specified only in the namespace self.add_uploads_routes() self.add_reports_routes() self.add_report_components_routes() self.add_editables_routes() self.add_features_routes() self.add_data_sync_routes() #self.add_decrypt_routes() def add_uploads_routes(self): ns_funcs = [ get_filenames_validation_namespace, get_filestream_validation_namespace, get_status_namespace, get_quota_namespace, ] for func in ns_funcs: self.register_namespace( func(ns=uploads_namespace, uploaders=self.uploaders) ) def add_reports_routes(self): ns_funcs = [ get_report_register_namespace, get_report_metadata_namespace, get_report_components_namespace, get_report_image_preview_namespace, get_report_image_preview_dark_namespace, ] for func in ns_funcs: self.register_namespace(func(ns=reports_namespace, reports=self.reports)) def add_report_components_routes(self): ns_funcs = [get_report_individual_components_namespace] for func in ns_funcs: self.register_namespace( func( ns=report_components_namespace, report_components=self.report_components, ) ) def add_editables_routes(self): for editable in self.editable_tables: if editable.namespace: self.register_namespace(editable.namespace) else: ns = SchemaNamespace(schema=editable.schema).initialize() self.register_namespace(ns) def add_features_routes(self): ns_funcs = [get_features_namespace] for func in ns_funcs: self.register_namespace(func(ns=features_namespace, features=self.features)) def add_data_sync_routes(self): if self.data_sync_schema is None: return ns_funcs = [get_data_sync_namespace] for func in ns_funcs: self.register_namespace( func(ns=data_sync_namespace, data_sync_schema=self.data_sync_schema) ) # def add_decrypt_routes(self): # ns_funcs = [get_decrypt_namespace] # for func in ns_funcs: # self.register_namespace( # func(ns=decrypt_namespace) # ) def get_serialized_app_dict(self): app_dict = { k: v for k, v in self.appvars.items() if k in [ "app_id", "name", "description", "flags", "icon", "refresh_registration_url", "app_activation_url", "editable_tables_url", "history_report_url", "tenant_registration_url", "terms_and_conditions_url", "features_url", "app_meta", ] } app_dict["features"] = [f.get_details() for f in self.features] app_dict["tenants_with_app_activated"] = get_activated_tenants() app_dict["tenants_with_data_available"] = get_tenants_with_data() return app_dict def get_serialized_reports(self): reports = [ { k: v for k, v in vars(r).items() if k in [ "app_id", "report_id", "name", "description", "flags", "url", "preview_image_url", "preview_image_dark_url", "report_components", "connected_apps", "filters", "registrable", ] } for r in self.reports ] return reports def get_serialized_uploaders(self): uploaders = [ { k: v for k, v in vars(r).items() if k in [ "app_id", "uploader_id", "name", "description", "accepted_file_types", "flags", "status", "icon", "upload_url", "upload_validation_url", "quota_validation_url", "status_check_url", "validation_parameters", "encryption_parameters", "query_params_on_upload", ] } for r in self.uploaders ] return uploaders def get_serialized_report_components(self): report_components = [ { k: v for k, v in vars(r).items() if k in [ "app_id", "component_id", "url", "order", "style_attributes", "attributes", "title", "type", "filters", ] } for r in self.report_components ] return report_components def register_app(self): """ Sending registration payloads to registry-service """ # Converting from objects to dictionaries app_dict = self.get_serialized_app_dict() reports = self.get_serialized_reports() uploaders = self.get_serialized_uploaders() report_components = self.get_serialized_report_components() payload = { "data": dict( apps=[app_dict], reports=reports, uploaders=uploaders, report_components=report_components, ) } register_all.send(payload) return payload def register_feature(self, feature_instance: FeatureBuilder): for feature in self.features: if feature.feature_id == feature_instance.feature_id: raise Exception( f"Feature id '{feature_instance.feature_id}' was already declared" ) self.features.append(feature_instance) def register_uploader(self, uploader_instance: UploaderBuilder): for uploader in self.uploaders: if uploader.uploader_id == uploader_instance.uploader_id: raise Exception( f"Uploader id '{uploader_instance.uploader_id}' was already declared" ) self.uploaders.append(uploader_instance) def register_report(self, report_instance: ReportBuilder): for report in self.reports: if report.report_id == report_instance.report_id: raise Exception( f"Report id '{report_instance.report_id}' was already declared" ) self.reports.append(report_instance) def register_report_component(self, report_component_instance: BaseReportComponent): for rep_component in self.report_components: if rep_component.component_id == report_component_instance.component_id: raise Exception( f"Report component_id: '{report_component_instance.component_id}' was already declared" ) metadata = report_component_instance.get_registration_payload() report_component_instance.order = ( metadata["order"] or len(self.report_components) + 1 ) report_component_instance.type = metadata.pop("component_type") report_component_instance.style_attributes = metadata["style_attributes"] report_component_instance.attributes = metadata["attributes"] report_component_instance.filters = metadata["filters"] self.report_components.append(report_component_instance) def register_endpoint(self, endpoint_instance): ns = endpoint_instance.build_namespace(endpoint_builder_namespace) self.register_namespace(ns) def register_editable_table(self, editable_table_instance: EditableTable): for registered_table in self.editable_tables: registered_table: EditableTable if ( registered_table.schema.__name__ == editable_table_instance.schema.__name__ ): raise Exception( f"Editable table '{registered_table.schema.__name__}' already registered" ) self.editable_tables.append(editable_table_instance) self.editable_tables_schemas.append(editable_table_instance.schema) def register_namespace(self, ns: Namespace, path: str = None): """Used for custom restx namespaces""" self.custom_namespaces.append((ns, path)) def init_namespaces(self): for namespace in self.custom_namespaces: self.api.add_namespace(*namespace) def add_resource(self, resource: Resource, path: str): self.api.add_resource(resource, path) def add_collection(self, collection_name: str, db_name: str=None, timeseries_config: dict=None): create_collection( collection_name=collection_name, db_name=db_name, timeseries_config=timeseries_config )
Methods
def add_collection(self, collection_name: str, db_name: str = None, timeseries_config: dict = None)
-
Expand source code
def add_collection(self, collection_name: str, db_name: str=None, timeseries_config: dict=None): create_collection( collection_name=collection_name, db_name=db_name, timeseries_config=timeseries_config )
def add_data_sync_routes(self)
-
Expand source code
def add_data_sync_routes(self): if self.data_sync_schema is None: return ns_funcs = [get_data_sync_namespace] for func in ns_funcs: self.register_namespace( func(ns=data_sync_namespace, data_sync_schema=self.data_sync_schema) )
def add_editables_routes(self)
-
Expand source code
def add_editables_routes(self): for editable in self.editable_tables: if editable.namespace: self.register_namespace(editable.namespace) else: ns = SchemaNamespace(schema=editable.schema).initialize() self.register_namespace(ns)
def add_features_routes(self)
-
Expand source code
def add_features_routes(self): ns_funcs = [get_features_namespace] for func in ns_funcs: self.register_namespace(func(ns=features_namespace, features=self.features))
def add_report_components_routes(self)
-
Expand source code
def add_report_components_routes(self): ns_funcs = [get_report_individual_components_namespace] for func in ns_funcs: self.register_namespace( func( ns=report_components_namespace, report_components=self.report_components, ) )
def add_reports_routes(self)
-
Expand source code
def add_reports_routes(self): ns_funcs = [ get_report_register_namespace, get_report_metadata_namespace, get_report_components_namespace, get_report_image_preview_namespace, get_report_image_preview_dark_namespace, ] for func in ns_funcs: self.register_namespace(func(ns=reports_namespace, reports=self.reports))
def add_resource(self, resource: flask_restx.resource.Resource, path: str)
-
Expand source code
def add_resource(self, resource: Resource, path: str): self.api.add_resource(resource, path)
def add_uploads_routes(self)
-
Expand source code
def add_uploads_routes(self): ns_funcs = [ get_filenames_validation_namespace, get_filestream_validation_namespace, get_status_namespace, get_quota_namespace, ] for func in ns_funcs: self.register_namespace( func(ns=uploads_namespace, uploaders=self.uploaders) )
def authenticate_app(self)
-
Expand source code
def authenticate_app(self): if envs.DESKTOP_ENVIRONMENT: return response, status_code = Authenticator.connect( max_retries="infinite", wait_seconds=2 ) if status_code != 200: raise Exception("App failed to authenticate!") log.info("Authentification succeded!") return response, status_code
def get_serialized_app_dict(self)
-
Expand source code
def get_serialized_app_dict(self): app_dict = { k: v for k, v in self.appvars.items() if k in [ "app_id", "name", "description", "flags", "icon", "refresh_registration_url", "app_activation_url", "editable_tables_url", "history_report_url", "tenant_registration_url", "terms_and_conditions_url", "features_url", "app_meta", ] } app_dict["features"] = [f.get_details() for f in self.features] app_dict["tenants_with_app_activated"] = get_activated_tenants() app_dict["tenants_with_data_available"] = get_tenants_with_data() return app_dict
def get_serialized_report_components(self)
-
Expand source code
def get_serialized_report_components(self): report_components = [ { k: v for k, v in vars(r).items() if k in [ "app_id", "component_id", "url", "order", "style_attributes", "attributes", "title", "type", "filters", ] } for r in self.report_components ] return report_components
def get_serialized_reports(self)
-
Expand source code
def get_serialized_reports(self): reports = [ { k: v for k, v in vars(r).items() if k in [ "app_id", "report_id", "name", "description", "flags", "url", "preview_image_url", "preview_image_dark_url", "report_components", "connected_apps", "filters", "registrable", ] } for r in self.reports ] return reports
def get_serialized_uploaders(self)
-
Expand source code
def get_serialized_uploaders(self): uploaders = [ { k: v for k, v in vars(r).items() if k in [ "app_id", "uploader_id", "name", "description", "accepted_file_types", "flags", "status", "icon", "upload_url", "upload_validation_url", "quota_validation_url", "status_check_url", "validation_parameters", "encryption_parameters", "query_params_on_upload", ] } for r in self.uploaders ] return uploaders
def init_api(self)
-
Expand source code
def init_api(self): self.api = Api( app=self.app, title=self.name, description=self.description, prefix=self.prefix, default=self.name, default_label=self.description, decorators=self.decorators, authorizations=self.authorizations, security=list(self.authorizations.keys()), doc=self.prefix + "/docs", )
def init_app(self, app: flask.app.Flask, register: bool = False)
-
Expand source code
def init_app(self, app: Flask, register: bool = False): # This hides flask_restx `X-fields` from swagger headers app.config["RESTX_MASK_SWAGGER"] = False @app.after_request def after_request(response): response.headers.set("Access-Control-Allow-Origin", "*") response.headers.set( "Access-Control-Allow-Headers", "Content-Type,Authorization,TenantId,Tenantid", ) response.headers.set( "Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS" ) response.headers.set("Access-Control-Allow-Credentials", "true") # https://flask.palletsprojects.com/en/2.1.x/security/#security-csp # XSS response.headers['X-Content-Type-Options'] = 'nosniff' return response # TODO CORS protection # CORS(app) self.app = app if not self.uploaders: log.warning("No uploaders provided") if not self.reports: log.warning("No reports provided") self.authenticate_app() self.init_api() self.init_routes() self.init_namespaces() self.init_broker() if register: self.register_app() return self.app
def init_broker(self, app: flask.app.Flask = None)
-
Expand source code
def init_broker(self, app: Flask = None): self.broker = broker.init_app(app or self.app) return self.broker
def init_namespaces(self)
-
Expand source code
def init_namespaces(self): for namespace in self.custom_namespaces: self.api.add_namespace(*namespace)
def init_routes(self)
-
Expand source code
def init_routes(self): # Here we are adding the routes available for each app # Api must be passed from route function back to this context api_funcs = [ add_refresh_registration_route, add_editable_tables_route, add_tenant_registration_route, add_app_activation_route, add_app_registration_route, add_terms_and_conditions_route, add_download_as_route, add_features_route, ] for func in api_funcs: self.api = func(self.api, self.appvars) # Another way is to group routes in namespaces # This way the url prefix is specified only in the namespace self.add_uploads_routes() self.add_reports_routes() self.add_report_components_routes() self.add_editables_routes() self.add_features_routes() self.add_data_sync_routes() #self.add_decrypt_routes()
def register_app(self)
-
Sending registration payloads to registry-service
Expand source code
def register_app(self): """ Sending registration payloads to registry-service """ # Converting from objects to dictionaries app_dict = self.get_serialized_app_dict() reports = self.get_serialized_reports() uploaders = self.get_serialized_uploaders() report_components = self.get_serialized_report_components() payload = { "data": dict( apps=[app_dict], reports=reports, uploaders=uploaders, report_components=report_components, ) } register_all.send(payload) return payload
def register_editable_table(self, editable_table_instance: EditableTable)
-
Expand source code
def register_editable_table(self, editable_table_instance: EditableTable): for registered_table in self.editable_tables: registered_table: EditableTable if ( registered_table.schema.__name__ == editable_table_instance.schema.__name__ ): raise Exception( f"Editable table '{registered_table.schema.__name__}' already registered" ) self.editable_tables.append(editable_table_instance) self.editable_tables_schemas.append(editable_table_instance.schema)
def register_endpoint(self, endpoint_instance)
-
Expand source code
def register_endpoint(self, endpoint_instance): ns = endpoint_instance.build_namespace(endpoint_builder_namespace) self.register_namespace(ns)
def register_feature(self, feature_instance: FeatureBuilder)
-
Expand source code
def register_feature(self, feature_instance: FeatureBuilder): for feature in self.features: if feature.feature_id == feature_instance.feature_id: raise Exception( f"Feature id '{feature_instance.feature_id}' was already declared" ) self.features.append(feature_instance)
def register_namespace(self, ns: flask_restx.namespace.Namespace, path: str = None)
-
Used for custom restx namespaces
Expand source code
def register_namespace(self, ns: Namespace, path: str = None): """Used for custom restx namespaces""" self.custom_namespaces.append((ns, path))
def register_report(self, report_instance: ReportBuilder)
-
Expand source code
def register_report(self, report_instance: ReportBuilder): for report in self.reports: if report.report_id == report_instance.report_id: raise Exception( f"Report id '{report_instance.report_id}' was already declared" ) self.reports.append(report_instance)
def register_report_component(self, report_component_instance: BaseReportComponent)
-
Expand source code
def register_report_component(self, report_component_instance: BaseReportComponent): for rep_component in self.report_components: if rep_component.component_id == report_component_instance.component_id: raise Exception( f"Report component_id: '{report_component_instance.component_id}' was already declared" ) metadata = report_component_instance.get_registration_payload() report_component_instance.order = ( metadata["order"] or len(self.report_components) + 1 ) report_component_instance.type = metadata.pop("component_type") report_component_instance.style_attributes = metadata["style_attributes"] report_component_instance.attributes = metadata["attributes"] report_component_instance.filters = metadata["filters"] self.report_components.append(report_component_instance)
def register_uploader(self, uploader_instance: UploaderBuilder)
-
Expand source code
def register_uploader(self, uploader_instance: UploaderBuilder): for uploader in self.uploaders: if uploader.uploader_id == uploader_instance.uploader_id: raise Exception( f"Uploader id '{uploader_instance.uploader_id}' was already declared" ) self.uploaders.append(uploader_instance)
class base_paths (app_activation_path: str = '/activate_app', register_app_path: str = '/register_app', refresh_registration_path: str = '/refresh_registration', editable_tables_path: str = '/editable_tables', history_report_path: str = '/reports/history_report', tenant_registration_path: str = '/register_tenant', terms_and_conditions_path: str = '/terms_and_conditions', features_path: str = '/features', data_sync_path: str = '/data-sync')
-
base_paths(app_activation_path: str = '/activate_app', register_app_path: str = '/register_app', refresh_registration_path: str = '/refresh_registration', editable_tables_path: str = '/editable_tables', history_report_path: str = '/reports/history_report', tenant_registration_path: str = '/register_tenant', terms_and_conditions_path: str = '/terms_and_conditions', features_path: str = '/features', data_sync_path: str = '/data-sync')
Expand source code
class base_paths: app_activation_path: str = "/activate_app" register_app_path: str = "/register_app" refresh_registration_path: str = "/refresh_registration" editable_tables_path: str = "/editable_tables" history_report_path: str = "/reports/history_report" tenant_registration_path: str = "/register_tenant" terms_and_conditions_path: str = "/terms_and_conditions" features_path: str = "/features" data_sync_path: str = "/data-sync"
Class variables
var app_activation_path : str
var data_sync_path : str
var editable_tables_path : str
var features_path : str
var history_report_path : str
var refresh_registration_path : str
var register_app_path : str
var tenant_registration_path : str
var terms_and_conditions_path : str