diff --git a/api/__init__.py b/api/__init__.py index 4cd493b..1d41200 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -5,10 +5,8 @@ from .project import copy_project from .change_set import ChangeSet from .operation import get_current_operation -from .operation import execute_undo as undo -from .operation import execute_redo as redo -from .operation import have_snapshot, take_snapshot, pick_snapshot -from .operation import have_transaction, start_transaction, commit_transaction, abort_transaction +from .operation import execute_undo, execute_redo +from .operation import have_snapshot, take_snapshot, pick_snapshot, sync_with_server from .s0_base import JUNCTION, RESERVOIR, TANK, PIPE, PUMP, VALVE from .s0_base import is_node, is_junction, is_reservoir, is_tank @@ -20,9 +18,7 @@ from .s0_base import get_node_links from .s1_title import set_title, get_title -from .s2_junctions import add_junction, delete_junction -from .s2_junctions import set_junction_elevation, set_junction_demand, set_junction_pattern, set_junction_coord -from .s2_junctions import get_junction_property_names, get_junction_properties +from .s2_junctions import get_junction_schema, add_junction, delete_junction, get_junction, set_junction from .s3_reservoirs import add_reservoir, delete_reservoir from .s3_reservoirs import set_reservoir_head, set_reservoir_pattern, set_reservoir_coord diff --git a/api/operation.py b/api/operation.py index ef7e081..c746383 100644 --- a/api/operation.py +++ b/api/operation.py @@ -1,111 +1,130 @@ from psycopg.rows import dict_row, Row from .connection import g_conn_dict as conn +from .utility import * +from .change_set import * -def _get_current_transaction(name: str) -> Row | None: - with conn[name].cursor(row_factory=dict_row) as cur: - cur.execute(f"select * from transaction_operation") - return cur.fetchone() -def _get_current_transaction_id(name: str) -> int: - row = _get_current_transaction(name) - return int(row['id']) +API_ADD = 'add' +API_DELETE = 'delete' +API_UPDATE = 'update' -def _remove_transaction(name: str) -> None: - with conn[name].cursor() as cur: - cur.execute(f"delete from transaction_operation") def _remove_operation(name: str, id: int) -> None: - with conn[name].cursor(row_factory=dict_row) as cur: - # can not be >= to cascade delete since there is a tree ! - cur.execute(f"delete from transaction_operation where id = {id}") # this should not happen - cur.execute(f"delete from snapshot_operation where id = {id}") # this may happen - cur.execute(f"delete from operation where id = {id}") + row = read(name, f'select * from operation where parent = {id}') + if row != None: + raise Exception('Disallow to remove parent operation !') + + sql += f'delete from snapshot_operation where id = {id};' + sql += f'delete from operation where id = {id}' + write(name, sql) + def _get_parents(name: str, id: int) -> list[int]: ids = [id] - with conn[name].cursor(row_factory=dict_row) as cur: - while ids[-1] != 0: - cur.execute(f"select parent from operation where id = {ids[-1]}") - ids.append(int(cur.fetchone()['parent'])) + while ids[-1] != 0: + row = read(name, f'select parent from operation where id = {ids[-1]}') + ids.append(int(row['parent'])) return ids + def get_current_operation(name: str) -> int: - with conn[name].cursor(row_factory=dict_row) as cur: - cur.execute(f"select id from current_operation") - return int(cur.fetchone()['id']) + row = read(name, f'select id from current_operation') + return int(row['id']) + def _update_current_operation(name: str, old_id: int, id: int) -> None: - with conn[name].cursor() as cur: - cur.execute(f"update current_operation set id = {id} where id = {old_id}") + return write(name, f'update current_operation set id = {id} where id = {old_id}') -def _add_redo_undo(name: str, redo: str, undo: str) -> int: - with conn[name].cursor(row_factory=dict_row) as cur: - parent = get_current_operation(name) - cur.execute(f"insert into operation (id, redo, undo, parent) values (default, '{redo}', '{undo}', {parent})") - cur.execute("select max(id) from operation") - return int(cur.fetchone()['max']) -# execute curr undo -def _query_undo(name: str, id: str) -> dict[str, str]: - with conn[name].cursor(row_factory=dict_row) as cur: - cur.execute(f"select undo, parent from operation where id = {id}") - return cur.fetchone() +def _add_redo_undo(name: str, redo: str, undo: str, api_id: str, api_op: str, api_object_type: str, api_object_id: str, api_object_properties: list[str]) -> int: + parent = get_current_operation(name) + ps = [] + for p in api_object_properties: + ps.append(f'"{p}"') + if len(ps) > 0: + ps = ','.join(ps) + ps = '{' + ps + '}' + sql = f"insert into operation (id, redo, undo, parent, api_id, api_op, api_object_type, api_object_id, api_object_properties) values (default, '{redo}', '{undo}', {parent}, '{api_id}', '{api_op}', '{api_object_type}', '{api_object_id}', '{ps}')" + else: + sql = f"insert into operation (id, redo, undo, parent, api_id, api_op, api_object_type, api_object_id) values (default, '{redo}', '{undo}', {parent}, '{api_id}', '{api_op}', '{api_object_type}', '{api_object_id}')" + write(name, sql) + + return int(read(name, 'select max(id) from operation')['max']) + + +def _query_operation(name: str, id: str) -> dict[str, str]: + return read(name, f'select * from operation where id = {id}') + -# execute next redo def _query_redo_child(name: str, id: str) -> str: - with conn[name].cursor(row_factory=dict_row) as cur: - cur.execute(f"select redo_child from operation where id = {id}") - return cur.fetchone()['redo_child'] + row = read(name, f'select redo_child from operation where id = {id}') + return row['redo_child'] -def _query_redo(name: str, id: str) -> dict[str, str]: - with conn[name].cursor(row_factory=dict_row) as cur: - cur.execute(f"select redo from operation where id = {id}") - return cur.fetchone()['redo'] def _set_redo_child(name: str, id: int, child: int | str) -> None: - with conn[name].cursor() as cur: - cur.execute(f"update operation set redo_child = {child} where id = {id}") + return write(name, f'update operation set redo_child = {child} where id = {id}') -def _execute(name: str, sql: str) -> None: - with conn[name].cursor() as cur: - sql = sql.replace("\"", "\'") - cur.execute(sql) -def add_operation(name: str, redo: str, undo: str) -> None: - curr = _add_redo_undo(name, redo, undo) +def add_operation(name: str, redo: str, undo: str, api_id: str, api_op: str, api_object_type: str, api_object_id: str, api_object_properties: list[str] = []) -> None: + curr = _add_redo_undo(name, redo, undo, api_id, api_op, api_object_type, api_object_id, api_object_properties) old = get_current_operation(name) _update_current_operation(name, old, curr) -def execute_undo(name: str, discard: bool = False) -> None: + +def _reverser_op(op: str) -> str: + if op == API_ADD: + return API_DELETE + elif op == API_DELETE: + return API_ADD + else: + return op + + +def _get_change_set(row: dict[str, str], undo: bool) -> ChangeSet: + op = row['api_op'] + if undo: + op = _reverser_op(op) + + type = row['api_object_type'] + id = row['api_object_id'] + + change = ChangeSet() + + if op == API_ADD: + change.add(type, id) + elif op == API_DELETE: + change.delete(type, id) + elif op == API_UPDATE: + ps = row['api_object_properties'].removeprefix('{').removesuffix('}').split(',') + change.update(type, id, ps) + + return change + + +def execute_undo(name: str, discard: bool) -> ChangeSet: curr = get_current_operation(name) - # transaction control - if have_transaction(name): - tran = _get_current_transaction(name) - if tran != None and int(tran['id']) >= 0: - if bool(tran['strict']): # strict mode disallow undo - print("Do not allow to undo in strict transaction mode!") - return - elif int(tran['id']) >= curr: # normal mode disallow undo start point, and there is foreign key constraint - print("Do not allow to undo transaction start point!") - return - - row = _query_undo(name, curr) + row = _query_operation(name, curr) undo = row['undo'] if undo == '': print("nothing to undo!") return - parent = int(row['parent']) - _set_redo_child(name, parent, 'NULL' if discard else curr) + change = _get_change_set(row, True) - _execute(name, undo) + parent = int(row['parent']) + _set_redo_child(name, parent, 'null' if discard else curr) + + write(name, undo) _update_current_operation(name, curr, parent) if discard: _remove_operation(name, curr) -def execute_redo(name: str) -> None: + return change + + +def execute_redo(name: str) -> ChangeSet: curr = get_current_operation(name) redoChild = _query_redo_child(name, curr) if redoChild == None: @@ -113,108 +132,127 @@ def execute_redo(name: str) -> None: return child = int(redoChild) - redo = _query_redo(name, child) + row = _query_operation(name, child) + redo = row['redo'] - _execute(name, redo) + change = _get_change_set(row, False) + + write(name, redo) _update_current_operation(name, curr, child) -# snapshot support to checkout between different version of database -# snapshot is persistent -# since redo always remember the recently undo path + return change + + +def _get_operation_by_tag(name: str, tag: str) -> int | None: + row = read(name, f"select id from snapshot_operation where tag = '{tag}'") + return int(row['id']) if row != None else None + def have_snapshot(name: str, tag: str) -> bool: - with conn[name].cursor(row_factory=dict_row) as cur: - cur.execute(f"select id from snapshot_operation where tag = '{tag}'") - return cur.rowcount > 0 + return _get_operation_by_tag(name, tag) != None -def take_snapshot(name: str, tag: str) -> None: + +def take_snapshot(name: str, tag: str) -> int | None: if tag == None or tag == '': print('Non empty tag is expected!') - return + return None curr = get_current_operation(name) + write(name, f"insert into snapshot_operation (id, tag) values ({curr}, '{tag}')") + return curr - with conn[name].cursor() as cur: - cur.execute(f"insert into snapshot_operation (id, tag) values ({curr}, '{tag}')") -def pick_snapshot(name: str, tag: str) -> None: +def pick_snapshot(name: str, tag: str, discard: bool) -> ChangeSet: if tag == None or tag == '': print('Non empty tag is expected!') - return + return ChangeSet() - if not have_snapshot(name, tag): + target = _get_operation_by_tag(name, tag) + if target == None: print('No such snapshot!') - return + return ChangeSet() curr = get_current_operation(name) + curr_parents = _get_parents(name, curr) + target_parents = _get_parents(name, target) - with conn[name].cursor(row_factory=dict_row) as cur: - cur.execute(f"select id from snapshot_operation where tag = '{tag}'") - target = int(cur.fetchone()['id']) - if target in curr_parents: # target -> curr - for i in range(curr_parents.index(target)): - execute_undo(name) - else: - target_parents = _get_parents(name, target) - if curr in target_parents: # curr -> target - for i in range(target_parents.index(curr)): - execute_redo(name) - else: - ancestor_index = -1 - while curr_parents[ancestor_index] == target_parents[ancestor_index]: - ancestor_index -= 1 + change = ChangeSet() - # ancestor -> curr - ancestor = curr_parents[ancestor_index + 1] # ancestor_index + 1 is common parent - for i in range(curr_parents.index(ancestor)): - execute_undo(name) - # ancestor -> redo, need assign redo_child - while target_parents[ancestor_index] != target: - cur.execute(f"update operation set redo_child = '{target_parents[ancestor_index]}' where id = '{target_parents[ancestor_index + 1]}'") - execute_redo(name) - ancestor_index -= 1 - cur.execute(f"update operation set redo_child = '{target}' where id = '{target_parents[1]}'") - execute_redo(name) + if target in curr_parents: + for _ in range(curr_parents.index(target)): + change.append(execute_undo(name, discard)) -# transaction is volatile, commit/abort will destroy transaction. -# can not redo an aborted transaction. -# but can undo a committed transaction... inconsistent... -# it may remove snapshot if it is in aborted transaction + elif curr in target_parents: + target_parents.reverse() + curr_index = target_parents.index(curr) + for i in range(curr_index, len(target_parents) - 1): + write(name, f"update operation set redo_child = '{target_parents[i + 1]}' where id = '{target_parents[i]}'") + change.append(execute_redo(name)) -def have_transaction(name: str) -> bool: - with conn[name].cursor(row_factory=dict_row) as cur: - cur.execute(f"select * from transaction_operation") - return cur.rowcount > 0 + else: + ancestor_index = -1 + while curr_parents[ancestor_index] == target_parents[ancestor_index]: + ancestor_index -= 1 + ancestor = curr_parents[ancestor_index + 1] -def start_transaction(name: str, strict: bool = False) -> None: - if have_transaction(name): - print("Only support single transaction now, please commit/abort current transaction!") - return + for _ in range(curr_parents.index(ancestor)): + change.append(execute_undo(name, discard)) - curr = get_current_operation(name) + target_parents.reverse() + curr_index = target_parents.index(ancestor) + for i in range(curr_index, len(target_parents) - 1): + write(name, f"update operation set redo_child = '{target_parents[i + 1]}' where id = '{target_parents[i]}'") + change.append(execute_redo(name)) - with conn[name].cursor() as cur: - cur.execute(f"insert into transaction_operation (id, strict) values ({curr}, {strict});") + return change.compress() -def commit_transaction(name: str) -> None: - if not have_transaction(name): - print("No active transaction!") - return - _remove_transaction(name) +def get_change_set(name: str, operation: int, undo: bool) -> ChangeSet: + row = read(name, f'select api_id, api_op, api_object_type, api_object_id, api_object_properties from operation where id = {operation}') + return _get_change_set(row, undo) -def abort_transaction(name: str) -> None: - if not have_transaction(name): - print("No active transaction!") - return - tran = _get_current_transaction_id(name) +def get_current_change_set(name: str) -> ChangeSet: + return get_change_set(name, get_current_operation(name), False) - curr = get_current_operation(name) - curr_parents = _get_parents(name, curr) - for i in range(curr_parents.index(tran)): - execute_undo(name, True) - - _remove_transaction(name) +def sync_with_server(name: str, operation: int) -> ChangeSet: + fr = operation + to = get_current_operation(name) + + fr_parents = _get_parents(name, fr) + to_parents = _get_parents(name, to) + + change = ChangeSet() + + if fr in to_parents: + index = to_parents.index(fr) - 1 + while index >= 0: + change.append(get_change_set(name, to_parents[index], False)) + index -= 1 + + elif to in fr_parents: + index = 0 + while index <= fr_parents.index(to) - 1: + change.append(get_change_set(name, fr_parents[index], True)) + index += 1 + + else: + ancestor_index = -1 + while fr_parents[ancestor_index] == to_parents[ancestor_index]: + ancestor_index -= 1 + + ancestor = fr_parents[ancestor_index + 1] + + index = 0 + while index <= fr_parents.index(ancestor) - 1: + change.append(get_change_set(name, fr_parents[index], True)) + index += 1 + + index = to_parents.index(ancestor) - 1 + while index >= 0: + change.append(get_change_set(name, to_parents[index], False)) + index -= 1 + + return change.compress() diff --git a/api/s0_base.py b/api/s0_base.py index c966e66..6a01d9b 100644 --- a/api/s0_base.py +++ b/api/s0_base.py @@ -99,15 +99,15 @@ def add_node(name: str, node_type: str, id: str, x: float, y: float, table_sql: return ChangeSet() with conn[name].cursor() as cur: - sql = f"insert into _node (id, type) values ('{id}', '{node_type}'); " + sql = f"insert into _node (id, type) values ('{id}', '{node_type}');" sql += table_sql - sql += f" insert into coordinates (node, coord) values ('{id}', '({x}, {y})');" + sql += f"insert into coordinates (node, coord) values ('{id}', '({x}, {y})');" cur.execute(sql) redo = sql.replace("'", '"') - undo = f'delete from coordinates where node = "{id}"; ' + undo = f'delete from coordinates where node = "{id}";' undo += table_undo_sql - undo += f' delete from _node where id = "{id}";' + undo += f'delete from _node where id = "{id}";' add_operation(name, redo, undo) change = ChangeSet() @@ -133,9 +133,9 @@ def delete_node(name: str, node_type: str, id: str, table_sql: str, table_undo_s cur.execute(sql) redo = sql.replace("'", '"') - undo = f'insert into _node (id, type) values ("{id}", "{node_type}"); ' + undo = f'insert into _node (id, type) values ("{id}", "{node_type}");' undo += table_undo_sql - undo += f' insert into coordinates (node, coord) values ("{id}", "{coord}");' + undo += f'insert into coordinates (node, coord) values ("{id}", "{coord}");' add_operation(name, redo, undo) change = ChangeSet() @@ -148,13 +148,13 @@ def add_link(name: str, link_type: str, id: str, table_sql: str, table_undo_sql: return ChangeSet() with conn[name].cursor() as cur: - sql = f"insert into _link (id, type) values ('{id}', '{link_type}'); " + sql = f"insert into _link (id, type) values ('{id}', '{link_type}');" sql += table_sql cur.execute(sql) redo = sql.replace("'", '"') undo = table_undo_sql - undo += f' delete from _link where id = "{id}";' + undo += f'delete from _link where id = "{id}";' add_operation(name, redo, undo) change = ChangeSet() diff --git a/api/s24_coordinates.py b/api/s24_coordinates.py index ddb8df1..0f5ed48 100644 --- a/api/s24_coordinates.py +++ b/api/s24_coordinates.py @@ -5,10 +5,8 @@ from .operation import * from .change_set import ChangeSet -def _to_point(coord: str) -> dict[str, float]: - coord = coord.removeprefix('(') - coord = coord.removesuffix(')') - coord = coord.split(',') +def _to_client_point(coord: str) -> dict[str, float]: + coord = coord.removeprefix('(').removesuffix(')').split(',') return { 'x': float(coord[0]), 'y': float(coord[1]) } @@ -20,7 +18,7 @@ def get_node_coord(name: str, id: str) -> dict[str, float] | None: return None coord = str(row['coord']) - return _to_point(coord) + return _to_client_point(coord) def set_node_coord(name: str, node_type: str, id: str, x: float, y: float) -> ChangeSet: diff --git a/api/s2_junctions.py b/api/s2_junctions.py index fdfee6d..ac775c3 100644 --- a/api/s2_junctions.py +++ b/api/s2_junctions.py @@ -4,82 +4,98 @@ from .s0_base import * from .change_set import ChangeSet from .s24_coordinates import * from .utility import * +from .schema import * + + +schema: dict[str, dict[str, Any]] = { \ + 'id' : define_property(str_type, False, True), \ + 'elevation' : define_property(float_type), \ + 'demand' : define_property(float_type, True), \ + 'pattern' : define_property(str_type, True), \ + 'coord' : define_property(client_point_type), \ + 'links' : define_property(str_list_type, False, True)} + + +def get_junction_schema(name: str) -> dict[str, str]: + return schema + + +def _query_junction(name: str, id: str) -> Row | None: + return read(name, f"select id, elevation, demand, pattern from junctions where id = '{id}'") def add_junction(name: str, id: str, x: float, y: float, elevation: float) -> ChangeSet: - sql = f"insert into junctions (id, elevation) values ('{id}', {elevation});" - undo_sql = f'delete from junctions where id = "{id}";' - return add_node(name, JUNCTION, id, x, y, sql, undo_sql) - - -def _get_junction(name: str, id: str) -> Row | None: - return query(name, f"select elevation, demand, pattern from junctions where id = '{id}'") - - -def delete_junction(name: str, id: str) -> ChangeSet: - if not is_junction(name, id): + if is_junction(name, id): return ChangeSet() - row = _get_junction(name, id) - if row == None: - return ChangeSet() + sql = f"insert into _node (id, type) values ('{id}', '{JUNCTION}');" + sql += f"\ninsert into junctions (id, elevation) values ('{id}', {elevation});" + sql += f"\ninsert into coordinates (node, coord) values ('{id}', '({x}, {y})');" - elevation = row['elevation'] - demand = decorate(row['demand'], 'float', True) - pattern = decorate(row['pattern'], 'str', True) + undo = f"delete from coordinates where node = ''{id}'';" + undo += f"\ndelete from junctions where id = ''{id}'';" + undo += f"\ndelete from _node where id = ''{id}'';" - sql = f"delete from junctions where id = '{id}';" - undo_sql = f'insert into junctions (id, elevation, demand, pattern) values ("{id}", {elevation}, {demand}, {pattern});' - - return delete_node(name, JUNCTION, id, sql, undo_sql) + write(name, sql) + add_operation(name, sql.replace("'", "''"), undo, 'add_junction', API_ADD, JUNCTION, id) + return get_current_change_set(name) -def _set_junction(name: str, id: str, key: str, key_type: str, value: str, optional: bool = False) -> ChangeSet: - if not is_junction(name, id): - return ChangeSet() - - row = _get_junction(name, id) - if row == None: - return ChangeSet() - - return update(name, JUNCTION, 'junctions', 'id', id, key, key_type, row[key], value, optional) - - -def set_junction_elevation(name: str, id: str, elevation: float) -> ChangeSet: - return _set_junction(name, id, 'elevation', 'float', str(elevation)) - - -def set_junction_demand(name: str, id: str, demand: float) -> ChangeSet: - return _set_junction(name, id, 'demand', 'float', str(demand), True) - - -def set_junction_pattern(name: str, id: str, pattern: str) -> ChangeSet: - if not is_pattern(name, pattern): - return ChangeSet() - - return _set_junction(name, id, 'pattern', 'str', pattern, True) - - -def set_junction_coord(name: str, id: str, x: float, y: float) -> ChangeSet: - if not is_junction(name, id): - return ChangeSet() - - return set_node_coord(name, JUNCTION, id, x, y) - - -def get_junction_property_names(name: str) -> list[str]: - return ['elevation', 'demand', 'pattern', 'coord', 'links'] - - -def get_junction_properties(name: str, id: str) -> dict[str, Any] | None: - row = _get_junction(name, id) +def get_junction(name: str, id: str) -> dict[str, Any] | None: + row = _query_junction(name, id) if row == None: return None ps: dict[str, str] = {} ps['elevation'] = float(row['elevation']) - ps['demand'] = float(row['demand']) if row != None and row['demand'] != None else None - ps['pattern'] = row['pattern'] if row != None and row['pattern'] != None else None + ps['demand'] = float(row['demand']) if row['demand'] != None else None + ps['pattern'] = row['pattern'] ps['coord'] = get_node_coord(name, id) ps['links'] = get_node_links(name, id) return ps + + +def delete_junction(name: str, id: str) -> ChangeSet: + row = get_junction(name, id) + if row == None: + return ChangeSet() + + old = Serialize(get_junction(name, id), schema).to_storage() + + sql = f"delete from coordinates where node = '{id}';" + sql += f"\ndelete from junctions where id = '{id}';" + sql += f"\ndelete from _node where id = '{id}';" + + undo = f"insert into _node (id, type) values (''{id}'', ''{JUNCTION}'');" + undo += f"\ninsert into junctions (id, elevation, demand, pattern) values (''{id}'', {old['elevation']}, {old['demand']}, {old['pattern']});" + undo += f"\ninsert into coordinates (node, coord) values (''{id}'', {old['coord']});" + + write(name, sql) + add_operation(name, sql.replace("'", "''"), undo, 'delete_junction', API_DELETE, JUNCTION, id) + return get_current_change_set(name) + + +def set_junction(name: str, id: str, properties: dict[str, Any]) -> ChangeSet: + if not is_junction(name, id): + return ChangeSet() + + old = Serialize(get_junction(name, id), schema).to_storage() + + new = get_junction(name, id) + ps: list[str] = [] + for key in properties: + if key in schema and schema[key]['readonly'] == False: + new[key] = properties[key] + ps.append(key) + new = Serialize(new, schema).to_execution() + + sql = f"update junctions set elevation = {new['elevation']}, demand = {new['demand']}, pattern = {new['pattern']} where id = '{id}';" + undo = "" + if 'coord' in ps: + sql += f"\nupdate coordinates set coord = {new['coord']} where node = '{id}';" + undo = f"update coordinates set coord = {old['coord']} where node = ''{id}'';" + undo += f"\nupdate junctions set elevation = {old['elevation']}, demand = {old['demand']}, pattern = {old['pattern']} where id = ''{id}'';" + + write(name, sql) + add_operation(name, sql.replace("'", "''"), undo, 'set_junction', API_UPDATE, JUNCTION, id, ps) + return get_current_change_set(name) diff --git a/tjnetwork.py b/tjnetwork.py index 2ca8ed2..1939b63 100644 --- a/tjnetwork.py +++ b/tjnetwork.py @@ -71,32 +71,23 @@ def copy_project(source: str, new: str) -> None: def get_current_operation(name: str) -> int: return api.get_current_operation(name) -def undo(name: str) -> None: - return api.undo(name) +def execute_undo(name: str, discard: bool = False) -> ChangeSet: + return api.execute_undo(name, discard) -def redo(name: str) -> None: - return api.redo(name) +def execute_redo(name: str) -> ChangeSet: + return api.execute_redo(name) def have_snapshot(name: str, tag: str) -> bool: return api.have_snapshot(name, tag) -def take_snapshot(name: str, tag: str) -> None: +def take_snapshot(name: str, tag: str) -> int: return api.take_snapshot(name, tag) -def pick_snapshot(name: str, tag: str) -> None: - return api.pick_snapshot(name, tag) +def pick_snapshot(name: str, tag: str, discard: bool = False) -> ChangeSet: + return api.pick_snapshot(name, tag, discard) -def have_transaction(name: str) -> bool: - return api.have_transaction(name) - -def start_transaction(name: str, strict: bool = False) -> None: - return api.start_transaction(name, strict) - -def commit_transaction(name: str) -> None: - return api.commit_transaction(name) - -def abort_transaction(name: str) -> None: - return api.abort_transaction(name) +def sync_with_server(name: str, operation: int) -> ChangeSet: + return api.sync_with_server(name, operation) ############################################################ @@ -161,8 +152,8 @@ def get_title(name: str) -> str: # junction 2.[JUNCTIONS] ############################################################ -def get_junction_property_names(name: str) -> list[str]: - return api.get_junction_property_names(name) +def get_junction_schema(name: str) -> dict[str, str]: + return api.get_junction_schema(name) def add_junction(name: str, junction_id: str, x: float, y: float, elevation: float) -> ChangeSet: return api.add_junction(name, junction_id, x, y, elevation) @@ -170,20 +161,11 @@ def add_junction(name: str, junction_id: str, x: float, y: float, elevation: flo def delete_junction(name: str, junction_id: str) -> ChangeSet: return api.delete_junction(name, junction_id) -def set_junction_elevation(name: str, junction_id: str, elevation: float) -> ChangeSet: - return api.set_junction_elevation(name, junction_id, elevation) +def get_junction(name: str, junction_id: str) -> dict[str, Any] | None: + return api.get_junction(name, junction_id) -def set_junction_demand(name: str, junction_id: str, demand: float) -> ChangeSet: - return api.set_junction_demand(name, junction_id, demand) - -def set_junction_pattern(name: str, junction_id: str, pattern: str) -> ChangeSet: - return api.set_junction_pattern(name, junction_id, pattern) - -def set_junction_coord(name: str, junction_id: str, x: float, y: float) -> ChangeSet: - return api.set_junction_coord(name, junction_id, x, y) - -def get_junction_properties(name: str, junction_id: str) -> dict[str, Any] | None: - return api.get_junction_properties(name, junction_id) +def set_junction(name: str, junction_id: str, properties: dict[str, Any]) -> ChangeSet: + return api.set_junction(name, junction_id, properties) ############################################################