bugfix for shipcall PUT validation
This commit is contained in:
parent
c0902c65ee
commit
401e0d4ae8
@ -11,7 +11,7 @@ from BreCal.schemas import model
|
|||||||
|
|
||||||
|
|
||||||
def pandas_series_to_data_model():
|
def pandas_series_to_data_model():
|
||||||
return
|
return
|
||||||
|
|
||||||
def set_participant_type(x, participant_df)->int:
|
def set_participant_type(x, participant_df)->int:
|
||||||
"""
|
"""
|
||||||
@ -23,12 +23,12 @@ def set_participant_type(x, participant_df)->int:
|
|||||||
participant_id = x["participant_id"]
|
participant_id = x["participant_id"]
|
||||||
participant_type = participant_df.loc[participant_id, "type"]
|
participant_type = participant_df.loc[participant_id, "type"]
|
||||||
return participant_type
|
return participant_type
|
||||||
|
|
||||||
def get_synchronous_shipcall_times_standalone(query_time:pd.Timestamp, all_df_times:pd.DataFrame, delta_threshold=900)->int:
|
def get_synchronous_shipcall_times_standalone(query_time:pd.Timestamp, all_df_times:pd.DataFrame, delta_threshold=900)->int:
|
||||||
"""
|
"""
|
||||||
This function counts all entries in {all_df_times}, which have the same timestamp as {query_time}.
|
This function counts all entries in {all_df_times}, which have the same timestamp as {query_time}.
|
||||||
It does so by:
|
It does so by:
|
||||||
1.) selecting all eta_berth & etd_berth entries
|
1.) selecting all eta_berth & etd_berth entries
|
||||||
2.) measuring the timedelta towards {query_time}
|
2.) measuring the timedelta towards {query_time}
|
||||||
3.) converting the timedelta to total absolute seconds (positive or negative time differences do not matter)
|
3.) converting the timedelta to total absolute seconds (positive or negative time differences do not matter)
|
||||||
4.) applying a {delta_threshold} to identify, whether two times are too closely together
|
4.) applying a {delta_threshold} to identify, whether two times are too closely together
|
||||||
@ -60,8 +60,8 @@ def get_synchronous_shipcall_times_standalone(query_time:pd.Timestamp, all_df_ti
|
|||||||
|
|
||||||
def execute_sql_query_standalone(query, param={}, pooledConnection=None, model=None, command_type="query"):
|
def execute_sql_query_standalone(query, param={}, pooledConnection=None, model=None, command_type="query"):
|
||||||
"""
|
"""
|
||||||
execute an arbitrary query with a set of parameters, return the output and convert it to a list.
|
execute an arbitrary query with a set of parameters, return the output and convert it to a list.
|
||||||
when the pooled connection is rebuilt, it will be closed at the end of the function.
|
when the pooled connection is rebuilt, it will be closed at the end of the function.
|
||||||
"""
|
"""
|
||||||
rebuild_pooled_connection = pooledConnection is None
|
rebuild_pooled_connection = pooledConnection is None
|
||||||
|
|
||||||
@ -99,13 +99,13 @@ def execute_sql_query_standalone(query, param={}, pooledConnection=None, model=N
|
|||||||
# when providing a model, such as model.Shipcall, the dataset is immediately translated into a data model.
|
# when providing a model, such as model.Shipcall, the dataset is immediately translated into a data model.
|
||||||
schemas = commands.query_single_or_default(query, sentinel, param=param) if model is None else commands.query_single_or_default(query, sentinel, param=param, model=model)
|
schemas = commands.query_single_or_default(query, sentinel, param=param) if model is None else commands.query_single_or_default(query, sentinel, param=param, model=model)
|
||||||
schemas = None if schemas is sentinel else schemas
|
schemas = None if schemas is sentinel else schemas
|
||||||
|
|
||||||
elif command_type=="execute_scalar":
|
elif command_type=="execute_scalar":
|
||||||
schemas = commands.execute_scalar(query)
|
schemas = commands.execute_scalar(query)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(command_type)
|
raise ValueError(command_type)
|
||||||
|
|
||||||
finally: # if needed, ensure that the pooled connection is closed.
|
finally: # if needed, ensure that the pooled connection is closed.
|
||||||
if rebuild_pooled_connection:
|
if rebuild_pooled_connection:
|
||||||
pooledConnection.close()
|
pooledConnection.close()
|
||||||
@ -114,23 +114,23 @@ def execute_sql_query_standalone(query, param={}, pooledConnection=None, model=N
|
|||||||
def get_assigned_participant_of_type(shipcall_id:int, participant_type:typing.Union[int,model.ParticipantType])->typing.Optional[model.Participant]:
|
def get_assigned_participant_of_type(shipcall_id:int, participant_type:typing.Union[int,model.ParticipantType])->typing.Optional[model.Participant]:
|
||||||
"""obtains the ShipcallParticipantMap of a given shipcall and finds the participant id of a desired type. Finally, returns the respective Participant"""
|
"""obtains the ShipcallParticipantMap of a given shipcall and finds the participant id of a desired type. Finally, returns the respective Participant"""
|
||||||
spm_shipcall_data = execute_sql_query_standalone(
|
spm_shipcall_data = execute_sql_query_standalone(
|
||||||
query=SQLQuery.get_shipcall_participant_map_by_shipcall_id_and_type(),
|
query=SQLQuery.get_shipcall_participant_map_by_shipcall_id_and_type(),
|
||||||
param={"id":shipcall_id, "type":participant_type},
|
param={"id":shipcall_id, "type":int(participant_type)},
|
||||||
command_type="query") # returns a list of matches
|
command_type="query") # returns a list of matches
|
||||||
|
|
||||||
if len(spm_shipcall_data)==0:
|
if len(spm_shipcall_data)==0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
query = 'SELECT * FROM participant WHERE id=?participant_id?'
|
query = 'SELECT * FROM participant WHERE id=?participant_id?'
|
||||||
assigned_participant = execute_sql_query_standalone(
|
assigned_participant = execute_sql_query_standalone(
|
||||||
query=query,
|
query=query,
|
||||||
param={"participant_id":spm_shipcall_data[0]["participant_id"]},
|
param={"participant_id":spm_shipcall_data[0]["participant_id"]},
|
||||||
model=model.Participant,
|
model=model.Participant,
|
||||||
command_type="single_or_none"
|
command_type="single_or_none"
|
||||||
) # returns a list of matches
|
) # returns a list of matches
|
||||||
return assigned_participant
|
return assigned_participant
|
||||||
|
|
||||||
|
|
||||||
class SQLHandler():
|
class SQLHandler():
|
||||||
"""
|
"""
|
||||||
An object that reads SQL queries from the sql_connection and stores it in pandas DataFrames. The object can read all available tables
|
An object that reads SQL queries from the sql_connection and stores it in pandas DataFrames. The object can read all available tables
|
||||||
@ -161,7 +161,7 @@ class SQLHandler():
|
|||||||
schema = cursor.fetchall()
|
schema = cursor.fetchall()
|
||||||
all_schemas = [schem[0] for schem in schema]
|
all_schemas = [schem[0] for schem in schema]
|
||||||
return all_schemas
|
return all_schemas
|
||||||
|
|
||||||
def build_str_to_model_dict(self):
|
def build_str_to_model_dict(self):
|
||||||
"""
|
"""
|
||||||
creates a simple dictionary, which maps a string to a data object
|
creates a simple dictionary, which maps a string to a data object
|
||||||
@ -181,7 +181,7 @@ class SQLHandler():
|
|||||||
cursor.execute(f"DESCRIBE {table_name}")
|
cursor.execute(f"DESCRIBE {table_name}")
|
||||||
cols = cursor.fetchall()
|
cols = cursor.fetchall()
|
||||||
column_names = [col_name[0] for col_name in cols]
|
column_names = [col_name[0] for col_name in cols]
|
||||||
|
|
||||||
# 2.) get the data tuples
|
# 2.) get the data tuples
|
||||||
cursor.execute(f"SELECT * FROM {table_name}")
|
cursor.execute(f"SELECT * FROM {table_name}")
|
||||||
data = cursor.fetchall()
|
data = cursor.fetchall()
|
||||||
@ -192,14 +192,14 @@ class SQLHandler():
|
|||||||
# 4.) build a dataframe from the respective data models (which ensures the correct data type)
|
# 4.) build a dataframe from the respective data models (which ensures the correct data type)
|
||||||
df = self.build_df_from_data_and_name(data, table_name)
|
df = self.build_df_from_data_and_name(data, table_name)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def build_df_from_data_and_name(self, data, table_name):
|
def build_df_from_data_and_name(self, data, table_name):
|
||||||
data_model = self.str_to_model_dict.get(table_name)
|
data_model = self.str_to_model_dict.get(table_name)
|
||||||
if data_model is not None:
|
if data_model is not None:
|
||||||
df = pd.DataFrame([data_model(**dat) for dat in data], columns=list(data_model.__annotations__.keys()))
|
df = pd.DataFrame([data_model(**dat) for dat in data], columns=list(data_model.__annotations__.keys()))
|
||||||
else:
|
else:
|
||||||
df = pd.DataFrame([dat for dat in data])
|
df = pd.DataFrame([dat for dat in data])
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def mysql_to_df(self, query, table_name):
|
def mysql_to_df(self, query, table_name):
|
||||||
"""provide an arbitrary sql query that should be read from a mysql server {sql_connection}. returns a pandas DataFrame with the obtained data"""
|
"""provide an arbitrary sql query that should be read from a mysql server {sql_connection}. returns a pandas DataFrame with the obtained data"""
|
||||||
@ -222,7 +222,7 @@ class SQLHandler():
|
|||||||
if 'id' in df.columns:
|
if 'id' in df.columns:
|
||||||
df = df.set_index('id', inplace=False) # avoid inplace updates, so the raw sql remains unchanged
|
df = df.set_index('id', inplace=False) # avoid inplace updates, so the raw sql remains unchanged
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def read_all(self, all_schemas):
|
def read_all(self, all_schemas):
|
||||||
# create a dictionary, which maps every mysql schema to pandas DataFrames
|
# create a dictionary, which maps every mysql schema to pandas DataFrames
|
||||||
self.df_dict = self.build_full_mysql_df_dict(all_schemas)
|
self.df_dict = self.build_full_mysql_df_dict(all_schemas)
|
||||||
@ -242,7 +242,7 @@ class SQLHandler():
|
|||||||
query = f"SELECT * FROM {schem}"
|
query = f"SELECT * FROM {schem}"
|
||||||
mysql_df_dict[schem] = self.mysql_to_df(query, table_name=schem)
|
mysql_df_dict[schem] = self.mysql_to_df(query, table_name=schem)
|
||||||
return mysql_df_dict
|
return mysql_df_dict
|
||||||
|
|
||||||
def initialize_shipcall_participant_list(self):
|
def initialize_shipcall_participant_list(self):
|
||||||
"""
|
"""
|
||||||
iteratively applies the .get_participants method to each shipcall.
|
iteratively applies the .get_participants method to each shipcall.
|
||||||
@ -256,10 +256,10 @@ class SQLHandler():
|
|||||||
# if the shipcall_id exists, the list contains ids
|
# if the shipcall_id exists, the list contains ids
|
||||||
# otherwise, return a blank list
|
# otherwise, return a blank list
|
||||||
df['participants'] = df.apply(
|
df['participants'] = df.apply(
|
||||||
lambda x: self.get_participants(x.name),
|
lambda x: self.get_participants(x.name),
|
||||||
axis=1)
|
axis=1)
|
||||||
return
|
return
|
||||||
|
|
||||||
def add_participant_type_to_map(self):
|
def add_participant_type_to_map(self):
|
||||||
"""
|
"""
|
||||||
applies a lambda function, where the 'type'-column in the shipcall_participant_map is updated by reading the
|
applies a lambda function, where the 'type'-column in the shipcall_participant_map is updated by reading the
|
||||||
@ -272,14 +272,14 @@ class SQLHandler():
|
|||||||
#spm.loc[:,"type"] = spm.loc[:].apply(lambda x: set_participant_type(x, participant_df=participant_df),axis=1)
|
#spm.loc[:,"type"] = spm.loc[:].apply(lambda x: set_participant_type(x, participant_df=participant_df),axis=1)
|
||||||
#self.df_dict["shipcall_participant_map"] = spm
|
#self.df_dict["shipcall_participant_map"] = spm
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_assigned_participants(self, shipcall)->pd.DataFrame:
|
def get_assigned_participants(self, shipcall)->pd.DataFrame:
|
||||||
"""return each participant of a respective shipcall, filtered by the shipcall id"""
|
"""return each participant of a respective shipcall, filtered by the shipcall id"""
|
||||||
# get the shipcall_participant_map
|
# get the shipcall_participant_map
|
||||||
spm = self.df_dict["shipcall_participant_map"]
|
spm = self.df_dict["shipcall_participant_map"]
|
||||||
assigned_participants = spm.loc[spm["shipcall_id"]==shipcall.id]
|
assigned_participants = spm.loc[spm["shipcall_id"]==shipcall.id]
|
||||||
return assigned_participants
|
return assigned_participants
|
||||||
|
|
||||||
def get_assigned_participants_by_type(self, assigned_participants:pd.DataFrame, participant_type:ParticipantType):
|
def get_assigned_participants_by_type(self, assigned_participants:pd.DataFrame, participant_type:ParticipantType):
|
||||||
"""filters a dataframe of assigned_participants by the provided type enumerator"""
|
"""filters a dataframe of assigned_participants by the provided type enumerator"""
|
||||||
if isinstance(participant_type,int):
|
if isinstance(participant_type,int):
|
||||||
@ -288,7 +288,7 @@ class SQLHandler():
|
|||||||
assigned_participants_of_type = assigned_participants.loc[[participant_type in ParticipantType(int(pt_)) for pt_ in list(assigned_participants["type"].values)]]
|
assigned_participants_of_type = assigned_participants.loc[[participant_type in ParticipantType(int(pt_)) for pt_ in list(assigned_participants["type"].values)]]
|
||||||
#assigned_participants_of_type = assigned_participants.loc[assigned_participants["type"]==participant_type.value]
|
#assigned_participants_of_type = assigned_participants.loc[assigned_participants["type"]==participant_type.value]
|
||||||
return assigned_participants_of_type
|
return assigned_participants_of_type
|
||||||
|
|
||||||
def check_if_any_participant_of_type_is_unassigned(self, shipcall, *args:list[ParticipantType])->bool:
|
def check_if_any_participant_of_type_is_unassigned(self, shipcall, *args:list[ParticipantType])->bool:
|
||||||
"""
|
"""
|
||||||
given a list of input arguments, where item is a participant type, the function determines, whether at least one participant
|
given a list of input arguments, where item is a participant type, the function determines, whether at least one participant
|
||||||
@ -305,13 +305,13 @@ class SQLHandler():
|
|||||||
unassignment = len(assignments_of_type)==0 # a participant type does not exist, when there is no match
|
unassignment = len(assignments_of_type)==0 # a participant type does not exist, when there is no match
|
||||||
unassigned.append(unassignment)
|
unassigned.append(unassignment)
|
||||||
return any(unassigned) # returns a single boolean, whether ANY of the types is not assigned
|
return any(unassigned) # returns a single boolean, whether ANY of the types is not assigned
|
||||||
|
|
||||||
def standardize_model_str(self, model_str:str)->str:
|
def standardize_model_str(self, model_str:str)->str:
|
||||||
"""check if the 'model_str' is valid and apply lowercasing to the string"""
|
"""check if the 'model_str' is valid and apply lowercasing to the string"""
|
||||||
model_str = model_str.lower()
|
model_str = model_str.lower()
|
||||||
assert model_str in list(self.df_dict.keys()), f"cannot find the requested 'model_str' in mysql: {model_str}"
|
assert model_str in list(self.df_dict.keys()), f"cannot find the requested 'model_str' in mysql: {model_str}"
|
||||||
return model_str
|
return model_str
|
||||||
|
|
||||||
def get_data(self, id:int, model_str:str):
|
def get_data(self, id:int, model_str:str):
|
||||||
"""
|
"""
|
||||||
obtains {id} from the respective mysql database and builds a data model from that.
|
obtains {id} from the respective mysql database and builds a data model from that.
|
||||||
@ -323,11 +323,11 @@ class SQLHandler():
|
|||||||
returns a Shipcall object
|
returns a Shipcall object
|
||||||
"""
|
"""
|
||||||
model_str = self.standardize_model_str(model_str)
|
model_str = self.standardize_model_str(model_str)
|
||||||
|
|
||||||
df = self.df_dict.get(model_str)
|
df = self.df_dict.get(model_str)
|
||||||
data = self.df_loc_to_data_model(df, id, model_str)
|
data = self.df_loc_to_data_model(df, id, model_str)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_all(self, model_str:str)->list:
|
def get_all(self, model_str:str)->list:
|
||||||
"""
|
"""
|
||||||
given a model string (e.g., 'shipcall'), return a list of all
|
given a model string (e.g., 'shipcall'), return a list of all
|
||||||
@ -341,13 +341,13 @@ class SQLHandler():
|
|||||||
for _aid in all_ids
|
for _aid in all_ids
|
||||||
]
|
]
|
||||||
return all_data
|
return all_data
|
||||||
|
|
||||||
def df_loc_to_data_model(self, df, id, model_str, loc_type:str="loc"):
|
def df_loc_to_data_model(self, df, id, model_str, loc_type:str="loc"):
|
||||||
if not len(df)>0:
|
if not len(df)>0:
|
||||||
import warnings
|
import warnings
|
||||||
warnings.warn(f"empty dataframe in SQLHandler.df_loc_to_data_model for model type: {model_str}\n")
|
warnings.warn(f"empty dataframe in SQLHandler.df_loc_to_data_model for model type: {model_str}\n")
|
||||||
return df
|
return df
|
||||||
|
|
||||||
# get a pandas series from the dataframe
|
# get a pandas series from the dataframe
|
||||||
series = df.loc[id] if loc_type=="loc" else df.iloc[id]
|
series = df.loc[id] if loc_type=="loc" else df.iloc[id]
|
||||||
|
|
||||||
@ -360,7 +360,7 @@ class SQLHandler():
|
|||||||
data = {**{'id':int(id)}, **series.to_dict()} # 'id' must be added manually, as .to_dict does not contain the index, which was set with .set_index
|
data = {**{'id':int(id)}, **series.to_dict()} # 'id' must be added manually, as .to_dict does not contain the index, which was set with .set_index
|
||||||
data = data_model(**data)
|
data = data_model(**data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def filter_df_by_participant_type(self, df, participant_type:typing.Union[int, ParticipantType])->pd.DataFrame:
|
def filter_df_by_participant_type(self, df, participant_type:typing.Union[int, ParticipantType])->pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
As ParticipantTypes are Flag objects, a dataframe's integer might resemble multiple participant types simultaneously.
|
As ParticipantTypes are Flag objects, a dataframe's integer might resemble multiple participant types simultaneously.
|
||||||
@ -376,7 +376,7 @@ class SQLHandler():
|
|||||||
participant_type = ParticipantType(participant_type)
|
participant_type = ParticipantType(participant_type)
|
||||||
filtered_df = df.loc[[participant_type in ParticipantType(df_pt) for df_pt in list(df["participant_type"].values)]]
|
filtered_df = df.loc[[participant_type in ParticipantType(df_pt) for df_pt in list(df["participant_type"].values)]]
|
||||||
return filtered_df
|
return filtered_df
|
||||||
|
|
||||||
def get_times_for_participant_type(self, df_times, participant_type:int):
|
def get_times_for_participant_type(self, df_times, participant_type:int):
|
||||||
filtered_series = self.filter_df_by_participant_type(df_times, participant_type)
|
filtered_series = self.filter_df_by_participant_type(df_times, participant_type)
|
||||||
#filtered_series = df_times.loc[df_times["participant_type"]==participant_type]
|
#filtered_series = df_times.loc[df_times["participant_type"]==participant_type]
|
||||||
@ -385,14 +385,14 @@ class SQLHandler():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
if not len(filtered_series)<=1:
|
if not len(filtered_series)<=1:
|
||||||
# correcting the error: ERROR:root:found multiple results
|
# correcting the error: ERROR:root:found multiple results
|
||||||
# however, a warning will still be issued
|
# however, a warning will still be issued
|
||||||
import warnings
|
import warnings
|
||||||
warnings.warn(f"found multiple results in function SQLHandler.get_times_for_participant_type\nConsidering only the first match!\nAffected Times Indexes: {filtered_series.index}")
|
warnings.warn(f"found multiple results in function SQLHandler.get_times_for_participant_type\nConsidering only the first match!\nAffected Times Indexes: {filtered_series.index}")
|
||||||
|
|
||||||
times = self.df_loc_to_data_model(filtered_series, id=0, model_str='times', loc_type="iloc") # use iloc! to retrieve the first result
|
times = self.df_loc_to_data_model(filtered_series, id=0, model_str='times', loc_type="iloc") # use iloc! to retrieve the first result
|
||||||
return times
|
return times
|
||||||
|
|
||||||
def dataframe_to_data_model_list(self, df, model_str)->list:
|
def dataframe_to_data_model_list(self, df, model_str)->list:
|
||||||
model_str = self.standardize_model_str(model_str)
|
model_str = self.standardize_model_str(model_str)
|
||||||
|
|
||||||
@ -413,22 +413,22 @@ class SQLHandler():
|
|||||||
df = self.df_dict.get("shipcall_participant_map")
|
df = self.df_dict.get("shipcall_participant_map")
|
||||||
if 'shipcall_id' in list(df.columns):
|
if 'shipcall_id' in list(df.columns):
|
||||||
df = df.set_index('shipcall_id', inplace=False)
|
df = df.set_index('shipcall_id', inplace=False)
|
||||||
|
|
||||||
# the 'if' call is needed to ensure, that no Exception is raised, when the shipcall_id is not present in the df
|
# the 'if' call is needed to ensure, that no Exception is raised, when the shipcall_id is not present in the df
|
||||||
participant_id_list = df.loc[shipcall_id, "participant_id"].tolist() if shipcall_id in list(df.index) else []
|
participant_id_list = df.loc[shipcall_id, "participant_id"].tolist() if shipcall_id in list(df.index) else []
|
||||||
if not isinstance(participant_id_list,list):
|
if not isinstance(participant_id_list,list):
|
||||||
participant_id_list = [participant_id_list]
|
participant_id_list = [participant_id_list]
|
||||||
return participant_id_list
|
return participant_id_list
|
||||||
|
|
||||||
def get_times_of_shipcall(self, shipcall)->pd.DataFrame:
|
def get_times_of_shipcall(self, shipcall)->pd.DataFrame:
|
||||||
df_times = self.df_dict.get('times') # -> pd.DataFrame
|
df_times = self.df_dict.get('times') # -> pd.DataFrame
|
||||||
df_times = df_times.loc[df_times["shipcall_id"]==shipcall.id]
|
df_times = df_times.loc[df_times["shipcall_id"]==shipcall.id]
|
||||||
return df_times
|
return df_times
|
||||||
|
|
||||||
def get_times_for_agency(self, non_null_column=None)->pd.DataFrame:
|
def get_times_for_agency(self, non_null_column=None)->pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
options:
|
options:
|
||||||
non_null_column:
|
non_null_column:
|
||||||
None or str. If provided, the 'non_null_column'-column of the dataframe will be filtered,
|
None or str. If provided, the 'non_null_column'-column of the dataframe will be filtered,
|
||||||
so only entries with provided values are returned (filters all NaN and NaT entries)
|
so only entries with provided values are returned (filters all NaN and NaT entries)
|
||||||
"""
|
"""
|
||||||
@ -437,8 +437,8 @@ class SQLHandler():
|
|||||||
|
|
||||||
# filter out all NaN and NaT entries
|
# filter out all NaN and NaT entries
|
||||||
if non_null_column is not None:
|
if non_null_column is not None:
|
||||||
# in the Pandas documentation, it says for .isnull():
|
# in the Pandas documentation, it says for .isnull():
|
||||||
# "This function takes a scalar or array-like object and indicates whether values are missing
|
# "This function takes a scalar or array-like object and indicates whether values are missing
|
||||||
# (NaN in numeric arrays, None or NaN in object arrays, NaT in datetimelike)."
|
# (NaN in numeric arrays, None or NaN in object arrays, NaT in datetimelike)."
|
||||||
df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter
|
df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter
|
||||||
|
|
||||||
@ -446,10 +446,10 @@ class SQLHandler():
|
|||||||
times_agency = self.filter_df_by_participant_type(df_times, ParticipantType.AGENCY.value)
|
times_agency = self.filter_df_by_participant_type(df_times, ParticipantType.AGENCY.value)
|
||||||
#times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
|
#times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
|
||||||
return times_agency
|
return times_agency
|
||||||
|
|
||||||
def filter_df_by_key_value(self, df, key, value)->pd.DataFrame:
|
def filter_df_by_key_value(self, df, key, value)->pd.DataFrame:
|
||||||
return df.loc[df[key]==value]
|
return df.loc[df[key]==value]
|
||||||
|
|
||||||
def get_unique_ship_counts(self, all_df_times:pd.DataFrame, times_agency:pd.DataFrame, query:str, rounding:str="min", maximum_threshold=3):
|
def get_unique_ship_counts(self, all_df_times:pd.DataFrame, times_agency:pd.DataFrame, query:str, rounding:str="min", maximum_threshold=3):
|
||||||
"""given a dataframe of all agency times, get all unique ship counts, their values (datetime) and the string tags. returns a tuple (values,unique,counts)"""
|
"""given a dataframe of all agency times, get all unique ship counts, their values (datetime) and the string tags. returns a tuple (values,unique,counts)"""
|
||||||
# #deprecated!
|
# #deprecated!
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class InputValidationShipcall():
|
|||||||
Example:
|
Example:
|
||||||
InputValidationShipcall.evaluate(user_data, loadedModel, content)
|
InputValidationShipcall.evaluate(user_data, loadedModel, content)
|
||||||
|
|
||||||
When the data violates one of the rules, a marshmallow.ValidationError is raised, which details the issues.
|
When the data violates one of the rules, a marshmallow.ValidationError is raised, which details the issues.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@ -39,7 +39,7 @@ class InputValidationShipcall():
|
|||||||
def evaluate_post_data(user_data:dict, loadedModel:dict, content:dict):
|
def evaluate_post_data(user_data:dict, loadedModel:dict, content:dict):
|
||||||
"""
|
"""
|
||||||
this function combines multiple validation functions to verify data, which is sent to the API as a shipcall's POST-request
|
this function combines multiple validation functions to verify data, which is sent to the API as a shipcall's POST-request
|
||||||
|
|
||||||
checks:
|
checks:
|
||||||
1. permission: only participants that belong to the BSMD or AGENCY groups are allowed to POST shipcalls
|
1. permission: only participants that belong to the BSMD or AGENCY groups are allowed to POST shipcalls
|
||||||
2. reference checks: all refered objects within the Shipcall must exist
|
2. reference checks: all refered objects within the Shipcall must exist
|
||||||
@ -61,16 +61,16 @@ class InputValidationShipcall():
|
|||||||
# check for reasonable values in the shipcall fields
|
# check for reasonable values in the shipcall fields
|
||||||
InputValidationShipcall.check_shipcall_values(loadedModel, content, forbidden_keys=["evaluation", "evaluation_message"]) # "canceled"
|
InputValidationShipcall.check_shipcall_values(loadedModel, content, forbidden_keys=["evaluation", "evaluation_message"]) # "canceled"
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def evaluate_put_data(user_data:dict, loadedModel:dict, content:dict):
|
def evaluate_put_data(user_data:dict, loadedModel:dict, content:dict):
|
||||||
"""
|
"""
|
||||||
this function combines multiple validation functions to verify data, which is sent to the API as a shipcall's PUT-request
|
this function combines multiple validation functions to verify data, which is sent to the API as a shipcall's PUT-request
|
||||||
|
|
||||||
checks:
|
checks:
|
||||||
1. user's authority:
|
1. user's authority:
|
||||||
a) whether the user's participant is assigned to the shipcall (via shipcall-participant-map)
|
a) whether the user's participant is assigned to the shipcall (via shipcall-participant-map)
|
||||||
b) whether the user is either an AGENCY (assigned) or the BSMD, in case the AGENCY allows the BSMD to edit their shipcalls
|
b) whether the user is either an AGENCY (assigned) or the BSMD, in case the AGENCY allows the BSMD to edit their shipcalls
|
||||||
2. existance of required fields
|
2. existance of required fields
|
||||||
3. all value-rules of the POST evaluation
|
3. all value-rules of the POST evaluation
|
||||||
4. a canceled shipcall may not be changed
|
4. a canceled shipcall may not be changed
|
||||||
@ -90,24 +90,24 @@ class InputValidationShipcall():
|
|||||||
# check the referenced IDs
|
# check the referenced IDs
|
||||||
InputValidationShipcall.check_referenced_ids(loadedModel)
|
InputValidationShipcall.check_referenced_ids(loadedModel)
|
||||||
|
|
||||||
# check for reasonable values in the shipcall fields and checks for forbidden keys.
|
# check for reasonable values in the shipcall fields and checks for forbidden keys.
|
||||||
InputValidationShipcall.check_shipcall_values(loadedModel, content, forbidden_keys=["evaluation", "evaluation_message"], is_put_data=True)
|
InputValidationShipcall.check_shipcall_values(loadedModel, content, forbidden_keys=["evaluation", "evaluation_message"], is_put_data=True)
|
||||||
|
|
||||||
# a canceled shipcall cannot be selected
|
# a canceled shipcall cannot be selected
|
||||||
# Note: 'canceled' is allowed in PUT-requests, if it is not already set (which is checked by InputValidationShipcall.check_shipcall_is_cancel)
|
# Note: 'canceled' is allowed in PUT-requests, if it is not already set (which is checked by InputValidationShipcall.check_shipcall_is_cancel)
|
||||||
InputValidationShipcall.check_shipcall_is_canceled(loadedModel, content)
|
InputValidationShipcall.check_shipcall_is_canceled(loadedModel, content)
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_shipcall_values(loadedModel:dict, content:dict, forbidden_keys:list=["evaluation", "evaluation_message"], is_put_data:bool=False):
|
def check_shipcall_values(loadedModel:dict, content:dict, forbidden_keys:list=["evaluation", "evaluation_message"], is_put_data:bool=False):
|
||||||
"""
|
"""
|
||||||
individually checks each value provided in the loadedModel/content.
|
individually checks each value provided in the loadedModel/content.
|
||||||
This function validates, whether the values are reasonable.
|
This function validates, whether the values are reasonable.
|
||||||
|
|
||||||
Also, some data may not be set in a POST-request.
|
Also, some data may not be set in a POST-request.
|
||||||
|
|
||||||
options:
|
options:
|
||||||
is_put_data: bool. Some validation rules do not apply to POST data, but apply to PUT data. This flag separates the two.
|
is_put_data: bool. Some validation rules do not apply to POST data, but apply to PUT data. This flag separates the two.
|
||||||
"""
|
"""
|
||||||
# Note: BreCal.schemas.model.ShipcallSchema has an internal validation, which the marshmallow library provides. This is used
|
# Note: BreCal.schemas.model.ShipcallSchema has an internal validation, which the marshmallow library provides. This is used
|
||||||
# to verify values individually, when the schema is loaded with data.
|
# to verify values individually, when the schema is loaded with data.
|
||||||
@ -117,13 +117,13 @@ class InputValidationShipcall():
|
|||||||
# voyage shall not contain special characters
|
# voyage shall not contain special characters
|
||||||
voyage_str_is_invalid = check_if_string_has_special_characters(text=content.get("voyage",""))
|
voyage_str_is_invalid = check_if_string_has_special_characters(text=content.get("voyage",""))
|
||||||
if voyage_str_is_invalid:
|
if voyage_str_is_invalid:
|
||||||
raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"})
|
raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"})
|
||||||
|
|
||||||
# the 'flags' integer must be valid
|
# the 'flags' integer must be valid
|
||||||
flags_value = content.get("flags", 0)
|
flags_value = content.get("flags", 0)
|
||||||
if check_if_int_is_valid_flag(flags_value, enum_object=ParticipantFlag):
|
if check_if_int_is_valid_flag(flags_value, enum_object=ParticipantFlag):
|
||||||
raise ValidationError({"flags":f"incorrect value provided for 'flags'. Must be a valid combination of the flags."})
|
raise ValidationError({"flags":f"incorrect value provided for 'flags'. Must be a valid combination of the flags."})
|
||||||
|
|
||||||
if is_put_data:
|
if is_put_data:
|
||||||
# the type of a shipcall may not be changed. It can only be set with the initial POST-request.
|
# the type of a shipcall may not be changed. It can only be set with the initial POST-request.
|
||||||
InputValidationShipcall.check_shipcall_type_is_unchanged(loadedModel)
|
InputValidationShipcall.check_shipcall_type_is_unchanged(loadedModel)
|
||||||
@ -134,7 +134,7 @@ class InputValidationShipcall():
|
|||||||
# some arguments must not be provided
|
# some arguments must not be provided
|
||||||
InputValidationShipcall.check_forbidden_arguments(content, forbidden_keys=forbidden_keys)
|
InputValidationShipcall.check_forbidden_arguments(content, forbidden_keys=forbidden_keys)
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_agency_in_shipcall_participant_map(user_data:dict, loadedModel:dict, content:dict, spm_shipcall_data:typing.Optional[list]=None):
|
def check_agency_in_shipcall_participant_map(user_data:dict, loadedModel:dict, content:dict, spm_shipcall_data:typing.Optional[list]=None):
|
||||||
"""
|
"""
|
||||||
@ -145,11 +145,11 @@ class InputValidationShipcall():
|
|||||||
Upon violation, this method issues 'Forbidden'-Exceptions with HTTP status code 403. There are four reasons for violations:
|
Upon violation, this method issues 'Forbidden'-Exceptions with HTTP status code 403. There are four reasons for violations:
|
||||||
a) an agency tries to self-assign for a shipcall
|
a) an agency tries to self-assign for a shipcall
|
||||||
b) there is no assigned agency for the current shipcall
|
b) there is no assigned agency for the current shipcall
|
||||||
c) an agency is assigned, but the current agency-user belongs to a different participant_id
|
c) an agency is assigned, but the current agency-user belongs to a different participant_id
|
||||||
d) the user must be of ParticipantType BSMD or AGENCY
|
d) the user must be of ParticipantType BSMD or AGENCY
|
||||||
|
|
||||||
args:
|
args:
|
||||||
spm_shipcall_data:
|
spm_shipcall_data:
|
||||||
a list of entries obtained from the ShipcallParticipantMap. These are deserialized dictionaries.
|
a list of entries obtained from the ShipcallParticipantMap. These are deserialized dictionaries.
|
||||||
e.g., [{'participant_id': 136, 'type': 8}, ]
|
e.g., [{'participant_id': 136, 'type': 8}, ]
|
||||||
"""
|
"""
|
||||||
@ -158,11 +158,11 @@ class InputValidationShipcall():
|
|||||||
# read the ShipcallParticipantMap entry of the current shipcall_id. This is used within the input validation of a PUT request
|
# read the ShipcallParticipantMap entry of the current shipcall_id. This is used within the input validation of a PUT request
|
||||||
spm_shipcall_data = execute_sql_query_standalone(
|
spm_shipcall_data = execute_sql_query_standalone(
|
||||||
# #TODO_refactor: place this within the SQLQuery object
|
# #TODO_refactor: place this within the SQLQuery object
|
||||||
query = "SELECT participant_id, type FROM shipcall_participant_map WHERE shipcall_id=?shipcall_id?",
|
query = "SELECT participant_id, type FROM shipcall_participant_map WHERE shipcall_id=?shipcall_id?",
|
||||||
param={"shipcall_id":loadedModel["id"]},
|
param={"shipcall_id":loadedModel["id"]},
|
||||||
pooledConnection=None
|
pooledConnection=None
|
||||||
)
|
)
|
||||||
|
|
||||||
# which role should be set by the PUT request? If the agency is about to be set, an error will be created
|
# which role should be set by the PUT request? If the agency is about to be set, an error will be created
|
||||||
# read the user data from the JWT token (set when login is performed)
|
# read the user data from the JWT token (set when login is performed)
|
||||||
user_type = get_participant_type_from_user_data(user_data) # decode JWT -> get 'type' value (guarantees to convert user type into an IntFlag)
|
user_type = get_participant_type_from_user_data(user_data) # decode JWT -> get 'type' value (guarantees to convert user type into an IntFlag)
|
||||||
@ -180,7 +180,7 @@ class InputValidationShipcall():
|
|||||||
# user not AGENCY or BSMD
|
# user not AGENCY or BSMD
|
||||||
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by AGENCY or BSMD users.") # Forbidden: 403
|
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by AGENCY or BSMD users.") # Forbidden: 403
|
||||||
|
|
||||||
# Placeholder: when a user is an AGENCY,
|
# Placeholder: when a user is an AGENCY,
|
||||||
|
|
||||||
if (ParticipantType.AGENCY in user_type) & (any_type_is_agency):
|
if (ParticipantType.AGENCY in user_type) & (any_type_is_agency):
|
||||||
# self-assignment: agency sets agency participant
|
# self-assignment: agency sets agency participant
|
||||||
@ -189,7 +189,7 @@ class InputValidationShipcall():
|
|||||||
if len(agency_entries)>0:
|
if len(agency_entries)>0:
|
||||||
# agency participant exists: participant id must be the same as shipcall participant map entry
|
# agency participant exists: participant id must be the same as shipcall participant map entry
|
||||||
matching_spm_entry = [spm_entry for spm_entry in spm_shipcall_data if (spm_entry.get("participant_id")==user_data["id"]) & (int(spm_entry.get("type"))==int(ParticipantType.AGENCY))]
|
matching_spm_entry = [spm_entry for spm_entry in spm_shipcall_data if (spm_entry.get("participant_id")==user_data["id"]) & (int(spm_entry.get("type"))==int(ParticipantType.AGENCY))]
|
||||||
|
|
||||||
if len(matching_spm_entry)==0:
|
if len(matching_spm_entry)==0:
|
||||||
# An AGENCY was found, but a different participant_id is assigned to that AGENCY
|
# An AGENCY was found, but a different participant_id is assigned to that AGENCY
|
||||||
raise werkzeug.exceptions.Forbidden(f"A different participant_id is assigned as the AGENCY of this shipcall. Provided ID: {user_data.get('id')}, Assigned ShipcallParticipantMap: {agency_entries}") # Forbidden: 403
|
raise werkzeug.exceptions.Forbidden(f"A different participant_id is assigned as the AGENCY of this shipcall. Provided ID: {user_data.get('id')}, Assigned ShipcallParticipantMap: {agency_entries}") # Forbidden: 403
|
||||||
@ -201,7 +201,7 @@ class InputValidationShipcall():
|
|||||||
# agency participant does not exist: there is no assigned agency role for the shipcall {shipcall_id}
|
# agency participant does not exist: there is no assigned agency role for the shipcall {shipcall_id}
|
||||||
raise werkzeug.exceptions.Forbidden(f"There is no assigned agency for this shipcall. Shipcall ID: {loadedModel['id']}") # Forbidden: 403
|
raise werkzeug.exceptions.Forbidden(f"There is no assigned agency for this shipcall. Shipcall ID: {loadedModel['id']}") # Forbidden: 403
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_user_is_bsmd_or_agent_type(user_data):
|
def check_user_is_bsmd_or_agent_type(user_data):
|
||||||
"""
|
"""
|
||||||
@ -226,7 +226,7 @@ class InputValidationShipcall():
|
|||||||
check, whether the referenced entries exist (e.g., when a Ship ID is referenced, but does not exist, the validation fails)
|
check, whether the referenced entries exist (e.g., when a Ship ID is referenced, but does not exist, the validation fails)
|
||||||
"""
|
"""
|
||||||
# #TODO: arrival and departure berth id should be coupled with the shipcall type. One shall not provide
|
# #TODO: arrival and departure berth id should be coupled with the shipcall type. One shall not provide
|
||||||
# arrival berth id when the shipcall type is departure or vise versa.
|
# arrival berth id when the shipcall type is departure or vise versa.
|
||||||
# a similar logic has already been implemented to the eta/etd or for the operation windows
|
# a similar logic has already been implemented to the eta/etd or for the operation windows
|
||||||
|
|
||||||
# get all IDs from the loadedModel
|
# get all IDs from the loadedModel
|
||||||
@ -238,23 +238,23 @@ class InputValidationShipcall():
|
|||||||
valid_ship_id = check_if_ship_id_is_valid(ship_id=ship_id)
|
valid_ship_id = check_if_ship_id_is_valid(ship_id=ship_id)
|
||||||
if not valid_ship_id:
|
if not valid_ship_id:
|
||||||
raise ValidationError({"ship_id":f"provided an invalid ship id, which is not found in the database: {ship_id}"})
|
raise ValidationError({"ship_id":f"provided an invalid ship id, which is not found in the database: {ship_id}"})
|
||||||
|
|
||||||
valid_arrival_berth_id = check_if_berth_id_is_valid(berth_id=arrival_berth_id)
|
valid_arrival_berth_id = check_if_berth_id_is_valid(berth_id=arrival_berth_id)
|
||||||
if not valid_arrival_berth_id:
|
if not valid_arrival_berth_id:
|
||||||
raise ValidationError({"arrival_berth_id":f"provided an invalid arrival berth id, which is not found in the database: {arrival_berth_id}"})
|
raise ValidationError({"arrival_berth_id":f"provided an invalid arrival berth id, which is not found in the database: {arrival_berth_id}"})
|
||||||
|
|
||||||
valid_departure_berth_id = check_if_berth_id_is_valid(berth_id=departure_berth_id)
|
valid_departure_berth_id = check_if_berth_id_is_valid(berth_id=departure_berth_id)
|
||||||
if not valid_departure_berth_id:
|
if not valid_departure_berth_id:
|
||||||
raise ValidationError({"departure_berth_id":f"provided an invalid departure berth id, which is not found in the database: {departure_berth_id}"})
|
raise ValidationError({"departure_berth_id":f"provided an invalid departure berth id, which is not found in the database: {departure_berth_id}"})
|
||||||
|
|
||||||
valid_participant_ids = check_if_participant_ids_are_valid(participants=participants)
|
valid_participant_ids = check_if_participant_ids_are_valid(participants=participants)
|
||||||
if not valid_participant_ids:
|
if not valid_participant_ids:
|
||||||
raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {participants}"})
|
raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {participants}"})
|
||||||
|
|
||||||
valid_participant_types = check_if_participant_ids_and_types_are_valid(participants=participants)
|
valid_participant_types = check_if_participant_ids_and_types_are_valid(participants=participants)
|
||||||
if not valid_participant_types: # #TODO: according to Daniel, there may eventually be multi-assignment of participants for the same role
|
if not valid_participant_types: # #TODO: according to Daniel, there may eventually be multi-assignment of participants for the same role
|
||||||
raise ValidationError({"participants":f"every participant id and type should be listed only once. Found multiple entries for one of the participants."})
|
raise ValidationError({"participants":f"every participant id and type should be listed only once. Found multiple entries for one of the participants."})
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_shipcall_type_is_unchanged(loadedModel:dict):
|
def check_shipcall_type_is_unchanged(loadedModel:dict):
|
||||||
# the type of a shipcall may only be set on POST requests. Afterwards, shipcall types may not be changed.
|
# the type of a shipcall may only be set on POST requests. Afterwards, shipcall types may not be changed.
|
||||||
@ -264,20 +264,20 @@ class InputValidationShipcall():
|
|||||||
if int(loadedModel["type"]) != int(shipcall.type):
|
if int(loadedModel["type"]) != int(shipcall.type):
|
||||||
raise ValidationError({"type":f"The shipcall type may only be set in the initial POST-request. Afterwards, changing the shipcall type is not allowed."}) # @pytest.raises
|
raise ValidationError({"type":f"The shipcall type may only be set in the initial POST-request. Afterwards, changing the shipcall type is not allowed."}) # @pytest.raises
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_forbidden_arguments(content:dict, forbidden_keys=["evaluation", "evaluation_message"]):
|
def check_forbidden_arguments(content:dict, forbidden_keys=["evaluation", "evaluation_message"]):
|
||||||
"""
|
"""
|
||||||
a post-request must not contain the arguments 'canceled', 'evaluation', 'evaluation_message'.
|
a post-request must not contain the arguments 'canceled', 'evaluation', 'evaluation_message'.
|
||||||
a put-request must not contain the arguments 'evaluation', 'evaluation_message'
|
a put-request must not contain the arguments 'evaluation', 'evaluation_message'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# the following keys should not be set in a POST-request.
|
# the following keys should not be set in a POST-request.
|
||||||
for forbidden_key in forbidden_keys:
|
for forbidden_key in forbidden_keys:
|
||||||
value = content.get(forbidden_key, None)
|
value = content.get(forbidden_key, None)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
raise ValidationError({"forbidden_key":f"'{forbidden_key}' may not be set on POST. Found: {value}"})
|
raise ValidationError({"forbidden_key":f"'{forbidden_key}' may not be set on POST. Found: {value}"})
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_required_fields_exist_based_on_type(loadedModel:dict, content:dict):
|
def check_required_fields_exist_based_on_type(loadedModel:dict, content:dict):
|
||||||
@ -296,30 +296,30 @@ class InputValidationShipcall():
|
|||||||
|
|
||||||
if int(type_)==int(ShipcallType.undefined):
|
if int(type_)==int(ShipcallType.undefined):
|
||||||
raise ValidationError({"type":f"providing 'type' is mandatory. Missing key!"})
|
raise ValidationError({"type":f"providing 'type' is mandatory. Missing key!"})
|
||||||
|
|
||||||
# arrival: arrival_berth_id & eta must exist
|
# arrival: arrival_berth_id & eta must exist
|
||||||
elif int(type_)==int(ShipcallType.arrival):
|
elif int(type_)==int(ShipcallType.arrival):
|
||||||
if eta is None:
|
if eta is None:
|
||||||
raise ValidationError({"eta":f"providing 'eta' is mandatory. Missing key!"})
|
raise ValidationError({"eta":f"providing 'eta' is mandatory. Missing key!"})
|
||||||
|
|
||||||
if arrival_berth_id is None:
|
if arrival_berth_id is None:
|
||||||
raise ValidationError({"arrival_berth_id":f"providing 'arrival_berth_id' is mandatory. Missing key!"})
|
raise ValidationError({"arrival_berth_id":f"providing 'arrival_berth_id' is mandatory. Missing key!"})
|
||||||
|
|
||||||
# departure: departive_berth_id and etd must exist
|
# departure: departive_berth_id and etd must exist
|
||||||
elif int(type_)==int(ShipcallType.departure):
|
elif int(type_)==int(ShipcallType.departure):
|
||||||
if etd is None:
|
if etd is None:
|
||||||
raise ValidationError({"etd":f"providing 'etd' is mandatory. Missing key!"})
|
raise ValidationError({"etd":f"providing 'etd' is mandatory. Missing key!"})
|
||||||
|
|
||||||
if departure_berth_id is None:
|
if departure_berth_id is None:
|
||||||
raise ValidationError({"departure_berth_id":f"providing 'departure_berth_id' is mandatory. Missing key!"})
|
raise ValidationError({"departure_berth_id":f"providing 'departure_berth_id' is mandatory. Missing key!"})
|
||||||
|
|
||||||
# shifting: arrival_berth_id, departure_berth_id, eta and etd must exist
|
# shifting: arrival_berth_id, departure_berth_id, eta and etd must exist
|
||||||
elif int(type_)==int(ShipcallType.shifting):
|
elif int(type_)==int(ShipcallType.shifting):
|
||||||
if (eta is None) or (etd is None):
|
if (eta is None) or (etd is None):
|
||||||
raise ValidationError({"eta_or_etd":f"providing 'eta' and 'etd' is mandatory. Missing one of those keys!"})
|
raise ValidationError({"eta_or_etd":f"providing 'eta' and 'etd' is mandatory. Missing one of those keys!"})
|
||||||
if (arrival_berth_id is None) or (departure_berth_id is None):
|
if (arrival_berth_id is None) or (departure_berth_id is None):
|
||||||
raise ValidationError({"arrival_berth_id_or_departure_berth_id":f"providing 'arrival_berth_id' & 'departure_berth_id' is mandatory. Missing key!"})
|
raise ValidationError({"arrival_berth_id_or_departure_berth_id":f"providing 'arrival_berth_id' & 'departure_berth_id' is mandatory. Missing key!"})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValidationError({"type":f"incorrect 'type' provided!"})
|
raise ValidationError({"type":f"incorrect 'type' provided!"})
|
||||||
return
|
return
|
||||||
@ -328,7 +328,7 @@ class InputValidationShipcall():
|
|||||||
def check_times_are_in_future(loadedModel:dict, content:dict):
|
def check_times_are_in_future(loadedModel:dict, content:dict):
|
||||||
"""
|
"""
|
||||||
Dates should be in the future. Depending on the ShipcallType, specific values should be checked
|
Dates should be in the future. Depending on the ShipcallType, specific values should be checked
|
||||||
Perfornms datetime checks in the loadedModel (datetime.datetime objects).
|
Perfornms datetime checks in the loadedModel (datetime.datetime objects).
|
||||||
"""
|
"""
|
||||||
# obtain the current datetime to check, whether the provided values are in the future
|
# obtain the current datetime to check, whether the provided values are in the future
|
||||||
time_now = datetime.datetime.now()
|
time_now = datetime.datetime.now()
|
||||||
@ -350,11 +350,11 @@ class InputValidationShipcall():
|
|||||||
|
|
||||||
# Estimated arrival or departure times
|
# Estimated arrival or departure times
|
||||||
InputValidationShipcall.check_times_in_future_based_on_type(type_, time_now, eta, etd)
|
InputValidationShipcall.check_times_in_future_based_on_type(type_, time_now, eta, etd)
|
||||||
|
|
||||||
# Tidal Window
|
# Tidal Window
|
||||||
InputValidationShipcall.check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to)
|
InputValidationShipcall.check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to)
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_times_in_future_based_on_type(type_, time_now, eta, etd):
|
def check_times_in_future_based_on_type(type_, time_now, eta, etd):
|
||||||
"""
|
"""
|
||||||
@ -366,7 +366,7 @@ class InputValidationShipcall():
|
|||||||
"""
|
"""
|
||||||
if (eta is None) and (etd is None):
|
if (eta is None) and (etd is None):
|
||||||
return
|
return
|
||||||
|
|
||||||
if type_ is None:
|
if type_ is None:
|
||||||
raise ValidationError({"type":f"when providing 'eta' or 'etd', one must provide the type of the shipcall, so the datetimes can be verified."})
|
raise ValidationError({"type":f"when providing 'eta' or 'etd', one must provide the type of the shipcall, so the datetimes can be verified."})
|
||||||
|
|
||||||
@ -412,7 +412,7 @@ class InputValidationShipcall():
|
|||||||
if (eta is not None and etd is None) or (eta is None and etd is not None):
|
if (eta is not None and etd is None) or (eta is None and etd is not None):
|
||||||
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must both be provided when the shipcall type is 'shifting'."})
|
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must both be provided when the shipcall type is 'shifting'."})
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to):
|
def check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to):
|
||||||
if tidal_window_to is not None:
|
if tidal_window_to is not None:
|
||||||
@ -422,12 +422,12 @@ class InputValidationShipcall():
|
|||||||
if tidal_window_from is not None:
|
if tidal_window_from is not None:
|
||||||
if not tidal_window_from >= time_now:
|
if not tidal_window_from >= time_now:
|
||||||
raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."})
|
raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."})
|
||||||
|
|
||||||
if (tidal_window_to is not None) and (tidal_window_from is not None):
|
if (tidal_window_to is not None) and (tidal_window_from is not None):
|
||||||
if tidal_window_to < tidal_window_from:
|
if tidal_window_to < tidal_window_from:
|
||||||
raise ValidationError({"tidal_window_to_or_tidal_window_from":f"'tidal_window_to' must take place after 'tidal_window_from'. Incorrect datetime provided. Found 'tidal_window_to': {tidal_window_to}, 'tidal_window_from': {tidal_window_to}."})
|
raise ValidationError({"tidal_window_to_or_tidal_window_from":f"'tidal_window_to' must take place after 'tidal_window_from'. Incorrect datetime provided. Found 'tidal_window_to': {tidal_window_to}, 'tidal_window_from': {tidal_window_to}."})
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_participant_list_not_empty_when_user_is_agency(loadedModel):
|
def check_participant_list_not_empty_when_user_is_agency(loadedModel):
|
||||||
"""
|
"""
|
||||||
@ -436,10 +436,10 @@ class InputValidationShipcall():
|
|||||||
participants = loadedModel.get("participants", [])
|
participants = loadedModel.get("participants", [])
|
||||||
is_agency_participant = [ParticipantType.AGENCY in ParticipantType(participant.get("type")) for participant in participants]
|
is_agency_participant = [ParticipantType.AGENCY in ParticipantType(participant.get("type")) for participant in participants]
|
||||||
|
|
||||||
if not any(is_agency_participant):
|
if not any(is_agency_participant):
|
||||||
raise ValidationError({"participants":f"One of the assigned participants *must* be of type 'ParticipantType.AGENCY'. Found list of participants: {participants}"})
|
raise ValidationError({"participants":f"One of the assigned participants *must* be of type 'ParticipantType.AGENCY'. Found list of participants: {participants}"})
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_shipcall_is_canceled(loadedModel, content):
|
def check_shipcall_is_canceled(loadedModel, content):
|
||||||
# read the shipcall_id from the PUT data
|
# read the shipcall_id from the PUT data
|
||||||
@ -455,13 +455,13 @@ class InputValidationShipcall():
|
|||||||
if shipcall.get("canceled", False):
|
if shipcall.get("canceled", False):
|
||||||
raise ValidationError({"canceled":f"The shipcall with id 'shipcall_id' is canceled. A canceled shipcall may not be changed."})
|
raise ValidationError({"canceled":f"The shipcall with id 'shipcall_id' is canceled. A canceled shipcall may not be changed."})
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_required_fields_of_put_request(content:dict):
|
def check_required_fields_of_put_request(content:dict):
|
||||||
shipcall_id = content.get("id", None)
|
shipcall_id = content.get("id", None)
|
||||||
if shipcall_id is None:
|
if shipcall_id is None:
|
||||||
raise ValidationError({"id":f"A PUT request requires an 'id' to refer to."})
|
raise ValidationError({"id":f"A PUT request requires an 'id' to refer to."})
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_shipcall_id_exists(loadedModel):
|
def check_shipcall_id_exists(loadedModel):
|
||||||
"""simply checks, whether the defined shipcall ID exists in the database. Otherwise, a PUT-request must fail."""
|
"""simply checks, whether the defined shipcall ID exists in the database. Otherwise, a PUT-request must fail."""
|
||||||
@ -483,8 +483,8 @@ class InputValidationShipcall():
|
|||||||
a) belong to the ASSIGNED agency participant group
|
a) belong to the ASSIGNED agency participant group
|
||||||
b) belong to a BSMD participant, if the assigned agency has enabled the bit flag
|
b) belong to a BSMD participant, if the assigned agency has enabled the bit flag
|
||||||
|
|
||||||
When there is not yet an assigned agency for the respective shipcall, only BSMD users are authorized.
|
When there is not yet an assigned agency for the respective shipcall, only BSMD users are authorized.
|
||||||
This mechanism prevents self-assignment of an agency to arbitrary shipcalls.
|
This mechanism prevents self-assignment of an agency to arbitrary shipcalls.
|
||||||
"""
|
"""
|
||||||
### preparation ###
|
### preparation ###
|
||||||
# use the decoded JWT token and extract the participant type & participant id
|
# use the decoded JWT token and extract the participant type & participant id
|
||||||
@ -521,7 +521,7 @@ class InputValidationShipcall():
|
|||||||
|
|
||||||
### USER authority ###
|
### USER authority ###
|
||||||
# determine, whether the user is a) the assigned agency or b) a BSMD participant
|
# determine, whether the user is a) the assigned agency or b) a BSMD participant
|
||||||
user_is_assigned_agency = (user_participant_id == assigned_agency.participant_id)
|
user_is_assigned_agency = (user_participant_id == assigned_agency.id)
|
||||||
|
|
||||||
# when the BSMD flag is set: the user must be either BSMD or the assigned agency
|
# when the BSMD flag is set: the user must be either BSMD or the assigned agency
|
||||||
# when the BSMD flag is not set: the user must be the assigned agency
|
# when the BSMD flag is not set: the user must be the assigned agency
|
||||||
@ -529,7 +529,7 @@ class InputValidationShipcall():
|
|||||||
|
|
||||||
if not user_is_authorized:
|
if not user_is_authorized:
|
||||||
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). Assigned Agency: {assigned_agency} with Flags: {assigned_agency.flags}") # Forbidden: 403
|
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). Assigned Agency: {assigned_agency} with Flags: {assigned_agency.flags}") # Forbidden: 403
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# when there is no assigned agency, only BSMD users can update the shipcall
|
# when there is no assigned agency, only BSMD users can update the shipcall
|
||||||
if not user_is_bsmd:
|
if not user_is_bsmd:
|
||||||
@ -537,4 +537,3 @@ class InputValidationShipcall():
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user