def download_changesets(changeset_ids, print_status): """Downloads changesets and all their contents from API, returns (diffs, changeset_users) tuple.""" ch_users = {} diffs = defaultdict(dict) for changeset_id in changeset_ids: print_status(changeset_id) root = api_download('changeset/{0}/download'.format(changeset_id), sysexit_message='Failed to download changeset {0}'.format(changeset_id)) # Iterate over each object, download previous version (unless it's creation) and make a diff count = total = 0 for action in root: if action.tag != 'create': total += len(action) for action in root: for obj_xml in action: if action.tag != 'create': count += 1 if changeset_id not in ch_users: ch_users[changeset_id] = obj_xml.get('user').encode('utf-8') obj = obj_to_dict(obj_xml) if obj['version'] > 1: print_status(changeset_id, obj['type'], obj['id'], count, total) try: obj_prev = obj_to_dict(api_download('{0}/{1}/{2}'.format( obj['type'], obj['id'], obj['version'] - 1), throw=[403])[0]) except HTTPError: msg = '\nCannot revert redactions, see version {0} at https://openstreetmap.org/{1}/{2}/history' raise RevertError(msg.format(obj['version'] - 1, obj['type'], obj['id'])) else: obj_prev = None diffs[(obj['type'], obj['id'])][obj['version']] = make_diff(obj, obj_prev) print_status('flush') return diffs, ch_users
def revert_changes(diffs, print_status): """Actually reverts changes in diffs dict. Returns a changes list for uploading to API.""" # merge versions of same objects in diffs for k in diffs: diff = None for v in sorted(diffs[k].keys()): diff = merge_diffs(diff, diffs[k][v]) diffs[k] = diff changes = [] count = 0 for kobj, change in diffs.iteritems(): count += 1 if change is None: continue try: # Download the latest version of an object print_status(None, kobj[0], kobj[1], count, len(diffs)) obj = obj_to_dict( api_download('{0}s?{0}s={1}'.format(kobj[0], kobj[1]))[0]) # Apply the change obj_new = None if len(change) == 2 and change[1][0] == 'create': if not obj['deleted']: obj_new = { 'type': obj['type'], 'id': obj['id'], 'deleted': True } elif len(change) == 2 and change[1][0] == 'delete': # Restore only if the object is still absent if obj['deleted']: obj_new = change[1][1] else: # Controversial, but I've decided to replace the object with the old one in this case obj_new = change[1][1] else: obj_new = apply_diff(change, deepcopy(obj)) if obj_new is not None: obj_new['version'] = obj['version'] if obj_new != obj: changes.append(obj_new) except Exception as e: raise RevertError( '\nFailed to download the latest version of {0} {1}: {2}'. format(kobj[0], kobj[1], e)) print_status('flush') return changes
def print_changesets_for_user(user, limit=15): """Prints last 15 changesets for a user.""" try: root = api_download('changesets?closed=true&display_name={0}'.format(quote(user)), throw=[404]) for changeset in root[:limit]: created_by = '???' comment = '<no comment>' for tag in changeset.findall('tag'): if tag.get('k') == 'created_by': created_by = tag.get('v').encode('utf-8') elif tag.get('k') == 'comment': comment = tag.get('v').encode('utf-8') print('Changeset {0} created on {1} with {2}:\t{3}'.format( changeset.get('id'), changeset.get('created_at'), created_by, comment)) except HTTPError: print('No such user found.')
def revert_changes(diffs, print_status): """Actually reverts changes in diffs dict. Returns a changes list for uploading to API.""" # merge versions of same objects in diffs for k in diffs: diff = None for v in sorted(diffs[k].keys()): diff = merge_diffs(diff, diffs[k][v]) diffs[k] = diff changes = [] count = 0 for kobj, change in diffs.iteritems(): count += 1 if change is None: continue try: # Download the latest version of an object print_status(None, kobj[0], kobj[1], count, len(diffs)) obj = obj_to_dict(api_download('{0}s?{0}s={1}'.format(kobj[0], kobj[1]))[0]) # Apply the change obj_new = None if len(change) == 2 and change[1][0] == 'create': if not obj['deleted']: obj_new = {'type': obj['type'], 'id': obj['id'], 'deleted': True} elif len(change) == 2 and change[1][0] == 'delete': # Restore only if the object is still absent if obj['deleted']: obj_new = change[1][1] else: # Controversial, but I've decided to replace the object with the old one in this case obj_new = change[1][1] else: obj_new = apply_diff(change, deepcopy(obj)) if obj_new is not None: obj_new['version'] = obj['version'] if obj_new != obj: changes.append(obj_new) except Exception as e: raise RevertError('\nFailed to download the latest version of {0} {1}: {2}'.format(kobj[0], kobj[1], e)) print_status('flush') return changes
print('Omit version number to see an object history.') sys.exit(1) obj_type, obj_id, obj_version = parse_url(sys.argv[1]) if obj_type is None or obj_id is None: safe_print('Please specify correct object type and id.') sys.exit(1) if len(sys.argv) > 2: obj_version = int(sys.argv[2]) # Download full object history # If we fail, revert to a given version blindly history = None safe_print('Downloading history of {0} {1}'.format(obj_type, obj_id)) try: history = api_download('{0}/{1}/history'.format(obj_type, obj_id), throw=[408, 500, 503, 504]) except HTTPError: # Failed to read the complete history due to a timeout, read only two versions safe_print( 'History is too large to download. Querying the last version only.' ) history = etree.Element('osm') try: obj = api_download('{0}/{1}'.format(obj_type, obj_id), throw=[410])[0] history.append(obj) except HTTPError: safe_print( 'To restore a deleted version, we need to know the last version number, and we failed.' ) sys.exit(2)
print('Omit version number to see an object history.') sys.exit(1) obj_type, obj_id, obj_version = parse_url(sys.argv[1]) if obj_type is None or obj_id is None: safe_print('Please specify correct object type and id.') sys.exit(1) if len(sys.argv) > 2: obj_version = int(sys.argv[2]) # Download full object history # If we fail, revert to a given version blindly history = None safe_print('Downloading history of {0} {1}'.format(obj_type, obj_id)) try: history = api_download('{0}/{1}/history'.format(obj_type, obj_id), throw=[408, 500, 503, 504]) except HTTPError: # Failed to read the complete history due to a timeout, read only two versions safe_print('History is too large to download. Querying the last version only.') history = etree.Element('osm') try: obj = api_download('{0}/{1}'.format(obj_type, obj_id), throw=[410])[0] history.append(obj) except HTTPError: safe_print('To restore a deleted version, we need to know the last version number, and we failed.') sys.exit(2) if obj_version is None: # Print history and exit for h in history[-MAX_DEPTH-1:]: print('Version {0}: {1}changeset {2} on {3} by {4}'.format(