|
@@ -0,0 +1,216 @@
|
|
|
|
|
+import json
|
|
|
|
|
+import pathlib
|
|
|
|
|
+import ipaddress
|
|
|
|
|
+import sqlalchemy
|
|
|
|
|
+
|
|
|
|
|
+class config_error(TypeError):
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+class settings:
|
|
|
|
|
+ """
|
|
|
|
|
+ This class is responsible for handling settings in app. It read config
|
|
|
|
|
+ file from specified path, and handle options from it. When any config
|
|
|
|
|
+ option is specified wrong, for example bad formated IP address, then
|
|
|
|
|
+ config_error will be raised.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, path: pathlib.Path) -> None:
|
|
|
|
|
+ """
|
|
|
|
|
+ This function initialize settings, it require path to parse it as
|
|
|
|
|
+ config file, and reading parameters from it.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters:
|
|
|
|
|
+ path (pathlib.Path): Path of config file
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ if not path.is_file():
|
|
|
|
|
+ raise FileNotFoundError("Config JSON not found.")
|
|
|
|
|
+
|
|
|
|
|
+ with path.open() as config:
|
|
|
|
|
+ try:
|
|
|
|
|
+ self.__source = json.loads(config.read())
|
|
|
|
|
+ except:
|
|
|
|
|
+ raise config_error("Syntax error in config file.")
|
|
|
|
|
+
|
|
|
|
|
+ @property
|
|
|
|
|
+ def app_name(self) -> str:
|
|
|
|
|
+ """ It return provider app name. """
|
|
|
|
|
+
|
|
|
|
|
+ return self.__get("provider.app_name", str) or "Key App"
|
|
|
|
|
+
|
|
|
|
|
+ @property
|
|
|
|
|
+ def app_description(self) -> str:
|
|
|
|
|
+ """ It return description of app given by provider. """
|
|
|
|
|
+
|
|
|
|
|
+ return self.__get("provider.description", str) or "Secure keys store."
|
|
|
|
|
+
|
|
|
|
|
+ @property
|
|
|
|
|
+ def organization_name(self) -> str:
|
|
|
|
|
+ """ This return name of organization which own app. """
|
|
|
|
|
+
|
|
|
|
|
+ return self.__get("provider.organization_name", str) or "cx-org"
|
|
|
|
|
+
|
|
|
|
|
+ @property
|
|
|
|
|
+ def database(self) -> str:
|
|
|
|
|
+ """
|
|
|
|
|
+ This return URL of the database, to build database connector. It
|
|
|
|
|
+ parse all options, that mean user could give all parameters one
|
|
|
|
|
+ by one, or URL as string.
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ (str): URL of the database connection
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ username = self.__get("database.username", str) or None
|
|
|
|
|
+ password = self.__get("database.password", str) or None
|
|
|
|
|
+ port = self.__get("database.port", int) or None
|
|
|
|
|
+ host = self.__get("database.host", str) or None
|
|
|
|
|
+ engine = self.__get("database.engine", str) or None
|
|
|
|
|
+ database = self.__get("database.name", str) or None
|
|
|
|
|
+ url = self.__get("database.url", str) or None
|
|
|
|
|
+
|
|
|
|
|
+ if url is not None:
|
|
|
|
|
+ check = username or None
|
|
|
|
|
+ check = check or password or None
|
|
|
|
|
+ check = check or port or None
|
|
|
|
|
+ check = check or host or None
|
|
|
|
|
+ check = check or engine or None
|
|
|
|
|
+ check = check or database or None
|
|
|
|
|
+
|
|
|
|
|
+ if check is None:
|
|
|
|
|
+ return url
|
|
|
|
|
+
|
|
|
|
|
+ error = "When database URL config parameter is in use, any other "
|
|
|
|
|
+ error = error + "database config can not being specified. Decide "
|
|
|
|
|
+ error = error + "which method to setting database connection use."
|
|
|
|
|
+
|
|
|
|
|
+ raise config_rror(error)
|
|
|
|
|
+
|
|
|
|
|
+ engine = engine or "sqlite"
|
|
|
|
|
+ database = database or "database.sqlite3"
|
|
|
|
|
+
|
|
|
|
|
+ url = sqlalchemy.URL(
|
|
|
|
|
+ engine,
|
|
|
|
|
+ username = username,
|
|
|
|
|
+ password = password,
|
|
|
|
|
+ host = host,
|
|
|
|
|
+ database = database,
|
|
|
|
|
+ port = port,
|
|
|
|
|
+ query = None
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return str(url)
|
|
|
|
|
+
|
|
|
|
|
+ @property
|
|
|
|
|
+ def host(self) -> str:
|
|
|
|
|
+ """
|
|
|
|
|
+ This return host to listen on. When in config is not valid ip address
|
|
|
|
|
+ v4 or v6, then raise error.
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ (str): Host to app listen on
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ host = self.__get("listen.ip", str) or "0.0.0.0"
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ ipaddress.ip_address(host)
|
|
|
|
|
+ except:
|
|
|
|
|
+ raise config_error("IP address is in invald form.")
|
|
|
|
|
+
|
|
|
|
|
+ return host
|
|
|
|
|
+
|
|
|
|
|
+ @property
|
|
|
|
|
+ def port(self) -> int:
|
|
|
|
|
+ """
|
|
|
|
|
+ This return port, or raise exception when port is failed.
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ (int): Number of port to listen on
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ port = self.__get("listen.port", int) or 8000
|
|
|
|
|
+
|
|
|
|
|
+ if not self.__is_port(port):
|
|
|
|
|
+ raise config_error("Port is not in valid range.")
|
|
|
|
|
+
|
|
|
|
|
+ return port
|
|
|
|
|
+
|
|
|
|
|
+ @property
|
|
|
|
|
+ def address(self) -> str:
|
|
|
|
|
+ """
|
|
|
|
|
+ This is address of app to make server listen on.
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ (str): Address of app to listen on
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ port = self.port
|
|
|
|
|
+ address = self.host
|
|
|
|
|
+
|
|
|
|
|
+ ip_type = ipaddress.ip_address(host)
|
|
|
|
|
+
|
|
|
|
|
+ if type(ip_type) is ipaddress.IPv4Address:
|
|
|
|
|
+ return "http://" + address + ":" + port
|
|
|
|
|
+
|
|
|
|
|
+ return "http://[" + address + "]:" + port
|
|
|
|
|
+
|
|
|
|
|
+ def __is_port(self, port: int) -> bool:
|
|
|
|
|
+ """
|
|
|
|
|
+ This function check that given number is proof port.
|
|
|
|
|
+
|
|
|
|
|
+ Parameters:
|
|
|
|
|
+ port (int): Port number to check
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ (bool): True when this is valid port, false when not
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ return port >= 0 and port <= 65535
|
|
|
|
|
+
|
|
|
|
|
+ def __get(
|
|
|
|
|
+ self,
|
|
|
|
|
+ name: str,
|
|
|
|
|
+ typed: type | None = None
|
|
|
|
|
+ ) -> str | int | float | bool | None:
|
|
|
|
|
+ """
|
|
|
|
|
+ This function load parameter, from source config file. It could
|
|
|
|
|
+ check type of loaded parameter, and raise error, if type is not
|
|
|
|
|
+ valid. When typed parameter is not provided, then it skip
|
|
|
|
|
+ check of parameter type. When item not exists in config file, then
|
|
|
|
|
+ return None. It could be used with "or". Config file has parts, to
|
|
|
|
|
+ navigate in parts use ".". For example: ["x"]["y"]["z"] => "x.y.z".
|
|
|
|
|
+
|
|
|
|
|
+ Example:
|
|
|
|
|
+ param = self.__get("default.param", str) or "default"
|
|
|
|
|
+
|
|
|
|
|
+ Parameters:
|
|
|
|
|
+ name (str): Name of the parameter, with dot "." notation
|
|
|
|
|
+ typed (type | None): Type of returned parameter, None to skip
|
|
|
|
|
+ checking. Default = None
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ (str | int | float | bool | None): Value of parameter, of None
|
|
|
|
|
+ when not exists
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ parts = name.split(".")
|
|
|
|
|
+ result = self.__source
|
|
|
|
|
+
|
|
|
|
|
+ for part in parts:
|
|
|
|
|
+ if not part in result:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ result = result[part]
|
|
|
|
|
+
|
|
|
|
|
+ if typed is not None and type(result) is not typed:
|
|
|
|
|
+ error = ""
|
|
|
|
|
+ error = error + "Error, type of " + name + " "
|
|
|
|
|
+ error = error + "must be in " + str(type(default)) + " type, "
|
|
|
|
|
+ error = error + "but it is in " + str(type(result)) + "type."
|
|
|
|
|
+ error = error + "Change it in config file."
|
|
|
|
|
+
|
|
|
|
|
+ raise config_error(error)
|
|
|
|
|
+
|
|
|
|
|
+ return result
|
|
|
|
|
+
|