settings.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import json
  2. import pathlib
  3. import ipaddress
  4. import sqlalchemy
  5. class config_error(TypeError):
  6. pass
  7. class settings:
  8. """
  9. This class is responsible for handling settings in app. It read config
  10. file from specified path, and handle options from it. When any config
  11. option is specified wrong, for example bad formated IP address, then
  12. config_error will be raised.
  13. """
  14. def __init__(self, path: pathlib.Path) -> None:
  15. """
  16. This function initialize settings, it require path to parse it as
  17. config file, and reading parameters from it.
  18. Parameters:
  19. path (pathlib.Path): Path of config file
  20. """
  21. if not path.is_file():
  22. raise FileNotFoundError("Config JSON not found.")
  23. with path.open() as config:
  24. try:
  25. self.__source = json.loads(config.read())
  26. except:
  27. raise config_error("Syntax error in config file.")
  28. @property
  29. def app_name(self) -> str:
  30. """ It return provider app name. """
  31. return self.__get("provider.app_name", str) or "Key App"
  32. @property
  33. def app_description(self) -> str:
  34. """ It return description of app given by provider. """
  35. return self.__get("provider.description", str) or "Secure keys store."
  36. @property
  37. def organization_name(self) -> str:
  38. """ This return name of organization which own app. """
  39. return self.__get("provider.organization_name", str) or "cx-org"
  40. @property
  41. def database(self) -> str:
  42. """
  43. This return URL of the database, to build database connector. It
  44. parse all options, that mean user could give all parameters one
  45. by one, or URL as string.
  46. Returns:
  47. (str): URL of the database connection
  48. """
  49. username = self.__get("database.username", str) or None
  50. password = self.__get("database.password", str) or None
  51. port = self.__get("database.port", int) or None
  52. host = self.__get("database.host", str) or None
  53. engine = self.__get("database.engine", str) or None
  54. database = self.__get("database.name", str) or None
  55. url = self.__get("database.url", str) or None
  56. if url is not None:
  57. check = username or None
  58. check = check or password or None
  59. check = check or port or None
  60. check = check or host or None
  61. check = check or engine or None
  62. check = check or database or None
  63. if check is None:
  64. return url
  65. error = "When database URL config parameter is in use, any other "
  66. error = error + "database config can not being specified. Decide "
  67. error = error + "which method to setting database connection use."
  68. raise config_rror(error)
  69. engine = engine or "sqlite"
  70. database = database or "database.sqlite3"
  71. url = sqlalchemy.URL(
  72. engine,
  73. username = username,
  74. password = password,
  75. host = host,
  76. database = database,
  77. port = port,
  78. query = None
  79. )
  80. return str(url)
  81. @property
  82. def host(self) -> str:
  83. """
  84. This return host to listen on. When in config is not valid ip address
  85. v4 or v6, then raise error.
  86. Returns:
  87. (str): Host to app listen on
  88. """
  89. host = self.__get("listen.ip", str) or "0.0.0.0"
  90. try:
  91. ipaddress.ip_address(host)
  92. except:
  93. raise config_error("IP address is in invald form.")
  94. return host
  95. @property
  96. def port(self) -> int:
  97. """
  98. This return port, or raise exception when port is failed.
  99. Returns:
  100. (int): Number of port to listen on
  101. """
  102. port = self.__get("listen.port", int) or 8000
  103. if not self.__is_port(port):
  104. raise config_error("Port is not in valid range.")
  105. return port
  106. @property
  107. def address(self) -> str:
  108. """
  109. This is address of app to make server listen on.
  110. Returns:
  111. (str): Address of app to listen on
  112. """
  113. port = self.port
  114. address = self.host
  115. ip_type = ipaddress.ip_address(host)
  116. if type(ip_type) is ipaddress.IPv4Address:
  117. return "http://" + address + ":" + port
  118. return "http://[" + address + "]:" + port
  119. def __is_port(self, port: int) -> bool:
  120. """
  121. This function check that given number is proof port.
  122. Parameters:
  123. port (int): Port number to check
  124. Returns:
  125. (bool): True when this is valid port, false when not
  126. """
  127. return port >= 0 and port <= 65535
  128. def __get(
  129. self,
  130. name: str,
  131. typed: type | None = None
  132. ) -> str | int | float | bool | None:
  133. """
  134. This function load parameter, from source config file. It could
  135. check type of loaded parameter, and raise error, if type is not
  136. valid. When typed parameter is not provided, then it skip
  137. check of parameter type. When item not exists in config file, then
  138. return None. It could be used with "or". Config file has parts, to
  139. navigate in parts use ".". For example: ["x"]["y"]["z"] => "x.y.z".
  140. Example:
  141. param = self.__get("default.param", str) or "default"
  142. Parameters:
  143. name (str): Name of the parameter, with dot "." notation
  144. typed (type | None): Type of returned parameter, None to skip
  145. checking. Default = None
  146. Returns:
  147. (str | int | float | bool | None): Value of parameter, of None
  148. when not exists
  149. """
  150. parts = name.split(".")
  151. result = self.__source
  152. for part in parts:
  153. if not part in result:
  154. return None
  155. result = result[part]
  156. if typed is not None and type(result) is not typed:
  157. error = ""
  158. error = error + "Error, type of " + name + " "
  159. error = error + "must be in " + str(type(default)) + " type, "
  160. error = error + "but it is in " + str(type(result)) + "type."
  161. error = error + "Change it in config file."
  162. raise config_error(error)
  163. return result