commit 38d8880b7b3ce3e9142303bb46ec9d6824935dfe Author: agatha Date: Fri Jun 14 13:54:40 2024 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b6fd94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +venv/ +__pycache__/ +*.py[cod] diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..4bf4483 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.md \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..524710f --- /dev/null +++ b/README.md @@ -0,0 +1,148 @@ +# Configurator + +`Configurator` is a Python package designed for efficient and standardized configuration management in your +applications. It uses a singleton pattern to ensure that configuration is loaded once and accessible throughout your +application. It supports merging custom configurations, validation of required configurations, and ease of access +through getter methods. + +## Features + +- Singleton pattern for configuration management +- Support for loading and merging custom configurations +- Simple validation for required configuration variables +- Easily extendable for additional configuration sources + +## Installation + +You can install the package via pip: + +```bash +pip install configurator +``` + +Or by cloning this repository and installing with: + +```bash +git clone https://github.com/yourusername/configurator.git +cd configurator +pip install . +``` + +## Usage + +### Initial Setup + +To use `Configurator` in your application, you first need to load the configuration. This should typically be done once +at the start of your application, for instance, in the main entry point or the initial setup script. + +```python +from configurator import load_config, get_config, ConfigurationError + +# Define custom configuration for the application +custom_config = { + 'DATABASE_URL': 'sqlite:///default.db', + 'SECRET_KEY': 'defaultsecretkey', +} + +def main(): + try: + # Load configuration with custom settings + load_config(custom_config=custom_config) + + # Example usage of get_config + database_url = get_config('DATABASE_URL') + print(f"Database URL: {database_url}") + + # Example usage of another config variable + secret_key = get_config('SECRET_KEY') + print(f"Secret Key: {secret_key}") + except ConfigurationError as e: + print(f"Failed to load configuration: {e}") + exit(1) + +if __name__ == "__main__": + main() + +``` + +### Accessing Configuration Values + +You can access configuration values using the `get_config` function. If the key does not exist, you can provide a +default value. + +```python +from configurator import get_config + +log_level = get_config('LOG_LEVEL', 'INFO') +print(f"Log Level: {log_level}") + +debug_mode = get_config('DEBUG', False) +print(f"Debug Mode: {debug_mode}") +``` + +### Configuration Validation + +The package includes basic validation to ensure that all required configurations are provided. If a required variable is +missing, a `ConfigurationError` will be raised. + +You can customize validation requirements in the `validators` module if needed. + +## Custom Configuration + +Custom configurations can be passed to the `load_config` function. These will be merged with the default configurations +provided in the environment or settings files. + +Example: + +```python +custom_config = { + 'API_ENDPOINT': 'https://api.example.com', + 'TIMEOUT': 30, +} +load_config(custom_config) +api_endpoint = get_config('API_ENDPOINT') +print(f"API Endpoint: {api_endpoint}") +``` + +## Extending Configurator + +You can extend the `Configurator` to support additional configuration sources like JSON files, remote configuration +services, etc. This would typically involve modifying the `load_config` function. + +```python +def load_config(custom_config=None, json_file=None) -> dict: + global _config_values + if _config_values is None: + default_config = { + 'LOG_LEVEL': config('LOG_LEVEL', default='INFO'), + 'DEBUG': config('DEBUG', default='false', cast=bool), + 'ENV': config('ENV', default='DEV') + } + + if json_file: + with open(json_file, 'r') as jf: + json_config = json.load(jf) + default_config.update(json_config) + + # Merge custom config if present + if custom_config: + default_config.update(custom_config) + + config_values = {key: config(key, default=value) for key, value in default_config.items()} + + required_vars = [key for key, value in default_config.items() if value is None] + validate_config(config_values, required_vars) + + _config_values = config_values + + return _config_values +``` + +## Contributing + +Contributions are welcome! Please fork the repository and submit a pull request with your changes. For major changes, +please open an issue first to discuss what you would like to change. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/configurator/__init__.py b/configurator/__init__.py new file mode 100644 index 0000000..0bf2b8a --- /dev/null +++ b/configurator/__init__.py @@ -0,0 +1,5 @@ +from .core import load_config, get_config +from .validators import validate_config +from .exceptions import ConfigurationError + +__all__ = ['load_config', 'get_config', 'validate_config', 'ConfigurationError'] diff --git a/configurator/core.py b/configurator/core.py new file mode 100644 index 0000000..204ce94 --- /dev/null +++ b/configurator/core.py @@ -0,0 +1,36 @@ +from decouple import config +from .validators import validate_config + +# Global state to hold loaded configuration (Singleton pattern) +_config_values = None + + +def load_config(custom_config=None) -> dict: + """Load all configuration variables and validate them.""" + global _config_values + + if _config_values is None: + default_config = { + 'LOG_LEVEL': config('LOG_LEVEL', default='INFO'), + 'DEBUG': config('DEBUG', default='false', cast=bool), + 'ENV': config('ENV', default='DEV') + } + + # Merge custom configurator if present + if custom_config: + default_config.update(custom_config) + + config_values = {key: config(key, default=value) for key, value in default_config.items()} + + required_vars = [key for key, value in default_config.items() if value is None] + validate_config(config_values, required_vars) + + _config_values = config_values + + return _config_values + + +def get_config(key: str, default=None): + """Get a configuration value by key.""" + config_values = load_config() + return config_values.get(key, default) diff --git a/configurator/exceptions.py b/configurator/exceptions.py new file mode 100644 index 0000000..91c0502 --- /dev/null +++ b/configurator/exceptions.py @@ -0,0 +1,3 @@ +class ConfigurationError(Exception): + """Custom Exception for configuration errors.""" + pass diff --git a/configurator/utils.py b/configurator/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/configurator/validators.py b/configurator/validators.py new file mode 100644 index 0000000..377eec6 --- /dev/null +++ b/configurator/validators.py @@ -0,0 +1,8 @@ +from .exceptions import ConfigurationError + + +def validate_config(config_values, required_vars): + """Validate the presence and format of required configuration variables.""" + for key in required_vars: + if key not in config_values or config_values[key] is None: + raise ConfigurationError(f'Missing required configuration: {key}') diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8db2356 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +python-decouple \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..59b263e --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +from setuptools import setup, find_packages + +with open("README.md", "r") as fh: + long_description = fh.read() + +with open("requirements.txt", "r") as fh: + required = fh.read().splitlines() + +setup( + name="configurator", + version="0.1.0", + author="agathanonymous", + author_email="agatha@juggalol.com", + description="A configuration management package", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://git.juggalol.com/agatha/configurator", + packages=find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires='>=3.6', + install_requires=required, +)