updating validators, time handling, creating tests for time & schemas. Decoupling schema validation according to the dataclasses. Starting to stub & create notifications

This commit is contained in:
max_metz 2023-09-15 13:16:21 +02:00
parent 4195655e4e
commit 3edc6d86ba
11 changed files with 218 additions and 2 deletions

1
.gitignore vendored
View File

@ -442,4 +442,5 @@ src/notebooks_metz
docs/traffic_light_examples
**/.~lock*
misc/berths_and_terminals.csv
times.md

View File

@ -37,6 +37,8 @@ dependencies:
- conda-forge::cached_property>=1.5.2=pyha770c72_1
- conda-forge::dsnparse>=0.2.1=pyhd8ed1ab_0
- conda-forge::schedule>=1.2.0=pyhd8ed1ab_0
- pip:
# pip packages and wheels
- pyjwt==2.7.0

View File

@ -1,6 +1,8 @@
from ._version import __version__
from brecal_utils.file_handling import get_project_root, ensure_path
from brecal_utils.test_handling import execute_test_with_pytest, execute_coverage_test
from brecal_utils.time_handling import difference_to_then
from brecal_utils.validators.time_logic import TimeLogic
from brecal_utils.validators.validation_rules import ValidationRules
from brecal_utils.validators.schema_validation import validation_state_and_validation_name
@ -10,6 +12,7 @@ __all__ = [
"ensure_path",
"execute_test_with_pytest",
"execute_coverage_test",
"difference_to_then",
"TimeLogic",
"ValidationRules",
"validation_state_and_validation_name",

View File

@ -0,0 +1,49 @@
import datetime
from brecal_utils.stubs import generate_uuid1_int
from BreCal.schemas.model import Notification
def get_notification_simple():
"""creates a default notification, where 'created' is now, and modified is now+10 seconds"""
notification_id = generate_uuid1_int() # uid?
times_id = generate_uuid1_int() # uid?
acknowledged = False
level = 10
type = 0
message = "hello world"
created = datetime.datetime.now()
modified = created+datetime.timedelta(seconds=10)
notification = Notification(
notification_id,
times_id,
acknowledged,
level,
type,
message,
created,
modified
)
return notification
def get_notification_in_the_past(created_delta_seconds, modified_delta_seconds, acknowledged=False):
"""
creates a notification of the past, where the
'created' date is {created_delta_seconds} seconds ago
'modified' date is {modified_delta_seconds} seconds ago
for example, if datetime.datetime.now() returns
now = datetime.datetime(2023, 9, 15, 7, 25, 50, 733644)), then calling this function
as get_notification_modified_in_the_past(2*60, 1*60) provides
'created':datetime.datetime(2023, 9, 15, 7, 23, 50, 733644) (two minutes ago)
'modified':datetime.datetime(2023, 9, 15, 7, 24, 50, 733644) (one minute ago)
optionally, one can also overwrite the 'acknowledged' attribute
returns notification
"""
notification = get_notification_simple()
notification.created = datetime.datetime.now()-datetime.timedelta(seconds=created_delta_seconds)
notification.modified = datetime.datetime.now()-datetime.timedelta(seconds=modified_delta_seconds)
notification.acknowledged = acknowledged
return notification

View File

@ -0,0 +1,21 @@
import datetime
def difference_to_then(event_time, tgt_time=None, make_absolute=False):
"""
measures the difference between {tgt_time} and {event_time}. this function automatically converts the datetime.timedelta object to seconds.
tgt_time defaults to {now}, if it is not specified.
Note: using divmod(time_diff, interval_duration) may be interesting to determine, how many units of {interval_duration} have passed.
e.g.,
divmod(time_diff, 3600) returns a float of hours. This will then return a tuple
options:
make_absolute: bool. Whether to return an absolute difference
Returns: time_diff (float)
"""
tgt_time = tgt_time or datetime.datetime.now()
time_diff = tgt_time - event_time
if make_absolute:
return abs(time_diff.total_seconds())
return time_diff.total_seconds()

View File

@ -51,6 +51,12 @@ def test_build_stub_times():
assert isinstance(times, Times)
return
def test_build_stub_notification():
from BreCal.schemas.model import Notification
from brecal_utils.stubs.notification import get_notification_simple
notification = get_notification_simple()
assert isinstance(notification, Notification)
if __name__=="__main__":
test_build_stub_berth()
test_build_stub_participant()
@ -60,3 +66,4 @@ if __name__=="__main__":
test_build_stub_tug()
test_build_stub_shipcall()
test_build_stub_times()
test_build_stub_notification()

View File

@ -79,6 +79,19 @@ def test_import_bcrypt():
import bcrypt
return
def test_import_math():
"""math.isclose can be interesting to measure differences between two times (e.g., to ignore milliseconds)"""
import math
math.isclose
return
def test_import_datetime():
"""datetime is the default library for times"""
import datetime
datetime.datetime.now()
return
if __name__=="__main__":
test_import_colorama()
test_import_matplotlib()

View File

@ -0,0 +1,50 @@
import pytest
def test_difference_to_then_tgt_time_none():
import math
import datetime
from brecal_utils import difference_to_then
difference_in_seconds = 42
event_time = datetime.datetime.now() - datetime.timedelta(seconds=difference_in_seconds)
event_time_diff = difference_to_then(event_time) # tgt_time = datetime.datetime.now()
# {difference_to_then} internally creates a .now() time, when the {then_time} is not defined
# hence, the difference will never be exactly 42 seconds due to slight latency
# math.isclose allows deviations up to 0.05 seconds
assert math.isclose(42, event_time_diff, abs_tol=0.05), f"both times are reasonably close"
return
def test_difference_to_then_tgt_time_not_none():
import math
import datetime
from brecal_utils import difference_to_then
difference_in_seconds = 42
event_time = datetime.datetime(2000, 1, 1, 0, 0, 0)
tgt_time = event_time - datetime.timedelta(seconds=difference_in_seconds)
event_time_diff = difference_to_then(event_time, tgt_time)
# tgt time is -42 seconds, as it is 42 seconds before event_time
assert event_time_diff==-42, f"event time difference is incorrect"
return
def test_difference_to_then_tgt_time_not_none_make_absolute():
import math
import datetime
from brecal_utils import difference_to_then
difference_in_seconds = 42
event_time = datetime.datetime(2000, 1, 1, 0, 0, 0)
tgt_time = event_time - datetime.timedelta(seconds=difference_in_seconds)
event_time_diff = difference_to_then(event_time, tgt_time, make_absolute=True) # difference: -42. make_absolute: +42
# tgt time is -42 seconds, as it is 42 seconds before event_time. However, we are interested in an absolute value
assert event_time_diff==42, f"event time difference is incorrect"
return
if __name__=="__main__":
test_difference_to_then()

View File

@ -0,0 +1,12 @@
import pytest
from brecal_utils.stubs.berth import get_berth_simple
def test_berth():
berth = get_berth_simple()
raise ValueError("copied from ships.")
from brecal_utils.validators.schema_validation import test____
ship = get_ship_simple()
ship.length = 234
assert ship_length_in_range(ship)[0], f"ship length must be between 0 and 500 meters"
return

View File

@ -0,0 +1,28 @@
import pytest
from brecal_utils.stubs.participant import get_participant_simple
def test_participant_postal_code_len_is_five():
from brecal_utils.validators.schema_validation import participant_postal_code_len_is_five
participant = get_participant_simple()
assert participant_postal_code_len_is_five(participant)[0], f"the postal code should be exactly 5 numbers"
return
def test_participant_postal_code_len_is_six_should_assert():
from brecal_utils.validators.schema_validation import participant_postal_code_len_is_five
participant = get_participant_simple()
participant.postal_code = "123456"
with pytest.raises(AssertionError, match="the postal code should be exactly 5 numbers"):
assert participant_postal_code_len_is_five(participant)[0], f"the postal code should be exactly 5 numbers"
return
# TODO_postal_code_zero -> assert? Is postal_code mandatory?
if __name__=="__main__":
test_participant_postal_code_len_is_five()
test_participant_postal_code_len_is_six_should_assert()

View File

@ -1,14 +1,15 @@
import pytest
from brecal_utils.stubs.ship import get_ship_simple
from brecal_utils.validators.schema_validation import ship_length_in_range, ship_width_in_range, ship_bollard_pull_is_none_or_in_range, ship_participant_id_is_none_or_int, ship_max_draft_is_none_or_in_range, ship_eni_len_is_eight, ship_callsign_len_is_seven_at_maximum, ship_imo_len_is_seven, ship_bollard_pull_is_defined_or_is_not_tug, ship_max_draft_is_defined_or_is_not_tug, ship_participant_id_is_defined_or_is_not_tug
def test_ship_length_valid_range_234_is_valid():
from brecal_utils.validators.schema_validation import ship_length_in_range
ship = get_ship_simple()
ship.length = 234
assert ship_length_in_range(ship)[0], f"ship length must be between 0 and 500 meters"
return
def test_ship_length_maximum_not_valid_range():
from brecal_utils.validators.schema_validation import ship_length_in_range
ship = get_ship_simple()
ship.length = 500
with pytest.raises(AssertionError):
@ -16,6 +17,7 @@ def test_ship_length_maximum_not_valid_range():
return
def test_ship_length_minimum_not_valid_range():
from brecal_utils.validators.schema_validation import ship_length_in_range
ship = get_ship_simple()
ship.length = 0
with pytest.raises(AssertionError):
@ -23,12 +25,14 @@ def test_ship_length_minimum_not_valid_range():
return
def test_ship_width_valid_range_137_is_valid():
from brecal_utils.validators.schema_validation import ship_width_in_range
ship = get_ship_simple()
ship.width = 137
assert ship_width_in_range(ship)[0], f"ship width must be between 0 and 500 meters"
return
def test_ship_width_maximum_not_valid_range():
from brecal_utils.validators.schema_validation import ship_width_in_range
ship = get_ship_simple()
ship.width = 500
with pytest.raises(AssertionError):
@ -36,6 +40,7 @@ def test_ship_width_maximum_not_valid_range():
return
def test_ship_width_minimum_not_valid_range():
from brecal_utils.validators.schema_validation import ship_width_in_range
ship = get_ship_simple()
ship.width = 0
with pytest.raises(AssertionError):
@ -44,6 +49,7 @@ def test_ship_width_minimum_not_valid_range():
# not tug: values can be None and raise no error
def test_ship_bollard_pull_is_none_and_not_tug():
from brecal_utils.validators.schema_validation import ship_bollard_pull_is_none_or_in_range
ship = get_ship_simple()
ship.is_tug = False
ship.bollard_pull = None
@ -51,6 +57,7 @@ def test_ship_bollard_pull_is_none_and_not_tug():
return
def test_ship_participant_id_is_none_and_not_tug():
from brecal_utils.validators.schema_validation import ship_participant_id_is_none_or_int
ship = get_ship_simple()
ship.is_tug = False
ship.participant_id = None
@ -58,6 +65,7 @@ def test_ship_participant_id_is_none_and_not_tug():
return
def test_ship_max_draft_is_none_and_not_tug():
from brecal_utils.validators.schema_validation import ship_max_draft_is_none_or_in_range
ship = get_ship_simple()
ship.is_tug = False
ship.max_draft = None
@ -67,6 +75,7 @@ def test_ship_max_draft_is_none_and_not_tug():
# tug: values must be set, and are set. all tests should be accepted without assertion
def test_ship_is_tug_bollard_pull_is_not_none():
from brecal_utils.validators.schema_validation import ship_bollard_pull_is_none_or_in_range
ship = get_ship_simple()
ship.is_tug = True
ship.bollard_pull = 311
@ -75,6 +84,7 @@ def test_ship_is_tug_bollard_pull_is_not_none():
def test_ship_is_tug_max_draft_is_not_none():
from brecal_utils.validators.schema_validation import ship_max_draft_is_none_or_in_range
ship = get_ship_simple()
ship.is_tug = True
ship.max_draft = 17
@ -82,6 +92,7 @@ def test_ship_is_tug_max_draft_is_not_none():
return
def test_ship_is_tug_participant_id_is_not_none():
from brecal_utils.validators.schema_validation import ship_participant_id_is_none_or_int
from brecal_utils.stubs import generate_uuid1_int
ship = get_ship_simple()
ship.is_tug = True
@ -90,6 +101,7 @@ def test_ship_is_tug_participant_id_is_not_none():
return
def test_ship_is_tug_participant_id_is_str_and_fails():
from brecal_utils.validators.schema_validation import ship_participant_id_is_none_or_int
# note: this is an artificial test case. However, it ensures that operators using the backend cannot create an id incorrectly
from brecal_utils.stubs import generate_uuid1_int
ship = get_ship_simple()
@ -102,6 +114,7 @@ def test_ship_is_tug_participant_id_is_str_and_fails():
# tug: values must be set, but are not. all tests should raise AssertionError
def test_ship_is_tug_bollard_pull_but_is_none_fails():
from brecal_utils.validators.schema_validation import ship_bollard_pull_is_defined_or_is_not_tug
ship = get_ship_simple()
ship.is_tug = True
ship.bollard_pull = None
@ -110,6 +123,7 @@ def test_ship_is_tug_bollard_pull_but_is_none_fails():
return
def test_ship_is_tug_max_draft_but_is_none_fails():
from brecal_utils.validators.schema_validation import ship_max_draft_is_defined_or_is_not_tug
ship = get_ship_simple()
ship.is_tug = True
ship.max_draft = None
@ -118,6 +132,7 @@ def test_ship_is_tug_max_draft_but_is_none_fails():
return
def test_ship_is_tug_participant_id_but_is_none_fails():
from brecal_utils.validators.schema_validation import ship_participant_id_is_defined_or_is_not_tug
ship = get_ship_simple()
ship.is_tug = True
ship.participant_id = None
@ -129,6 +144,7 @@ def test_ship_is_tug_participant_id_but_is_none_fails():
# tug: values must be in valid range
# # sequence: 1.) is valid, 2.) is too small, 3.) is too large
def test_ship_is_tug_bollard_pull_in_range_311_valid():
from brecal_utils.validators.schema_validation import ship_bollard_pull_is_none_or_in_range
ship = get_ship_simple()
ship.is_tug = True
ship.bollard_pull = 311
@ -136,6 +152,7 @@ def test_ship_is_tug_bollard_pull_in_range_311_valid():
return
def test_ship_is_tug_bollard_pull_in_range_minimum_not_valid():
from brecal_utils.validators.schema_validation import ship_bollard_pull_is_none_or_in_range
ship = get_ship_simple()
ship.is_tug = True
ship.bollard_pull = 0
@ -144,6 +161,7 @@ def test_ship_is_tug_bollard_pull_in_range_minimum_not_valid():
return
def test_ship_is_tug_bollard_pull_in_range_maximum_not_valid():
from brecal_utils.validators.schema_validation import ship_bollard_pull_is_none_or_in_range
ship = get_ship_simple()
ship.is_tug = True
ship.bollard_pull = 500
@ -152,6 +170,7 @@ def test_ship_is_tug_bollard_pull_in_range_maximum_not_valid():
return
def test_ship_is_tug_max_draft_in_range_11_valid():
from brecal_utils.validators.schema_validation import ship_max_draft_is_none_or_in_range
ship = get_ship_simple()
ship.is_tug = True
ship.max_draft = 11
@ -159,6 +178,7 @@ def test_ship_is_tug_max_draft_in_range_11_valid():
return
def test_ship_is_tug_max_draft_in_range_minimum_not_valid():
from brecal_utils.validators.schema_validation import ship_max_draft_is_none_or_in_range
ship = get_ship_simple()
ship.is_tug = True
ship.max_draft = 0
@ -167,6 +187,7 @@ def test_ship_is_tug_max_draft_in_range_minimum_not_valid():
return
def test_ship_is_tug_max_draft_in_range_maximum_not_valid():
from brecal_utils.validators.schema_validation import ship_max_draft_is_none_or_in_range
ship = get_ship_simple()
ship.is_tug = True
ship.max_draft = 20
@ -177,12 +198,14 @@ def test_ship_is_tug_max_draft_in_range_maximum_not_valid():
# Length tests
def test_ship_eni_len_is_eight_and_passes():
from brecal_utils.validators.schema_validation import ship_eni_len_is_eight
ship = get_ship_simple()
ship.eni = "01234567" # 8 character example
assert ship_eni_len_is_eight(ship)[0], f"the eni-no. should have exactly 8 characters"
return
def test_ship_eni_len_is_eight_but_has_nine():
from brecal_utils.validators.schema_validation import ship_eni_len_is_eight
ship = get_ship_simple()
ship.eni = "012345678" # 9 character example
with pytest.raises(AssertionError):
@ -190,18 +213,21 @@ def test_ship_eni_len_is_eight_but_has_nine():
return
def test_ship_callsign_len_is_seven_at_maximum_seven_passes():
from brecal_utils.validators.schema_validation import ship_callsign_len_is_seven_at_maximum
ship = get_ship_simple()
ship.callsign = "0123456" # 7 character example
assert ship_callsign_len_is_seven_at_maximum(ship)[0], f"the callsign no. should have at maximum 7 characters"
return
def test_ship_callsign_len_is_seven_at_maximum_six_passes():
from brecal_utils.validators.schema_validation import ship_callsign_len_is_seven_at_maximum
ship = get_ship_simple()
ship.callsign = "012345" # 6 character example
assert ship_callsign_len_is_seven_at_maximum(ship)[0], f"the callsign no. should have at maximum 7 characters"
return
def test_ship_callsign_len_is_seven_at_maximum_eight_fails():
from brecal_utils.validators.schema_validation import ship_callsign_len_is_seven_at_maximum
ship = get_ship_simple()
ship.callsign = "01234567" # 8 character example
@ -210,18 +236,21 @@ def test_ship_callsign_len_is_seven_at_maximum_eight_fails():
return
def test_ship_callsign_len_is_seven_at_maximum_zero_passes():
from brecal_utils.validators.schema_validation import ship_callsign_len_is_seven_at_maximum
ship = get_ship_simple()
ship.callsign = "" # 0 character example
assert ship_callsign_len_is_seven_at_maximum(ship)[0], f"the callsign no. should have at maximum 7 characters"
return
def test_imo_len_is_seven_and_seven_passes():
from brecal_utils.validators.schema_validation import ship_imo_len_is_seven
ship = get_ship_simple()
ship.imo = 1234567 # integer required
assert ship_imo_len_is_seven(ship)[0], f"a ship's IMO no. should have exactly 7 characters"
return
def test_imo_len_is_seven_and_eight_fails():
from brecal_utils.validators.schema_validation import ship_imo_len_is_seven
ship = get_ship_simple()
ship.imo = 12345678 # integer required
with pytest.raises(AssertionError):
@ -229,6 +258,7 @@ def test_imo_len_is_seven_and_eight_fails():
return
def test_imo_len_is_seven_and_one_fails():
from brecal_utils.validators.schema_validation import ship_imo_len_is_seven
ship = get_ship_simple()
ship.imo = 1 # integer required
with pytest.raises(AssertionError):