Skip to content

Commit c1ec444

Browse files
committed
Refactoring editor upload
1 parent 1540fbb commit c1ec444

1 file changed

Lines changed: 79 additions & 26 deletions

File tree

web_api/editor.py

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import io
2-
from typing import Dict, Optional
2+
import re
3+
from typing import Dict, List, Optional, Tuple
34
from uuid import UUID
45

56
import requests
@@ -63,32 +64,26 @@ async def save(
6364
session_data.changeset = changeset
6465
await backend.update(session_id, session_data)
6566

66-
# OsmChange
67-
out = io.StringIO()
68-
o = OsmSax.OsmSaxWriter(out, "UTF-8")
69-
o.startDocument()
70-
o.startElement("osmChange", {"version": "0.6", "generator": "OsmSax"})
71-
72-
methode = {"node": o.NodeCreate, "way": o.WayCreate, "relation": o.RelationCreate}
67+
osmchange: List[Dict] = []
7368
for action in ("modify", "delete"):
7469
if action in json and len(json[action]) > 0:
75-
o.startElement(action, {})
7670
for e in json[action]:
7771
try:
7872
ee = utils.fetch_osm_elem(e["type"], e["id"])
7973
except Exception:
8074
ee = None
8175
if ee and ee["version"] == int(e["version"]):
82-
ee["changeset"] = changeset
83-
ee["tag"] = e["tags"]
84-
methode[e["type"]](ee)
76+
osmchange.append(
77+
{
78+
"action": action,
79+
"type": e["type"],
80+
"osm_elem": ee,
81+
"tags": e["tags"],
82+
}
83+
)
8584
else:
8685
# FIXME reject
8786
pass
88-
o.endElement(action)
89-
90-
o.endElement("osmChange")
91-
osmchange = out.getvalue()
9287

9388
# Fire the changeset
9489
_changeset_upload(session_data.oauth2_token, changeset, osmchange)
@@ -143,13 +138,71 @@ def _changeset_close(oauth2_token: str, id: str) -> None:
143138
request.raise_for_status()
144139

145140

146-
def _changeset_upload(oauth2_token: str, id: str, osmchange) -> None:
147-
request = requests.post(
148-
utils.remote_url_write + "api/0.6/changeset/" + id + "/upload",
149-
data=osmchange.encode(),
150-
headers={
151-
"Authorization": f"Bearer {oauth2_token}",
152-
"Content-Type": "application/xml; charset=utf-8",
153-
},
154-
)
155-
request.raise_for_status()
141+
def _build_osmchange(changeset_id: str, elements: List[Dict]) -> str:
142+
out = io.StringIO()
143+
o = OsmSax.OsmSaxWriter(out, "UTF-8")
144+
o.startDocument()
145+
o.startElement("osmChange", {"version": "0.6", "generator": "OsmSax"})
146+
147+
methode = {"node": o.NodeCreate, "way": o.WayCreate, "relation": o.RelationCreate}
148+
for action in ("modify", "delete"):
149+
action_elems = [el for el in elements if el["action"] == action]
150+
if action_elems:
151+
o.startElement(action, {})
152+
for el in action_elems:
153+
ee = dict(el["osm_elem"])
154+
ee["changeset"] = changeset_id
155+
ee["tag"] = el["tags"]
156+
methode[el["type"]](ee)
157+
o.endElement(action)
158+
159+
o.endElement("osmChange")
160+
return out.getvalue()
161+
162+
163+
def _parse_conflict_element(body: str) -> Tuple[Optional[str], Optional[int]]:
164+
# Typical body: "Version mismatch: Provided 3, server had: 4 of Node 12345"
165+
match = re.search(r"of (Node|Way|Relation) (\d+)", body, re.IGNORECASE)
166+
if match:
167+
return match.group(1).lower(), int(match.group(2))
168+
return None, None
169+
170+
171+
def _changeset_upload(
172+
oauth2_token: str, changeset_id: str, elements: List[Dict]
173+
) -> None:
174+
while True: # Do as much retries as we can
175+
osmchange = _build_osmchange(changeset_id, elements)
176+
response = requests.post(
177+
utils.remote_url_write + "api/0.6/changeset/" + changeset_id + "/upload",
178+
data=osmchange.encode(),
179+
headers={
180+
"Authorization": f"Bearer {oauth2_token}",
181+
"Content-Type": "application/xml; charset=utf-8",
182+
},
183+
)
184+
185+
if response.status_code == 200:
186+
break
187+
188+
if response.status_code != 409:
189+
response.raise_for_status()
190+
191+
elem_type, elem_id = _parse_conflict_element(response.text)
192+
193+
if elem_type is None or elem_id is None:
194+
# Cannot parse the conflict; raise so the caller sees the error.
195+
response.raise_for_status()
196+
197+
elements = list(
198+
filter(
199+
lambda el: not (
200+
el["type"] == elem_type and el["osm_elem"]["id"] == elem_id
201+
),
202+
elements,
203+
)
204+
)
205+
206+
# If no elements remain there is nothing left to upload.
207+
if len(elements) == 0:
208+
break

0 commit comments

Comments
 (0)