Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

finalize literal_eval enhancements #1077

Merged
merged 16 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

## Unreleased

- refer to latitude and longitude parameters as lat and lon consistently across package (#1068 #1069)
- fix references to latitude and longitude parameters as lat and lon consistently across package (#1068 #1069)
- fix handling dict and set attribute types when reloading GraphML files (#1075 #1077)

## 1.7.0 (2023-10-11)

Expand Down
16 changes: 14 additions & 2 deletions osmnx/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,15 @@ def _convert_node_attr_types(G, dtypes=None):
G : networkx.MultiDiGraph
"""
for _, data in G.nodes(data=True):
# first, eval stringified lists, dicts, or sets to convert them to objects
# lists, dicts, or sets would be custom attribute types added by a user
for attr, value in data.items():
if (value.startswith("[") and value.endswith("]")) or (
value.startswith("{") and value.endswith("}")
):
with contextlib.suppress(SyntaxError, ValueError):
data[attr] = ast.literal_eval(value)

for attr in data.keys() & dtypes.keys():
data[attr] = dtypes[attr](data[attr])
return G
Expand All @@ -446,10 +455,13 @@ def _convert_edge_attr_types(G, dtypes=None):
# remove extraneous "id" attribute added by graphml saving
data.pop("id", None)

# first, eval stringified lists to convert them to list objects
# first, eval stringified lists, dicts, or sets to convert them to objects
# edge attributes might have a single value, or a list if simplified
# dicts or sets would be custom attribute types added by a user
for attr, value in data.items():
if value.startswith("[") and value.endswith("]"):
if (value.startswith("[") and value.endswith("]")) or (
value.startswith("{") and value.endswith("}")
):
with contextlib.suppress(SyntaxError, ValueError):
data[attr] = ast.literal_eval(value)

Expand Down
25 changes: 21 additions & 4 deletions tests/test_osmnx.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ def test_endpoints():

def test_graph_save_load():
"""Test saving/loading graphs to/from disk."""
fp = Path(ox.settings.data_folder) / "graph.graphml"

# save graph as shapefile and geopackage
G = ox.graph_from_point(location_point, dist=500, network_type="drive")
ox.save_graph_shapefile(G, directed=True)
Expand Down Expand Up @@ -474,13 +476,28 @@ def test_graph_save_load():
edge_attrs = {n: bool(b) for n, b in zip(G.edges, bools)}
nx.set_edge_attributes(G, edge_attrs, attr_name)

# create list, set, and dict attributes for nodes and edges
rand_ints_nodes = np.random.randint(0, 10, len(G.nodes))
rand_ints_edges = np.random.randint(0, 10, len(G.edges))
list_node_attrs = {n: [n, r] for n, r in zip(G.nodes, rand_ints_nodes)}
nx.set_node_attributes(G, list_node_attrs, "test_list")
list_edge_attrs = {e: [e, r] for e, r in zip(G.edges, rand_ints_edges)}
nx.set_edge_attributes(G, list_edge_attrs, "test_list")
set_node_attrs = {n: {n, r} for n, r in zip(G.nodes, rand_ints_nodes)}
nx.set_node_attributes(G, set_node_attrs, "test_set")
set_edge_attrs = {e: {e, r} for e, r in zip(G.edges, rand_ints_edges)}
nx.set_edge_attributes(G, set_edge_attrs, "test_set")
dict_node_attrs = {n: {n: r} for n, r in zip(G.nodes, rand_ints_nodes)}
nx.set_node_attributes(G, dict_node_attrs, "test_dict")
dict_edge_attrs = {e: {e: r} for e, r in zip(G.edges, rand_ints_edges)}
nx.set_edge_attributes(G, dict_edge_attrs, "test_dict")

# save/load graph as graphml file
ox.save_graphml(G, gephi=True)
ox.save_graphml(G, gephi=False)
ox.save_graphml(G, gephi=False, filepath=Path(ox.settings.data_folder) / "graph.graphml")
filepath = Path(ox.settings.data_folder) / "graph.graphml"
ox.save_graphml(G, gephi=False, filepath=fp)
G2 = ox.load_graphml(
filepath,
fp,
graph_dtypes={attr_name: ox.io._convert_bool_string},
node_dtypes={attr_name: ox.io._convert_bool_string},
edge_dtypes={attr_name: ox.io._convert_bool_string},
Expand All @@ -505,7 +522,7 @@ def test_graph_save_load():
# test custom data types
nd = {"osmid": str}
ed = {"length": str, "osmid": float}
G2 = ox.load_graphml(filepath, node_dtypes=nd, edge_dtypes=ed)
G2 = ox.load_graphml(fp, node_dtypes=nd, edge_dtypes=ed)

# test loading graphml from a file stream
file_bytes = Path.open(Path("tests/input_data/short.graphml"), "rb").read()
Expand Down