Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d0ab94d
feat: implement SQLAlchemy-based repository for Miner and MinerContro…
markoceri Jan 20, 2026
2a6b26f
feat: enhance error handling in miner controller config deserialization
markoceri Jan 20, 2026
772511b
feat: add database ORM and migrations dependencies to requirements
markoceri Jan 20, 2026
b41cf3a
feat: implement SQLAlchemy repository and ORM mappings for ForecastPr…
markoceri Jan 20, 2026
18eb8bc
feat: implement SQLAlchemy repositories for HomeForecastProvider and …
markoceri Jan 20, 2026
b138b9c
feat: implement SQLAlchemy repository for Notifier with custom JSON s…
markoceri Jan 20, 2026
7e6cff1
feat: add SQLAlchemy implementation for EnergyOptimizationUnit reposi…
markoceri Jan 20, 2026
a182ee8
feat: add SQLAlchemy implementation for MiningPerformanceTracker repo…
markoceri Jan 20, 2026
1ea06f9
feat: implement SQLAlchemy repository for SystemSettings with ORM map…
markoceri Jan 20, 2026
ed8283f
feat: add SQLAlchemy implementation for ExternalService repository wi…
markoceri Jan 20, 2026
07977d0
feat: exclude composite columns from mapping to avoid conflicts
markoceri Jan 20, 2026
b62b9df
feat: implement SQLAlchemy repositories for EnergySource and EnergyMo…
markoceri Jan 20, 2026
522a5e9
feat: add SQLAlchemy support in bootstrap and settings for persistenc…
markoceri Jan 20, 2026
73aa140
feat: implement SQLAlchemy repository for OptimizationPolicy with ORM…
markoceri Jan 20, 2026
0209ec2
feat: add SQLAlchemyOptimizationPolicyRepository to bootstrap for pol…
markoceri Jan 20, 2026
d767a19
feat: add aiosqlite dependency for database support in requirements
markoceri Jan 20, 2026
30893e3
feat: add SQLAlchemy, Alembic, and aiosqlite dependencies for databas…
markoceri Jan 20, 2026
389d573
feat: add registry loader for SQLAlchemy table definitions and ensure…
markoceri Jan 22, 2026
96bd4e8
refactor: remove aiosqlite dependency and update database URL in sett…
markoceri Jan 22, 2026
af497df
feat: implement ConfigurationType for JSON serialization of Configura…
markoceri Jan 22, 2026
25de6ac
feat: add custom SQLAlchemy type for MinerStatus enum to handle strin…
markoceri Jan 22, 2026
4d24966
feat: enhance Miner and MinerController mappings with event listeners…
markoceri Jan 22, 2026
e72230c
feat: implement event listeners for value object conversions in Energ…
markoceri Jan 22, 2026
ea6be5e
feat: add event listener to convert adapter_type from string to enum …
markoceri Jan 22, 2026
dece37e
fix: add foreign key constraint to forecast_provider_id in energy_sou…
markoceri Jan 22, 2026
efc82ce
feat: add foreign key constraint to external_service_id in multiple t…
markoceri Jan 22, 2026
ccf674f
feat: update persistence_adapter to use sqlalchemy as default
markoceri Jan 22, 2026
0ca06da
feat: implement SQLAlchemy persistence with Alembic migrations support
markoceri Jan 22, 2026
0a864b6
feat: add CLI utility for managing Alembic migrations
markoceri Jan 22, 2026
5e7ccae
feat: update migrate.py usage instructions and add validate_migration…
markoceri Jan 22, 2026
1fd2c91
feat: add SQLAlchemy database file patterns to .gitignore
markoceri Jan 22, 2026
202733f
feat: enhance SQLAlchemy tables docstring
markoceri Jan 22, 2026
3daa952
fix: update type hints and add type ignore comments for JSON serializ…
markoceri Jan 23, 2026
192fb28
feat: add database backup option before running migrations
markoceri Jan 23, 2026
645fea4
fix: update comments for migration settings and ensure backup option …
markoceri Jan 23, 2026
6db7858
feat: add comprehensive migration documentation for adding temperatur…
markoceri Jan 23, 2026
524f035
feat: add developer warning for schema changes requiring Alembic migr…
markoceri Jan 23, 2026
aa4884c
feat: update README with environment variable configuration and datab…
markoceri Jan 23, 2026
0065332
feat: add README for utility scripts with usage instructions and conf…
markoceri Jan 23, 2026
3bd8d58
feat: add README for Alembic single-database configuration
markoceri Jan 23, 2026
907beaa
fix: update test migration path for automated testing
markoceri Jan 23, 2026
988b1f4
feat: add unit tests for BaseSQLAlchemyRepository initialization and …
markoceri Jan 23, 2026
70f872e
feat: update CHANGELOG with automatic Alembic migrations, new CLI too…
markoceri Jan 23, 2026
31c09e4
fix: update launch and settings configuration for consistent Python i…
markoceri Jan 23, 2026
90258f0
fix: initializate database schema with the first migration
markoceri Jan 23, 2026
ac645ee
feat: add Alembic migration script template for schema upgrades and d…
markoceri Jan 23, 2026
25b8300
fix: enhance database schema initialization documentation for Alembic…
markoceri Jan 23, 2026
6db0222
docs: enhance Alembic migration documentation with initial setup inst…
markoceri Jan 23, 2026
de7c46b
refactor: rename energy_optimization_units to optimization_units and …
markoceri Jan 23, 2026
5576d14
feat: update energy source schema to store complex value objects as JSON
markoceri Jan 23, 2026
fe4028d
feat: add integration and unit test modules for adapters and energy d…
markoceri Jan 23, 2026
06336a2
feat: add event listeners to convert and restore value objects for En…
markoceri Jan 23, 2026
35af125
feat: added tests for persistence
markoceri Jan 23, 2026
28e244f
feat: enhance testing infrastructure with comprehensive unit and inte…
markoceri Jan 23, 2026
92a35bd
Merge branch 'dev' into sqlalchemy-and-elembic
markoceri Jan 23, 2026
95c5d0b
fix: update changelog to reflect version 0.1.0 release
markoceri Jan 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,27 @@ LATITUDE=41.9028
LONGITUDE=12.4964

