Skip to content

Conversation

@yarcod
Copy link

@yarcod yarcod commented Dec 10, 2024

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

  • New Features
    • Organisation management via the public client: list/manage organisations, charge points, partners, users, charging sessions, and RFIDs; RFID validation/formatting and organisation-scoped models (feature-gated).
  • Bug Fixes
    • Improved handling and caching of the authenticated user on login/token refresh.
  • Documentation
    • Added "How to use" README section with installation instructions (standard and org-enabled).
  • Chores
    • Examples updated to read config.json; added an optional "organisations" install group.

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.
@jschlyter jschlyter self-requested a review December 11, 2024 05:59
@jschlyter
Copy link
Member

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 11, 2024

✅ 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
Copy link

coderabbitai bot commented Dec 11, 2024

Walkthrough

Adds 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 organisations extras entry in pyproject.

Changes

Cohort / File(s) Change Summary
Docs
README.md
Added "How to use" section with pip installation instructions and optional organisations extra.
Packaging
pyproject.toml
Added optional-dependencies group organisations = [].
Examples
examples/org_test.py, examples/test.py
Added examples/org_test.py demonstrating OrganisationClient; updated examples/test.py to read config.json instead of credentials.json.
Core — models & feature flag
chargeamps/models.py
Added feature_required decorator and new data models: RfidTag, User, Partner, Rfid, Organisation, OrganisationChargingSession; applied @feature_required("organisations") to relevant classes.
Core — external client
chargeamps/external.py
ChargeAmpsExternalClient: added self._user cache; populate/clear user on token acquire/refresh/failure; added async get_partner(charge_point_id: str) -> Partner and get_logged_in_user() -> User.
Core — organisation API client
chargeamps/organisation.py
New OrganisationClient(ChargeAmpsExternalClient) with async methods for organisations, chargepoints, statuses, organisation charging sessions, partners, RFIDs (list/add/get/revoke), users (list/add/get), and RFID validation/format helpers.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

"I hopped through models, methods, and readme bright,
I carried tokens, partners, RFIDs by night.
New clients and examples, a tidy new cart,
I validated hex and gave each field a start.
A rabbit's small cheer for this merged part! 🐇"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “Adds organisation API support” succinctly captures the primary change in this pull request, which introduces organisation‐scoped API methods, models, and feature gating across the client and documentation. It clearly reflects the addition of new organisation-related functionality without extraneous detail or ambiguity.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3bcdec and c79a15e.

📒 Files selected for processing (1)
  • chargeamps/external.py (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • chargeamps/external.py

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.

Copy link

@coderabbitai coderabbitai bot left a 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 comprehensions

In 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 logging

Using print statements in production code is not recommended. Use the logging framework to log debug information.

-    print(payload)
+    self._logger.debug(f"Payload: {payload}")

215-220: Align password length validation condition and error message

The condition checks if len(password) >= 8, but the error message states "must exceed 8 characters," implying len(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 blocks

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between c1e1a9b and e6b71b8.

📒 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: ⚠️ Potential issue

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.

Comment on lines 18 to 25
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
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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 decorator

Ensure that you import the settings module or wherever the feature flags are defined.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 165 to 170
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)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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)

Comment on lines 198 to 214
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:
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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:

Comment on lines 32 to 35
if __name__ == "__main__":
# asyncio.run(test())
loop = asyncio.get_event_loop()
loop.run_until_complete(test())
Copy link

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.

Suggested change
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())

@jschlyter jschlyter marked this pull request as draft December 17, 2024 06:22
@jschlyter
Copy link
Member

jschlyter commented Dec 17, 2024

Please fix issues and mark as ready for review

@yarcod yarcod marked this pull request as ready for review December 17, 2024 22:39
Copy link

@coderabbitai coderabbitai bot left a 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 prefix

The f-string on line 99 does not contain any placeholders, so the f prefix 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 f prefix

(F541)

chargeamps/external.py (1)

243-254: Simplify get_logged_in_user by returning cached user data

Since self._user now holds a validated User instance, you can simplify the get_logged_in_user method 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

📥 Commits

Reviewing files that changed from the base of the PR and between e6b71b8 and f11dda2.

📒 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 decorator

Ensure you import the settings module or define where FEATURE_FLAGS are stored.

@jschlyter
Copy link
Member

Please confirm this still works as expected and I'll merge.

@jschlyter
Copy link
Member

@yarcod
Copy link
Author

yarcod commented Sep 16, 2025

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.

@yarcod
Copy link
Author

yarcod commented Oct 13, 2025

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 :/

Copy link

@coderabbitai coderabbitai bot left a 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 decorator

Add the import at top of the file:

import os

Based 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 remove

organisations = [] 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 uppercase

Current 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 defaults

rfid_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 parameter

Rename 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 mismatch

This 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

📥 Commits

Reviewing files that changed from the base of the PR and between f11dda2 and bbb6e4d.

⛔ Files ignored due to path filters (1)
  • uv.lock is 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.

Copy link

@coderabbitai coderabbitai bot left a 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 rfid as 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

📥 Commits

Reviewing files that changed from the base of the PR and between bbb6e4d and 1a49a6d.

📒 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._user is correctly typed as User | 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 OrganisationClient class is correctly feature-gated and extends ChargeAmpsExternalClient. 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_rfid validation
  • 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_user method 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_user method follows the same pattern as get_organisation_users, with proper query parameter handling for optional RFID expansion and decimal format length.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1a49a6d and a3bcdec.

📒 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)

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.

3 participants