fastapi-startkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. fastapi_startkit/__init__.py +3 -0
  2. fastapi_startkit/application.py +40 -0
  3. fastapi_startkit/configuration/Configuration.py +80 -0
  4. fastapi_startkit/configuration/__init__.py +2 -0
  5. fastapi_startkit/configuration/helpers.py +5 -0
  6. fastapi_startkit/configuration/providers/ConfigurationProvider.py +16 -0
  7. fastapi_startkit/configuration/providers/__init__.py +1 -0
  8. fastapi_startkit/container/__init__.py +1 -0
  9. fastapi_startkit/container/container.py +494 -0
  10. fastapi_startkit/environment/environment.py +76 -0
  11. fastapi_startkit/exceptions/DD.py +38 -0
  12. fastapi_startkit/exceptions/ExceptionHandler.py +76 -0
  13. fastapi_startkit/exceptions/__init__.py +38 -0
  14. fastapi_startkit/exceptions/exceptionite/__init__.py +0 -0
  15. fastapi_startkit/exceptions/exceptionite/blocks.py +101 -0
  16. fastapi_startkit/exceptions/exceptionite/controllers.py +13 -0
  17. fastapi_startkit/exceptions/exceptionite/solutions.py +66 -0
  18. fastapi_startkit/exceptions/exceptionite/tabs.py +19 -0
  19. fastapi_startkit/exceptions/exceptions.py +218 -0
  20. fastapi_startkit/exceptions/handlers/DumpExceptionHandler.py +104 -0
  21. fastapi_startkit/exceptions/handlers/HttpExceptionHandler.py +28 -0
  22. fastapi_startkit/exceptions/handlers/ModelNotFoundHandler.py +13 -0
  23. fastapi_startkit/facades/Auth.py +5 -0
  24. fastapi_startkit/facades/Auth.pyi +32 -0
  25. fastapi_startkit/facades/Broadcast.py +5 -0
  26. fastapi_startkit/facades/Cache.py +5 -0
  27. fastapi_startkit/facades/Config.py +5 -0
  28. fastapi_startkit/facades/Config.pyi +14 -0
  29. fastapi_startkit/facades/Dump.py +5 -0
  30. fastapi_startkit/facades/Dump.pyi +26 -0
  31. fastapi_startkit/facades/Facade.py +5 -0
  32. fastapi_startkit/facades/Gate.py +5 -0
  33. fastapi_startkit/facades/Gate.pyi +32 -0
  34. fastapi_startkit/facades/Hash.py +5 -0
  35. fastapi_startkit/facades/Hash.pyi +28 -0
  36. fastapi_startkit/facades/Loader.py +5 -0
  37. fastapi_startkit/facades/Loader.pyi +30 -0
  38. fastapi_startkit/facades/Mail.py +5 -0
  39. fastapi_startkit/facades/Mail.pyi +14 -0
  40. fastapi_startkit/facades/Notification.py +5 -0
  41. fastapi_startkit/facades/Notification.pyi +25 -0
  42. fastapi_startkit/facades/Queue.py +5 -0
  43. fastapi_startkit/facades/Queue.pyi +10 -0
  44. fastapi_startkit/facades/RateLimiter.py +5 -0
  45. fastapi_startkit/facades/RateLimiter.pyi +43 -0
  46. fastapi_startkit/facades/Request.py +5 -0
  47. fastapi_startkit/facades/Request.pyi +88 -0
  48. fastapi_startkit/facades/Response.py +5 -0
  49. fastapi_startkit/facades/Response.pyi +68 -0
  50. fastapi_startkit/facades/Session.py +5 -0
  51. fastapi_startkit/facades/Session.pyi +59 -0
  52. fastapi_startkit/facades/Storage.py +5 -0
  53. fastapi_startkit/facades/Storage.pyi +12 -0
  54. fastapi_startkit/facades/Url.py +5 -0
  55. fastapi_startkit/facades/Url.pyi +22 -0
  56. fastapi_startkit/facades/View.py +5 -0
  57. fastapi_startkit/facades/View.pyi +54 -0
  58. fastapi_startkit/facades/__init__.py +19 -0
  59. fastapi_startkit/loader/Loader.py +78 -0
  60. fastapi_startkit/loader/__init__.py +1 -0
  61. fastapi_startkit/providers/ConfigurationProvider.py +13 -0
  62. fastapi_startkit/providers/Provider.py +14 -0
  63. fastapi_startkit/providers/__init__.py +4 -0
  64. fastapi_startkit/utils/__init__.py +0 -0
  65. fastapi_startkit/utils/collections.py +545 -0
  66. fastapi_startkit/utils/console.py +39 -0
  67. fastapi_startkit/utils/data/mime.types +1863 -0
  68. fastapi_startkit/utils/filesystem.py +100 -0
  69. fastapi_startkit/utils/http.py +101 -0
  70. fastapi_startkit/utils/location.py +90 -0
  71. fastapi_startkit/utils/str.py +120 -0
  72. fastapi_startkit/utils/structures.py +97 -0
  73. fastapi_startkit/utils/time.py +58 -0
  74. fastapi_startkit-0.1.0.dist-info/METADATA +13 -0
  75. fastapi_startkit-0.1.0.dist-info/RECORD +76 -0
  76. fastapi_startkit-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,100 @@