# Persistence Settings
# Optional: Path to the SQLite database file (default is 'edgemining.db' in core/)
SQLITE_DB_FILE=./edgemining.db
# Optional: Path to the directory for storing optimization policies
# Adapter type: "in_memory", "sqlite", "sqlalchemy"
PERSISTENCE_ADAPTER=sqlalchemy

# Policies adapter type: "in_memory", "sqlite", "yaml", "sqlalchemy"
POLICIES_PERSISTENCE_ADAPTER=yaml

# Database configuration
# For SQLite: sqlite:///path/to/file.db
# For PostgreSQL: postgresql://user:password@localhost:5432/dbname
# For MySQL: mysql+pymysql://user:password@localhost:3306/dbname
DB_PATH=sqlite:///edgemining.db

# Alembic Migrations
# Automatically run database migrations on startup (recommended)
RUN_MIGRATIONS_ON_STARTUP=true


# Create a backup of the database before running migrations (SQLite only)
BACKUP_BEFORE_MIGRATION=true

# Optional: Path to the directory for storing optimization policies (when using YAML)
YAML_POLICIES_DIR=optimization_policies

# API Settings
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,9 @@ htmlcov/

# Edge Mining stuffs
edgemining.db

# SQLAlchemy database files
*.db
*.db-journal
*.db-shm
*.db-wal
8 changes: 6 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@
"request": "launch",
"console": "integratedTerminal",
"module": "edge_mining",
"args": "standard"
"args": ["standard"],
"python": "${workspaceFolder}/.venv/bin/python",
"cwd": "${workspaceFolder}"
},
{
"name": "Edge Mining Debugger: CLI Mode",
"type": "debugpy",
"request": "launch",
"console": "integratedTerminal",
"module": "edge_mining",
"args": "cli interactive"
"args": ["cli", "interactive"],
"python": "${workspaceFolder}/.venv/bin/python",
"cwd": "${workspaceFolder}"
}
]
}
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"python.defaultInterpreterPath": "./.venv/bin/python",
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
"python.terminal.activateEnvironment": true,
"ruff.nativeServer": true,
"ruff.interpreter": ["${workspaceFolder}/.venv/bin/python"],
"mypy-type-checker.interpreter": ["${workspaceFolder}/.venv/bin/python"],
"python.analysis.extraPaths": ["${workspaceFolder}"],
"python.envFile": "${workspaceFolder}/.env",
"python.testing.pytestEnabled": true,
"python.testing.unittestEnabled": false,
"python.testing.pytestArgs": [
Expand Down
64 changes: 64 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.0]

### Added
- **Automatic Alembic Migrations**: Database migrations now run automatically on application startup
- New module `edge_mining/adapters/infrastructure/persistence/sqlalchemy/migrations.py`
- CLI tool `scripts/migrate.py` for manual migration management
- Configuration option `RUN_MIGRATIONS_ON_STARTUP` in settings (default: true)
- Commands: `status`, `upgrade`, `downgrade`, `create`, `history`
- Method `initialize_database()` in `BaseSQLAlchemyRepository` that handles complete DB initialization

- **Documentation**:
- `docs/ALEMBIC_MIGRATIONS.md` - Complete guide to migration system
- `docs/MIGRATION_EXAMPLE.md` - Practical example of adding a field

- **Testing Infrastructure**:
- **Unit Tests** (42 tests):
- `tests/unit/adapters/domain/energy/test_tables_event_listeners.py` - Complete test suite for SQLAlchemy event listeners
- Tests for `load` event listeners (EntityId, enum, and value object conversions from database)
- Tests for `before_insert/update` event listeners (flattening composites before persistence)
- Tests for `after_insert/update` event listeners (restoring composites after persistence)
- Tests for configuration deserialization and value object round-trip conversion
- **Integration Tests** (34 tests):
- `tests/integration/adapters/persistence/test_sqlalchemy_energy_repositories.py` (21 tests) - Full CRUD operations with real database
- `tests/integration/adapters/persistence/test_alembic_migrations.py` (9 tests) - Alembic migration system validation
- `tests/integration/adapters/persistence/test_e2e_persistence.py` (8 tests) - End-to-end persistence workflows

### Changed
- **`BaseSQLAlchemyRepository`**:
- Added `initialize_database()` method that encapsulates all database setup logic
- Added `run_migrations` parameter to constructor
- Improved separation of concerns by moving migration logic from bootstrap to repository
- **BREAKING**: Removed `create_all_tables()` method - all schema changes must now go through Alembic migrations
- Fail-fast approach: initialization fails clearly if migrations fail (no silent fallback)
- **SQLAlchemy Event Listeners** (`edge_mining/adapters/domain/energy/tables.py`):
- Enhanced 4-phase conversion system for domain entities ↔ database mapping
- `load` listeners: Convert database strings to EntityId, enums, and value objects
- `before_insert/update` listeners: Flatten value objects to primitives before persistence
- `after_insert/update` listeners: Restore EntityId, enums, and value objects after persistence
- Added type ignore comments with explanatory notes for runtime type conversions
- Fixed foreign key conversions (energy_monitor_id, forecast_provider_id, external_service_id)
- **`bootstrap.py`**: Simplified database initialization using `initialize_database()`
- **`alembic/env.py`**: Updated to use shared metadata registry from SQLAlchemy imperative mapping
- **`.env.example`**: Added migration configuration examples and multi-database support
- **`README.md`**: Added database migrations section with usage instructions
- **Settings**: Added `run_migrations_on_startup` configuration option

### Fixed
- Migration path calculation now correctly resolves project root
- Better error handling for migration failures with graceful fallback
- Improved encapsulation: database initialization logic moved to appropriate layer
- Fixed import error in `tests/unit/adapters/infrastructure/rule_engine/test_rule_evaluator.py` (OperatorType import path)
- SQLAlchemy event listeners now correctly handle all type conversions between domain and database layers
- EntityId conversions for primary keys and foreign keys (energy_monitor_id, forecast_provider_id, external_service_id)
- Enum conversions (EnergySourceType, EnergyMonitorAdapter) in both directions
- Value object conversions (Watts, Battery, Grid) with proper serialization/deserialization

## [Previous Versions]
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,22 @@ The project uses **Hexagonal Architecture (Ports and Adapters)** to clearly sepa
pip install -r requirements.txt
```
4. **Configure environment variables:**
Copy `.env.example` to `.env` and change the values ​​according to your configuration (log level, timezone and geographical position).
Copy `.env.example` to `.env` and change the values according to your configuration:
```bash
cp .env.example .env
nano .env # Change the file .env
nano .env # Edit the .env file
```

Key settings:
- `PERSISTENCE_ADAPTER`: Choose between `in_memory`, `sqlite`, or `sqlalchemy` (recommended)
- `DB_PATH`: Database URL (e.g., `sqlite:///edgemining.db` or PostgreSQL URL)
- `RUN_MIGRATIONS_ON_STARTUP`: Set to `true` to automatically apply database migrations

