-
Notifications
You must be signed in to change notification settings - Fork 6
feat: enhance file module with improved documentation and tests #92
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
Changes from 32 commits
46da58a
354f3a2
90e9777
009181e
2dcbe84
72e216b
a6ecd20
8d3effb
647388b
f9defd4
aadb14c
7f0d214
b830d8c
aa2c3e9
d455085
73b21b0
1983eeb
db5e300
aca1fd6
2b605cf
fe9e928
64a9fbb
fb81471
299063b
721fa35
f3f203b
2afc2cc
d21901c
db041a6
a62300c
0385e01
e5043ea
8dfba01
f5e2a29
7f72379
f81b6de
077c95f
01f22bd
8f30319
2eea376
1b37310
42f9468
b5b6d64
0f2e6ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,6 @@ | |
| # This module contains helper methods for calling file.* Notecard API commands. | ||
| # This module is optional and not required for use with the Notecard. | ||
|
|
||
| import notecard | ||
| from notecard.validators import validate_card_object | ||
|
|
||
|
|
||
|
|
@@ -23,14 +22,31 @@ def changes(card, tracker=None, files=None): | |
| files (array): A list of Notefiles to retrieve changes for. | ||
|
|
||
| Returns: | ||
| string: The result of the Notecard request. | ||
| dict: The result of the Notecard request containing: | ||
| - changes (int): Notes with pending changes | ||
| - total (int): Total Notes | ||
| - info (dict): Per-file details | ||
| """ | ||
| req = {"req": "file.changes"} | ||
| if tracker: | ||
| req["tracker"] = tracker | ||
| if files: | ||
| if files is not None: # Allow empty list | ||
| req["files"] = files | ||
| return card.Transaction(req) | ||
|
|
||
| response = card.Transaction(req) | ||
| if "err" in response: | ||
| return response | ||
| # Check for required fields first | ||
| if not all(key in response for key in ['total', 'changes', 'info']): | ||
| return {"err": "Missing required fields in response"} | ||
| # Then validate field types | ||
| if not isinstance(response['total'], int): | ||
| return {"err": "Malformed response: total must be an integer"} | ||
| if not isinstance(response['changes'], int): | ||
| return {"err": "Malformed response: changes must be an integer"} | ||
| if not isinstance(response['info'], dict): | ||
| return {"err": "Malformed response: info must be a dictionary"} | ||
| return response | ||
|
|
||
|
|
||
| @validate_card_object | ||
|
|
@@ -42,27 +58,47 @@ def delete(card, files=None): | |
| files (array): A list of Notefiles to delete. | ||
|
|
||
| Returns: | ||
| string: The result of the Notecard request. | ||
| dict: The result of the Notecard request. An empty object {} indicates | ||
| success. | ||
| """ | ||
| req = {"req": "file.delete"} | ||
| if files: | ||
| req["files"] = files | ||
| return card.Transaction(req) | ||
| response = card.Transaction(req) | ||
| return response | ||
|
|
||
|
|
||
| @validate_card_object | ||
| def stats(card): | ||
| """Obtain statistics about local notefiles. | ||
| def stats(card, file=None): | ||
| """Get resource statistics about local Notefiles. | ||
|
|
||
| Args: | ||
| card (Notecard): The current Notecard object. | ||
| file (string, optional): Return stats for the specified Notefile only. | ||
|
|
||
| Returns: | ||
| string: The result of the Notecard request. | ||
| dict: The result of the Notecard request containing: | ||
| - total (int): Total Notes across all Notefiles | ||
| - changes (int): Notes pending sync | ||
| - sync (bool): True if sync is recommended | ||
| """ | ||
| req = {"req": "file.stats"} | ||
|
|
||
| return card.Transaction(req) | ||
| if file: | ||
| req["file"] = file | ||
| response = card.Transaction(req) | ||
| if "err" in response: | ||
| return response | ||
| # Check for required fields | ||
|
||
| if not all(key in response for key in ['total', 'changes', 'sync']): | ||
| return {"err": "Missing required fields in response"} | ||
| # Validate field types | ||
| if not isinstance(response['total'], int): | ||
| return {"err": "Malformed response: total must be an integer"} | ||
| if not isinstance(response['changes'], int): | ||
| return {"err": "Malformed response: changes must be an integer"} | ||
| if not isinstance(response['sync'], bool): | ||
| return {"err": "Malformed response: sync must be a boolean"} | ||
| return response | ||
|
|
||
|
|
||
| @validate_card_object | ||
|
|
@@ -73,8 +109,19 @@ def pendingChanges(card): | |
| card (Notecard): The current Notecard object. | ||
|
|
||
| Returns: | ||
| string: The result of the Notecard request. | ||
| dict: The result of the Notecard request containing pending changes | ||
| information. | ||
| """ | ||
| req = {"req": "file.changes.pending"} | ||
|
|
||
| return card.Transaction(req) | ||
| response = card.Transaction(req) | ||
| if "err" in response: | ||
| return response | ||
| # Validate response format - should contain total and changes | ||
|
||
| if not all(key in response for key in ['total', 'changes']): | ||
| return {"err": "Missing required fields in response"} | ||
| # Validate field types | ||
| if not isinstance(response.get('total'), int): | ||
| return {"err": "Malformed response: total must be an integer"} | ||
| if not isinstance(response.get('changes'), int): | ||
| return {"err": "Malformed response: changes must be an integer"} | ||
| return response | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,26 +9,28 @@ | |
| # This module contains helper methods for calling note.* Notecard API commands. | ||
| # This module is optional and not required for use with the Notecard. | ||
|
|
||
| import notecard | ||
| from notecard.validators import validate_card_object | ||
|
|
||
|
|
||
| @validate_card_object | ||
| def add(card, file=None, body=None, payload=None, sync=None, port=None): | ||
| def add(card, file=None, body=None, payload=None, binary=None, sync=None, port=None): | ||
| """Add a Note to a Notefile. | ||
|
|
||
| Args: | ||
| card (Notecard): The current Notecard object. | ||
| file (string): The name of the file. | ||
| body (JSON object): A developer-defined tracker ID. | ||
| payload (string): An optional base64-encoded string. | ||
| binary (bool): When true, indicates the note contains binary data. | ||
| sync (bool): Perform an immediate sync after adding. | ||
| port (int): If provided, a unique number to represent a notefile. | ||
| Required for Notecard LoRa. | ||
|
|
||
| Returns: | ||
| string: The result of the Notecard request. | ||
| """ | ||
| from notecard import binary_helpers | ||
|
|
||
| req = {"req": "note.add"} | ||
| if file: | ||
| req["file"] = file | ||
|
|
@@ -40,6 +42,12 @@ def add(card, file=None, body=None, payload=None, sync=None, port=None): | |
| req["port"] = port | ||
| if sync is not None: | ||
| req["sync"] = sync | ||
|
|
||
| if binary is not None: | ||
| if not isinstance(binary, bool): | ||
| return {"err": "binary parameter must be a boolean"} | ||
| req["binary"] = binary | ||
|
|
||
| return card.Transaction(req) | ||
|
|
||
|
|
||
|
|
@@ -93,8 +101,11 @@ def get(card, file="data.qi", note_id=None, delete=None, deleted=None): | |
| deleted (bool): Whether to allow retrieval of a deleted note. | ||
|
|
||
| Returns: | ||
| string: The result of the Notecard request. | ||
| dict: The result of the Notecard request. If the note contains binary data, | ||
| the 'binary' field in the response will contain the binary data as a bytearray. | ||
| """ | ||
| from notecard import binary_helpers | ||
|
|
||
| req = {"req": "note.get"} | ||
| req["file"] = file | ||
| if note_id: | ||
|
|
@@ -103,6 +114,7 @@ def get(card, file="data.qi", note_id=None, delete=None, deleted=None): | |
| req["delete"] = delete | ||
| if deleted is not None: | ||
| req["deleted"] = deleted | ||
|
|
||
| return card.Transaction(req) | ||
|
|
||
|
|
||
|
|
@@ -154,34 +166,80 @@ def update(card, file=None, note_id=None, body=None, payload=None): | |
|
|
||
|
|
||
| @validate_card_object | ||
| def template(card, file=None, body=None, length=None, port=None, compact=False): | ||
| def template(card, file=None, body=None, length=None, port=None, | ||
| format=None, compact=None, verify=None, delete=None): | ||
| """Create a template for new Notes in a Notefile. | ||
|
|
||
| Args: | ||
| card (Notecard): The current Notecard object. | ||
| file (string): The file name of the notefile. | ||
| body (JSON): A sample JSON body that specifies field names and | ||
| values as "hints" for the data type. | ||
| values as "hints" for the data type. Supported types are: | ||
| boolean, integer, float, and string. | ||
| length (int): If provided, the maximum length of a payload that | ||
| can be sent in Notes for the template Notefile. | ||
| port (int): If provided, a unique number to represent a notefile. | ||
| Required for Notecard LoRa. | ||
| compact (boolean): If true, sets the format to compact to tell the | ||
| Notecard to omit this additional metadata to save on storage | ||
| and bandwidth. Required for Notecard LoRa. | ||
| format (string): If set to "compact", tells the Notecard to omit | ||
| additional metadata to save on storage and bandwidth. | ||
| compact (bool): Legacy parameter. If True, equivalent to setting | ||
| format="compact". Retained for backward compatibility. | ||
| verify (bool): When True, verifies the template against existing | ||
| notes in the Notefile. | ||
| delete (bool): When True, deletes the template from the Notefile. | ||
|
|
||
| Returns: | ||
| string: The result of the Notecard request. | ||
| dict: The result of the Notecard request. Returns error object if | ||
| validation fails. | ||
| """ | ||
| req = {"req": "note.template"} | ||
| if file: | ||
| req["file"] = file | ||
|
|
||
| if body: | ||
| for key, value in body.items(): | ||
rdlauer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if not isinstance(value, (bool, int, float, str)): | ||
| return { | ||
| "err": ( | ||
| f"Field '{key}' has unsupported type. " | ||
| "Must be boolean, integer, float, or string.") | ||
| } | ||
| if isinstance(value, float) and value.is_integer(): | ||
| body[key] = int(value) | ||
| req["body"] = body | ||
| if length: | ||
|
|
||
| if verify is not None: | ||
| if not isinstance(verify, bool): | ||
| return {"err": "verify parameter must be a boolean"} | ||
|
|
||
| if length is not None: | ||
| if not isinstance(length, int) or length < 0: | ||
|
||
| return {"err": "Length must be a non-negative integer"} | ||
| req["length"] = length | ||
| if port: | ||
|
|
||
| if port is not None: | ||
| if not isinstance(port, int) or not (1 <= port <= 100): | ||
| return {"err": "Port must be an integer between 1 and 100"} | ||
| req["port"] = port | ||
| if compact: | ||
|
|
||
| if compact is True: | ||
| format = "compact" | ||
|
|
||
| if format == "compact": | ||
| req["format"] = "compact" | ||
| if body: | ||
| allowed_metadata = {"_time", "_lat", "_lon", "_loc"} | ||
|
||
| for key in body.keys(): | ||
| if key.startswith("_") and key not in allowed_metadata: | ||
| return { | ||
| "err": ( | ||
| f"Field '{key}' is not allowed in compact mode. " | ||
| f"Only {allowed_metadata} are allowed.") | ||
| } | ||
|
|
||
| if verify is not None: | ||
| req["verify"] = verify | ||
| if delete is not None: | ||
| req["delete"] = delete | ||
|
|
||
| return card.Transaction(req) | ||
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.
checking for "required" fields in the response is not a valid test. this is because notecard uses omitempty by default, meaning if there is a 0, false, or empty string "", that value would be omitted from the response.