Skip to content

feat: refactor database engine management with dual-backend support (…#613

Open
joda9 wants to merge 4 commits intodevfrom
bug/450-bug-database-imports-not-working-ssh-tunnel-limitations
Open

feat: refactor database engine management with dual-backend support (…#613
joda9 wants to merge 4 commits intodevfrom
bug/450-bug-database-imports-not-working-ssh-tunnel-limitations

Conversation

@joda9
Copy link
Collaborator

@joda9 joda9 commented Mar 20, 2026

Description

(OEP + local egon-data)

This commit introduces a comprehensive refactoring of how eDisGo manages database connections, enabling seamless switching between the OpenEnergyPlatform (OEP) and local egon-data PostgreSQL databases.

Engine as EDisGo property (edisgo/edisgo.py)

  • Add lazy-initialized engine property to the EDisGo class, replacing the previous pattern of passing engine explicitly to every function call
  • Support multiple initialization modes via new constructor parameters: engine, db_config_path, db_url, db_ssh, db_token
  • Default to OEP engine when no configuration is provided
  • Add __deepcopy__ method that excludes the unpicklable SQLAlchemy engine (contains _thread._local objects) and lets the copy lazily recreate its own connection

Optional engine parameter in all DB functions

Make the engine parameter optional (default=None) in all 33 functions across 8 modules that access the database. When engine is None, each function falls back to edisgo_object.engine. This maintains full backward compatibility — callers can still pass an explicit engine.

Modified modules:

  • edisgo/io/timeseries_import.py (10 functions)
  • edisgo/io/electromobility_import.py (4 functions)
  • edisgo/io/dsm_import.py (3 functions)
  • edisgo/io/heat_pump_import.py (2 functions)
  • edisgo/io/generators_import.py (1 function)
  • edisgo/io/storage_import.py (1 function)
  • edisgo/network/heat.py (2 engine fallbacks)
  • edisgo/network/timeseries.py (1 engine fallback)

Local DB schema resolution (edisgo/tools/config.py)

Extend import_tables_from_oep() to handle local egon-data databases:

  • Auto-resolve schema mismatches: tables may reside in different schemas locally vs on OEP (e.g. egon_etrago_bus is in "grid" locally but imported via "supply" on OEP). The method now searches all schemas when a table is not found in the expected schema.
  • Handle tables without primary keys (e.g. egon_map_zensus_grid_districts, egon_daily_heat_demand_per_climate_zone) by passing all columns as synthetic PK via __mapper_args__["primary_key"].

SSH tunnel lifecycle management (edisgo/io/db.py)

  • ssh_tunnel() now returns tuple[str, SSHTunnelForwarder] instead of just the port string, so the server object is no longer lost
  • engine() stores the SSH server as engine._ssh_server for later cleanup via server.stop()
  • This fixes the "I/O operation on closed file" logging errors caused by orphaned paramiko keepalive threads writing to closed log handlers

Test infrastructure (tests/conftest.py)

  • Add --runlocal flag to run all DB tests against both OEP and local egon-data database
  • Add --egon-data-config flag for custom YAML config path
  • Parametrize tests via db_engine fixture: each DB test runs as test_name[oep] and test_name[local] when --runlocal is active
  • Add pytest_sessionfinish hook that disposes engines and stops SSH tunnels cleanly after all tests complete
  • Suppress paramiko DEBUG keepalive logging (defense-in-depth)
  • Migrate all 29 test methods across 10 test files from hardcoded pytest.engine to parametrized db_engine fixture

New files

  • tests/io/test_db.py: 4 tests for SSH tunnel lifecycle (tunnel returns server, engine stores server, cleanup stops tunnel, OEP has no tunnel)
  • egon-data.configuration.yaml: Template config file with placeholder credentials for local egon-data database connections

Fixes #450 #578

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist:

  • New and adjusted code is formatted using the pre-commit hooks
  • New and adjusted code includes type hinting now
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • The Read the Docs documentation is compiling correctly
  • If new packages are needed, I added them the setup.py, and if needed the rtd_requirements.txt, the eDisGo_env.yml and the eDisGo_env_dev.yml.
  • I have added new features to the corresponding whatsnew file

@joda9 joda9 linked an issue Mar 20, 2026 that may be closed by this pull request
…OEP + local egon-data)

This commit introduces a comprehensive refactoring of how eDisGo manages
database connections, enabling seamless switching between the
OpenEnergyPlatform (OEP) and local egon-data PostgreSQL databases.

## Engine as EDisGo property (edisgo/edisgo.py)

- Add lazy-initialized `engine` property to the EDisGo class, replacing
  the previous pattern of passing engine explicitly to every function call
- Support multiple initialization modes via new constructor parameters:
  `engine`, `db_config_path`, `db_url`, `db_ssh`, `db_token`
- Default to OEP engine when no configuration is provided
- Add `__deepcopy__` method that excludes the unpicklable SQLAlchemy
  engine (contains _thread._local objects) and lets the copy lazily
  recreate its own connection

## Optional engine parameter in all DB functions

Make the `engine` parameter optional (default=None) in all 33 functions
across 8 modules that access the database. When engine is None, each
function falls back to `edisgo_object.engine`. This maintains full
backward compatibility — callers can still pass an explicit engine.

Modified modules:
- edisgo/io/timeseries_import.py (10 functions)
- edisgo/io/electromobility_import.py (4 functions)
- edisgo/io/dsm_import.py (3 functions)
- edisgo/io/heat_pump_import.py (2 functions)
- edisgo/io/generators_import.py (1 function)
- edisgo/io/storage_import.py (1 function)
- edisgo/network/heat.py (2 engine fallbacks)
- edisgo/network/timeseries.py (1 engine fallback)

## Local DB schema resolution (edisgo/tools/config.py)

Extend `import_tables_from_oep()` to handle local egon-data databases:
- Auto-resolve schema mismatches: tables may reside in different schemas
  locally vs on OEP (e.g. egon_etrago_bus is in "grid" locally but
  imported via "supply" on OEP). The method now searches all schemas
  when a table is not found in the expected schema.
- Handle tables without primary keys (e.g. egon_map_zensus_grid_districts,
  egon_daily_heat_demand_per_climate_zone) by passing all columns as
  synthetic PK via `__mapper_args__["primary_key"]`.

## SSH tunnel lifecycle management (edisgo/io/db.py)

- `ssh_tunnel()` now returns `tuple[str, SSHTunnelForwarder]` instead of
  just the port string, so the server object is no longer lost
- `engine()` stores the SSH server as `engine._ssh_server` for later
  cleanup via `server.stop()`
- This fixes the "I/O operation on closed file" logging errors caused by
  orphaned paramiko keepalive threads writing to closed log handlers

## Test infrastructure (tests/conftest.py)

- Add `--runlocal` flag to run all DB tests against both OEP and local
  egon-data database
- Add `--egon-data-config` flag for custom YAML config path
- Parametrize tests via `db_engine` fixture: each DB test runs as
  `test_name[oep]` and `test_name[local]` when --runlocal is active
- Add `pytest_sessionfinish` hook that disposes engines and stops SSH
  tunnels cleanly after all tests complete
- Suppress paramiko DEBUG keepalive logging (defense-in-depth)
- Migrate all 29 test methods across 10 test files from hardcoded
  `pytest.engine` to parametrized `db_engine` fixture

## New files

- tests/io/test_db.py: 4 tests for SSH tunnel lifecycle (tunnel returns
  server, engine stores server, cleanup stops tunnel, OEP has no tunnel)
- egon-data.configuration.yaml: Template config file with placeholder
  credentials for local egon-data database connections
@joda9 joda9 requested a review from MoritzSchloesser March 21, 2026 05:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Database Imports Not Working & SSH Tunnel Limitations

1 participant