1
+ import os
2
+ import platform
3
+ import pathlib
4
+ import mimetypes
5
+
6
+
7
+ def make_directory(directory):
8
+ """Create a directory at the given path for a file if it does not exist"""
9
+ if not os.path.isfile(directory):
10
+ if not os.path.exists(os.path.dirname(directory)):
11
+ # Create the path to the model if it does not exist
12
+ os.makedirs(os.path.dirname(directory))
13
+
14
+ return True
15
+
16
+ return False
17
+
18
+
19
+ def file_exists(directory):
20
+ """Create a directory at the given path for a file if it does not exist"""
21
+ return os.path.exists(os.path.dirname(directory))
22
+
23
+
24
+ def make_full_directory(directory):
25
+ """Create all directories to the given path if they do not exist"""
26
+ if not os.path.isfile(directory):
27
+ if not os.path.exists(directory):
28
+ # Create the path to the model if it does not exist
29
+ os.makedirs(directory)
30
+
31
+ return True
32
+
33
+ return False
34
+
35
+
36
+ def creation_date(path_to_file):
37
+ """Try to get the date that a file was created, falling back to when it was
38
+ last modified if that isn't possible.
39
+ """
40
+ if platform.system() == "Windows":
41
+ return os.path.getctime(path_to_file)
42
+ else:
43
+ stat = os.stat(path_to_file)
44
+ try:
45
+ return stat.st_birthtime
46
+ except AttributeError:
47
+ # We're probably on Linux. No easy way to get creation dates here,
48
+ # so we'll settle for when its content was last modified.
49
+ return stat.st_mtime
50
+
51
+
52
+ def modified_date(path_to_file):
53
+ if platform.system() == "Windows":
54
+ return os.path.getmtime(path_to_file)
55
+ else:
56
+ stat = os.stat(path_to_file)
57
+ try:
58
+ return stat.st_mtime
59
+ except AttributeError:
60
+ # We're probably on Linux. No easy way to get creation dates here,
61
+ # so we'll settle for when its content was last modified.
62
+ return 0
63
+
64
+
65
+ def render_stub_file(stub_file, name):
66
+ """Read stub file, replace placeholders and return content."""
67
+ with open(stub_file, "r") as f:
68
+ content = f.read()
69
+ content = content.replace("__class__", name)
70
+ return content
71
+
72
+
73
+ def get_module_dir(module_file):
74
+ return os.path.dirname(os.path.realpath(module_file))
75
+
76
+
77
+ mimetypes.init([os.path.join(get_module_dir(__file__), "data/mime.types")])
78
+
79
+ KNOWN_MIME_TYPES = mimetypes.types_map.keys()
80
+
81
+
82
+ def get_extension(filepath: str, without_dot=False) -> str:
83
+ """Get file extension from a filepath. If without_dot=True the . prefix will be removed from
84
+ the extension."""
85
+ extension_parts = pathlib.Path(filepath).suffixes
86
+ extension = ""
87
+ if extension_parts:
88
+ # try to join all the parts until only one part to check if it's a known extension
89
+ for i in range(len(extension_parts)):
90
+ try_extension = "".join(extension_parts[i:])
91
+ if try_extension in KNOWN_MIME_TYPES:
92
+ extension = try_extension
93
+ break
94
+ # if no known extension found, return the last part as the extension
95
+ if not extension:
96
+ extension = extension_parts[-1]
97
+
98
+ if without_dot:
99
+ extension = extension[1:]
100
+ return extension
@@ -0,0 +1,101 @@
1
+ """Helpers for working with HTTP."""
2
+
3
+
4
+ HTTP_STATUS_CODES = {
5
+ 100: "100 Continue",
6
+ 101: "101 Switching Protocol",
7
+ 102: "102 Processing",
8
+ 103: "Early Hints",
9
+ 200: "200 OK",
10
+ 201: "201 Created",
11
+ 202: "202 Accepted",
12
+ 203: "203 Non-Authoritative Information",
13
+ 204: "204 No Content",
14
+ 205: "205 Reset Content",
15
+ 206: "206 Partial Content",
16
+ 207: "207 Multi-Status",
17
+ 208: "208 Multi-Status",
18
+ 226: "226 IM Used",
19
+ 300: "300 Multiple Choice",
20
+ 301: "301 Moved Permanently",
21
+ 302: "302 Found",
22
+ 303: "303 See Other",
23
+ 304: "304 Not Modified",
24
+ 307: "307 Temporary Redirect",
25
+ 308: "308 Permanent Redirect",
26
+ 400: "400 Bad Request",
27
+ 401: "401 Unauthorized",
28
+ 402: "402 Payment Required",
29
+ 403: "403 Forbidden",
30
+ 404: "404 Not Found",
31
+ 405: "405 Method Not Allowed",
32
+ 406: "406 Not Acceptable",
33
+ 407: "407 Proxy Authentication Required",
34
+ 408: "408 Request Timeout",
35
+ 409: "409 Conflict",
36
+ 410: "410 Gone",
37
+ 411: "411 Length Required",
38
+ 412: "412 Precondition Failed",
39
+ 413: "413 Payload Too Large",
40
+ 414: "414 URI Too Long",
41
+ 415: "415 Unsupported Media Type",
42
+ 416: "416 Requested Range Not Satisfiable",
43
+ 417: "417 Expectation Failed",
44
+ 418: "418 I'm a teapot",
45
+ 421: "421 Misdirected Request",
46
+ 422: "422 Unprocessable Entity",
47
+ 423: "423 Locked",
48
+ 424: "424 Failed Dependency",
49
+ 425: "425 Too Early",
50
+ 426: "426 Upgrade Required",
51
+ 428: "428 Precondition Required",
52
+ 429: "429 Too Many Requests",
53
+ 431: "431 Request Header Fields Too Large",
54
+ 451: "451 Unavailable For Legal Reasons",
55
+ 500: "500 Internal Server Error",
56
+ 501: "501 Not Implemented",
57
+ 502: "502 Bad Gateway",
58
+ 503: "503 Service Unavailable",
59
+ 504: "504 Gateway Timeout",
60
+ 505: "505 HTTP Version Not Supported",
61
+ 506: "506 Variant Also Negotiates",
62
+ 507: "507 Insufficient Storage",
63
+ 508: "508 Loop Detected",
64
+ 510: "510 Not Extended",
65
+ 511: "511 Network Authentication Required",
66
+ }
67
+
68
+
69
+ def generate_wsgi(wsgi={}, path="/", query_string="", method="GET"):
70
+ """Generate the WSGI environment dictionary that we receive from a HTTP request."""
71
+ import io
72
+
73
+ data = {
74
+ "wsgi.version": (1, 0),
75
+ "wsgi.multithread": False,
76
+ "wsgi.multiprocess": True,
77
+ "wsgi.run_once": False,
78
+ "wsgi.input": io.BytesIO(),
79
+ "SERVER_SOFTWARE": "gunicorn/19.7.1",
80
+ "REQUEST_METHOD": method,
81
+ "QUERY_STRING": query_string,
82
+ "RAW_URI": path,
83
+ "SERVER_PROTOCOL": "HTTP/1.1",
84
+ "HTTP_HOST": "127.0.0.1:8000",
85
+ "HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
86
+ "HTTP_UPGRADE_INSECURE_REQUESTS": "1",
87
+ "HTTP_COOKIE": "",
88
+ "HTTP_USER_AGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7",
89
+ "HTTP_ACCEPT_LANGUAGE": "en-us",
90
+ "HTTP_ACCEPT_ENCODING": "gzip, deflate",
91
+ "HTTP_CONNECTION": "keep-alive",
92
+ "wsgi.url_scheme": "http",
93
+ "REMOTE_ADDR": "127.0.0.1",
94
+ "REMOTE_PORT": "62241",
95
+ "SERVER_NAME": "127.0.0.1",
96
+ "SERVER_PORT": "8000",
97
+ "PATH_INFO": path,
98
+ "SCRIPT_NAME": "",
99
+ }
100
+ data.update(wsgi)
101
+ return data
@@ -0,0 +1,90 @@
1
+ """Helpers to resolve absolute paths to the different app resources using a configured
2
+ location."""
3
+ from os.path import join, abspath
4
+
5
+ from .str import as_filepath
6
+
7
+
8
+ def _build_path(location_key, relative_path, absolute):
9
+ from wsgi import application
10
+
11
+ relative_dir = join(as_filepath(application.make(location_key)), relative_path)
12
+ return abspath(relative_dir) if absolute else relative_dir
13
+
14
+
15
+ def base_path(relative_path=""):
16
+ """Build the absolute path to the project root directory or build the absolute path to a
17
+ given file relative to the project root directory."""
18
+ return abspath(relative_path)
19
+
20
+
21
+ def views_path(relative_path="", absolute=True):
22
+ """Build the absolute path to the project views directory or build the absolute path to a given
23
+ file relative to the project views directory.
24
+
25
+ The relative path can be returned instead by setting absolute=False."""
26
+ return _build_path("views.location", relative_path, absolute)
27
+
28
+
29
+ def controllers_path(relative_path="", absolute=True):
30
+ """Build the absolute path to the project controllers directory or build the absolute path to a given
31
+ file relative to the project controllers directory.
32
+
33
+ The relative path can be returned instead by setting absolute=False."""
34
+ return _build_path("controllers.location", relative_path, absolute)
35
+
36
+
37
+ def mailables_path(relative_path="", absolute=True):
38
+ """Build the absolute path to the project controllers directory or build the absolute path to a given
39
+ file relative to the project controllers directory.
40
+
41
+ The relative path can be returned instead by setting absolute=False."""
42
+ return _build_path("mailables.location", relative_path, absolute)
43
+
44
+
45
+ def config_path(relative_path="", absolute=True):
46
+ """Build the absolute path to the project configuration directory or build the absolute path to a given
47
+ file relative to the project configuration directory.
48
+
49
+ The relative path can be returned instead by setting absolute=False."""
50
+ return _build_path("config.location", relative_path, absolute)
51
+
52
+
53
+ def migrations_path(relative_path="", absolute=True):
54
+ """Build the absolute path to the project migrations directory or build the absolute path to a given
55
+ file relative to the project migrations directory.
56
+
57
+ The relative path can be returned instead by setting absolute=False."""
58
+ return _build_path("migrations.location", relative_path, absolute)
59
+
60
+
61
+ def seeds_path(relative_path="", absolute=True):
62
+ """Build the absolute path to the project seeds directory or build the absolute path to a given
63
+ file relative to the project seeds directory.
64
+
65
+ The relative path can be returned instead by setting absolute=False."""
66
+ return _build_path("seeds.location", relative_path, absolute)
67
+
68
+
69
+ def jobs_path(relative_path="", absolute=True):
70
+ """Build the absolute path to the project jobs directory or build the absolute path to a given
71
+ file relative to the project jobs directory.
72
+
73
+ The relative path can be returned instead by setting absolute=False."""
74
+ return _build_path("jobs.location", relative_path, absolute)
75
+
76
+
77
+ def resources_path(relative_path="", absolute=True):
78
+ """Build the absolute path to the project resources directory or build the absolute path to a given
79
+ file relative to the project resources directory.
80
+
81
+ The relative path can be returned instead by setting absolute=False."""
82
+ return _build_path("resources.location", relative_path, absolute)
83
+
84
+
85
+ def models_path(relative_path="", absolute=True):
86
+ """Build the absolute path to the project models directory or build the absolute path to a given
87
+ file relative to the project models directory.
88
+
89
+ The relative path can be returned instead by setting absolute=False."""
90
+ return _build_path("models.location", relative_path, absolute)
@@ -0,0 +1,120 @@
1
+ """String generators and helpers"""
2
+
3
+ import random
4
+ import string
5
+ from urllib import parse
6
+ from typing import Any
7
+
8
+
9
+ def random_string(length=4):
10
+ """Generate a random string based on the given length.
11
+
12
+ Keyword Arguments:
13
+ length {int} -- The amount of the characters to generate (default: {4})
14
+
15
+ Returns:
16
+ string
17
+ """
18
+ return "".join(
19
+ random.choice(string.ascii_uppercase + string.digits) for _ in range(length)
20
+ )
21
+
22
+
23
+ def modularize(file_path, suffix=".py"):
24
+ """Transforms a file path to a dotted path. On UNIX paths contains / and on Windows \\.
25
+
26
+ Keyword Arguments:
27
+ file_path {str} -- A file path such app/controllers
28
+
29
+ Returns:
30
+ value {str} -- a dotted path such as app.controllers
31
+ """
32
+ # if the file had the .py extension remove it as it's not needed for a module
33
+ return removesuffix(file_path.replace("/", ".").replace("\\", "."), suffix)
34
+
35
+
36
+ def as_filepath(dotted_path):
37
+ """Inverse of modularize, transforms a dotted path to a file path (with /).
38
+
39
+ Keyword Arguments:
40
+ dotted_path {str} -- A dotted path such app.controllers
41
+
42
+ Returns:
43
+ value {str} -- a file path such as app/controllers
44
+ """
45
+ return dotted_path.replace(".", "/")
46
+
47
+
48
+ def removeprefix(string, prefix):
49
+ """Implementation of str.removeprefix() function available for Python versions lower than 3.9."""
50
+ if string.startswith(prefix):
51
+ return string[len(prefix) :]
52
+ else:
53
+ return string
54
+
55
+
56
+ def removesuffix(string, suffix):
57
+ """Implementation of str.removesuffix() function available for Python versions lower than 3.9."""
58
+ if suffix and string.endswith(suffix):
59
+ return string[: -len(suffix)]
60
+ else:
61
+ return string
62
+
63
+
64
+ def match(string: str, ref_string: str) -> str:
65
+ """Check if a given string matches a reference string. Wildcard '*' can be used at start, end
66
+ or middle of the string."""
67
+ if ref_string.startswith("*"):
68
+ ref_string = ref_string.replace("*", "")
69
+ return string.endswith(ref_string)
70
+ elif ref_string.endswith("*"):
71
+ ref_string = ref_string.replace("*", "")
72
+ return string.startswith(ref_string)
73
+ elif "*" in ref_string:
74
+ split_search = ref_string.split("*")
75
+ return string.startswith(split_search[0]) and string.endswith(split_search[1])
76
+ else:
77
+ return ref_string == string
78
+
79
+
80
+ def add_query_params(url: str, query_params: dict) -> str:
81
+ """Add query params dict to a given url (which can already contain some query parameters)."""
82
+ path_result = parse.urlsplit(url)
83
+
84
+ base_url = (
85
+ f"{path_result.scheme}://{path_result.hostname}" if path_result.hostname else ""
86
+ )
87
+ base_path = path_result.path
88
+
89
+ # parse existing query parameters if any
90
+ existing_query_params = dict(parse.parse_qsl(path_result.query))
91
+ all_query_params = {**existing_query_params, **query_params}
92
+
93
+ # add query parameters to url if any
94
+ if all_query_params:
95
+ base_path += "?" + parse.urlencode(all_query_params)
96
+
97
+ result_url = f"{base_url}{base_path}"
98
+
99
+ # add fragment if exists
100
+ if path_result.fragment:
101
+ result_url = f"{result_url}#{path_result.fragment}"
102
+
103
+ return result_url
104
+
105
+
106
+ def get_controller_name(controller: "str|Any") -> str:
107
+ """Get a controller string name from a controller argument used in routes."""
108
+ # controller is a class or class.method
109
+ if hasattr(controller, "__qualname__"):
110
+ if "." in controller.__qualname__:
111
+ controller_str = controller.__qualname__.replace(".", "@")
112
+ else:
113
+ controller_str = f"{controller.__qualname__}@__call__"
114
+ # controller is an instance, so the method will automatically be __call__
115
+ elif not isinstance(controller, str):
116
+ controller_str = f"{controller.__class__.__qualname__}@__call__"
117
+ # controller is anything else: "Controller@method"
118
+ else:
119
+ controller_str = str(controller)
120
+ return controller_str
@@ -0,0 +1,97 @@
1
+ """Helpers for multiple data structures"""
2
+
3
+ import importlib
4
+ from importlib.abc import Loader
5
+ from dotty_dict import dotty
6
+
7
+ from ..exceptions.exceptions import LoaderNotFound
8
+
9
+ from .str import modularize
10
+
11
+
12
+ def load(path, object_name=None, default=None, raise_exception=False):
13
+ """Load the given object from a Python module located at path and returns a default
14
+ value if not found. If no object name is provided, loads the module.
15
+
16
+ Arguments:
17
+ path {str} -- A file path or a dotted path of a Python module
18
+ object {str} -- The object name to load in this module (None)
19
+ default {str} -- The default value to return if object not found in module (None)
20
+ Returns:
21
+ {object} -- The value (or default) read in the module or the module if no object name
22
+ """
23
+ # modularize path if needed
24
+ module_path = modularize(path)
25
+ # module = pydoc.locate(dotted_path)
26
+ try:
27
+ module = importlib.import_module(module_path)
28
+ except Exception as e:
29
+ error_message = (
30
+ f"'{module_path}' not found OR error when importing this module: {str(e)}"
31
+ )
32
+ print("Warning: " + error_message)
33
+
34
+ if raise_exception:
35
+ raise LoaderNotFound(error_message)
36
+ return None
37
+
38
+ if object_name is None:
39
+ return module
40
+ else:
41
+ try:
42
+ return getattr(module, object_name)
43
+ except KeyError:
44
+ if raise_exception:
45
+ raise LoaderNotFound(f"{object_name} not found in {module_path}")
46
+ else:
47
+ return default
48
+
49
+
50
+ def data(dictionary={}):
51
+ """Transform the given dictionary to be read/written with dot notation.
52
+
53
+ Arguments:
54
+ dictionary {dict} -- a dictionary structure
55
+
56
+ Returns:
57
+ {dict} -- A dot dictionary
58
+ """
59
+ return dotty(dictionary)
60
+
61
+
62
+ def data_get(dictionary, key, default=None):
63
+ """Read dictionary value from key using nested notation.
64
+
65
+ Arguments:
66
+ dictionary {dict} -- a dictionary structure
67
+ key {str} -- the dotted (or not) key to look for
68
+ default {object} -- the default value to return if the key does not exist (None)
69
+
70
+ Returns:
71
+ value {object}
72
+ """
73
+ # dotty dict uses : instead of * for wildcards
74
+ dotty_key = key.replace("*", ":")
75
+ return data(dictionary).get(dotty_key, default)
76
+
77
+
78
+ def data_set(dictionary, key, value, overwrite=True):
79
+ """Set dictionary value at key using nested notation. Values are overridden by default but
80
+ this behaviour can be changed by passing overwrite=False.
81
+ The dictionary is edited in place but is also returned.
82
+
83
+ Arguments:
84
+ dictionary {dict} -- a dictionary structure
85
+ key {str} -- the dotted (or not) key to set
86
+ value {object} -- the value to set at key
87
+ overwrite {bool} -- override the value if key exists in dictionary (True)
88
+
89
+ Returns:
90
+ dictionary {dict} -- the edited dictionary
91
+ """
92
+ if "*" in key:
93
+ raise ValueError("You cannot set values with wildcards *")
94
+ if not overwrite and data_get(dictionary, key):
95
+ return
96
+ data(dictionary)[key] = value
97
+ return dictionary
@@ -0,0 +1,58 @@
1
+ """Time related helpers"""
2
+ import pendulum
3
+
4
+
5
+ def cookie_expire_time(str_time):
6
+ """Take a string like 1 month or 5 minutes and returns a datetime formatted with cookie format.
7
+
8
+ Arguments:
9
+ str_time {string} -- Could be values like 1 second or 3 minutes
10
+
11
+ Returns:
12
+ str -- Cookie expiration time (Thu, 21 Oct 2021 07:28:00)
13
+ """
14
+ instance = parse_human_time(str_time)
15
+ return instance.format("ddd, DD MMM YYYY HH:mm:ss")
16
+
17
+
18
+ def parse_human_time(str_time):
19
+ """Take a string like 1 month or 5 minutes and returns a pendulum instance.
20
+
21
+ Arguments:
22
+ str_time {string} -- Could be values like 1 second or 3 minutes
23
+
24
+ Returns:
25
+ pendulum -- Returns Pendulum instance
26
+ """
27
+ if str_time == "now":
28
+ return pendulum.now("GMT")
29
+
30
+ if str_time != "expired":
31
+ number = int(str_time.split(" ")[0])
32
+ length = str_time.split(" ")[1]
33
+
34
+ if length in ("second", "seconds"):
35
+ return pendulum.now("GMT").add(seconds=number)
36
+ elif length in ("minute", "minutes"):
37
+ return pendulum.now("GMT").add(minutes=number)
38
+ elif length in ("hour", "hours"):
39
+ return pendulum.now("GMT").add(hours=number)
40
+ elif length in ("day", "days"):
41
+ return pendulum.now("GMT").add(days=number)
42
+ elif length in ("week", "weeks"):
43
+ return pendulum.now("GMT").add(weeks=number)
44
+ elif length in ("month", "months"):
45
+ return pendulum.now("GMT").add(months=number)
46
+ elif length in ("year", "years"):
47
+ return pendulum.now("GMT").add(years=number)
48
+
49
+ return None
50
+ else:
51
+ return pendulum.now("GMT").subtract(years=20)
52
+
53
+
54
+ def migration_timestamp():
55
+ """Return current time formatted for creating migration filenames.
56
+ Example: 2021_01_09_043202
57
+ """
58
+ return pendulum.now().format("YYYY_MM_DD_HHmmss")
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-startkit
3
+ Version: 0.1.0
4
+ Summary: Core Masonite components (Container, Config, Loader)
5
+ Author: Bedram Tamang
6
+ Author-email: tmgbedu@gmail.com
7
+ Requires-Python: >=3.12,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Requires-Dist: dotty-dict (>=1.3.1)
13
+ Requires-Dist: fastapi[standard] (>=0.124.4,<0.125.0)
@@ -0,0 +1,76 @@
1
+ fastapi_startkit/__init__.py,sha256=ObTUylrPyR4uTL4oz6CJfgs0Mk73TxEjPiVVDfn8dDQ,64
2
+ fastapi_startkit/application.py,sha256=WluaoQ7J18actW7N6y5Uf_7VR7oqj0WXXZG7KbvbRL8,1077
3
+ fastapi_startkit/configuration/Configuration.py,sha256=XpvbFVMQsiNTxWoT0mxDOC8IQONDvZpH-GcMwLdasH8,2772
4
+ fastapi_startkit/configuration/__init__.py,sha256=o9cbhMDID-lec6tqPHX8_2csC2eYZYP1Z5c3igzjeIQ,69
5
+ fastapi_startkit/configuration/helpers.py,sha256=yfoP_7xCFw_6dRLQu82GzdEZMRm8P79q76HJRDSc6PA,98
6
+ fastapi_startkit/configuration/providers/ConfigurationProvider.py,sha256=uW3tu7asNk1CzkfkrFHqb0rf1a-PwlZJs42yXujGQhI,372
7
+ fastapi_startkit/configuration/providers/__init__.py,sha256=CBjuHUetZl0Wd7Jgp1Qg5mFjRAiIq8e_socQQccXlUc,57
8
+ fastapi_startkit/container/__init__.py,sha256=zfF4wNDfI-_iFYISsQc_gUjoHKV6AaPlO4ljevEFAow,33
9
+ fastapi_startkit/container/container.py,sha256=t664orgo2zC4aCJhMOlVB41GUmoOJWOdGMtbQlfb9FM,16946
10
+ fastapi_startkit/environment/environment.py,sha256=HN4zxmTs9-gm-CwBa0sr418P7isPLnZTFH-j6iD1wgY,2634
11
+ fastapi_startkit/exceptions/DD.py,sha256=WgBlhCalfx4eY7RGPrx5NTsnF4s8Jr30cCVht5d_q2U,1080
12
+ fastapi_startkit/exceptions/ExceptionHandler.py,sha256=keGkPZY7BvCP1PMUpq8iC5WuzDtNC_FoHaFcYzIMzVw,2924
13
+ fastapi_startkit/exceptions/__init__.py,sha256=TBJJYH1tmIurA239fxCmBPCrHsyTP0ZaBmn63BpaoOs,1098
14
+ fastapi_startkit/exceptions/exceptionite/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ fastapi_startkit/exceptions/exceptionite/blocks.py,sha256=SDIIBAykKvKrjOl9beeT6z3eqJMoVI06n1yl4XMz8zI,2793
16
+ fastapi_startkit/exceptions/exceptionite/controllers.py,sha256=px_HUBpr9Ff09lEYq2wapbLncXzgS4P7i-z7w7UUQko,581
17
+ fastapi_startkit/exceptions/exceptionite/solutions.py,sha256=7PS192xcysA0w2UZBs6cvb6zn8ONNbvbR8Me-chqx8c,2352
18
+ fastapi_startkit/exceptions/exceptionite/tabs.py,sha256=iknhFIrK_51tveRURFS3wZ0_eT66R1mgGggsMPfteRI,445
19
+ fastapi_startkit/exceptions/exceptions.py,sha256=HNO_ud_eCqQoOMM0m4yQ1XWWwx5N1pkyeZT03091K2o,3569
20
+ fastapi_startkit/exceptions/handlers/DumpExceptionHandler.py,sha256=Dkzn9aX5vaSuBR0qvR5h8wQFVbUbLLquMkiud5Z7i0Y,3110
21
+ fastapi_startkit/exceptions/handlers/HttpExceptionHandler.py,sha256=b80QI4FgFN5QaTqx5Rz7PcC8H6FHy6k8J7FgqO-DarE,1084
22
+ fastapi_startkit/exceptions/handlers/ModelNotFoundHandler.py,sha256=MCJGkAa0_EJwU9WjJMVnbw-oLE_MAdMQ1A73LvObnz4,440
23
+ fastapi_startkit/facades/Auth.py,sha256=yLOZFMvG1HKbvd0OKkSVUNTXflWQC_o45IvcJDaVlIc,76
24
+ fastapi_startkit/facades/Auth.pyi,sha256=V3mv1CaW03hmLAa2hvxiB21dznVqlimImkXhxB1BshY,1142
25
+ fastapi_startkit/facades/Broadcast.py,sha256=LuAe-MJg7jTalJ3pSs1Q_FJae-TGZfQq7rrnEGmr0qQ,86
26
+ fastapi_startkit/facades/Cache.py,sha256=nCo_xCSHnBKz_pk8aiHGspjKypWrE6EGTQqXq62MWqI,78
27
+ fastapi_startkit/facades/Config.py,sha256=Q-2HlviEXRmzVSJ35S-lfr4b4_SOxitxtoL0Hn_1WHo,80
28
+ fastapi_startkit/facades/Config.pyi,sha256=1xIbzbhGrcLVjfDzSModminRoG_CjwPpdjMqIZU3FjQ,399
29
+ fastapi_startkit/facades/Dump.py,sha256=rnHsG1ceW4p4FT6jNPSIlXCSZiWrf_IBMXdwV-wOFqM,78
30
+ fastapi_startkit/facades/Dump.pyi,sha256=MdEIxM3UYlz5-DExdiN0cUu6kOwqvcz8Vkvgvmia92c,936
31
+ fastapi_startkit/facades/Facade.py,sha256=rSpVuH4ehojA9OjSv7q8rYPvQtVOFYN5fUDBSVXtDVQ,175
32
+ fastapi_startkit/facades/Gate.py,sha256=6cJZMHRH1z95As5-svvwTSFm9ZeEJfWKKnLkrA7muXM,76
33
+ fastapi_startkit/facades/Gate.pyi,sha256=ACKCDXW_x2eJ0IDHnlLL-QJAu8eQOJ2seph2Bv5IKgk,1382
34
+ fastapi_startkit/facades/Hash.py,sha256=kMOa5ydml2wMyfeCiRckDn9j0CqNxR6GtShvSVmpNjE,76
35
+ fastapi_startkit/facades/Hash.pyi,sha256=zfIzKSX-OXl-WFEXhAuDJTBitf-yIyS7AcQb8YBeXgU,1121
36
+ fastapi_startkit/facades/Loader.py,sha256=zGpoa0rfpQySGYG6kCkGlDIqYAeqKV9p0W_Up3qperQ,80
37
+ fastapi_startkit/facades/Loader.pyi,sha256=5va6whUqnFGRvtW13o-LCfLl5lJWvu7S3eWqqh2qoU4,1289
38
+ fastapi_startkit/facades/Mail.py,sha256=VePnQMH3tCC-C6x2MzrAmJ9yuKmAq6jf0HExoC9ALN4,76
39
+ fastapi_startkit/facades/Mail.pyi,sha256=HLjPw5cdnQnTxrEr2beEyfjNynUXAI7jia_V-9PX1nU,430
40
+ fastapi_startkit/facades/Notification.py,sha256=GgnFpoP_ZVqxQbLMYGqROlKBKSelGMpNh4HSQ1KlgqE,92
41
+ fastapi_startkit/facades/Notification.pyi,sha256=YCKT3oV6jdUDIQvvdJdCfZWQQLwzUSgbrnA6KjgeMjk,902
42
+ fastapi_startkit/facades/Queue.py,sha256=zCARcIhbRSPOASPjNl3Y-sT30ZomnpKO65nxhcK4hlc,78
43
+ fastapi_startkit/facades/Queue.pyi,sha256=5j6KhiO04h9yxobzo27CbZuvX3OU7Bq6vllnjMUirFc,364
44
+ fastapi_startkit/facades/RateLimiter.py,sha256=-85JDDz1aBQ3tzUvMNQvSTJ91sASGJM8bXmO9KpYsd0,83
45
+ fastapi_startkit/facades/RateLimiter.pyi,sha256=65KACncDSR-13NTxMtJwmFAJ7qMZ9T8eBUaSNm2UUcY,1770
46
+ fastapi_startkit/facades/Request.py,sha256=dW1LMWAhCwoqGhOiLBgXqAhZHzd868neqcBcabZsE30,82
47
+ fastapi_startkit/facades/Request.pyi,sha256=ecQ1K49JAtcHbc5kp0LhORzOfu1KXkpfxPiCu3TCEPM,3472
48
+ fastapi_startkit/facades/Response.py,sha256=WeFinS3pADucCOCrbUQpmGIs5lTH0Yiq_5UnWq4YAVU,84
49
+ fastapi_startkit/facades/Response.pyi,sha256=ROUaSHWzyVjdTa6-CZ8vJc_zKDxdl9t2O-U9LmirUm8,2630
50
+ fastapi_startkit/facades/Session.py,sha256=2A4bEOJLeuctUhol0xawQWffvr_71B0AbyA-j6430ls,82
51
+ fastapi_startkit/facades/Session.pyi,sha256=3oLikb3epJB775_dhIuqt7Vrqa7yIAYLTujKcqAzYnE,2056
52
+ fastapi_startkit/facades/Storage.py,sha256=iMdvlfA5fPT6lJhBYdlzuisHGM8E26nWpqm4RhB1GU8,82
53
+ fastapi_startkit/facades/Storage.pyi,sha256=3U_hPjHI9dvf1CG0NbgvnepolzUkhJwaqDEpv3ep8hQ,409
54
+ fastapi_startkit/facades/Url.py,sha256=j_y_DdLw08xPFt9Fn09V2H9SAKRDyR9s43QWxAR51sg,74
55
+ fastapi_startkit/facades/Url.pyi,sha256=QKgjH6wfqEEdsyLO2mMHUhOBhWcC9kmaOSr0PKnQ5jg,960
56
+ fastapi_startkit/facades/View.py,sha256=J9gB1VTByvfzSFFB9H7Ppvg7Tej_9uo1VHJHQa70dUs,76
57
+ fastapi_startkit/facades/View.pyi,sha256=Pp9ml6h9hoNnEFJjtxW0w2w_Nsc9TQ5DGK1XEO29Gis,2158
58
+ fastapi_startkit/facades/__init__.py,sha256=yj_dPGoCipuFRrxW-lgl_NhnzdN4ZdS9PN2yxW4htm0,517
59
+ fastapi_startkit/loader/Loader.py,sha256=5rYY3oURkNSiyfypfjGiJg-8HagflPQVHXoVayJWmhU,3131
60
+ fastapi_startkit/loader/__init__.py,sha256=GWjMnz8OzbJJL2x_DaoaeNzZ-ESYQNeyLnYLAl4psxE,27
61
+ fastapi_startkit/providers/ConfigurationProvider.py,sha256=MSt-AV28skbNY_iESL8r8zQbpufdlyriHrL95wUFbDo,422
62
+ fastapi_startkit/providers/Provider.py,sha256=o1pVTzEl516Kx0qe30K67phpUR6ygDdGHRKklyI3-iI,279
63
+ fastapi_startkit/providers/__init__.py,sha256=j78PJlEuaeaEO4xCsfNDPrWnHqOnwc2plbHMzKJG7_I,137
64
+ fastapi_startkit/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
+ fastapi_startkit/utils/collections.py,sha256=QBXCcNpoowY9JSrccDZ6YArxAhZHUxBxA7WxguVA-rM,14697
66
+ fastapi_startkit/utils/console.py,sha256=KeFNVeFWmi_OaRVOC1PTbuBM5kWmv_twmDXWrK0lYtU,1054
67
+ fastapi_startkit/utils/data/mime.types,sha256=YxY5l5GdijuD3RsffyPpEkzV-GYJmMN02kLGQbiGX1k,61134
68
+ fastapi_startkit/utils/filesystem.py,sha256=UnEFo8r4gR09OcriVaGh5cDScC5jpxAlg0rnrZ-3BNE,3165
69
+ fastapi_startkit/utils/http.py,sha256=AKfDx-8aY_YQh-ERHN97bpG-qUdwzN7gAsQphfGjEU0,3323
70
+ fastapi_startkit/utils/location.py,sha256=xh6KWfPCYrhkZEr0s6zrn-3NgyOO0Tf1HOUdiZDMTVU,3817
71
+ fastapi_startkit/utils/str.py,sha256=8_WsHSaOKkWA4GuQUaiOzROYihGkIdXZs_txYi79ro0,3958
72
+ fastapi_startkit/utils/structures.py,sha256=VwRIcsNR1PhdUBtSzmwqxgXvBedKHOHV6T901_ZKOfY,3142
73
+ fastapi_startkit/utils/time.py,sha256=ItbzAowY5MyTBQX6bMvgDh1WciR61ldTYyUPmKq1SSs,1882
74
+ fastapi_startkit-0.1.0.dist-info/METADATA,sha256=kKNsod_LvvMmvQR_e3rsxa0Cu1xZhiLBNSWejoYPiU0,496
75
+ fastapi_startkit-0.1.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
76
+ fastapi_startkit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.2.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any