fdc-shared-kernel 0.0.3__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 (33) hide show
  1. fdc_shared_kernel-0.0.3.dist-info/METADATA +159 -0
  2. fdc_shared_kernel-0.0.3.dist-info/RECORD +33 -0
  3. fdc_shared_kernel-0.0.3.dist-info/WHEEL +5 -0
  4. fdc_shared_kernel-0.0.3.dist-info/top_level.txt +1 -0
  5. shared_kernel/__init__.py +0 -0
  6. shared_kernel/config/__init__.py +49 -0
  7. shared_kernel/database/__init__.py +43 -0
  8. shared_kernel/exceptions/__init__.py +7 -0
  9. shared_kernel/exceptions/configuration_exceptions.py +20 -0
  10. shared_kernel/exceptions/custom_exceptions.py +14 -0
  11. shared_kernel/exceptions/data_validation_exceptions.py +26 -0
  12. shared_kernel/exceptions/http_exceptions.py +60 -0
  13. shared_kernel/exceptions/infrastructure_exceptions.py +26 -0
  14. shared_kernel/exceptions/operational_exceptions.py +26 -0
  15. shared_kernel/exceptions/security_exceptions.py +26 -0
  16. shared_kernel/interfaces/__init__.py +0 -0
  17. shared_kernel/interfaces/databus.py +73 -0
  18. shared_kernel/logger/__init__.py +75 -0
  19. shared_kernel/messaging/__init__.py +1 -0
  20. shared_kernel/messaging/nats_databus.py +171 -0
  21. shared_kernel/models/__init__.py +0 -0
  22. shared_kernel/tests/__init__.py +0 -0
  23. shared_kernel/tests/config/test_config.py +35 -0
  24. shared_kernel/tests/logger/test_logger.py +48 -0
  25. shared_kernel/tests/messaging/test_nats_interface.py +36 -0
  26. shared_kernel/tests/utils/test_data_validators.py +18 -0
  27. shared_kernel/tests/utils/test_date_format_utils.py +20 -0
  28. shared_kernel/tests/utils/test_string_utils.py +28 -0
  29. shared_kernel/tests/utils/utils.py +618 -0
  30. shared_kernel/utils/__init__.py +3 -0
  31. shared_kernel/utils/data_validators_utils.py +15 -0
  32. shared_kernel/utils/date_format_utils.py +13 -0
  33. shared_kernel/utils/string_utils.py +28 -0
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.1
2
+ Name: fdc_shared_kernel
3
+ Version: 0.0.3
4
+ Summary: Shared library for microservice
5
+ Author-email: Shikhil S <shikhil.s@dbizsolution.com>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.7
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: alembic ==1.13.2
12
+ Requires-Dist: asynctest ==0.13.0
13
+ Requires-Dist: build ==1.2.1
14
+ Requires-Dist: colorama ==0.4.6
15
+ Requires-Dist: greenlet ==3.0.3
16
+ Requires-Dist: iniconfig ==2.0.0
17
+ Requires-Dist: Mako ==1.3.5
18
+ Requires-Dist: MarkupSafe ==2.1.5
19
+ Requires-Dist: nats-py ==2.8.0
20
+ Requires-Dist: packaging ==24.1
21
+ Requires-Dist: pluggy ==1.5.0
22
+ Requires-Dist: psycopg2-binary ==2.9.9
23
+ Requires-Dist: pytest ==8.2.2
24
+ Requires-Dist: pytest-asyncio ==0.23.8
25
+ Requires-Dist: python-dotenv ==1.0.1
26
+ Requires-Dist: setuptools ==71.0.0
27
+ Requires-Dist: SQLAlchemy ==2.0.31
28
+ Requires-Dist: typing-extensions ==4.12.2
29
+
30
+ # Shared Kernel
31
+
32
+ Shared Kernel is a lightweight, modular Python library designed to facilitate rapid development of microservices. It provides essential utilities for data manipulation, logging, configuration management, and database connectivity, making it an ideal foundation for building scalable and maintainable microservices.
33
+
34
+ ## Table of Contents
35
+
36
+ - [Getting Started](#getting-started)
37
+ - [Prerequisites](#prerequisites)
38
+ - [Installation](#installation)
39
+ - [Usage](#usage)
40
+ - [Importing Modules](#importing-modules)
41
+ - [Initializing Database Connection](#initializing-database-connection)
42
+
43
+
44
+ ## Getting Started
45
+
46
+ ### Prerequisites
47
+
48
+ - Python 3.6+
49
+ - Pip
50
+
51
+ ### Installation
52
+
53
+ To install Shared Kernel, clone the repository and install it using pip:
54
+
55
+ ```sh
56
+ git clone https://bitbucket.org/Weavers/shared-kernel.git
57
+ cd shared-kernel pip install .
58
+ ```
59
+
60
+ ##### Step 1: Set Up Your Environment
61
+ First, ensure you have Python installed on your system. Then, set up a virtual environment for your project to manage dependencies cleanly. Open your terminal and navigate to your project directory:
62
+
63
+ ```sh
64
+ cd path/to/shared-kernel
65
+ python -m venv venv
66
+ source venv/bin/activate # On Windows, use `venv\Scripts\activate`
67
+ ```
68
+
69
+ ##### Step 2: Install Python build frontend.
70
+ Ensure you python's build frontend. installed in your environment. This is necessary for building the wheel package from **.toml** file. You can install them using pip:
71
+ ```sh
72
+ pip install --upgrade build
73
+ ```
74
+
75
+ ##### Step 3: Build the Wheel Package
76
+ ```sh
77
+ python -m build
78
+ ```
79
+ This command will build a wheel distribution and also a source distribution. After running this command, you'll find the .whl and a tar file inside the dist/ directory within your project folder.
80
+
81
+
82
+ ##### Step 4: Distribute the Wheel
83
+ Now that you have a .whl file, you can distribute it to others. Users can install your library using pip by pointing to the .whl file:
84
+
85
+ ```sh
86
+ pip install dist/shared_kernel-0.1.0-py3-none-any.whl
87
+ ```
88
+
89
+
90
+ ## Usage
91
+
92
+ ### Importing Modules
93
+
94
+ Import the required modules from Shared Kernel into your project:
95
+
96
+ ```
97
+ from shared_kernel.logger import Logger
98
+ from shared_kernel.config import Config
99
+ from dotenv import find_dotenv
100
+
101
+
102
+ def main():
103
+ logger = Logger(name="my_app")
104
+ logger.configure_logger()
105
+
106
+ # Specify the path to the .env file if it's not in the current directory
107
+ config_manager = Config(env_path=find_dotenv())
108
+
109
+ # Access environment variables
110
+ api_key = config_manager.get("KEY", "default_api_key")
111
+
112
+ # Example usage
113
+ logger.logger.info("This is an info message.")
114
+ logger.logger.error("This is an error message.")
115
+ logger.logger.info(api_key)
116
+
117
+
118
+ if __name__ == "__main__":
119
+ main()
120
+
121
+ ```
122
+
123
+
124
+ ### Initializing Database Connection
125
+
126
+ Use the `DB` class to initialize a database connection:
127
+
128
+ ```
129
+ from shared_kernel.DB import DB
130
+
131
+ db_instance = DB("postgresql://user:password@localhost/dbname")
132
+ engine, SessionLocal = db_instance.init_db_connection()
133
+
134
+ ```
135
+
136
+
137
+
138
+
139
+ ### Initializing NATS Connection
140
+
141
+ Use the `NATSClient` class to initialize a messaging connection:
142
+
143
+ ```
144
+ from shared_kernel.messaging import NATSClient
145
+ import asyncio
146
+
147
+ def run():
148
+ nats_instance = NATSClient("nats://localhost:4222")
149
+ await nc_interface.connect()
150
+
151
+ async def message_callback(data):
152
+ print(f"Received a message: {data}")
153
+
154
+ await nc_interface.subscribe("example_subject", message_callback)
155
+ await nc_interface.publish("example_subject", "Hello NATS!")
156
+
157
+ if __name__ == '__main__':
158
+ asyncio.run(run())
159
+ ```
@@ -0,0 +1,33 @@
1
+ shared_kernel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ shared_kernel/config/__init__.py,sha256=9PiY-e-lNE4eqO_wWcmyIhobHSTH8-aDNV3hNJSSLRw,1583
3
+ shared_kernel/database/__init__.py,sha256=5jZGb1Vbzd7ZyblagiQNhvYiX86Fbx3azy1mDpG-BZs,1567
4
+ shared_kernel/exceptions/__init__.py,sha256=yc2u7LPVGIr6G7SDee3rO2KBGnG6vVeCdpbSMAjVmKc,428
5
+ shared_kernel/exceptions/configuration_exceptions.py,sha256=LFzOt3XKVpbeDP23-tQ4xYfThAOAmQAyDNLfWl-fnkQ,995
6
+ shared_kernel/exceptions/custom_exceptions.py,sha256=MaZime3JejcpyJH-aQVmoGqvS4malLLgw2QX8ZUy7Xc,631
7
+ shared_kernel/exceptions/data_validation_exceptions.py,sha256=IV_l_U2Dz6gJ2mNKY9Lw-sCSSm07eBmP0wSOG4ATEbU,1194
8
+ shared_kernel/exceptions/http_exceptions.py,sha256=y_sz0VUD5kFLVExD9_TheaJl-89Pa0aUOF9WB0SL55k,2214
9
+ shared_kernel/exceptions/infrastructure_exceptions.py,sha256=xti8u1KNgUezD0rKrvZh98TKdDkiaopVE9Dgi3GxXoI,1201
10
+ shared_kernel/exceptions/operational_exceptions.py,sha256=ukz9EkLJ8PXAgLxfIL6zDITv9rbyT5fgV-ktNp8RooE,1007
11
+ shared_kernel/exceptions/security_exceptions.py,sha256=lmgMLe9prLKSn1LCGJAJ5aoFsv4B1ttdO8jmZf1SCEA,1122
12
+ shared_kernel/interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ shared_kernel/interfaces/databus.py,sha256=SO1AlB84rVGzdmI5NC1kMu2pTqmbYbq5e_U7suBV2DI,1918
14
+ shared_kernel/logger/__init__.py,sha256=yV0u1V8gIHrN7NQUAJyWm9p9w3m4dCLMsC94P-zQANQ,2435
15
+ shared_kernel/messaging/__init__.py,sha256=fb5iy10OehXWGAR6clGfHirgrE5j8ZRlKjLXhVcPF4E,58
16
+ shared_kernel/messaging/nats_databus.py,sha256=YTchy41S72XTk7Ttdcz9Sv8gmAjca2KUQLLKDkK_35g,5608
17
+ shared_kernel/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ shared_kernel/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ shared_kernel/tests/config/test_config.py,sha256=nuC48_D1SYBtA04q69WM4Q5McLHxNOWMWEjiMMqvTDo,1413
20
+ shared_kernel/tests/logger/test_logger.py,sha256=2bi-k9N8AEhc_ShfRfej-hxU9-ipRG0VfvIe6Hw03vs,1713
21
+ shared_kernel/tests/messaging/test_nats_interface.py,sha256=ESVyZv8eb-mDfXKzvCMyVBoiR4VzXmnWA0zfjbV5Tfw,1071
22
+ shared_kernel/tests/utils/test_data_validators.py,sha256=VqA_qLBCa2AtQPQg6pkJko-MULwCyGIwgnRf8HvS7q8,628
23
+ shared_kernel/tests/utils/test_date_format_utils.py,sha256=Efok35oJlIpT7EF0IYb1MEwuZ1SY8eiqu8iKm8JzgE8,568
24
+ shared_kernel/tests/utils/test_string_utils.py,sha256=F4bfWTqjfZl6SGe_axNIMr74yXx0G-q95HnnSkY5THg,992
25
+ shared_kernel/tests/utils/utils.py,sha256=7r6jXZACNyfyBUyalyFB2JSJ_0ziYJeS_jteV_xGmpw,17101
26
+ shared_kernel/utils/__init__.py,sha256=6plsQ8FreOHaDhvKlvRAwu52A3ROXLyu1be0oAq24V4,155
27
+ shared_kernel/utils/data_validators_utils.py,sha256=kbSpgY2sNCg-nUsnTLQYy-9UyOVet2bA-lI1IqQB1Wg,394
28
+ shared_kernel/utils/date_format_utils.py,sha256=OqPtncuPulLGWVv3X0IIOin-ECoWdtll3esWEVpaNMI,426
29
+ shared_kernel/utils/string_utils.py,sha256=m-ZpJmN8yTc1nEc0kFDXu_m5uWl-MZf54rWw1D39IL8,850
30
+ fdc_shared_kernel-0.0.3.dist-info/METADATA,sha256=FiIoEbkfpJ854nMr_hIUiCJ39fwUOsQBHK6tDawpsvo,4479
31
+ fdc_shared_kernel-0.0.3.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
32
+ fdc_shared_kernel-0.0.3.dist-info/top_level.txt,sha256=lKSOEkyFzaMd58KLHqJF-8tmK54xxWRcgmbPE9CuaKM,14
33
+ fdc_shared_kernel-0.0.3.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (71.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ shared_kernel
File without changes
@@ -0,0 +1,49 @@
1
+ import os
2
+ from typing import Any
3
+ from dotenv import load_dotenv, find_dotenv
4
+ from shared_kernel.exceptions import MissingConfiguration, InvalidConfiguration
5
+ import logging
6
+
7
+ logging.basicConfig(level=logging.INFO)
8
+
9
+
10
+ class Config:
11
+
12
+ _instance = None
13
+
14
+ def __new__(cls, env_path=None):
15
+ if cls._instance is None:
16
+ cls._instance = super(Config, cls).__new__(cls)
17
+ cls._instance._initialize(env_path)
18
+ return cls._instance
19
+
20
+ def _initialize(self, env_path=None):
21
+ """
22
+ Initializes the Config class with an optional path to the .env file.
23
+
24
+ :param env_path: Optional path to the .env file. Defaults to finding the .env file in the current directory.
25
+ """
26
+ if env_path is None:
27
+ dotenv_path = find_dotenv()
28
+ if not dotenv_path:
29
+ logging.error(".env file not found")
30
+ raise InvalidConfiguration(".env file not found")
31
+ else:
32
+ dotenv_path = env_path
33
+
34
+ logging.info(f"Loading environment variables from {dotenv_path}")
35
+ load_dotenv(dotenv_path)
36
+
37
+ @staticmethod
38
+ def get(key: str, default: Any = None) -> Any:
39
+ """
40
+ Retrieves the value of an environment variable.
41
+
42
+ :param key: The name of the environment variable.
43
+ :param default: Default value to return if the environment variable is not set.
44
+ :return: The value of the environment variable.
45
+ """
46
+ value = os.getenv(key, default)
47
+ if value is None:
48
+ raise MissingConfiguration(key)
49
+ return value
@@ -0,0 +1,43 @@
1
+ from sqlalchemy import create_engine
2
+ from sqlalchemy.ext.declarative import declarative_base
3
+ from sqlalchemy.orm import sessionmaker
4
+ from shared_kernel.logger import Logger
5
+ from typing import Tuple
6
+
7
+
8
+ class DB:
9
+ def __init__(self, db_url: str):
10
+ """
11
+ Initializes the DB class with the provided database configuration.
12
+
13
+ :param db_url: The database URL (e.g., postgresql://user:password@localhost/dbname).
14
+ """
15
+ self.engine = create_engine(db_url, pool_size=20, max_overflow=100)
16
+ self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
17
+ self.Base = declarative_base()
18
+ self.logger = Logger(name="database")
19
+ self.logger.configure_logger()
20
+
21
+ def init_db_connection(self) -> Tuple[create_engine, sessionmaker]:
22
+ """
23
+ Initializes the database connection and returns the engine and session maker.
24
+
25
+ :return: A tuple containing the SQLAlchemy engine and session maker.
26
+ """
27
+ self.logger.info("Database connection initialized successfully.")
28
+ return self.engine, self.SessionLocal
29
+
30
+ def create_tables(self):
31
+ """
32
+ Creates all tables stored in declarative Base.
33
+ """
34
+ self.logger.info("Creating tables...")
35
+ self.Base.metadata.create_all(bind=self.engine)
36
+ self.logger.info("Tables created successfully.")
37
+
38
+ def close_session(self):
39
+ """
40
+ Closes the database session.
41
+ """
42
+ self.SessionLocal.remove()
43
+ self.logger.info("Session closed successfully.")
@@ -0,0 +1,7 @@
1
+ from shared_kernel.exceptions.configuration_exceptions import *
2
+ from shared_kernel.exceptions.custom_exceptions import *
3
+ from shared_kernel.exceptions.data_validation_exceptions import *
4
+ from shared_kernel.exceptions.http_exceptions import *
5
+ from shared_kernel.exceptions.infrastructure_exceptions import *
6
+ from shared_kernel.exceptions.operational_exceptions import *
7
+ from shared_kernel.exceptions.security_exceptions import *
@@ -0,0 +1,20 @@
1
+ class ConfigurationException(Exception):
2
+ """Base class for all configuration exceptions."""
3
+ def __init__(self, message: str = "", config_key: str = "") -> None:
4
+ super().__init__(message)
5
+ self.config_key = config_key
6
+
7
+ def __str__(self) -> str:
8
+ return f"{type(self).__name__}: {self.args[0]} (Config Key: {self.config_key})"
9
+
10
+
11
+ class MissingConfiguration(ConfigurationException):
12
+ """Raised when a required configuration setting is missing."""
13
+ def __init__(self, message: str = "Required configuration setting is missing", config_key: str = "") -> None:
14
+ super().__init__(message=message, config_key=config_key)
15
+
16
+
17
+ class InvalidConfiguration(ConfigurationException):
18
+ """Raised when a configuration setting is invalid."""
19
+ def __init__(self, message: str = "Invalid configuration setting", config_key: str = "", valid_value: str = "") -> None:
20
+ super().__init__(message=f"{message} (Valid Value: {valid_value})", config_key=config_key)
@@ -0,0 +1,14 @@
1
+ class BaseCustomException(Exception):
2
+ """Base class for all custom logic exceptions."""
3
+ def __init__(self, message: str = "", details: str = "") -> None:
4
+ super().__init__(message)
5
+ self.details = details
6
+
7
+ def __str__(self) -> str:
8
+ return f"{type(self).__name__}: {self.args[0]} (Details: {self.details})"
9
+
10
+
11
+ class UnsupportedProfiling(BaseCustomException):
12
+ """Raised when an unsupported data profiling method is requested."""
13
+ def __init__(self, message: str = "The profiling method is not supported.", details: str = "") -> None:
14
+ super().__init__(message=message, details=details)
@@ -0,0 +1,26 @@
1
+ class DataValidationException(Exception):
2
+ """Base class for all data validation exceptions."""
3
+ def __init__(self, message: str = "", field: str = "") -> None:
4
+ super().__init__(message)
5
+ self.field = field
6
+
7
+ def __str__(self) -> str:
8
+ return f"{type(self).__name__}: {self.args[0]} (Field: {self.field})"
9
+
10
+
11
+ class MissingField(DataValidationException):
12
+ """Raised when a required field is missing."""
13
+ def __init__(self, message: str = "Required field is missing", field: str = "") -> None:
14
+ super().__init__(message=message, field=field)
15
+
16
+
17
+ class IncorrectDataType(DataValidationException):
18
+ """Raised when the data type of a field is incorrect."""
19
+ def __init__(self, message: str = "Incorrect data type", field: str = "", expected_type: str = "") -> None:
20
+ super().__init__(message=f"{message} (Expected: {expected_type})", field=field)
21
+
22
+
23
+ class InvalidValue(DataValidationException):
24
+ """Raised when the value of a field is invalid."""
25
+ def __init__(self, message: str = "Invalid value", field: str = "", valid_values: str = "") -> None:
26
+ super().__init__(message=f"{message} (Valid Values: {valid_values})", field=field)
@@ -0,0 +1,60 @@
1
+ import http
2
+
3
+
4
+ class HTTPException(Exception):
5
+ """Base class for all HTTP exceptions."""
6
+
7
+ def __init__(self, message: str = "", status_code: int = None) -> None:
8
+ super().__init__(message)
9
+ self.status_code = status_code
10
+
11
+ def __str__(self) -> str:
12
+ return f"{self.status_code}: {super().__str__()}"
13
+
14
+
15
+ class BadRequest(HTTPException):
16
+ """Raised for HTTP 400 Bad Request errors."""
17
+ status_code = http.HTTPStatus.BAD_REQUEST.value
18
+
19
+ def __init__(self, message: str = "Bad Request", status_code: int = None) -> None:
20
+ super().__init__(message=message, status_code=status_code or self.status_code)
21
+
22
+
23
+ class Unauthorized(HTTPException):
24
+ """Raised for HTTP 401 Unauthorized errors."""
25
+ status_code = http.HTTPStatus.UNAUTHORIZED.value
26
+
27
+ def __init__(self, message: str = "Unauthorized", status_code: int = None) -> None:
28
+ super().__init__(message=message, status_code=status_code or self.status_code)
29
+
30
+
31
+ class Forbidden(HTTPException):
32
+ """Raised for HTTP 403 Forbidden errors."""
33
+ status_code = http.HTTPStatus.FORBIDDEN.value
34
+
35
+ def __init__(self, message: str = "Forbidden", status_code: int = None) -> None:
36
+ super().__init__(message=message, status_code=status_code or self.status_code)
37
+
38
+
39
+ class NotFound(HTTPException):
40
+ """Raised for HTTP 404 Not Found errors."""
41
+ status_code = http.HTTPStatus.NOT_FOUND.value
42
+
43
+ def __init__(self, message: str = "Not Found", status_code: int = None) -> None:
44
+ super().__init__(message=message, status_code=status_code or self.status_code)
45
+
46
+
47
+ class Conflict(HTTPException):
48
+ """Raised for HTTP 409 Conflict errors."""
49
+ status_code = http.HTTPStatus.CONFLICT.value
50
+
51
+ def __init__(self, message: str = "Conflict", status_code: int = None) -> None:
52
+ super().__init__(message=message, status_code=status_code or self.status_code)
53
+
54
+
55
+ class InternalServerError(HTTPException):
56
+ """Raised for HTTP 500 Internal Server Error."""
57
+ status_code = http.HTTPStatus.INTERNAL_SERVER_ERROR.value
58
+
59
+ def __init__(self, message: str = "Internal Server Error", status_code: int = None) -> None:
60
+ super().__init__(message=message, status_code=status_code or self.status_code)
@@ -0,0 +1,26 @@
1
+ class InfrastructureException(Exception):
2
+ """Base class for all infrastructure exceptions."""
3
+ def __init__(self, message: str = "", resource: str = "") -> None:
4
+ super().__init__(message)
5
+ self.resource = resource
6
+
7
+ def __str__(self) -> str:
8
+ return f"{type(self).__name__}: {self.args[0]} (Resource: {self.resource})"
9
+
10
+
11
+ class DatabaseConnectionError(InfrastructureException):
12
+ """Raised when there is an issue connecting to the database."""
13
+ def __init__(self, message: str = "Failed to connect to the database", resource: str = "database") -> None:
14
+ super().__init__(message=message, resource=resource)
15
+
16
+
17
+ class FileNotFoundError(InfrastructureException):
18
+ """Raised when a required file or directory cannot be found."""
19
+ def __init__(self, message: str = "File or directory not found", resource: str = "") -> None:
20
+ super().__init__(message=message, resource=resource)
21
+
22
+
23
+ class NetworkError(InfrastructureException):
24
+ """Raised when there is a network connectivity issue."""
25
+ def __init__(self, message: str = "Network connectivity issue", resource: str = "network") -> None:
26
+ super().__init__(message=message, resource=resource)
@@ -0,0 +1,26 @@
1
+ class OperationalException(Exception):
2
+ """Base class for all operational exceptions."""
3
+ def __init__(self, message: str = "", cause=None) -> None:
4
+ super().__init__(message)
5
+ self.cause = cause
6
+
7
+ def __str__(self) -> str:
8
+ return f"{type(self).__name__}: {self.args[0]}"
9
+
10
+
11
+ class ServiceUnavailable(OperationalException):
12
+ """Raised when the service is temporarily unavailable."""
13
+ def __init__(self, message: str = "Service Unavailable", cause=None) -> None:
14
+ super().__init__(message=message, cause=cause)
15
+
16
+
17
+ class Timeout(OperationalException):
18
+ """Raised when an operation times out."""
19
+ def __init__(self, message: str = "Operation Timed Out", cause=None) -> None:
20
+ super().__init__(message=message, cause=cause)
21
+
22
+
23
+ class RateLimitExceeded(OperationalException):
24
+ """Raised when a rate limit is exceeded."""
25
+ def __init__(self, message: str = "Rate Limit Exceeded", cause=None) -> None:
26
+ super().__init__(message=message, cause=cause)
@@ -0,0 +1,26 @@
1
+ class SecurityException(Exception):
2
+ """Base class for all security exceptions."""
3
+ def __init__(self, message: str = "", detail: str = "") -> None:
4
+ super().__init__(message)
5
+ self.detail = detail
6
+
7
+ def __str__(self) -> str:
8
+ return f"{type(self).__name__}: {self.args[0]} (Detail: {self.detail})"
9
+
10
+
11
+ class PermissionDenied(SecurityException):
12
+ """Raised when a user tries to access a resource without sufficient permissions."""
13
+ def __init__(self, message: str = "Access Denied", detail: str = "") -> None:
14
+ super().__init__(message=message, detail=detail)
15
+
16
+
17
+ class EncryptionError(SecurityException):
18
+ """Raised when there is an issue with encryption or decryption."""
19
+ def __init__(self, message: str = "Encryption Error", detail: str = "") -> None:
20
+ super().__init__(message=message, detail=detail)
21
+
22
+
23
+ class CertificateVerificationError(SecurityException):
24
+ """Raised when SSL certificate verification fails."""
25
+ def __init__(self, message: str = "SSL Verification Failed", detail: str = "") -> None:
26
+ super().__init__(message=message, detail=detail)
File without changes
@@ -0,0 +1,73 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Callable
3
+
4
+
5
+ class DataBus(ABC):
6
+ """
7
+ A Databus Interface class to handle both async messaging and synchronous messaging.
8
+ """
9
+
10
+ @abstractmethod
11
+ def __init__(self) -> None:
12
+ pass
13
+
14
+ @abstractmethod
15
+ def make_connection(self):
16
+ """
17
+ Connect to the Databus server.
18
+ """
19
+ pass
20
+
21
+ @abstractmethod
22
+ def close_connection(self):
23
+ """
24
+ Close connection from the Databus server.
25
+ """
26
+ pass
27
+
28
+ @abstractmethod
29
+ def publish_event(self, topic: str, event_payload: dict):
30
+ """
31
+ Publish an async message to a Databus topic.
32
+
33
+ Args:
34
+ topic (str): The topic to publish the message to.
35
+ event_payload (dict): The message to be published.
36
+ """
37
+ pass
38
+
39
+ @abstractmethod
40
+ def request_event(self, topic, event_payload):
41
+ """
42
+ Send a synchronous request/message to a Databus topic and recieve response.
43
+
44
+ Args:
45
+ topic (str): The topic to publish the message to.
46
+ event_payload (dict): The message to be published.
47
+
48
+ Returns:
49
+ response (Any): response message
50
+ """
51
+ pass
52
+
53
+ @abstractmethod
54
+ def subscribe_sync_event(self, topic, callback: Callable[[Any], None]):
55
+ """
56
+ Subscribe to a databus topic and process messages synchronously.
57
+
58
+ Args:
59
+ topic: The topic to subscribe to.
60
+ callback: A callback function to handle received messages.
61
+ """
62
+ pass
63
+
64
+ @abstractmethod
65
+ def subscribe_async_event(self, topic, callback: Callable[[Any], None]):
66
+ """
67
+ Subscribe to a databus topic and process messages asynchronously.
68
+
69
+ Args:
70
+ topic: The topic to subscribe to.
71
+ callback: A callback function to handle received messages.
72
+ """
73
+ pass
@@ -0,0 +1,75 @@
1
+ import logging
2
+ import os
3
+
4
+
5
+ class Logger:
6
+ """
7
+ A singleton logger class that ensures only one logger instance is created.
8
+ This logger supports both console and file logging.
9
+
10
+ Attributes:
11
+ _instance (Optional[Logger]): The single instance of the logger.
12
+ """
13
+
14
+ _instance = None
15
+
16
+ def __new__(cls, name=None):
17
+ """
18
+ override __new__ to ensure singleton pattern.
19
+ """
20
+ if cls._instance is None:
21
+ cls._instance = super(Logger, cls).__new__(cls)
22
+ cls._instance._initialize(name=name)
23
+ return cls._instance
24
+
25
+ def _initialize(self, name=None, log_file: str = "fdc_app_logs.log"):
26
+
27
+ self.logger = logging.getLogger(name if name else __name__)
28
+ self.logger.setLevel(logging.DEBUG)
29
+ self.formatter = logging.Formatter(
30
+ "%(asctime)s - %(name)s - %(filename)s - %(module)s - %(levelname)s - %(message)s"
31
+ )
32
+ self.log_file = log_file
33
+
34
+ # ensure handlers are configured only once
35
+ if not self.logger.handlers:
36
+ self.configure_logger()
37
+
38
+ def configure_logger(self):
39
+ """
40
+ Configures logger with stream and file handlers.
41
+ """
42
+ self.add_stream_handler()
43
+ self.add_file_handler(log_file=self.log_file)
44
+
45
+ def add_stream_handler(self):
46
+ """
47
+ Adds a stream handler to the logger.
48
+ """
49
+ stream_handler = logging.StreamHandler()
50
+ stream_handler.setLevel(self.logger.level)
51
+ stream_handler.setFormatter(self.formatter)
52
+ self.logger.addHandler(stream_handler)
53
+
54
+ def add_file_handler(self, log_file, log_directory="./logs"):
55
+ """
56
+ Adds a file handler to the logger.
57
+ """
58
+ if not os.path.exists(log_directory):
59
+ os.makedirs(log_directory)
60
+ file_handler = logging.FileHandler(os.path.join(log_directory, log_file))
61
+ file_handler.setLevel(self.logger.level)
62
+ file_handler.setFormatter(self.formatter)
63
+ self.logger.addHandler(file_handler)
64
+
65
+ def info(self, message, *args, **kwargs):
66
+ self.logger.info(message, *args, **kwargs)
67
+
68
+ def error(self, message, *args, **kwargs):
69
+ self.logger.error(message, *args, **kwargs)
70
+
71
+ def debug(self, message, *args, **kwargs):
72
+ self.logger.debug(message, *args, **kwargs)
73
+
74
+ def warning(self, message, *args, **kwargs):
75
+ self.logger.warning(message, *args, **kwargs)
@@ -0,0 +1 @@
1
+ from shared_kernel.messaging.nats_databus import * # noqa