Skip to content

Commit c98c2fb

Browse files
authored
Merge pull request #6 from LexianDEV/revert-5-copilot/fix-30608041-4694-46cc-8bd1-2ce8b482dc47
Revert "Replace .env file with bundled settings.py for application distribution"
2 parents 27fdf56 + e374903 commit c98c2fb

6 files changed

Lines changed: 65 additions & 102 deletions

File tree

.env.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Environment Configuration
2+
# This file contains environment-specific settings that override config files
3+
4+
# Application Settings
5+
APP_NAME="My Custom App"
6+
APP_ENV=development
7+
APP_DEBUG=true
8+
APP_RESPONSE_NUMBER=100
9+
APP_MESSAGE="Hello from the environment!"
10+
11+
# Logging
12+
APP_LOGGING_LEVEL=DEBUG

CONFIG.md

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
# Configuration System
22

3-
This Python template includes a Laravel-inspired configuration system that allows you to manage application settings through Python files and settings overrides.
3+
This Python template includes a Laravel-inspired configuration system that allows you to manage application settings through Python files and environment variable overrides.
44

55
## Features
66

77
- **Multiple Config Files**: Organize your configuration into separate Python files (e.g., `app.py`)
8-
- **Settings Overrides**: Use `config/settings.py` file or environment variables to override config values
9-
- **Bundled Settings**: Settings are bundled with the application binary (no external .env files needed)
8+
- **Environment Overrides**: Use `.env` file or environment variables to override config values
109
- **Dot Notation Access**: Access nested configuration using dot notation (e.g., `app.name`, `app.logging.level`)
1110
- **Runtime Changes**: Modify configuration values at runtime for testing or dynamic behavior
1211
- **Helper Functions**: Easy-to-use helper functions for common config operations
@@ -31,37 +30,22 @@ if helpers.has_config('app.name'):
3130
all_app_config = helpers.get_all_config('app')
3231
```
3332

34-
### Settings File Overrides
33+
### Environment Variable Overrides
3534

36-
Settings take precedence over config files but can be overridden by environment variables. Use uppercase and underscores:
35+
Environment variables take precedence over config files. Use uppercase and underscores:
3736

3837
- `app.name``APP_NAME`
3938
- `app.response_number``APP_RESPONSE_NUMBER`
4039
- `app.logging.level``APP_LOGGING_LEVEL`
4140

42-
Example `config/settings.py` file:
43-
```python
44-
# Application Settings - these override config/app.py values
45-
APP_NAME = "My Custom App"
46-
APP_DEBUG = True
47-
APP_RESPONSE_NUMBER = 100
48-
APP_MESSAGE = "Hello from the bundled settings!"
49-
```
50-
51-
### Environment Variable Overrides
52-
53-
Environment variables have the highest precedence and will override both config files and settings:
54-
41+
Example `.env` file:
5542
```bash
56-
export APP_NAME="Production App"
57-
export APP_DEBUG=false
43+
APP_NAME=My Custom App
44+
APP_DEBUG=true
45+
APP_RESPONSE_NUMBER=100
46+
APP_MESSAGE=Hello from the environment!
5847
```
5948

60-
**Precedence Order (highest to lowest):**
61-
1. Environment variables
62-
2. Settings file (`config/settings.py`)
63-
3. Config files (`config/*.py`)
64-
6549
### Runtime Configuration Changes
6650

6751
```python
@@ -101,7 +85,6 @@ cache = {
10185
### Existing Config Files
10286

10387
- **`app.py`**: Application-wide settings (name, version, debug mode, response number, etc.)
104-
- **`settings.py`**: Override settings bundled with the application
10588

10689
## Advanced Usage
10790

@@ -111,7 +94,7 @@ cache = {
11194
from config_manager import ConfigManager
11295

11396
# Create a custom config manager
114-
config = ConfigManager(config_dir="custom_config", settings_file="custom_settings")
97+
config = ConfigManager(config_dir="custom_config", env_file="custom.env")
11598

11699
# Use the manager directly
117100
value = config.get('custom.setting', 'default')
@@ -129,11 +112,10 @@ value = helpers.env('app.name') # Falls back to config if no env var
129112
## Best Practices
130113

131114
1. **Organize by Feature**: Create separate config files for different aspects (database, mail, cache, etc.)
132-
2. **Use Settings File**: For non-sensitive application-specific overrides that should be bundled
133-
3. **Use Environment Variables**: For sensitive data and deployment-specific settings
134-
4. **Provide Defaults**: Always provide sensible default values
135-
5. **Document Settings**: Add comments to explain complex configuration options
136-
6. **Validate Config**: Add validation for critical configuration values in your application startup
115+
2. **Use Environment Variables**: For sensitive data and environment-specific settings
116+
3. **Provide Defaults**: Always provide sensible default values
117+
4. **Document Settings**: Add comments to explain complex configuration options
118+
5. **Validate Config**: Add validation for critical configuration values in your application startup
137119

138120
## Example: Full Configuration Workflow
139121

@@ -159,4 +141,4 @@ if helpers.get_config('app.env') == 'testing':
159141
helpers.set_config('database.default', 'sqlite')
160142
```
161143

162-
This configuration system provides the flexibility of Laravel's config system while maintaining Python's simplicity and power. Settings are now bundled with the application for easier distribution.
144+
This configuration system provides the flexibility of Laravel's config system while maintaining Python's simplicity and power.

build.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ a = Analysis(
55
['main.py'],
66
pathex=[],
77
binaries=[],
8-
datas=[('config/settings.py', 'config')],
8+
datas=[],
99
hiddenimports=[],
1010
hookspath=[],
1111
hooksconfig={},

config/settings.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

config_manager.py

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
22
Laravel-like configuration manager for Python applications.
3-
Supports loading config files from the config directory and settings overrides.
3+
Supports loading config files from the config directory and environment overrides.
44
"""
55

66
import os
@@ -10,35 +10,26 @@
1010

1111

1212
class ConfigManager:
13-
"""Manages application configuration with support for multiple config files and settings overrides."""
13+
"""Manages application configuration with support for multiple config files and environment overrides."""
1414

15-
def __init__(self, config_dir: str = "config", settings_file: str = "settings"):
15+
def __init__(self, config_dir: str = "config", env_file: str = ".env"):
1616
self.config_dir = Path(config_dir)
17-
self.settings_file = settings_file
17+
self.env_file = Path(env_file)
1818
self._config_cache: Dict[str, Dict[str, Any]] = {}
19-
self._settings_vars: Dict[str, str] = {}
19+
self._env_vars: Dict[str, str] = {}
2020

21-
# Load settings variables from settings file
22-
self._load_settings_file()
21+
# Load environment variables from .env file
22+
self._load_env_file()
2323

24-
def _load_settings_file(self) -> None:
25-
"""Load settings variables from settings.py file if it exists."""
26-
settings_file_path = self.config_dir / f"{self.settings_file}.py"
27-
if settings_file_path.exists():
28-
# Load the settings file as a module
29-
spec = importlib.util.spec_from_file_location(self.settings_file, settings_file_path)
30-
if spec is None or spec.loader is None:
31-
return
32-
33-
module = importlib.util.module_from_spec(spec)
34-
spec.loader.exec_module(module)
35-
36-
# Extract all non-private attributes as settings values
37-
for attr_name in dir(module):
38-
if not attr_name.startswith('_') and not callable(getattr(module, attr_name)):
39-
value = getattr(module, attr_name)
40-
# Store as string for consistency with old env behavior
41-
self._settings_vars[attr_name] = str(value)
24+
def _load_env_file(self) -> None:
25+
"""Load environment variables from .env file if it exists."""
26+
if self.env_file.exists():
27+
with open(self.env_file, 'r') as f:
28+
for line in f:
29+
line = line.strip()
30+
if line and not line.startswith('#') and '=' in line:
31+
key, value = line.split('=', 1)
32+
self._env_vars[key.strip()] = value.strip().strip('"').strip("'")
4233

4334
def _load_config_file(self, config_name: str) -> Dict[str, Any]:
4435
"""Load a specific config file."""
@@ -65,28 +56,21 @@ def _load_config_file(self, config_name: str) -> Dict[str, Any]:
6556
def get(self, key: str, default: Any = None) -> Any:
6657
"""
6758
Get a configuration value using dot notation (e.g., 'app.name' or 'database.host').
68-
Environment variables have highest precedence, then settings variables, then config files.
59+
Environment variables take precedence over config files.
6960
"""
70-
# Check for environment variable override first (highest precedence)
61+
# Check for environment variable override first
7162
env_key = key.upper().replace('.', '_')
72-
if env_key in os.environ:
73-
value = os.environ[env_key]
63+
if env_key in self._env_vars:
64+
value = self._env_vars[env_key]
7465
# Convert string boolean values
7566
if value.lower() in ('true', 'false'):
7667
return value.lower() == 'true'
7768
return value
78-
79-
# Check for settings variable override second
80-
if env_key in self._settings_vars:
81-
value = self._settings_vars[env_key]
69+
if env_key in os.environ:
70+
value = os.environ[env_key]
8271
# Convert string boolean values
8372
if value.lower() in ('true', 'false'):
8473
return value.lower() == 'true'
85-
# Try to convert to int if it looks like a number
86-
try:
87-
return int(value)
88-
except ValueError:
89-
pass
9074
return value
9175

9276
# Parse the key to get config file and setting
@@ -140,7 +124,7 @@ def reload(self, config_name: Optional[str] = None) -> None:
140124
del self._config_cache[config_name]
141125
else:
142126
self._config_cache.clear()
143-
self._load_settings_file()
127+
self._load_env_file()
144128

145129
def all(self, config_name: str) -> Dict[str, Any]:
146130
"""Get all configuration values for a specific config file."""

tests/test_config.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ def test_config_loading():
4747
print("✓ Config file loading works")
4848

4949

50-
def test_settings_overrides():
51-
"""Test settings file overrides."""
52-
print("Testing settings overrides...")
50+
def test_env_overrides():
51+
"""Test environment variable overrides."""
52+
print("Testing environment overrides...")
5353

5454
with tempfile.TemporaryDirectory() as temp_dir:
5555
config_dir = Path(temp_dir) / "config"
@@ -62,21 +62,21 @@ def test_settings_overrides():
6262
debug = False
6363
""")
6464

65-
# Create settings file
66-
settings_file = config_dir / "settings.py"
67-
settings_file.write_text("""
68-
APP_NAME = "Override App"
69-
APP_DEBUG = True
65+
# Create .env file
66+
env_file = Path(temp_dir) / ".env"
67+
env_file.write_text("""
68+
APP_NAME=Override App
69+
APP_DEBUG=true
7070
""")
7171

7272
# Test config manager
73-
manager = ConfigManager(config_dir=str(config_dir), settings_file="settings")
73+
manager = ConfigManager(config_dir=str(config_dir), env_file=str(env_file))
7474

75-
# Test settings overrides
75+
# Test environment overrides
7676
assert manager.get('app.name') == "Override App"
7777
assert manager.get('app.debug') == True
7878

79-
print("✓ Settings overrides work")
79+
print("✓ Environment overrides work")
8080

8181

8282
def test_runtime_config_changes():
@@ -93,8 +93,8 @@ def test_runtime_config_changes():
9393
name = "Original App"
9494
""")
9595

96-
# Test config manager with no settings file
97-
manager = ConfigManager(config_dir=str(config_dir), settings_file="nonexistent")
96+
# Test config manager with no .env file
97+
manager = ConfigManager(config_dir=str(config_dir), env_file="/nonexistent/.env")
9898

9999
# Test runtime changes
100100
original_name = manager.get('app.name')
@@ -133,7 +133,7 @@ def run_tests():
133133

134134
try:
135135
test_config_loading()
136-
test_settings_overrides()
136+
test_env_overrides()
137137
test_runtime_config_changes()
138138
test_helpers_integration()
139139

0 commit comments

Comments
 (0)