diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..65927c8 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +max-line-length = 100 +max-doc-length = 100 +per-file-ignores = + __init__.py: F401 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c7069d..c204061 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,16 @@ jobs: - name: Install packages run: | sudo apt-get update -y -qq - sudo apt-get install -y -qq libboost-dev libexpat1-dev zlib1g-dev libbz2-dev libproj-dev libgeos-dev liblz4-dev + sudo apt-get install -y -qq libboost-dev libexpat1-dev zlib1g-dev libbz2-dev libproj-dev libgeos-dev liblz4-dev pipx + pipx install mypy + pipx inject mypy types-requests + pipx install flake8 + + - name: Lint package + run: flake8 src examples + + - name: Typecheck package + run: mypy src - name: Set up Python 3.7 uses: actions/setup-python@v5 diff --git a/examples/amenity_list.py b/examples/amenity_list.py index 46e49c5..5393ce8 100644 --- a/examples/amenity_list.py +++ b/examples/amenity_list.py @@ -13,6 +13,7 @@ wkbfab = osmium.geom.WKBFactory() + class AmenityListHandler(osmium.SimpleHandler): def print_amenity(self, tags, lon, lat): @@ -37,6 +38,7 @@ def main(osmfile): return 0 + if __name__ == '__main__': if len(sys.argv) != 2: print("Usage: python %s " % sys.argv[0]) diff --git a/examples/convert.py b/examples/convert.py index b387468..aa12063 100644 --- a/examples/convert.py +++ b/examples/convert.py @@ -24,4 +24,3 @@ writer.add_relation(obj) writer.close() - diff --git a/examples/convert_to_geojson.py b/examples/convert_to_geojson.py index 295e317..17c2c30 100644 --- a/examples/convert_to_geojson.py +++ b/examples/convert_to_geojson.py @@ -10,6 +10,7 @@ geojsonfab = osmium.geom.GeoJSONFactory() + class GeoJsonWriter(osmium.SimpleHandler): def __init__(self): @@ -48,7 +49,8 @@ def print_object(self, geojson, tags): def main(osmfile): handler = GeoJsonWriter() - handler.apply_file(osmfile,filters=[osmium.filter.EmptyTagFilter().enable_for(osmium.osm.NODE)]) + handler.apply_file(osmfile, + filters=[osmium.filter.EmptyTagFilter().enable_for(osmium.osm.NODE)]) handler.finish() return 0 diff --git a/examples/filter_coastlines.py b/examples/filter_coastlines.py index 383fbca..00dbd01 100644 --- a/examples/filter_coastlines.py +++ b/examples/filter_coastlines.py @@ -32,7 +32,8 @@ way_filter = osmium.filter.KeyFilter('natural').enable_for(osmium.osm.WAY) # We need nodes and ways in the second pass. - for obj in osmium.FileProcessor(sys.argv[1], osmium.osm.WAY | osmium.osm.NODE).with_filter(way_filter): + for obj in osmium.FileProcessor(sys.argv[1], osmium.osm.WAY | osmium.osm.NODE) \ + .with_filter(way_filter): if obj.is_node() and obj.id in nodes: # Strip the object of tags along the way writer.add_node(obj.replace(tags={})) diff --git a/examples/normalize_boolean.py b/examples/normalize_boolean.py index b2fee0b..0a871dd 100644 --- a/examples/normalize_boolean.py +++ b/examples/normalize_boolean.py @@ -6,6 +6,7 @@ import osmium import sys + class BoolNormalizer(osmium.SimpleHandler): def __init__(self, writer): diff --git a/examples/osm_diff_stats.py b/examples/osm_diff_stats.py index 5df9a85..a7f9d06 100644 --- a/examples/osm_diff_stats.py +++ b/examples/osm_diff_stats.py @@ -7,6 +7,7 @@ import osmium import sys + class Stats: def __init__(self): @@ -40,6 +41,7 @@ def main(osmfile): return 0 + if __name__ == '__main__': if len(sys.argv) != 2: print("Usage: python %s " % sys.argv[0]) diff --git a/examples/osm_file_stats.py b/examples/osm_file_stats.py index a7f020a..5808bfe 100644 --- a/examples/osm_file_stats.py +++ b/examples/osm_file_stats.py @@ -6,6 +6,7 @@ import osmium import sys + class FileStatsHandler(osmium.SimpleHandler): def __init__(self): @@ -35,6 +36,7 @@ def main(osmfile): return 0 + if __name__ == '__main__': if len(sys.argv) != 2: print("Usage: python %s " % sys.argv[0]) diff --git a/examples/osm_replication_stats.py b/examples/osm_replication_stats.py index c3d2e07..ecd8590 100644 --- a/examples/osm_replication_stats.py +++ b/examples/osm_replication_stats.py @@ -9,7 +9,8 @@ import datetime as dt import osmium.replication.server as rserv -class Stats(object): + +class Stats: def __init__(self): self.added = 0 @@ -24,12 +25,12 @@ def add(self, o): else: self.modified += 1 - def outstats(self, prefix): print("%s added: %d" % (prefix, self.added)) print("%s modified: %d" % (prefix, self.modified)) print("%s deleted: %d" % (prefix, self.deleted)) + class FileStatsHandler(osmium.SimpleHandler): def __init__(self): super(FileStatsHandler, self).__init__() @@ -54,8 +55,7 @@ def relation(self, r): server_url = sys.argv[1] start = dt.datetime.strptime(sys.argv[2], "%Y-%m-%dT%H:%M:%SZ") - if sys.version_info >= (3,0): - start = start.replace(tzinfo=dt.timezone.utc) + start = start.replace(tzinfo=dt.timezone.utc) maxkb = min(int(sys.argv[3]), 10 * 1024) repserv = rserv.ReplicationServer(server_url) diff --git a/examples/osm_url_stats.py b/examples/osm_url_stats.py index 42393ac..7121a67 100644 --- a/examples/osm_url_stats.py +++ b/examples/osm_url_stats.py @@ -13,7 +13,6 @@ print("Usage: python osm_url_stats.py ") sys.exit(-1) - data = urlrequest.urlopen(sys.argv[1]).read() counter = {'n': 0, 'w': 0, 'r': 0} diff --git a/examples/pub_names.py b/examples/pub_names.py index f9046e0..69dd890 100644 --- a/examples/pub_names.py +++ b/examples/pub_names.py @@ -4,6 +4,7 @@ import osmium import sys + def main(osmfile): for obj in osmium.FileProcessor(osmfile)\ .with_filter(osmium.filter.KeyFilter('amenity'))\ @@ -13,6 +14,7 @@ def main(osmfile): return 0 + if __name__ == '__main__': if len(sys.argv) != 2: print("Usage: python %s " % sys.argv[0]) diff --git a/examples/road_length.py b/examples/road_length.py index b7afc64..89cbc2f 100644 --- a/examples/road_length.py +++ b/examples/road_length.py @@ -6,13 +6,14 @@ import osmium import sys + def main(osmfile): total = 0.0 # As we need the way geometry, the node locations need to be cached. # This is enabled with the with_locations() function. for obj in osmium.FileProcessor(osmfile, osmium.osm.NODE | osmium.osm.WAY)\ - .with_locations()\ - .with_filter(osmium.filter.KeyFilter('highway')): + .with_locations()\ + .with_filter(osmium.filter.KeyFilter('highway')): if obj.is_way(): try: total += osmium.geom.haversine_distance(obj.nodes) @@ -25,6 +26,7 @@ def main(osmfile): return 0 + if __name__ == '__main__': if len(sys.argv) != 2: print("Usage: python %s " % sys.argv[0]) diff --git a/examples/use_nodecache.py b/examples/use_nodecache.py index 3764024..4933ea3 100644 --- a/examples/use_nodecache.py +++ b/examples/use_nodecache.py @@ -4,15 +4,18 @@ import osmium import sys + class WayHandler: def __init__(self, idx): self.idx = idx def way(self, w): + locations = [] for n in w.nodes: - loc = idx.get(n.ref) # note that cache is used here - print("%d %s" % (w.id, len(w.nodes))) + locations.append(idx.get(n.ref)) # note that cache is used here + print(w.id, len(w.nodes), locations) + if len(sys.argv) != 3: print("Usage: python use_nodecache.py ") diff --git a/src/osmium/back_reference_writer.py b/src/osmium/back_reference_writer.py index a06673f..5de2b49 100644 --- a/src/osmium/back_reference_writer.py +++ b/src/osmium/back_reference_writer.py @@ -14,6 +14,7 @@ from osmium.file_processor import FileProcessor, zip_processors from osmium import IdTracker + class BackReferenceWriter: """ Writer that adds referenced objects, so that all written objects are reference-complete. @@ -28,7 +29,7 @@ class BackReferenceWriter: def __init__(self, outfile: Union[str, 'os.PathLike[str]', File], ref_src: Union[str, 'os.PathLike[str]', File, FileBuffer], - overwrite: bool=False, remove_tags: bool=True, + overwrite: bool = False, remove_tags: bool = True, relation_depth: int = 0): """ Create a new writer. diff --git a/src/osmium/file_processor.py b/src/osmium/file_processor.py index b9fbdf2..dfb02a0 100644 --- a/src/osmium/file_processor.py +++ b/src/osmium/file_processor.py @@ -2,9 +2,9 @@ # # This file is part of pyosmium. (https://osmcode.org/pyosmium/) # -# Copyright (C) 2024 Sarah Hoffmann and others. +# Copyright (C) 2025 Sarah Hoffmann and others. # For a full list of authors see the git log. -from typing import Iterable, Iterator, Tuple, Any, Union, Optional, List +from typing import Iterable, Iterator, Tuple, Union, Optional, List import os import osmium @@ -12,6 +12,7 @@ from osmium.io import File, FileBuffer from osmium.osm.types import OSMEntity + class FileProcessor: """ A processor that reads an OSM file in a streaming fashion, optionally pre-filters the data, enhances it with geometry information, @@ -19,7 +20,7 @@ class FileProcessor: """ def __init__(self, indata: Union[File, FileBuffer, str, 'os.PathLike[str]'], - entities: osmium.osm.osm_entity_bits=osmium.osm.ALL) -> None: + entities: osmium.osm.osm_entity_bits = osmium.osm.ALL) -> None: """ Initialise a new file processor for the given input source _indata_. This may either be a filename, an instance of [File](IO.md#osmium.io.File) or buffered data in form of a [FileBuffer](IO.md#osmium.io.FileBuffer). @@ -54,7 +55,7 @@ def node_location_storage(self) -> Optional[LocationTable]: """ return self._node_store - def with_locations(self, storage: str='flex_mem') -> 'FileProcessor': + def with_locations(self, storage: str = 'flex_mem') -> 'FileProcessor': """ Enable caching of node locations. The file processor will keep the coordinates of all nodes that are read from the file in memory and automatically enhance the node list of ways with @@ -77,7 +78,8 @@ def with_locations(self, storage: str='flex_mem') -> 'FileProcessor': elif storage is None or isinstance(storage, osmium.index.LocationTable): self._node_store = storage else: - raise TypeError("'storage' argument must be a LocationTable or a string describing the index") + raise TypeError("'storage' argument must be a LocationTable " + "or a string describing the index") return self @@ -117,7 +119,6 @@ def with_filter(self, filt: 'osmium._osmium.HandlerLike') -> 'FileProcessor': self._filters.append(filt) return self - def handler_for_filtered(self, handler: 'osmium._osmium.HandlerLike') -> 'FileProcessor': """ Set a fallback handler for object that have been filtered out. @@ -214,13 +215,12 @@ def next(self, nextid: Optional[Tuple[int, int]]) -> Tuple[int, int]: if self.comp == nextid: self.current = next(self.iter, None) if self.current is None: - self.comp = (100, 0) # end of file marker. larger than any ID + self.comp = (100, 0) # end of file marker. larger than any ID else: self.comp = (TID[self.current.type_str()], self.current.id) assert self.comp is not None return self.comp - iters = [_CompIter(p) for p in procs] nextid = min(i.next(None) for i in iters) diff --git a/src/osmium/forward_reference_writer.py b/src/osmium/forward_reference_writer.py index bf1befa..b65e32c 100644 --- a/src/osmium/forward_reference_writer.py +++ b/src/osmium/forward_reference_writer.py @@ -14,6 +14,7 @@ from osmium.io import File, FileBuffer from osmium.file_processor import FileProcessor, zip_processors + class ForwardReferenceWriter: """ Writer that adds forward-referenced objects optionally also making the final file reference complete. An object is a forward reference @@ -29,9 +30,9 @@ class ForwardReferenceWriter: def __init__(self, outfile: Union[str, 'os.PathLike[str]', File], ref_src: Union[str, 'os.PathLike[str]', File, FileBuffer], - overwrite: bool=False, back_references: bool=True, - remove_tags: bool=True, forward_relation_depth: int=0, - backward_relation_depth: int=1) -> None: + overwrite: bool = False, back_references: bool = True, + remove_tags: bool = True, forward_relation_depth: int = 0, + backward_relation_depth: int = 1) -> None: """ Create a new writer. `outfile` is the name of the output file to write. The file must @@ -59,11 +60,9 @@ def __init__(self, outfile: Union[str, 'os.PathLike[str]', File], self.forward_relation_depth = forward_relation_depth self.backward_relation_depth = backward_relation_depth - def __enter__(self) -> 'ForwardReferenceWriter': return self - def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: if exc_type is None: self.close() @@ -71,7 +70,6 @@ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: # Exception occured. Do not write out the final file. self.writer.close() - def add(self, obj: Any) -> None: """ Write an arbitrary OSM object. This can be either an osmium object or a Python object that has the appropriate @@ -85,28 +83,24 @@ def add(self, obj: Any) -> None: self.id_tracker.add_relation(obj.id) self.writer.add(obj) - def add_node(self, n: Any) -> None: """ Write out an OSM node. """ self.id_tracker.add_node(n.id) self.writer.add_node(n) - def add_way(self, w: Any) -> None: """ Write out an OSM way. """ self.id_tracker.add_way(w.id) self.writer.add_way(w) - def add_relation(self, r: Any) -> None: """ Write out an OSM relation. """ self.id_tracker.add_relation(r.id) self.writer.add_relation(r) - def close(self) -> None: """ Close the writer and write out the final file. @@ -116,16 +110,17 @@ def close(self) -> None: if self.tmpdir is not None: self.writer.close() - self.id_tracker.complete_forward_references(self.ref_src, - relation_depth=self.forward_relation_depth) + self.id_tracker.complete_forward_references( + self.ref_src, + relation_depth=self.forward_relation_depth) if self.back_references: - self.id_tracker.complete_backward_references(self.ref_src, - relation_depth=self.backward_relation_depth) + self.id_tracker.complete_backward_references( + self.ref_src, + relation_depth=self.backward_relation_depth) fp1 = FileProcessor(Path(self.tmpdir.name, 'forward_writer.osm.pbf')) fp2 = FileProcessor(self.ref_src).with_filter(self.id_tracker.id_filter()) - with SimpleWriter(self.outfile, overwrite=self.overwrite) as writer: for o1, o2 in zip_processors(fp1, fp2): if o1: @@ -135,5 +130,3 @@ def close(self) -> None: self.tmpdir.cleanup() self.tmpdir = None - - diff --git a/src/osmium/helper.py b/src/osmium/helper.py index 8cffe7b..82f7047 100644 --- a/src/osmium/helper.py +++ b/src/osmium/helper.py @@ -2,7 +2,7 @@ # # This file is part of pyosmium. (https://osmcode.org/pyosmium/) # -# Copyright (C) 2024 Sarah Hoffmann and others. +# Copyright (C) 2025 Sarah Hoffmann and others. # For a full list of authors see the git log. from typing import Optional, Callable, TypeVar, TYPE_CHECKING @@ -56,11 +56,12 @@ class WriteHandler(SimpleWriter): documentation. """ - def __init__(self, filename: str, bufsz: int=4096*1024, filetype: str="") -> None: + def __init__(self, filename: str, bufsz: int = 4096*1024, filetype: str = "") -> None: super().__init__(filename, bufsz=bufsz, filetype=filetype) -def _merge_apply(self: MergeInputReader, *handlers: 'HandlerLike', idx: str = '', simplify: bool = True) -> None: +def _merge_apply(self: MergeInputReader, *handlers: 'HandlerLike', + idx: str = '', simplify: bool = True) -> None: if idx: lh = NodeLocationsForWays(create_map(idx)) lh.ignore_errors() @@ -68,4 +69,5 @@ def _merge_apply(self: MergeInputReader, *handlers: 'HandlerLike', idx: str = '' self._apply_internal(*handlers, simplify=simplify) -MergeInputReader.apply = _merge_apply # type: ignore[method-assign] + +MergeInputReader.apply = _merge_apply # type: ignore[method-assign] diff --git a/src/osmium/osm/__init__.py b/src/osmium/osm/__init__.py index 59c2157..6daabb2 100644 --- a/src/osmium/osm/__init__.py +++ b/src/osmium/osm/__init__.py @@ -2,7 +2,7 @@ # # This file is part of pyosmium. (https://osmcode.org/pyosmium/) # -# Copyright (C) 2023 Sarah Hoffmann and others. +# Copyright (C) 2025 Sarah Hoffmann and others. # For a full list of authors see the git log. from osmium.osm.mutable import create_mutable_node, create_mutable_way, create_mutable_relation @@ -36,12 +36,12 @@ ALL as ALL) setattr(Location, '__repr__', - lambda l: f'osmium.osm.Location(x={l.x!r}, y={l.y!r})' - if l.valid() else 'osmium.osm.Location()') + lambda loc: f'osmium.osm.Location(x={loc.x!r}, y={loc.y!r})' + if loc.valid() else 'osmium.osm.Location()') setattr(Location, '__str__', - lambda l: f'{l.lon_without_check():.7f}/{l.lat_without_check():.7f}' - if l.valid() else 'invalid') + lambda loc: f'{loc.lon_without_check():.7f}/{loc.lat_without_check():.7f}' + if loc.valid() else 'invalid') -setattr(Box, '__repr__', lambda b: f"osmium.osm.Box(bottom_left={b.bottom_left!r}, top_right={b.top_right!r})") +setattr(Box, '__repr__', + lambda b: f"osmium.osm.Box(bottom_left={b.bottom_left!r}, top_right={b.top_right!r})") setattr(Box, '__str__', lambda b: f'({b.bottom_left!s} {b.top_right!s})') - diff --git a/src/osmium/osm/mutable.py b/src/osmium/osm/mutable.py index 8d8522b..4416041 100644 --- a/src/osmium/osm/mutable.py +++ b/src/osmium/osm/mutable.py @@ -2,11 +2,12 @@ # # This file is part of pyosmium. (https://osmcode.org/pyosmium/) # -# Copyright (C) 2023 Sarah Hoffmann and others. +# Copyright (C) 2025 Sarah Hoffmann and others. # For a full list of authors see the git log. from typing import Optional, Union, Any, Mapping, Sequence, Tuple, TYPE_CHECKING from datetime import datetime + if TYPE_CHECKING: import osmium.osm @@ -16,7 +17,8 @@ LocationLike = Union[osmium.osm.Location, Tuple[float, float]] NodeSequence = Union[osmium.osm.NodeRefList, Sequence[Union[osmium.osm.NodeRef, int]]] MemberSequence = Union[osmium.osm.RelationMemberList, - Sequence[Union[osmium.osm.RelationMember, Tuple[str, int, str]]]] + Sequence[Union[osmium.osm.RelationMember, Tuple[str, int, str]]]] + class OSMObject: """ Mutable version of [osmium.osm.OSMObject][]. @@ -93,6 +95,7 @@ def __init__(self, base: Optional[Union['Way', 'osmium.osm.Way']] = None, else: self.nodes = nodes if nodes is not None else base.nodes + class Relation(OSMObject): """ The mutable version of [osmium.osm.Relation][]. """ @@ -120,6 +123,7 @@ def create_mutable_node(node: Union[Node, 'osmium.osm.Node'], **args: Any) -> No """ return Node(base=node, **args) + def create_mutable_way(way: Union[Way, 'osmium.osm.Way'], **args: Any) -> Way: """ Create a mutable way replacing the properties given in the named parameters. Note that this function only creates a shallow @@ -127,10 +131,10 @@ def create_mutable_way(way: Union[Way, 'osmium.osm.Way'], **args: Any) -> Way: """ return Way(base=way, **args) + def create_mutable_relation(rel: Union[Relation, 'osmium.osm.Relation'], **args: Any) -> Relation: """ Create a mutable relation replacing the properties given in the named parameters. Note that this function only creates a shallow copy which is still bound to the scope of the original object. """ return Relation(base=rel, **args) - diff --git a/src/osmium/osm/types.py b/src/osmium/osm/types.py index dc1b455..f0649df 100644 --- a/src/osmium/osm/types.py +++ b/src/osmium/osm/types.py @@ -2,9 +2,9 @@ # # This file is part of pyosmium. (https://osmcode.org/pyosmium/) # -# Copyright (C) 2024 Sarah Hoffmann and others. +# Copyright (C) 2025 Sarah Hoffmann and others. # For a full list of authors see the git log. -from typing import Sequence, Any, NamedTuple, Callable, Optional, Iterator, \ +from typing import Any, NamedTuple, Callable, Optional, Iterator, \ Iterable, TYPE_CHECKING, TypeVar, Generic, Tuple, Union import datetime as dt @@ -16,6 +16,7 @@ T_obj = TypeVar('T_obj', 'cosm.COSMNode', 'cosm.COSMWay', 'cosm.COSMRelation', 'cosm.COSMArea') + def _make_repr(name: str, *attrs: str) -> Callable[[Any], str]: fmt_string = f'osmium.osm.{name}('\ + ', '.join([f'{x}={{0.{x}!r}}' for x in attrs])\ @@ -223,15 +224,17 @@ class OuterRing(NodeRefList): For its members see [`osmium.osm.NodeRefList`][]. """ + class InnerRing(NodeRefList): """ List of nodes in an inner ring. " For its members see [`osmium.osm.NodeRefList`][]. """ + class RelationMember: """ Single member of a relation. """ - ref : int + ref: int "OSM ID of the object. Only unique within the type." type: str "Type of object referenced, a node, way or relation." @@ -252,7 +255,8 @@ def __str__(self) -> str: return f"{self.type}{self.ref:d}" def __repr__(self) -> str: - return f"osmium.osm.RelationMember(ref={self.ref!r}, type={self.type!r}, role={self.role!r})" + return f"osmium.osm.RelationMember(ref={self.ref!r}, " \ + f"type={self.type!r}, role={self.role!r})" class MemberIterator: @@ -442,7 +446,6 @@ def lat(self) -> float: """ return self.location.lat - @property def lon(self) -> float: """ Return longitude of the node. @@ -458,7 +461,6 @@ def __str__(self) -> str: return '' - __repr__ = _make_repr('Node', 'id', 'deleted', 'visible', 'version', 'changeset', 'uid', 'timestamp', 'user', 'tags', 'location') @@ -566,7 +568,7 @@ def __str__(self) -> str: if self._pyosmium_data.is_valid(): return f"r{self.id:d}: members={self.members!s}, tags={self.tags!s}" - return f"" + return '' __repr__ = _make_repr('Relation', 'id', 'deleted', 'visible', 'version', 'changeset', 'uid', 'timestamp', 'user', @@ -635,7 +637,7 @@ def is_multipolygon(self) -> bool: """ return self._pyosmium_data.is_multipolygon() - def num_rings(self) -> Tuple[int,int]: + def num_rings(self) -> Tuple[int, int]: """ Return a tuple with the number of outer rings and inner rings. This function goes through all rings to count them. @@ -659,7 +661,7 @@ def __str__(self) -> str: if self._pyosmium_data.is_valid(): return f"a{self.id:d}: num_rings={self.num_rings()}, tags={self.tags!s}" - return f"" + return '' __repr__ = _make_repr('Area', 'id', 'deleted', 'visible', 'version', 'changeset', 'uid', 'timestamp', 'user', @@ -751,13 +753,14 @@ def type_str(self) -> str: def __str__(self) -> str: if self._pyosmium_data.is_valid(): - return f'c{self.id:d}: closed_at={self.closed_at!s}, bounds={self.bounds!s}, tags={self.tags!s}' + return f"c{self.id:d}: closed_at={self.closed_at!s}, " \ + f"bounds={self.bounds!s}, tags={self.tags!s}" - return f"" + return '' - __repr__ = _make_repr('Changeset', 'id', 'uid', 'created_at', 'closed_at', - 'open', 'num_changes', 'bounds', 'user', - 'tags') + __repr__ = _make_repr('Changeset', 'id', 'uid', 'created_at', 'closed_at', + 'open', 'num_changes', 'bounds', 'user', + 'tags') OSMEntity = Union[Node, Way, Relation, Area, Changeset] diff --git a/src/osmium/replication/server.py b/src/osmium/replication/server.py index 787fecf..64917d5 100644 --- a/src/osmium/replication/server.py +++ b/src/osmium/replication/server.py @@ -2,15 +2,14 @@ # # This file is part of pyosmium. (https://osmcode.org/pyosmium/) # -# Copyright (C) 2023 Sarah Hoffmann and others. +# Copyright (C) 2025 Sarah Hoffmann and others. # For a full list of authors see the git log. """ Helper functions to communicate with replication servers. """ -from typing import NamedTuple, Optional, Any, Iterator, cast, Dict, Mapping, Tuple +from typing import NamedTuple, Optional, Any, Iterator, cast, Mapping, Tuple import urllib.request as urlrequest from urllib.error import URLError import datetime as dt -from collections import namedtuple from contextlib import contextmanager from math import ceil @@ -27,6 +26,7 @@ LOG = logging.getLogger('pyosmium') LOG.addHandler(logging.NullHandler()) + class OsmosisState(NamedTuple): """ Represents a state file of a replication server. """ @@ -35,6 +35,7 @@ class OsmosisState(NamedTuple): timestamp: dt.datetime "Date until when changes are contained in the change file." + class DownloadResult(NamedTuple): """ Downloaded change. """ @@ -45,6 +46,7 @@ class DownloadResult(NamedTuple): newest: int "ID of the newest change available on the server." + class ReplicationServer: """ Represents a connection to a server that publishes replication data. Replication change files allow to keep local OSM data up-to-date without @@ -93,11 +95,11 @@ def set_request_parameter(self, key: str, value: Any) -> None: See the `requests documentation `_ for possible parameters. Per default, a timeout of 60 sec is set and streaming download enabled. - """ + """ # noqa self.extra_request_params[key] = value def make_request(self, url: str) -> urlrequest.Request: - headers = {"User-Agent" : f"pyosmium/{version.pyosmium_release}"} + headers = {"User-Agent": f"pyosmium/{version.pyosmium_release}"} return urlrequest.Request(url, headers=headers) def open_url(self, url: urlrequest.Request) -> Any: @@ -108,7 +110,7 @@ def open_url(self, url: urlrequest.Request) -> Any: get_params = self.extra_request_params else: get_params = dict(self.extra_request_params) - get_params['headers'] = {k: v for k,v in url.header_items()} + get_params['headers'] = {k: v for k, v in url.header_items()} if self.session is not None: return self.session.get(url.get_full_url(), **get_params) @@ -153,7 +155,7 @@ def collect_diffs(self, start_id: int, max_size: int = 1024) -> Optional[Downloa while left_size > 0 and current_id <= newest.sequence: try: diffdata = self.get_diff_block(current_id) - except: + except: # noqa: E722 LOG.error("Error during diff download. Bailing out.") diffdata = '' if len(diffdata) == 0: @@ -248,7 +250,8 @@ def apply_diffs_to_file(self, infile: str, outfile: str, h.set("osmosis_replication_sequence_number", str(diffs.id)) info = self.get_state_info(diffs.id) if info is not None: - h.set("osmosis_replication_timestamp", info.timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")) + h.set("osmosis_replication_timestamp", + info.timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")) if extra_headers is not None: for k, v in extra_headers.items(): h.set(k, v) @@ -270,7 +273,6 @@ def apply_diffs_to_file(self, infile: str, outfile: str, return (diffs.id, diffs.newest) - def timestamp_to_sequence(self, timestamp: dt.datetime, balanced_search: bool = False) -> Optional[int]: """ Get the sequence number of the replication file that contains the @@ -353,7 +355,6 @@ def timestamp_to_sequence(self, timestamp: dt.datetime, if lower.sequence + 1 >= upper.sequence: return lower.sequence - def get_state_info(self, seq: Optional[int] = None, retries: int = 2) -> Optional[OsmosisState]: """ Downloads and returns the state information for the given sequence. If the download is successful, a namedtuple with @@ -416,7 +417,6 @@ def get_diff_block(self, seq: int) -> str: # generated by urllib.request return cast(str, resp.read()) - def get_state_url(self, seq: Optional[int]) -> str: """ Returns the URL of the state.txt files for a given sequence id. @@ -430,7 +430,6 @@ def get_state_url(self, seq: Optional[int]) -> str: return '%s/%03i/%03i/%03i.state.txt' % \ (self.baseurl, seq / 1000000, (seq % 1000000) / 1000, seq % 1000) - def get_diff_url(self, seq: int) -> str: """ Returns the URL to the diff file for the given sequence id. """ diff --git a/src/osmium/replication/utils.py b/src/osmium/replication/utils.py index 5518532..ef67ca1 100644 --- a/src/osmium/replication/utils.py +++ b/src/osmium/replication/utils.py @@ -2,18 +2,18 @@ # # This file is part of pyosmium. (https://osmcode.org/pyosmium/) # -# Copyright (C) 2023 Sarah Hoffmann and others. +# Copyright (C) 2025 Sarah Hoffmann and others. # For a full list of authors see the git log. """ Helper functions for change file handling. """ from typing import NamedTuple, Optional import logging import datetime as dt -from collections import namedtuple from osmium.io import Reader as oreader from osmium.osm import NOTHING LOG = logging.getLogger('pyosmium') + class ReplicationHeader(NamedTuple): """ Description of a replication state. """ @@ -72,7 +72,8 @@ def get_replication_header(fname: str) -> ReplicationHeader: ts = ts.replace(tzinfo=dt.timezone.utc) except ValueError: - LOG.warning("Date in OSM file header is not in ISO8601 format (e.g. 2015-12-24T08:08Z). Ignored.") + LOG.warning("Date in OSM file header is not in ISO8601 format" + "(e.g. 2015-12-24T08:08Z). Ignored.") ts = None else: ts = None diff --git a/src/osmium/simple_handler.py b/src/osmium/simple_handler.py index a89f082..511a05d 100644 --- a/src/osmium/simple_handler.py +++ b/src/osmium/simple_handler.py @@ -2,10 +2,9 @@ # # This file is part of pyosmium. (https://osmcode.org/pyosmium/) # -# Copyright (C) 2024 Sarah Hoffmann and others. +# Copyright (C) 2025 Sarah Hoffmann and others. # For a full list of authors see the git log. from typing import Union, List, TYPE_CHECKING -from pathlib import Path if TYPE_CHECKING: import os @@ -18,6 +17,7 @@ from .area import AreaManager from .index import create_map + class SimpleHandler: """ The most generic of OSM data handlers. Derive your data processor from this class and implement callbacks for each object type you are @@ -48,8 +48,8 @@ def enabled_for(self) -> osm_entity_bits: return entities def apply_file(self, filename: Union[str, 'os.PathLike[str]', File], - locations: bool=False, idx: str='flex_mem', - filters: List['HandlerLike']=[]) -> None: + locations: bool = False, idx: str = 'flex_mem', + filters: List['HandlerLike'] = []) -> None: """ Apply the handler to the given file. If locations is true, then a location handler will be applied before, which saves the node positions. In that case, the type of this position index can be @@ -60,16 +60,14 @@ def apply_file(self, filename: Union[str, 'os.PathLike[str]', File], """ self._apply_object(filename, locations, idx, filters) - def apply_buffer(self, buffer: 'Buffer', format: str, - locations: bool=False, idx: str='flex_mem', - filters: List['HandlerLike']=[]) -> None: + locations: bool = False, idx: str = 'flex_mem', + filters: List['HandlerLike'] = []) -> None: """Apply the handler to a string buffer. The buffer must be a byte string. """ self._apply_object(FileBuffer(buffer, format), locations, idx, filters) - def _apply_object(self, obj: Union[str, 'os.PathLike[str]', File, FileBuffer], locations: bool, idx: str, filters: List['HandlerLike']) -> None: