-
Notifications
You must be signed in to change notification settings - Fork 8
Adds organisation API support #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Apart from operating a single charge station, the Charge Amps API also allows controlling (part) of an organization's fleet of charge stations and users. Since this is most likely an uncommon usecase, I placed it behind a feature flag "organisations". This does not add support for CLI flags for accessing the API, but would be a nice future addition.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
WalkthroughAdds organisation support: new organisation models and OrganisationClient, user/partner handling in the external client, RFID helpers and endpoints, example scripts, README usage instructions, and an optional Changes
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer (example)
participant OrgClient as OrganisationClient
participant ExtClient as ChargeAmpsExternalClient
participant API as ChargeAmps API
Dev->>OrgClient: init(credentials)
OrgClient->>ExtClient: ensure token / login
ExtClient->>API: POST /auth (credentials)
API-->>ExtClient: token + user payload
Note right of ExtClient #DDEFE8: caches token and `User` payload
ExtClient-->>OrgClient: ready
Dev->>OrgClient: get_organisations()
OrgClient->>API: GET /organisations (auth token)
API-->>OrgClient: organisations list
OrgClient-->>Dev: organisations
Dev->>OrgClient: add_organisation_rfid(org_id, rfid)
OrgClient->>OrgClient: verify_rfid(format/length)
OrgClient->>API: POST /organisations/{id}/rfids
API-->>OrgClient: rfid created
OrgClient-->>Dev: RfidTag
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🧹 Outside diff range and nitpick comments (6)
chargeamps/organisation.py (3)
53-56: Simplify list constructions using list comprehensionsIn several methods, you are building lists using loops where list comprehensions can enhance readability and conciseness.
For example, in
get_organisation_chargepoints:payload = await response.json() - res = [] - for charge_point in payload: - res.append(ChargePoint.model_validate(charge_point)) - return res + return [ChargePoint.model_validate(cp) for cp in payload]Apply similar changes in the following methods:
get_organisation_chargepoint_statuses(lines 66-69)get_organisation_rfids(lines 140-143)get_organisation_users(lines 186-189)Also applies to: 66-69, 140-143, 186-189
222-222: Replace print statement with proper loggingUsing
- print(payload) + self._logger.debug(f"Payload: {payload}")
215-220: Align password length validation condition and error messageThe condition checks if
len(password) >= 8, but the error message states "must exceed 8 characters," implyinglen(password) > 8. To avoid confusion, align the condition with the error message.Option 1: Change the condition to match the error message:
- if len(password) >= 8: + if len(password) > 8:Option 2: Update the error message to match the condition:
else: raise ValueError( - "The provided password is too short, must exceed 8 characters" + "The provided password is too short, must be at least 8 characters" )README.md (1)
11-13: Specify the language identifier in fenced code blocksThe fenced code blocks on lines 11-13 and 18-19 do not specify a language, which affects syntax highlighting and readability. This issue is flagged by Markdownlint.
Add the language identifier (e.g.,
bash) to the code blocks:```bash pip install chargeamps```diff ```bash pip install chargeamps["organisations"]Also applies to: 18-19 <details> <summary>🧰 Tools</summary> <details> <summary>🪛 Markdownlint (0.35.0)</summary> 11-11: null Fenced code blocks should have a language specified (MD040, fenced-code-language) </details> </details> </blockquote></details> <details> <summary>chargeamps/external.py (2)</summary><blockquote> `49-49`: **Consider thread-safety implications of the _user field** The `_user` field maintains state that could lead to race conditions in concurrent usage scenarios. Consider making this field immutable or implementing proper synchronization. --- `78-78`: **Ensure consistent state management across token operations** The `_user` field is managed across multiple locations. Consider centralizing this logic to prevent inconsistencies. Consider creating a dedicated method for user state management: ```python def _clear_auth_state(self): """Clear all authentication-related state.""" self._token = None self._user = None self._refresh_token = None self._token_expire = 0 def _update_auth_state(self, response_payload: dict): """Update authentication state from response payload.""" self._token = response_payload["token"] self._user = response_payload["user"] self._refresh_token = response_payload["refreshToken"] # Update token expiration...Also applies to: 96-96, 108-108
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (6)
README.md(1 hunks)chargeamps/external.py(7 hunks)chargeamps/models.py(2 hunks)chargeamps/organisation.py(1 hunks)examples/org_test.py(1 hunks)examples/test.py(1 hunks)
🧰 Additional context used
🪛 Markdownlint (0.35.0)
README.md
11-11: null
Fenced code blocks should have a language specified
(MD040, fenced-code-language)
17-17: null
Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (2)
chargeamps/organisation.py (1)
123-125:
Fix incorrect usage of await payload.json() in loop
In the get_organisation_charging_sessions method, you're calling await payload.json() inside the loop, but payload is already a JSON-decoded object. This will raise an error.
Update the loop to iterate directly over payload:
- for session in await payload.json():
+ for session in payload:Likely invalid or redundant comment.
examples/test.py (1)
8-8: Change looks good
The update to read from "config.json" instead of "credentials.json" ensures consistency with other example scripts.
| def feature_required(feature_flag): | ||
| def decorator(cls): | ||
| if feature_flag: | ||
| return cls | ||
| else: | ||
| raise ImportError(f"Feature '{feature_flag}' is not enabled") | ||
|
|
||
| return decorator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the feature_required decorator to correctly enforce feature flags
The current implementation of the feature_required decorator checks if feature_flag is truthy, but since feature_flag is a non-empty string (e.g., "organisations"), this condition will always be true. As a result, the feature gating doesn't function as intended. Instead, the decorator should check whether the feature is enabled in the application settings or configuration.
Consider modifying the decorator to check a global settings object or configuration that maintains the status of feature flags. For example:
def feature_required(feature_flag):
def decorator(cls):
- if feature_flag:
+ if settings.FEATURE_FLAGS.get(feature_flag, False):
return cls
else:
raise ImportError(f"Feature '{feature_flag}' is not enabled")
return decoratorEnsure that you import the settings module or wherever the feature flags are defined.
Committable suggestion skipped: line range outside the PR's diff.
| async def revoke_organisation_rfid(self, org_id: str, rfid: Rfid) -> None: | ||
| """Revoke an RFID tag""" | ||
| request_uri = f"/api/{API_VERSION}/organisations/{org_id}/rfids/revoke" | ||
| rfid_id = rfid.rfid | ||
| payload = rfid_id.toJson() | ||
| await self._put(request_uri, json=payload) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix incorrect use of toJson() on rfid_id in revoke_organisation_rfid
The variable rfid_id is a string, and strings do not have a toJson() method. This will raise an AttributeError.
Modify the code to construct the payload correctly:
rfid_id = rfid.rfid
- payload = rfid_id.toJson()
+ payload = {"rfid": rfid_id}
await self._put(request_uri, json=payload)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async def revoke_organisation_rfid(self, org_id: str, rfid: Rfid) -> None: | |
| """Revoke an RFID tag""" | |
| request_uri = f"/api/{API_VERSION}/organisations/{org_id}/rfids/revoke" | |
| rfid_id = rfid.rfid | |
| payload = rfid_id.toJson() | |
| await self._put(request_uri, json=payload) | |
| async def revoke_organisation_rfid(self, org_id: str, rfid: Rfid) -> None: | |
| """Revoke an RFID tag""" | |
| request_uri = f"/api/{API_VERSION}/organisations/{org_id}/rfids/revoke" | |
| rfid_id = rfid.rfid | |
| payload = {"rfid": rfid_id} | |
| await self._put(request_uri, json=payload) |
chargeamps/organisation.py
Outdated
| rfid: [Rfid] = None, | ||
| password: str = None, | ||
| ) -> User: | ||
| request_uri = f"/api/{API_VERSION}/organisations/{org_id}/users" | ||
| payload = {} | ||
|
|
||
| if first_name: | ||
| payload["firstName"] = first_name | ||
| if last_name: | ||
| payload["lastName"] = last_name | ||
| if email: | ||
| payload["email"] = email | ||
| if mobile: | ||
| payload["mobile"] = mobile | ||
| if rfid: | ||
| payload["rfidTags"] = rfid.model_dump(by_alias=True) | ||
| if password: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct handling of rfid parameter in add_organisation_user
The parameter rfid is annotated as [Rfid], which is invalid syntax. Additionally, when adding it to the payload, you're calling model_dump on a list, which will cause an error.
Update the type annotation and adjust the payload construction:
- rfid: [Rfid] = None,
+ rfid: Optional[list[Rfid]] = None,
...
if rfid:
- payload["rfidTags"] = rfid.model_dump(by_alias=True)
+ payload["rfidTags"] = [tag.model_dump(by_alias=True) for tag in rfid]Ensure you import Optional and list from the typing module.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| rfid: [Rfid] = None, | |
| password: str = None, | |
| ) -> User: | |
| request_uri = f"/api/{API_VERSION}/organisations/{org_id}/users" | |
| payload = {} | |
| if first_name: | |
| payload["firstName"] = first_name | |
| if last_name: | |
| payload["lastName"] = last_name | |
| if email: | |
| payload["email"] = email | |
| if mobile: | |
| payload["mobile"] = mobile | |
| if rfid: | |
| payload["rfidTags"] = rfid.model_dump(by_alias=True) | |
| if password: | |
| rfid: Optional[list[Rfid]] = None, | |
| password: str = None, | |
| ) -> User: | |
| request_uri = f"/api/{API_VERSION}/organisations/{org_id}/users" | |
| payload = {} | |
| if first_name: | |
| payload["firstName"] = first_name | |
| if last_name: | |
| payload["lastName"] = last_name | |
| if email: | |
| payload["email"] = email | |
| if mobile: | |
| payload["mobile"] = mobile | |
| if rfid: | |
| payload["rfidTags"] = [tag.model_dump(by_alias=True) for tag in rfid] | |
| if password: |
examples/org_test.py
Outdated
| if __name__ == "__main__": | ||
| # asyncio.run(test()) | ||
| loop = asyncio.get_event_loop() | ||
| loop.run_until_complete(test()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use asyncio.run() instead of manual event loop handling
The manual event loop handling is deprecated. Use asyncio.run() for better maintainability.
- loop = asyncio.get_event_loop()
- loop.run_until_complete(test())
+ asyncio.run(test())📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if __name__ == "__main__": | |
| # asyncio.run(test()) | |
| loop = asyncio.get_event_loop() | |
| loop.run_until_complete(test()) | |
| if __name__ == "__main__": | |
| # asyncio.run(test()) | |
| asyncio.run(test()) |
|
Please fix issues and mark as ready for review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
chargeamps/organisation.py (1)
99-99: Remove unnecessary f-string prefixThe f-string on line 99 does not contain any placeholders, so the
fprefix is unnecessary and can be removed.Apply this diff to fix the issue:
- raise ValueError(f"RFID length must be 7 bytes if the (default) format type 'Hex' is set.") + raise ValueError("RFID length must be 7 bytes if the (default) format type 'Hex' is set.")🧰 Tools
🪛 Ruff (0.8.2)
99-99: f-string without any placeholders
Remove extraneous
fprefix(F541)
chargeamps/external.py (1)
243-254: Simplifyget_logged_in_userby returning cached user dataSince
self._usernow holds a validatedUserinstance, you can simplify theget_logged_in_usermethod by returning the cached user data without making an additional API call.Apply this diff to streamline the method:
async def get_logged_in_user(self) -> User: """Get authenticated user info""" if not self._user: raise ValueError("No user is currently logged in") - user_id = self._user.id - request_uri = f"/api/{API_VERSION}/users/{user_id}" - response = await self._get(request_uri) - payload = await response.json() - return User.model_validate(payload) + return self._user
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
chargeamps/external.py(7 hunks)chargeamps/models.py(2 hunks)chargeamps/organisation.py(1 hunks)examples/org_test.py(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- examples/org_test.py
🧰 Additional context used
🪛 Ruff (0.8.2)
chargeamps/organisation.py
99-99: f-string without any placeholders
Remove extraneous f prefix
(F541)
🔇 Additional comments (2)
chargeamps/organisation.py (1)
84-106: Correct parameter types in verify_rfid method
The parameters rfid_length and rfid_dec_format_length are typed as str | None, but they are used as integers in comparisons. This can lead to type errors at runtime. Update the parameter types to int | None for consistency.
🧰 Tools
🪛 Ruff (0.8.2)
99-99: f-string without any placeholders
Remove extraneous f prefix
(F541)
chargeamps/models.py (1)
18-24: Fix the feature_required decorator to correctly enforce feature flags
The current implementation of the feature_required decorator only allows the "organisations" feature, regardless of actual feature flag settings. This does not correctly enforce feature flags as intended.
Consider modifying the decorator to check a configuration or settings object that maintains the status of feature flags. For example:
def feature_required(feature_flag):
def decorator(cls):
- if feature_flag == "organisations":
+ if settings.FEATURE_FLAGS.get(feature_flag, False):
return cls
else:
raise ImportError(f"Feature '{feature_flag}' is not enabled")
return decoratorEnsure you import the settings module or define where FEATURE_FLAGS are stored.
|
Please confirm this still works as expected and I'll merge. |
|
Sorry, I had forgotten that I never finalized this! I will try to find some time too fix the merge with master, fix the review issues above and give it another round of tests. |
|
Finally had some time to look into this. A few small adaptations and from a quick test it seems to work. Sorry, I really want this PR to be finished, but I worked around the issue that required this feature and don't have much time to test it properly :/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
chargeamps/models.py (1)
15-23: Feature flag gating is ineffective; always enabled for "organisations"The decorator returns the class whenever feature_flag == "organisations", regardless of any runtime flag. This defeats gating.
Apply this diff to enforce a real flag via env (minimal, no new deps):
def feature_required(feature_flag): - def decorator(cls): - if feature_flag == "organisations": - return cls - else: - raise ImportError(f"Feature '{feature_flag}' is not enabled") - - return decorator + def decorator(cls): + enabled = os.getenv(f"CHARGEAMPS_FEATURE_{feature_flag.upper()}", "0").lower() in ( + "1", + "true", + "yes", + ) + if enabled: + return cls + raise ImportError(f"Feature '{feature_flag}' is not enabled") + return decoratorAdd the import at top of the file:
import osBased on learnings
chargeamps/external.py (1)
113-116: Store a validated User object in self._user (type consistency)self._user is annotated as User | None but assigned a dict. Validate it to keep type guarantees and simplify consumers.
Apply:
- self._token = response_payload["token"] - self._user = response_payload["user"] + self._token = response_payload["token"] + self._user = User.model_validate(response_payload["user"])Based on learnings
🧹 Nitpick comments (5)
pyproject.toml (1)
39-41: Empty optional-dependencies group — clarify intent or removeorganisations = [] has no effect. If meant to gate optional deps, add packages; otherwise consider removing to avoid confusion.
chargeamps/organisation.py (4)
64-66: Accept lowercase hex; normalize to uppercaseCurrent check rejects lowercase hex. Normalize before validation.
- def is_valid_hex(self, rfid: str) -> bool: - return len(rfid) % 2 == 0 and all(char in "0123456789ABCDEF" for char in rfid) + def is_valid_hex(self, rfid: str) -> bool: + r = rfid.upper() + return len(r) % 2 == 0 and all(ch in "0123456789ABCDEF" for ch in r)Optionally upper() the value where used.
83-89: Tighten verify_rfid signature defaultsrfid_format need not be Optional if defaulting to "Hex".
- def verify_rfid( + def verify_rfid( self, rfid: str, - rfid_format: str | None, + rfid_format: str = "Hex", rfid_length: int | None, rfid_dec_format_length: int | None, ) -> dict[str, str]:
210-213: Variable shadowing: loop variable named 'rfid' shadows parameterRename to avoid confusion.
- res = [] - for rfid in payload: - res.append(User.model_validate(rfid)) - return res + res = [] + for item in payload: + res.append(User.model_validate(item)) + return res
258-259: Docstring mismatchThis method returns a single user, not “users”.
- """Get organisation's registered users""" + """Get organisation user"""
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (5)
chargeamps/external.py(7 hunks)chargeamps/models.py(2 hunks)chargeamps/organisation.py(1 hunks)examples/org_test.py(1 hunks)pyproject.toml(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- examples/org_test.py
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2024-12-17T22:36:01.742Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: chargeamps/external.py:243-251
Timestamp: 2024-12-17T22:36:01.742Z
Learning: In `chargeamps/external.py`, `self._user` is of type `User`, and `id` is not optional; thus, `self._user["id"]` will always exist when a user is assigned.
Applied to files:
chargeamps/external.py
📚 Learning: 2024-12-17T22:36:20.497Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: chargeamps/external.py:218-223
Timestamp: 2024-12-17T22:36:20.497Z
Learning: In `chargeamps/external.py`, ensure that docstrings and error handling remain consistent with the rest of the module, avoiding the addition of detailed documentation or extensive error handling to individual functions unless it's applied uniformly.
Applied to files:
chargeamps/external.py
📚 Learning: 2024-12-17T22:36:01.742Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: chargeamps/external.py:243-251
Timestamp: 2024-12-17T22:36:01.742Z
Learning: In `chargeamps/external.py`, error handling follows a consistent design pattern across the module; additional error handling should be added separately to maintain consistency.
Applied to files:
chargeamps/external.py
📚 Learning: 2024-12-17T22:35:45.820Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: examples/org_test.py:25-25
Timestamp: 2024-12-17T22:35:45.820Z
Learning: In the `python-chargeamps` project, when suggesting improvements to docstrings or error handling, ensure recommendations are consistent with existing coding practices in the codebase.
Applied to files:
chargeamps/external.py
🧬 Code graph analysis (2)
chargeamps/external.py (1)
chargeamps/models.py (2)
User(113-120)Partner(123-128)
chargeamps/organisation.py (2)
chargeamps/external.py (4)
ChargeAmpsExternalClient(28-274)_get(145-149)_put(151-155)_post(139-143)chargeamps/models.py (9)
ChargePoint(40-48)ChargePointStatus(68-71)Rfid(133-136)RfidTag(106-110)Partner(123-128)Organisation(140-143)User(113-120)OrganisationChargingSession(147-161)feature_required(15-22)
🔇 Additional comments (1)
chargeamps/external.py (1)
238-244: Remove await when calling response.json()At chargeamps/external.py lines 242 and 272, change:
- payload = await response.json() + payload = response.json()Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
chargeamps/organisation.py (1)
197-214: Clarify loop variable naming.Line 212 uses
rfidas the loop variable when iterating over users, which is confusing. Consider renaming for clarity.Apply this diff:
res = [] - for rfid in payload: - res.append(User.model_validate(rfid)) + for user in payload: + res.append(User.model_validate(user)) return res
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
chargeamps/external.py(7 hunks)chargeamps/organisation.py(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2024-12-17T22:36:01.742Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: chargeamps/external.py:243-251
Timestamp: 2024-12-17T22:36:01.742Z
Learning: In `chargeamps/external.py`, `self._user` is of type `User`, and `id` is not optional; thus, `self._user["id"]` will always exist when a user is assigned.
Applied to files:
chargeamps/external.py
📚 Learning: 2024-12-17T22:36:20.497Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: chargeamps/external.py:218-223
Timestamp: 2024-12-17T22:36:20.497Z
Learning: In `chargeamps/external.py`, ensure that docstrings and error handling remain consistent with the rest of the module, avoiding the addition of detailed documentation or extensive error handling to individual functions unless it's applied uniformly.
Applied to files:
chargeamps/external.py
📚 Learning: 2024-12-17T22:36:01.742Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: chargeamps/external.py:243-251
Timestamp: 2024-12-17T22:36:01.742Z
Learning: In `chargeamps/external.py`, error handling follows a consistent design pattern across the module; additional error handling should be added separately to maintain consistency.
Applied to files:
chargeamps/external.py
📚 Learning: 2024-12-17T22:35:45.820Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: examples/org_test.py:25-25
Timestamp: 2024-12-17T22:35:45.820Z
Learning: In the `python-chargeamps` project, when suggesting improvements to docstrings or error handling, ensure recommendations are consistent with existing coding practices in the codebase.
Applied to files:
chargeamps/external.py
🧬 Code graph analysis (2)
chargeamps/external.py (1)
chargeamps/models.py (2)
User(113-120)Partner(123-128)
chargeamps/organisation.py (2)
chargeamps/external.py (4)
ChargeAmpsExternalClient(28-267)_get(145-149)_put(151-155)_post(139-143)chargeamps/models.py (9)
ChargePoint(40-48)ChargePointStatus(68-71)Rfid(133-136)RfidTag(106-110)Partner(123-128)Organisation(140-143)User(113-120)OrganisationChargingSession(147-161)feature_required(15-22)
🔇 Additional comments (7)
chargeamps/external.py (2)
52-52: LGTM! User state management is now correct.The user caching mechanism is well-implemented:
self._useris correctly typed asUser | None- Cleared on authentication failures (lines 86, 104)
- Properly validated and stored using
User.model_validate(line 115)This addresses previous concerns about type consistency.
Also applies to: 86-86, 104-104, 115-115
263-267: LGTM! Clean implementation of cached user retrieval.The method correctly returns the cached authenticated user and provides clear error messaging when no user is logged in. This avoids unnecessary API calls and is consistent with the module's design pattern.
Based on learnings.
chargeamps/organisation.py (5)
22-62: LGTM! Well-structured organisation client with proper feature gating.The
OrganisationClientclass is correctly feature-gated and extendsChargeAmpsExternalClient. The data-fetching methods follow consistent patterns:
- Clear endpoint construction
- Proper async/await usage
- Model validation via
model_validate
64-113: LGTM! Robust RFID validation helpers.The RFID validation methods are well-implemented:
is_valid_hex: Correctly validates hex format (even length, valid characters)verify_rfid_length: Properly validates byte length against allowed values (4, 7, 10)verify_rfid: Comprehensive validation with clear error messages for different format types
115-195: LGTM! RFID and partner management methods are correct.The RFID-related methods are well-implemented:
- Proper query parameter handling with
verify_rfidvalidation- Correct payload construction (e.g., line 194 uses dict instead of the previously incorrect
.toJson())- Returns are properly typed and validated
- Consistent with the external client's patterns
216-250: LGTM! User creation with proper validation.The
add_organisation_usermethod correctly:
- Handles optional parameters by conditionally building the payload
- Validates password length (minimum 8 characters) with clear error message
- Properly processes RFID tags by iterating and dumping each (addressing previous review feedback)
- Returns validated User object
252-270: LGTM! Consistent user retrieval implementation.The
get_organisation_usermethod follows the same pattern asget_organisation_users, with proper query parameter handling for optional RFID expansion and decimal format length.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
chargeamps/external.py(7 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2024-12-17T22:36:01.742Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: chargeamps/external.py:243-251
Timestamp: 2024-12-17T22:36:01.742Z
Learning: In `chargeamps/external.py`, `self._user` is of type `User`, and `id` is not optional; thus, `self._user["id"]` will always exist when a user is assigned.
Applied to files:
chargeamps/external.py
📚 Learning: 2024-12-17T22:36:20.497Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: chargeamps/external.py:218-223
Timestamp: 2024-12-17T22:36:20.497Z
Learning: In `chargeamps/external.py`, ensure that docstrings and error handling remain consistent with the rest of the module, avoiding the addition of detailed documentation or extensive error handling to individual functions unless it's applied uniformly.
Applied to files:
chargeamps/external.py
📚 Learning: 2024-12-17T22:36:01.742Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: chargeamps/external.py:243-251
Timestamp: 2024-12-17T22:36:01.742Z
Learning: In `chargeamps/external.py`, error handling follows a consistent design pattern across the module; additional error handling should be added separately to maintain consistency.
Applied to files:
chargeamps/external.py
📚 Learning: 2024-12-17T22:35:45.820Z
Learnt from: yarcod
PR: kirei/python-chargeamps#30
File: examples/org_test.py:25-25
Timestamp: 2024-12-17T22:35:45.820Z
Learning: In the `python-chargeamps` project, when suggesting improvements to docstrings or error handling, ensure recommendations are consistent with existing coding practices in the codebase.
Applied to files:
chargeamps/external.py
🧬 Code graph analysis (1)
chargeamps/external.py (1)
chargeamps/models.py (2)
User(113-120)Partner(123-128)
Apart from operating a single charge station, the Charge Amps API also allows controlling (part) of an organisation's fleet of charge stations and users. Since this is most likely an uncommon usecase, I placed it behind a feature flag "organisations".
This does not add support for CLI flags for accessing the API, but would be a nice future addition.
Summary by CodeRabbit