lvi-api-client 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/PKG-INFO +1 -1
- lvi_api_client-0.2.1/VERSION +1 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/src/logicvein/lvi_api_client.py +55 -13
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/src/lvi_api_client.egg-info/PKG-INFO +1 -1
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/tests/test_server_request_shapes.py +48 -0
- lvi_api_client-0.2.0/VERSION +0 -1
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/LICENSE +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/README.md +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/pyproject.toml +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/setup.cfg +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/src/logicvein/__init__.py +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/src/lvi_api_client.egg-info/SOURCES.txt +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/src/lvi_api_client.egg-info/dependency_links.txt +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/src/lvi_api_client.egg-info/requires.txt +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/src/lvi_api_client.egg-info/top_level.txt +0 -0
- {lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/tests/test_lvi_api_client.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.2.1
|
|
@@ -2907,8 +2907,14 @@ class LogicVeinAPIClient:
|
|
|
2907
2907
|
time_zone: str = "Etc/GMT",
|
|
2908
2908
|
paused: bool = False,
|
|
2909
2909
|
filter_name: str = "",
|
|
2910
|
+
trigger_id: Optional[int] = None,
|
|
2910
2911
|
) -> Optional[Dict[str, Any]]:
|
|
2911
|
-
"""Schedule a trigger for an existing job.
|
|
2912
|
+
"""Schedule a trigger for an existing job, or change one it has.
|
|
2913
|
+
|
|
2914
|
+
Without ``trigger_id`` this always adds a trigger, even one identical to
|
|
2915
|
+
a trigger the job already has, so each repeat adds another schedule. Use
|
|
2916
|
+
:meth:`get_job_triggers` to check first. With ``trigger_id`` it updates
|
|
2917
|
+
that trigger in place, and every field is set from these arguments.
|
|
2912
2918
|
|
|
2913
2919
|
For a one-time run, pass an ISO datetime string as ``cron_expression``
|
|
2914
2920
|
(e.g. ``"2026-03-17T09:00"``). Standard cron syntax works for recurring
|
|
@@ -2918,29 +2924,65 @@ class LogicVeinAPIClient:
|
|
|
2918
2924
|
job_id (int): ID of the job to schedule (returned by :meth:`save_job`).
|
|
2919
2925
|
trigger_name (str): Display name for the trigger.
|
|
2920
2926
|
cron_expression (str): ISO datetime (one-time) or cron expression (recurring).
|
|
2921
|
-
time_zone (str): Timezone name (e.g. ``"America/Halifax"``). Default ``"
|
|
2927
|
+
time_zone (str): Timezone name (e.g. ``"America/Halifax"``). Default ``"Etc/GMT"``.
|
|
2922
2928
|
paused (bool): Whether to create the trigger in a paused state.
|
|
2923
2929
|
filter_name (str): Optional filter name; pass empty string for none.
|
|
2930
|
+
trigger_id (Optional[int]): ID of the trigger to update, or None to add one.
|
|
2924
2931
|
|
|
2925
2932
|
Returns:
|
|
2926
2933
|
Dict[str, Any]: The saved trigger record (contains ``triggerId``,
|
|
2927
2934
|
``nextExpectedFireTime``, etc.), or None on failure.
|
|
2928
2935
|
"""
|
|
2929
|
-
|
|
2930
|
-
"
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
)
|
|
2936
|
+
trigger_data: Dict[str, Any] = {
|
|
2937
|
+
"triggerName": trigger_name,
|
|
2938
|
+
"paused": paused,
|
|
2939
|
+
"cronExpression": cron_expression,
|
|
2940
|
+
"timeZone": time_zone,
|
|
2941
|
+
"filterName": filter_name,
|
|
2942
|
+
"jobId": job_id,
|
|
2943
|
+
}
|
|
2944
|
+
if trigger_id is not None:
|
|
2945
|
+
trigger_data["triggerId"] = trigger_id
|
|
2946
|
+
response = self.call("Scheduler.saveTrigger", triggerData=trigger_data)
|
|
2940
2947
|
if response and isinstance(response, dict) and "result" in response:
|
|
2941
2948
|
return response["result"]
|
|
2942
2949
|
return None
|
|
2943
2950
|
|
|
2951
|
+
def get_job_triggers(self, job_id: int) -> List[Dict[str, Any]]:
|
|
2952
|
+
"""Get the triggers of a job.
|
|
2953
|
+
|
|
2954
|
+
Args:
|
|
2955
|
+
job_id (int): ID of the job. An unknown job has no triggers.
|
|
2956
|
+
|
|
2957
|
+
Returns:
|
|
2958
|
+
List[Dict[str, Any]]: The trigger records (``triggerId``, ``jobId``,
|
|
2959
|
+
``triggerName``, ``cronExpression``, ``timeZone``, ``paused``,
|
|
2960
|
+
``filterName``, ``nextExpectedFireTime``, ``accessLimited``). The list
|
|
2961
|
+
is empty only when the server says the job has none.
|
|
2962
|
+
|
|
2963
|
+
Raises:
|
|
2964
|
+
ValueError: If the server's answer is not a list. Callers check this list
|
|
2965
|
+
before adding a trigger, so a failed read must not look like no triggers.
|
|
2966
|
+
"""
|
|
2967
|
+
response = self.call("Scheduler.getTriggersOfJob", jobId=job_id)
|
|
2968
|
+
triggers = response.get("result") if isinstance(response, dict) else None
|
|
2969
|
+
if not isinstance(triggers, list):
|
|
2970
|
+
raise ValueError(f"Scheduler.getTriggersOfJob did not return a list for job {job_id}")
|
|
2971
|
+
return triggers
|
|
2972
|
+
|
|
2973
|
+
def delete_trigger(self, trigger_id: int) -> bool:
|
|
2974
|
+
"""Delete a trigger.
|
|
2975
|
+
|
|
2976
|
+
Args:
|
|
2977
|
+
trigger_id (int): ID of the trigger to delete.
|
|
2978
|
+
|
|
2979
|
+
Returns:
|
|
2980
|
+
bool: The server's answer. It answers True for a trigger that is
|
|
2981
|
+
already gone, so this does not show that one existed.
|
|
2982
|
+
"""
|
|
2983
|
+
response = self.call("Scheduler.deleteTrigger", triggerId=trigger_id)
|
|
2984
|
+
return bool(response and isinstance(response, dict) and response.get("result"))
|
|
2985
|
+
|
|
2944
2986
|
def create_command_runner_job(
|
|
2945
2987
|
self,
|
|
2946
2988
|
device_ip: str,
|
|
@@ -281,6 +281,54 @@ XML = (
|
|
|
281
281
|
SAVED = {"Compliance.saveRuleSet": {"ruleSetId": 5, "ruleSetName": "rs1"}}
|
|
282
282
|
|
|
283
283
|
|
|
284
|
+
class TestTriggers:
|
|
285
|
+
TRIGGER = {
|
|
286
|
+
"triggerName": "t1",
|
|
287
|
+
"paused": True,
|
|
288
|
+
"cronExpression": "0 2 * * *",
|
|
289
|
+
"timeZone": "Etc/GMT",
|
|
290
|
+
"filterName": "",
|
|
291
|
+
"jobId": 7,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
def save(self, connect, **extra):
|
|
295
|
+
client, server = connect({"Scheduler.saveTrigger": {"triggerId": 3}})
|
|
296
|
+
saved = client.save_trigger(job_id=7, trigger_name="t1", cron_expression="0 2 * * *", paused=True, **extra)
|
|
297
|
+
return saved, server.sent("Scheduler.saveTrigger")
|
|
298
|
+
|
|
299
|
+
def test_a_save_without_an_id_sends_none_so_the_server_adds_one(self, connect):
|
|
300
|
+
saved, sent = self.save(connect)
|
|
301
|
+
assert saved == {"triggerId": 3}
|
|
302
|
+
assert sent == {"triggerData": self.TRIGGER}
|
|
303
|
+
|
|
304
|
+
def test_a_save_with_an_id_sends_it_so_the_server_updates_that_trigger(self, connect):
|
|
305
|
+
_, sent = self.save(connect, trigger_id=3)
|
|
306
|
+
assert sent == {"triggerData": {**self.TRIGGER, "triggerId": 3}}
|
|
307
|
+
|
|
308
|
+
def test_triggers_of_a_job_are_read_by_job_id(self, connect):
|
|
309
|
+
rows = [{"triggerId": 3, "triggerName": "t1"}]
|
|
310
|
+
client, server = connect({"Scheduler.getTriggersOfJob": rows})
|
|
311
|
+
assert client.get_job_triggers(7) == rows
|
|
312
|
+
assert server.sent("Scheduler.getTriggersOfJob") == {"jobId": 7}
|
|
313
|
+
|
|
314
|
+
def test_a_job_with_no_triggers_gives_an_empty_list(self, connect):
|
|
315
|
+
client, _ = connect({"Scheduler.getTriggersOfJob": []})
|
|
316
|
+
assert client.get_job_triggers(7) == []
|
|
317
|
+
|
|
318
|
+
@pytest.mark.parametrize("answer", [None, {"result": None}, {}, "<html>error</html>"])
|
|
319
|
+
def test_an_answer_that_is_not_a_list_raises_rather_than_reading_as_no_triggers(self, connect, answer):
|
|
320
|
+
client, _ = connect()
|
|
321
|
+
with patch.object(client, "call", return_value=answer):
|
|
322
|
+
with pytest.raises(ValueError):
|
|
323
|
+
client.get_job_triggers(7)
|
|
324
|
+
|
|
325
|
+
@pytest.mark.parametrize("answer", [True, False])
|
|
326
|
+
def test_a_delete_is_by_trigger_id_and_returns_the_answer(self, connect, answer):
|
|
327
|
+
client, server = connect({"Scheduler.deleteTrigger": answer})
|
|
328
|
+
assert client.delete_trigger(3) is answer
|
|
329
|
+
assert server.sent("Scheduler.deleteTrigger") == {"triggerId": 3}
|
|
330
|
+
|
|
331
|
+
|
|
284
332
|
class TestCreateRuleSet:
|
|
285
333
|
def test_an_agreeing_document_is_sent_unchanged(self, connect):
|
|
286
334
|
client, server = connect(SAVED)
|
lvi_api_client-0.2.0/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.2.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lvi_api_client-0.2.0 → lvi_api_client-0.2.1}/src/lvi_api_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|