Skip to content

refactor: modularize library structure and support session reuse#202

Merged
firstof9 merged 2 commits into
mainfrom
refactor-library-structure
May 22, 2026
Merged

refactor: modularize library structure and support session reuse#202
firstof9 merged 2 commits into
mainfrom
refactor-library-structure

Conversation

@firstof9

@firstof9 firstof9 commented May 22, 2026

Copy link
Copy Markdown
Owner

This PR refactors the python-openei library structure to modularize the codebase, support aiohttp.ClientSession reuse, and replace blocking cache I/O calls.

Changes Summary

  • Modularization:
    • Moved constants to const.py.
    • Extracted custom exceptions to exceptions.py.
    • Moved the main Rates client class to client.py.
    • Expose the public API clean in init.py via __all__.
  • Client Enhancement:
    • Added support for passing and reusing an external aiohttp.ClientSession instance.
    • Changed coordinate defaults (lat and lon) to None instead of 9000.
    • Added full type hints to classes and methods.
  • Cache Improvement:
    • Swapped blocking os.path.exists for async aiofiles.ospath.exists to avoid event-loop blockage (crucial for Home Assistant integration).
  • Tests:
    • Added test coverage for session reuse (test_session_reuse) and coordinate defaults (test_default_none_coordinates).

All environments and checks (mypy, ruff, tests across python 3.9-3.14) are fully passing via tox.

Summary by CodeRabbit

  • New Features

    • Introduced a new async Rates client with comprehensive rate lookup, caching, and refresh behavior.
    • Added explicit package exports and new constants for headers, timeouts, and cache sizing.
    • Introduced dedicated exception types for clearer error signaling.
  • Improvements

    • Async-safe cache handling and safer cache directory creation.
    • Minor docstring correction.
  • Tests

    • Added tests for session reuse and coordinate initialization behavior.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32d7fd6f-8f70-433c-aa13-4d20e5cec4bc

📥 Commits

Reviewing files that changed from the base of the PR and between ef1ede1 and a6dcefb.

📒 Files selected for processing (3)
  • openeihttp/cache.py
  • openeihttp/client.py
  • tests/test_init.py

📝 Walkthrough

Walkthrough

This PR refactors the openeihttp package by extracting the Rates client class from __init__.py into a dedicated async client.py module, moving exception types to a new exceptions.py module, updating constants, and converting cache file operations to async patterns. The public API remains unchanged through re-exports, and new integration tests verify session reuse and coordinate initialization behavior.

Changes

Rates Client Module Refactoring

Layer / File(s) Summary
Module Infrastructure: Constants, Exceptions, Package Init
openeihttp/const.py, openeihttp/exceptions.py, openeihttp/__init__.py
New const.py adds DEFAULT_HEADERS, ERROR_TIMEOUT, and MIN_CACHE_SIZE constants. New exceptions.py defines custom exception types (UrlNotFound, NotAuthorized, APIError, RateLimit, InvalidCall). __init__.py becomes a thin re-export layer importing Rates from .client and exceptions from .exceptions.
Async Cache Operations
openeihttp/cache.py
Replaces synchronous os.path.exists calls with async aiofiles.ospath.exists in write_cache and read_cache. Ensures cache directories with aiofiles.os.makedirs(..., exist_ok=True). Replaces hardcoded cache size constant (194) with MIN_CACHE_SIZE to validate cache entries.
Rates Client: Module Setup and Request Handling
openeihttp/client.py
Establishes client.py module with imports, logger, and Rates class constructor wiring API, location, plan, and session configuration. Implements process_request and _execute_request for async HTTP GET requests, JSON response decoding with fallback, HTTP status-to-exception mapping (404→UrlNotFound, 401→NotAuthorized, others→APIError), and timeout/decode error handling.
Rates Client: Plan Lookup and Caching
openeihttp/client.py
Implements lookup_plans to validate location inputs, construct query parameters, and return per-utility rate plan lists. Implements update with 24-hour cache staleness detection and update_data with rate-limit detection and JSON cache serialization. Provides clear_cache to delete cached data.
Rates Client: Scheduling and Energy Rate Lookups
openeihttp/client.py
Implements next_rate_schedule core scheduling logic that iterates through month/day/hour boundaries across weekday/weekend schedule tables to find the next rate structure change. Exposes current_energy_rate_structure and next_energy_rate_structure properties. Implements current_rate/rate and current_adjustment/adjustment methods that resolve applicable structures and select tier rates based on optional meter readings. Implements all_rates aggregate reporting and monthly_tier_rate/tier_rate_for_month tier selection using reading-dependent max thresholds.
Rates Client: Demand Rates, Metadata, and Sell Rates
openeihttp/client.py
Adds current_demand_rate/demand_rate and current_demand_adjustment/demand_adjustment lookups, plus demand_unit metadata. Exposes plan metadata properties: rate_name, approval, distributed_generation, mincharge, fixedchargefirstmeter. Implements current_sell_rate/sell_rate with error-tolerant access to optional JSON keys.
Integration Tests
tests/test_init.py
Adds test_session_reuse verifying custom aiohttp sessions are reused without being closed and lookup_plans() returns non-None. Adds test_default_none_coordinates verifying Rates initialization without coordinates sets _lat and _lon to None. Adds test_partial_coordinates verifying partial coordinates raise InvalidCall.

🎯 4 (Complex) | ⏱️ ~75 minutes

🐰 Code hops from __init.py to client.py bright,
Exceptions bound in their own delight,
Async caches speeding through the night,
Rates and schedules now structured just right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately describes the main objectives of the PR: refactoring the library into a modular structure and enabling session reuse.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor-library-structure

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@firstof9

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openeihttp/cache.py`:
- Around line 29-31: The check-then-create for the cache directory in the method
using self._directory with aiofiles.ospath.exists and aiofiles.os.makedirs is
racy; replace it with an idempotent creation by attempting to create the
directory and swallowing FileExistsError (or OSError with errno.EEXIST) so
concurrent callers won’t error, i.e., remove the separate exists() branch and
wrap the makedirs call in a try/except that logs success/failure but ignores
EEXIST; reference self._directory and the current usage of aiofiles.os.makedirs
in openeihttp/cache.py when applying the change.
- Line 59: The cache size check currently rejects files equal to the configured
minimum because it uses a strict greater-than; update the condition from "size >
MIN_CACHE_SIZE" to "size >= MIN_CACHE_SIZE" so files that meet the minimum are
accepted—modify the return expression in the cache validation/check function
where the local variable size is compared to the MIN_CACHE_SIZE constant.

In `@openeihttp/client.py`:
- Around line 106-110: The current validation in lookup_plans allows a partial
coordinate (one of self._lat or self._lon being None); change the check so it
requires either a non-empty self._address OR both self._lat and self._lon to be
not None (i.e., validate that both coordinates exist together), and if that
requirement is not met log an error and raise InvalidCall from lookup_plans;
reference the lookup_plans method and the self._lat / self._lon fields when
updating the condition and error path so incomplete coordinates cannot proceed
to the API call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4a6198af-b862-4afd-8b11-9e516d99106a

📥 Commits

Reviewing files that changed from the base of the PR and between d359a39 and ef1ede1.

📒 Files selected for processing (6)
  • openeihttp/__init__.py
  • openeihttp/cache.py
  • openeihttp/client.py
  • openeihttp/const.py
  • openeihttp/exceptions.py
  • tests/test_init.py

Comment thread openeihttp/cache.py Outdated
Comment thread openeihttp/cache.py Outdated
Comment thread openeihttp/client.py
@firstof9

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@firstof9 firstof9 merged commit f91d633 into main May 22, 2026
10 checks passed
@firstof9 firstof9 deleted the refactor-library-structure branch May 22, 2026 17:03
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.

1 participant