5. **Initialize the database (SQLAlchemy only):**
If using SQLAlchemy persistence, migrations **will run automatically on startup**.

See [docs/ALEMBIC_MIGRATIONS.md](docs/ALEMBIC_MIGRATIONS.md) for detailed migration management.

## Execution

You can run the application in different modes via the main entry point:
Expand Down Expand Up @@ -132,20 +142,18 @@ docker compose exec edge-mining-core python -m edge_mining cli interactive

### Available adapters

- **Energy Monitor:** `dummy`, `home_assistant` (*new*)
- **Energy Monitor:** `dummy`, `home_assistant`
- **Miner Controller:** `dummy`
- **Forecast Provider:** `dummy`, `home_assistant` (*new*)
- **Persistence:** `in_memory`, `sqlite`, `YAML` (*new*)
- **Notification:** `dummy`, `telegram` (*new*)
- **Interaction:** `cli`, `api`(*new*)
- **Forecast Provider:** `dummy`, `home_assistant`
- **Persistence:** `in_memory`, `sqlite`, `sqlalchemy` (with Alembic migrations), `yaml` (for policies)
- **Notification:** `dummy`, `telegram`
- **Interaction:** `cli`, `api`


## TODO

- [ ] Implement real adapters for specific scenarios (HomeAssistant MQTT, specific ASIC APIs).
- [ ] Implement real adapters for external APIs (Solcast, OpenWeatherMap, Mining Pools).
- [ ] Add unit, integration and acceptance tests.
- [ ] Improve error handling and logging.
- [ ] Develop a web UI (could be a separate driving adapter using the API, maybe in a different repository).
- [ ] Implement more sophisticated home load forecasting logic.
- [ ] Handle authentication and authorization (especially for the API).
- [x] Improve the rules engine.
149 changes: 149 additions & 0 deletions alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# A generic, single database configuration.

[alembic]
# path to migration scripts.
# this is typically a path given in POSIX (e.g. forward slashes)
# format, relative to the token %(here)s which refers to the location of this
# ini file
script_location = %(here)s/alembic

# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
# for all available tokens
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
# Or organize into date-based subdirectories (requires recursive_version_locations = true)
# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s

# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory. for multiple paths, the path separator
# is defined by "path_separator" below.
prepend_sys_path = .


# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the tzdata library which can be installed by adding
# `alembic[tz]` to the pip requirements.
# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =

# max length of characters to apply to the "slug" field
# truncate_slug_length = 40

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false

# version location specification; This defaults
# to <script_location>/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "path_separator"
# below.
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions

# path_separator; This indicates what character is used to split lists of file
# paths, including version_locations and prepend_sys_path within configparser
# files such as alembic.ini.
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
# to provide os-dependent path splitting.
#
# Note that in order to support legacy alembic.ini files, this default does NOT
# take place if path_separator is not present in alembic.ini. If this
# option is omitted entirely, fallback logic is as follows:
#
# 1. Parsing of the version_locations option falls back to using the legacy
# "version_path_separator" key, which if absent then falls back to the legacy
# behavior of splitting on spaces and/or commas.
# 2. Parsing of the prepend_sys_path option falls back to the legacy
# behavior of splitting on spaces, commas, or colons.
#
# Valid values for path_separator are:
#
# path_separator = :
# path_separator = ;
# path_separator = space
# path_separator = newline
#
# Use os.pathsep. Default configuration used for new projects.
path_separator = os

# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
# recursive_version_locations = false

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

# database URL. This is consumed by the user-maintained env.py script only.
# other means of configuring database URLs may be customized within the env.py
# file.
sqlalchemy.url = sqlite:///./edgemining.db


[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples

# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME

# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
# hooks = ruff
# ruff.type = module
# ruff.module = ruff
# ruff.options = check --fix REVISION_SCRIPT_FILENAME

# Alternatively, use the exec runner to execute a binary found on your PATH
# hooks = ruff
# ruff.type = exec
# ruff.executable = ruff
# ruff.options = check --fix REVISION_SCRIPT_FILENAME

# Logging configuration. This is also consumed by the user-maintained
# env.py script only.
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARNING
handlers = console
qualname =

[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
1 change: 1 addition & 0 deletions alembic/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generic single-database configuration.
Loading