[Git][debian-gis-team/pyosmium][bullseye-backports] 7 commits: New upstream version 3.5.0
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Thu Nov 17 07:25:31 GMT 2022
Bas Couwenberg pushed to branch bullseye-backports at Debian GIS Project / pyosmium
Commits:
52a0bf72 by Bas Couwenberg at 2022-11-09T17:13:27+01:00
New upstream version 3.5.0
- - - - -
159e21cb by Bas Couwenberg at 2022-11-09T17:13:31+01:00
Update upstream source from tag 'upstream/3.5.0'
Update to upstream version '3.5.0'
with Debian dir b2c873d5b9c52c38e09632344ed3e371b7bbf130
- - - - -
ad5ff9e9 by Bas Couwenberg at 2022-11-09T17:14:10+01:00
New upstream release.
- - - - -
927fb086 by Bas Couwenberg at 2022-11-09T17:24:44+01:00
Update lintian overrides.
- - - - -
d67ab4c2 by Bas Couwenberg at 2022-11-09T17:24:44+01:00
Set distribution to unstable.
- - - - -
4b286b41 by Bas Couwenberg at 2022-11-17T08:16:16+01:00
Merge tag 'debian/3.5.0-1' into bullseye-backports
releasing package pyosmium version 3.5.0-1
- - - - -
3b61a766 by Bas Couwenberg at 2022-11-17T08:16:22+01:00
Rebuild for bullseye-backports.
- - - - -
27 changed files:
- CHANGELOG.md
- debian/changelog
- debian/python3-pyosmium.lintian-overrides
- lib/osm.cc
- lib/osmium.cc
- lib/simple_writer.cc
- setup.py
- + src/osmium/_osmium.pyi
- + src/osmium/geom.pyi
- src/osmium/helper.py
- + src/osmium/index.pyi
- + src/osmium/io.pyi
- src/osmium/osm/__init__.py
- + src/osmium/osm/_osm.pyi
- src/osmium/osm/mutable.py
- + src/osmium/py.typed
- + src/osmium/replication/_replication.pyi
- src/osmium/replication/server.py
- src/osmium/replication/utils.py
- src/osmium/version.py
- test/helpers.py
- test/test_osm.py
- test/test_pyosmium_get_changes.py
- test/test_taglist.py
- test/test_writer.py
- tools/pyosmium-get-changes
- tools/pyosmium-up-to-date
Changes:
=====================================
CHANGELOG.md
=====================================
@@ -4,6 +4,32 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
+## [3.5.0] - 2022-11-09
+
+### Added
+
+- type annotations for the public interface
+- new `ReplicationServer.set_request_parameter()` function to specify additional
+ parameters to give to `requests.get()`
+
+### Fixed
+
+- writer now rolls back data buffer after exceptions (fixes #212)
+- off-by-one error in computation of change ID from a start date
+- socket timeout in pyosmium-get-changes/pyosmium-up-to-date was ignored
+ falling back to waiting forever on requests
+
+### Changed
+
+- use format strings instead of `format()` where possible
+- pyosmium-get-changes now prints an error message when there is a HTTP error
+ during download
+- overwriting `ReplicationServer.open_url()` is no longer recommended,
+ use new `ReplicationServer.set_request_parameter()` function instead
+- cookies for pyosmium-get-changes/pyosmium-up-to-date are now set via
+ request parameters, removing the last use-case where urllib was used
+- update bundled pybind11 to 2.10.1
+
## [3.4.1] - 2022-07-28
### Fixed
=====================================
debian/changelog
=====================================
@@ -1,3 +1,16 @@
+pyosmium (3.5.0-1~bpo11+1) bullseye-backports; urgency=medium
+
+ * Rebuild for bullseye-backports.
+
+ -- Bas Couwenberg <sebastic at debian.org> Thu, 17 Nov 2022 08:16:19 +0100
+
+pyosmium (3.5.0-1) unstable; urgency=medium
+
+ * New upstream release.
+ * Update lintian overrides.
+
+ -- Bas Couwenberg <sebastic at debian.org> Wed, 09 Nov 2022 17:15:15 +0100
+
pyosmium (3.4.1-1~bpo11+1) bullseye-backports; urgency=medium
* Rebuild for bullseye-backports.
=====================================
debian/python3-pyosmium.lintian-overrides
=====================================
@@ -2,3 +2,6 @@
# Fortify Source functions: no, only unprotected functions found!
hardening-no-fortify-functions *
+# False positive, lat/lon
+spelling-error-in-binary lon long *
+
=====================================
lib/osm.cc
=====================================
@@ -102,7 +102,8 @@ PYBIND11_MODULE(_osm, m) {
py::return_value_policy::reference_internal,
"Extend the box to include the given location. If the location "
"is invalid the box remains unchanged. If the box is invalid, it "
- "will contain only the location after the operation.")
+ "will contain only the location after the operation. "
+ "Returns a reference to itself.")
.def("extend",
(osmium::Box& (osmium::Box::*)(osmium::Box const &))
&osmium::Box::extend,
@@ -110,7 +111,8 @@ PYBIND11_MODULE(_osm, m) {
py::return_value_policy::reference_internal,
"Extend the box to include the given box. If the box to be added "
"is invalid the input box remains unchanged. If the input box is invalid, it "
- "will become equal to the box that was added.")
+ "will become equal to the box that was added. "
+ "Returns a reference to itself.")
.def("valid", &osmium::Box::valid,
"Check if the box coordinates are defined and with the usual bounds.")
.def("size", &osmium::Box::size,
=====================================
lib/osmium.cc
=====================================
@@ -37,7 +37,7 @@ PYBIND11_MODULE(_osmium, m) {
"Apply a chain of handlers.");
m.def("apply", [](osmium::io::Reader &rd, NodeLocationHandler &h)
{ py::gil_scoped_release release; osmium::apply(rd, h); },
- py::arg("reader"), py::arg("handler"),
+ py::arg("reader"), py::arg("node_handler"),
"Apply a chain of handlers.");
m.def("apply", [](osmium::io::Reader &rd, NodeLocationHandler &l,
BaseHandler &h)
=====================================
lib/simple_writer.cc
=====================================
@@ -30,6 +30,12 @@ public:
void add_osmium_object(const osmium::OSMObject& o)
{
+ if (!buffer) {
+ throw std::runtime_error{"Writer already closed."};
+ }
+
+ buffer.rollback();
+
buffer.add_item(o);
flush_buffer();
}
@@ -40,6 +46,8 @@ public:
throw std::runtime_error{"Writer already closed."};
}
+ buffer.rollback();
+
if (py::isinstance<osmium::Node>(o)) {
buffer.add_item(o.cast<osmium::Node &>());
} else {
@@ -65,6 +73,8 @@ public:
throw std::runtime_error{"Writer already closed."};
}
+ buffer.rollback();
+
if (py::isinstance<osmium::Way>(o)) {
buffer.add_item(o.cast<osmium::Way &>());
} else {
@@ -88,6 +98,8 @@ public:
throw std::runtime_error{"Writer already closed."};
}
+ buffer.rollback();
+
if (py::isinstance<osmium::Relation>(o)) {
buffer.add_item(o.cast<osmium::Relation &>());
} else {
=====================================
setup.py
=====================================
@@ -166,6 +166,9 @@ setup(
ext_modules=[CMakeExtension('cmake_example')],
packages = ['osmium', 'osmium/osm', 'osmium/replication'],
package_dir = {'' : 'src'},
+ package_data = { 'osmium': ['py.typed', '*.pyi',
+ 'replication/_replication.pyi',
+ 'osm/_osm.pyi']},
python_requires = ">=3.6",
install_requires = ['requests'],
cmdclass=dict(build_ext=CMakeBuild, sdist=Pyosmium_sdist),
=====================================
src/osmium/_osmium.pyi
=====================================
@@ -0,0 +1,55 @@
+from typing import overload, ByteString, Union
+import os
+
+import osmium.index
+import osmium.io
+
+StrPath = Union[str, 'os.PathLike[str]']
+
+class InvalidLocationError(Exception): ...
+
+class NodeLocationsForWays:
+ def __init__(self, locations: osmium.index.LocationTable) -> None: ...
+ def ignore_errors(self) -> None: ...
+
+class BaseHandler: ...
+
+class SimpleHandler(BaseHandler):
+ def __init__(self) -> None: ...
+ def apply_buffer(self, buffer: Union[ByteString, str], format: str, locations: bool = ..., idx: str = ...) -> None: ...
+ def apply_file(self, filename: StrPath, locations: bool = ..., idx: str = ...) -> None: ...
+
+class MergeInputReader:
+ def __init__(self) -> None: ...
+ def add_buffer(self, buffer: Union[ByteString, str], format: str) -> int: ...
+ def add_file(self, file: str) -> int: ...
+ def apply(self, handler: BaseHandler, idx: str = ..., simplify: bool = ...) -> None: ...
+ def apply_to_reader(self, reader: osmium.io.Reader, writer: osmium.io.Writer, with_history: bool = ...) -> None: ...
+
+class WriteHandler(BaseHandler):
+ @overload
+ def __init__(self, filename: str, bufsz: int, filetype: str) -> None: ...
+ @overload
+ def __init__(self, filename: str, bufsz: int) -> None: ...
+ @overload
+ def __init__(self, filename: str) -> None: ...
+ def close(self) -> None: ...
+
+class SimpleWriter:
+ @overload
+ def __init__(self, filename: str, bufsz: int, header: osmium.io.Header) -> None: ...
+ @overload
+ def __init__(self, filename: str, bufsz: int) -> None: ...
+ @overload
+ def __init__(self, filename: str) -> None: ...
+ def add_node(self, node: object) -> None: ...
+ def add_relation(self, relation: object) -> None: ...
+ def add_way(self, way: object) -> None: ...
+ def close(self) -> None: ...
+
+ at overload
+def apply(reader: osmium.io.Reader, handler: BaseHandler) -> None: ...
+ at overload
+def apply(reader: osmium.io.Reader, node_handler: NodeLocationsForWays) -> None: ...
+ at overload
+def apply(reader: osmium.io.Reader, node_handler: NodeLocationsForWays, handler: BaseHandler) -> None: ...
=====================================
src/osmium/geom.pyi
=====================================
@@ -0,0 +1,99 @@
+from typing import ClassVar
+
+from typing import overload
+import osmium.osm
+ALL: use_nodes
+BACKWARD: direction
+FORWARD: direction
+UNIQUE: use_nodes
+
+
+class use_nodes:
+ ALL: ClassVar[use_nodes] = ...
+ UNIQUE: ClassVar[use_nodes] = ...
+ def __init__(self, value: int) -> None: ...
+ @property
+ def name(self) -> str: ...
+ @property
+ def value(self) -> int: ...
+
+class direction:
+ BACKWARD: ClassVar[direction] = ...
+ FORWARD: ClassVar[direction] = ...
+ def __init__(self, value: int) -> None: ...
+ @property
+ def name(self) -> str: ...
+ @property
+ def value(self) -> int: ...
+
+class Coordinates:
+ @overload
+ def __init__(self) -> None: ...
+ @overload
+ def __init__(self, cx: float, cy: float) -> None: ...
+ @overload
+ def __init__(self, location: osmium.osm.Location) -> None: ...
+ def valid(self) -> bool: ...
+ @property
+ def x(self) -> float: ...
+ @property
+ def y(self) -> float: ...
+
+
+class GeoJSONFactory:
+ def __init__(self) -> None: ...
+ @overload
+ def create_linestring(self, list: osmium.osm.WayNodeList, use_nodes: use_nodes = ..., direction: direction = ...) -> str: ...
+ @overload
+ def create_linestring(self, way: osmium.osm.Way, use_nodes: use_nodes = ..., direction: direction = ...) -> str: ...
+ def create_multipolygon(self, area: osmium.osm.Area) -> str: ...
+ @overload
+ def create_point(self, location: osmium.osm.Location) -> str: ...
+ @overload
+ def create_point(self, node: osmium.osm.Node) -> str: ...
+ @overload
+ def create_point(self, ref: osmium.osm.NodeRef) -> str: ...
+ @property
+ def epsg(self) -> int: ...
+ @property
+ def proj_string(self) -> str: ...
+
+class WKBFactory:
+ def __init__(self) -> None: ...
+ @overload
+ def create_linestring(self, list: osmium.osm.WayNodeList, use_nodes: use_nodes = ..., direction: direction = ...) -> str: ...
+ @overload
+ def create_linestring(self, way: osmium.osm.Way, use_nodes: use_nodes = ..., direction: direction = ...) -> str: ...
+ def create_multipolygon(self, area: osmium.osm.Area) -> str: ...
+ @overload
+ def create_point(self, location: osmium.osm.Location) -> str: ...
+ @overload
+ def create_point(self, node: osmium.osm.Node) -> str: ...
+ @overload
+ def create_point(self, ref: osmium.osm.NodeRef) -> str: ...
+ @property
+ def epsg(self) -> int: ...
+ @property
+ def proj_string(self) -> str: ...
+
+class WKTFactory:
+ def __init__(self) -> None: ...
+ @overload
+ def create_linestring(self, list: osmium.osm._osm.WayNodeList, use_nodes: use_nodes = ..., direction: direction = ...) -> str: ...
+ @overload
+ def create_linestring(self, way: osmium.osm._osm.Way, use_nodes: use_nodes = ..., direction: direction = ...) -> str: ...
+ def create_multipolygon(self, area: osmium.osm._osm.Area) -> str: ....
+ @overload
+ def create_point(self, location: osmium.osm._osm.Location) -> str: ...
+ @overload
+ def create_point(self, node: osmium.osm._osm.Node) -> str: ...
+ @overload
+ def create_point(self, ref: osmium.osm._osm.NodeRef) -> str: ...
+ @property
+ def epsg(self) -> int: ...
+ @property
+ def proj_string(self) -> str: ...
+
+def haversine_distance(list: osmium.osm.WayNodeList) -> float: ...
+def lonlat_to_mercator(coordinate: Coordinates) -> Coordinates: ...
+def mercator_to_lonlat(coordinate: Coordinates) -> Coordinates: ...
=====================================
src/osmium/helper.py
=====================================
@@ -1,6 +1,17 @@
+from typing import Optional, Callable, TypeVar
+
from osmium._osmium import SimpleHandler
+from osmium.osm import Node, Way, Relation, Area, Changeset
+
+T = TypeVar('T')
+HandlerFunc = Optional[Callable[[T], None]]
+
-def make_simple_handler(node=None, way=None, relation=None, area=None, changeset=None):
+def make_simple_handler(node: HandlerFunc[Node] = None,
+ way: HandlerFunc[Way] = None,
+ relation: HandlerFunc[Relation] = None,
+ area: HandlerFunc[Area] = None,
+ changeset: HandlerFunc[Changeset] = None) -> SimpleHandler:
""" Convenience function that creates a `SimpleHandler` from a set of
callback functions. Each of the parameters takes an optional callable
that must expect a single positional parameter with the object being
@@ -10,14 +21,14 @@ def make_simple_handler(node=None, way=None, relation=None, area=None, changeset
pass
if node is not None:
- __HandlerWithCallbacks.node = staticmethod(node)
+ setattr(__HandlerWithCallbacks, "node", staticmethod(node))
if way is not None:
- __HandlerWithCallbacks.way = staticmethod(way)
+ setattr(__HandlerWithCallbacks, "way", staticmethod(way))
if relation is not None:
- __HandlerWithCallbacks.relation = staticmethod(relation)
+ setattr(__HandlerWithCallbacks, "relation", staticmethod(relation))
if area is not None:
- __HandlerWithCallbacks.area = staticmethod(area)
+ setattr(__HandlerWithCallbacks, "area", staticmethod(area))
if changeset is not None:
- __HandlerWithCallbacks.changeset = staticmethod(changeset)
+ setattr(__HandlerWithCallbacks, "changeset", staticmethod(changeset))
return __HandlerWithCallbacks()
=====================================
src/osmium/index.pyi
=====================================
@@ -0,0 +1,12 @@
+from typing import List
+
+import osmium.osm
+
+class LocationTable:
+ def clear(self) -> None: ...
+ def get(self, id: int) -> osmium.osm.Location: ...
+ def set(self, id: int, loc: osmium.osm.Location) -> None: ...
+ def used_memory(self) -> int: ...
+
+def create_map(map_type: str) -> LocationTable: ...
+def map_types() -> List[str]: ...
=====================================
src/osmium/io.pyi
=====================================
@@ -0,0 +1,41 @@
+from typing import Any
+
+from typing import overload
+
+import osmium.osm
+
+class File:
+ has_multiple_object_versions: bool
+ @overload
+ def __init__(self, filename: str) -> None: ...
+ @overload
+ def __init__(self, filename: str, format: str) -> None: ...
+ def parse_format(self, arg0: str) -> None: ...
+
+class Header:
+ has_multiple_object_versions: bool
+ def __init__(self) -> None: ...
+ def add_box(self, box: osmium.osm.Box) -> Header: ...
+ def box(self) -> osmium.osm.Box: ...
+ def get(self, key: str, default: str = ...) -> str: ...
+ def set(self, key: str, value: str) -> None: ...
+
+class Reader:
+ @overload
+ def __init__(self, filename: str) -> None: ...
+ @overload
+ def __init__(self, filename: str, types: osmium.osm.osm_entity_bits) -> None: ...
+ def close(self) -> None: ...
+ def eof(self) -> bool: ...
+ def header(self) -> Header: ...
+
+class Writer:
+ @overload
+ def __init__(self, filename: str) -> None: ...
+ @overload
+ def __init__(self, ffile: File) -> None: ...
+ @overload
+ def __init__(self, filename: str, header: Header) -> None: ...
+ @overload
+ def __init__(self, ffile: File, header: Header) -> None: ...
+ def close(self) -> int: ...
=====================================
src/osmium/osm/__init__.py
=====================================
@@ -1,97 +1,79 @@
-import osmium.osm.mutable
+from typing import Any, Callable, Sequence
+
+from osmium.osm.mutable import create_mutable_node, create_mutable_way, create_mutable_relation
from ._osm import *
-def create_mutable_node(node, **args):
- """ Create a mutable node 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 osmium.osm.mutable.Node(base=node, **args)
-
-def create_mutable_way(way, **args):
- """ Create a mutable way 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 osmium.osm.mutable.Way(base=way, **args)
-
-def create_mutable_relation(rel, **args):
- """ 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 osmium.osm.mutable.Relation(base=rel, **args)
-
-Node.replace = create_mutable_node
-Way.replace = create_mutable_way
-Relation.replace = create_mutable_relation
-
-def _make_repr(attr_list):
+setattr(Node, 'replace', create_mutable_node)
+setattr(Way, 'replace', create_mutable_way)
+setattr(Relation, 'replace', create_mutable_relation)
+
+def _make_repr(*attrs: str) -> Callable[[object], str]:
fmt_string = 'osmium.osm.{0}('\
- + ', '.join(['{0}={{1.{0}!r}}'.format(x) for x in attr_list])\
+ + ', '.join([f'{x}={{1.{x}!r}}' for x in attrs])\
+ ')'
return lambda o: fmt_string.format(o.__class__.__name__, o)
-def _list_repr(obj):
+def _list_repr(obj: Sequence[Any]) -> str:
return 'osmium.osm.{}([{}])'.format(obj.__class__.__name__,
', '.join(map(repr, obj)))
-def _list_elipse(obj):
+def _list_elipse(obj: Sequence[Any]) -> str:
objects = ','.join(map(str, obj))
if len(objects) > 50:
objects = objects[:47] + '...'
return objects
-Location.__repr__ = lambda l: 'osmium.osm.Location(x={0.x!r}, y={0.y!r})'.format(l) \
- if l.valid() else 'osmium.osm.Location()'
-Location.__str__ = lambda l: '{:f}/{:f}'.format(l.lon_without_check(),
- l.lat_without_check()) \
- if l.valid() else 'invalid'
+setattr(Location, '__repr__',
+ lambda l: f'osmium.osm.Location(x={l.x!r}, y={l.y!r})'
+ if l.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')
-Box.__repr__ = _make_repr(['bottom_left', 'top_right'])
-Box.__str__ = lambda b: '({0.bottom_left!s} {0.top_right!s})'.format(b)
+setattr(Box, '__repr__', _make_repr('bottom_left', 'top_right'))
+setattr(Box, '__str__', lambda b: f'({b.bottom_left!s} {b.top_right!s})')
-Tag.__repr__ = _make_repr(['k', 'v'])
-Tag.__str__ = lambda t: '{0.k}={0.v}'.format(t)
+setattr(Tag, '__repr__', _make_repr('k', 'v'))
+setattr(Tag, '__str__', lambda t: f'{t.k}={t.v}')
-TagList.__repr__ = lambda t: "osmium.osm.TagList({%s})" \
- % ', '.join(["%r: %r" % (i.k, i.v) for i in t])
-TagList.__str__ = lambda t: '{' + _list_elipse(t) + '}'
+setattr(TagList, '__repr__', lambda t: "osmium.osm.TagList({%s})"
+ % ', '.join([f"{i.k!r}: {i.v!r}" for i in t]))
+setattr(TagList, '__str__', lambda t: f'{{{_list_elipse(t)}}}')
-NodeRef.__repr__ = _make_repr(['ref', 'location'])
-NodeRef.__str__ = lambda n: '{0.ref:d}@{0.location!s}'.format(n) \
- if n.location.valid() else str(n.ref)
+setattr(NodeRef, '__repr__', _make_repr('ref', 'location'))
+setattr(NodeRef, '__str__', lambda n: f'{n.ref:d}@{n.location!s}'
+ if n.location.valid() else str(n.ref))
-NodeRefList.__repr__ = _list_repr
-NodeRefList.__str__ = lambda o: '[' + _list_elipse(o) + ']'
+setattr(NodeRefList, '__repr__', _list_repr)
+setattr(NodeRefList, '__str__', lambda o: f'[{_list_elipse(o)}]')
-RelationMember.__repr__ = _make_repr(['ref', 'type', 'role'])
-RelationMember.__str__ = lambda r: ('{0.type}{0.ref:d}@{0.role}' \
- if r.role else '{0.type}{0.ref:d}').format(r)
+setattr(RelationMember, '__repr__', _make_repr('ref', 'type', 'role'))
+setattr(RelationMember, '__str__', lambda r: f'{r.type}{r.ref:d}@{r.role}' \
+ if r.role else f'{r.type}{r.ref:d}')
-RelationMemberList.__repr__ = _list_repr
-RelationMemberList.__str__ = lambda o: '[' + _list_elipse(o) + ']'
+setattr(RelationMemberList, '__repr__', _list_repr)
+setattr(RelationMemberList, '__str__', lambda o: f'[{_list_elipse(o)}]')
-OSMObject.__repr__ = _make_repr(['id', 'deleted', 'visible', 'version', 'changeset',
- 'uid', 'timestamp', 'user', 'tags'])
+setattr(OSMObject, '__repr__', _make_repr('id', 'deleted', 'visible', 'version',
+ 'changeset', 'uid', 'timestamp', 'user',
+ 'tags'))
-Node.__repr__ = _make_repr(['id', 'deleted', 'visible', 'version', 'changeset',
- 'uid', 'timestamp', 'user', 'tags', 'location'])
-Node.__str__ = lambda n: 'n{0.id:d}: location={0.location!s} tags={0.tags!s}'\
- .format(n)
+setattr(Node, '__repr__', _make_repr('id', 'deleted', 'visible', 'version',
+ 'changeset', 'uid', 'timestamp', 'user',
+ 'tags', 'location'))
+setattr(Node, '__str__', lambda n: f'n{n.id:d}: location={n.location!s} tags={n.tags!s}')
-Way.__repr__ = _make_repr(['id', 'deleted', 'visible', 'version', 'changeset',
- 'uid', 'timestamp', 'user', 'tags', 'nodes'])
-Way.__str__ = lambda o: 'w{0.id:d}: nodes={0.nodes!s} tags={0.tags!s}' \
- .format(o)
+setattr(Way, '__repr__', _make_repr('id', 'deleted', 'visible', 'version', 'changeset',
+ 'uid', 'timestamp', 'user', 'tags', 'nodes'))
+setattr(Way, '__str__', lambda o: f'w{o.id:d}: nodes={o.nodes!s} tags={o.tags!s}')
-Relation.__repr__ = _make_repr(['id', 'deleted', 'visible', 'version', 'changeset',
- 'uid', 'timestamp', 'user', 'tags', 'members'])
-Relation.__str__ = lambda o: 'r{0.id:d}: members={0.members!s}, tags={0.tags!s}' \
- .format(o)
+setattr(Relation, '__repr__', _make_repr('id', 'deleted', 'visible', 'version',
+ 'changeset', 'uid', 'timestamp', 'user',
+ 'tags', 'members'))
+setattr(Relation, '__str__', lambda o: f'r{o.id:d}: members={o.members!s}, tags={o.tags!s}')
-Changeset.__repr__ = _make_repr(['id', 'uid', 'created_at', 'closed_at', 'open',
- 'num_changes', 'bounds', 'user', 'tags'])
-Changeset.__str__ = lambda o: 'c{0.id:d}: closed_at={0.closed_at!s}, bounds={0.bounds!s}, tags={0.tags!s}' \
- .format(o)
+setattr(Changeset, '__repr__', _make_repr('id', 'uid', 'created_at', 'closed_at',
+ 'open', 'num_changes', 'bounds', 'user',
+ 'tags'))
+setattr(Changeset, '__str__', lambda o: f'c{o.id:d}: closed_at={o.closed_at!s}, bounds={o.bounds!s}, tags={o.tags!s}')
=====================================
src/osmium/osm/_osm.pyi
=====================================
@@ -0,0 +1,202 @@
+from typing import ClassVar, Iterator, Tuple, Optional, Any
+
+from typing import overload
+import datetime
+
+import osmium.osm.mutable
+
+ALL: osm_entity_bits
+AREA: osm_entity_bits
+CHANGESET: osm_entity_bits
+NODE: osm_entity_bits
+NOTHING: osm_entity_bits
+OBJECT: osm_entity_bits
+RELATION: osm_entity_bits
+WAY: osm_entity_bits
+
+
+class Location:
+ @overload
+ def __init__(self) -> None: ...
+ @overload
+ def __init__(self, lon: float, lat: float) -> None: ...
+ def lat_without_check(self) -> float: ...
+ def lon_without_check(self) -> float: ...
+ def valid(self) -> bool: ...
+ @property
+ def lat(self) -> float: ...
+ @property
+ def lon(self) -> float: ...
+ @property
+ def x(self) -> int: ...
+ @property
+ def y(self) -> int: ...
+
+class Box:
+ @overload
+ def __init__(self, minx: float, miny: float, maxx: float, maxy: float) -> None: ...
+ @overload
+ def __init__(self, bottom_left: Location, top_right: Location) -> None: ...
+ def contains(self, location: Location) -> bool: ...
+ @overload
+ def extend(self, location: Location) -> Box: ...
+ @overload
+ def extend(self, box: Box) -> Box: ...
+ def size(self) -> float: ...
+ def valid(self) -> bool: ...
+ @property
+ def bottom_left(self) -> Location: ...
+ @property
+ def top_right(self) -> Location: ...
+
+class TagIterator:
+ def __iter__(self) -> TagIterator: ...
+ def __len__(self) -> int: ...
+ def __next__(self) -> str: ...
+
+class Tag:
+ def __iter__(self) -> TagIterator: ...
+ @property
+ def k(self) -> str: ...
+ @property
+ def v(self) -> str: ...
+
+class TagList:
+ @overload
+ def get(self, key: str, default: str) -> str: ...
+ @overload
+ def get(self, key: str) -> Optional[str]: ...
+ def __contains__(self, key: str) -> bool: ...
+ def __getitem__(self, key: str) -> str: ...
+ def __iter__(self) -> Iterator[Tag]: ...
+ def __len__(self) -> int: ...
+
+class NodeRef:
+ @property
+ def lat(self) -> float: ...
+ @property
+ def location(self) -> Location: ...
+ @property
+ def lon(self) -> float: ...
+ @property
+ def ref(self) -> int: ...
+ @property
+ def x(self) -> int: ...
+ @property
+ def y(self) -> int: ...
+
+class RelationMember:
+ @property
+ def ref(self) -> int: ...
+ @property
+ def role(self) -> str: ...
+ @property
+ def type(self) -> str: ...
+
+class RelationMemberList:
+ def __iter__(self) -> Iterator[RelationMember]: ...
+ def __len__(self) -> int: ...
+
+class NodeRefList:
+ def ends_have_same_id(self) -> bool: ...
+ def ends_have_same_location(self) -> bool: ...
+ def is_closed(self) -> bool: ...
+ def __getitem__(self, idx: int) -> NodeRef: ...
+ def __iter__(self) -> Iterator[NodeRef]: ...
+ def __len__(self) -> int: ...
+
+class WayNodeList(NodeRefList):
+ pass
+
+class OuterRing(NodeRefList):
+ pass
+
+class InnerRing(NodeRefList):
+ pass
+
+class InnerRingIterator:
+ def __iter__(self) -> Iterator[InnerRing]: ...
+
+class OSMObject:
+ def positive_id(self) -> int: ...
+ def user_is_anonymous(self) -> bool: ...
+ @property
+ def changeset(self) -> int: ...
+ @property
+ def deleted(self) -> bool: ...
+ @property
+ def id(self) -> int: ...
+ @property
+ def tags(self) -> TagList: ...
+ @property
+ def timestamp(self) -> datetime.datetime: ...
+ @property
+ def uid(self) -> int: ...
+ @property
+ def user(self) -> str: ...
+ @property
+ def version(self) -> int: ...
+ @property
+ def visible(self) -> bool: ...
+
+class Node(OSMObject):
+ @property
+ def location(self) -> Location: ...
+ def replace(**args: Any) -> osmium.osm.mutable.Node: ...
+
+class Way(OSMObject):
+ def ends_have_same_id(self) -> bool: ...
+ def ends_have_same_location(self) -> bool: ...
+ def is_closed(self) -> bool: ...
+ @property
+ def nodes(self) -> WayNodeList: ...
+ def replace(**args: Any) -> osmium.osm.mutable.Way: ...
+
+class Relation(OSMObject):
+ @property
+ def members(self) -> RelationMemberList: ...
+ def replace(**args: Any) -> osmium.osm.mutable.Relation: ...
+
+class Area(OSMObject):
+ def from_way(self) -> bool: ...
+ def inner_rings(self, outer_ring: OuterRing) -> InnerRingIterator: ...
+ def is_multipolygon(self) -> bool: ...
+ def num_rings(self) -> Tuple[int,int]: ...
+ def orig_id(self) -> int: ...
+ def outer_rings(self) -> Iterator[OuterRing]: ...
+
+class Changeset:
+ def user_is_anonymous(self) -> bool: ...
+ @property
+ def bounds(self) -> Box: ...
+ @property
+ def closed_at(self) -> datetime.datetime: ...
+ @property
+ def created_at(self) -> datetime.datetime: ...
+ @property
+ def id(self) -> int: ...
+ @property
+ def num_changes(self) -> int: ...
+ @property
+ def open(self) -> bool: ...
+ @property
+ def tags(self) -> TagList: ...
+ @property
+ def uid(self) -> int: ...
+ @property
+ def user(self) -> str: ...
+
+class osm_entity_bits:
+ ALL: ClassVar[osm_entity_bits] = ...
+ AREA: ClassVar[osm_entity_bits] = ...
+ CHANGESET: ClassVar[osm_entity_bits] = ...
+ NODE: ClassVar[osm_entity_bits] = ...
+ NOTHING: ClassVar[osm_entity_bits] = ...
+ OBJECT: ClassVar[osm_entity_bits] = ...
+ RELATION: ClassVar[osm_entity_bits] = ...
+ WAY: ClassVar[osm_entity_bits] = ...
+ def __init__(self, value: int) -> None: ...
+ @property
+ def name(self) -> str: ...
+ @property
+ def value(self) -> int: ...
=====================================
src/osmium/osm/mutable.py
=====================================
@@ -1,4 +1,21 @@
-class OSMObject(object):
+from typing import Optional, Union, Any, Mapping, Sequence, Tuple, TYPE_CHECKING
+from datetime import datetime
+
+if TYPE_CHECKING:
+ import osmium.osm
+
+ OSMObjectLike = Union['OSMObject', osmium.osm.OSMObject]
+ NodeLike = Union[Node, osmium.osm.Node]
+ WayLike = Union[Way, osmium.osm.Way]
+ RelationLike = Union[Relation, osmium.osm.Relation]
+
+ TagSequence = Union[osmium.osm.TagList, Mapping[str, str], Sequence[Tuple[str, str]]]
+ 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]]]]
+
+class OSMObject:
"""Mutable version of ``osmium.osm.OSMObject``. It exposes the following
attributes ``id``, ``version``, ``visible``, ``changeset``, ``timestamp``,
``uid`` and ``tags``. Timestamps may be strings or datetime objects.
@@ -9,8 +26,11 @@ class OSMObject(object):
will be initialised first from the attributes of this base object..
"""
- def __init__(self, base=None, id=None, version=None, visible=None, changeset=None,
- timestamp=None, uid=None, tags=None, user=None):
+ def __init__(self, base: Optional['OSMObjectLike'] = None,
+ id: Optional[int] = None, version: Optional[int] = None,
+ visible: Optional[bool] = None, changeset: Optional[int] = None,
+ timestamp: Optional[datetime] = None, uid: Optional[int] = None,
+ tags: Optional['TagSequence'] = None, user: Optional[str] = None) -> None:
if base is None:
self.id = id
self.version = version
@@ -37,7 +57,9 @@ class Node(OSMObject):
may either be an `osmium.osm.Location` or a tuple of lon/lat coordinates.
"""
- def __init__(self, base=None, location=None, **attrs):
+ def __init__(self, base: Optional['NodeLike'] = None,
+ location: Optional['LocationLike'] = None,
+ **attrs: Any) -> None:
OSMObject.__init__(self, base=base, **attrs)
if base is None:
self.location = location
@@ -52,7 +74,8 @@ class Way(OSMObject):
``osmium.osm.NodeRef`` or simple node ids.
"""
- def __init__(self, base=None, nodes=None, **attrs):
+ def __init__(self, base: Optional['WayLike'] = None,
+ nodes: Optional['NodeSequence'] = None, **attrs: Any) -> None:
OSMObject.__init__(self, base=base, **attrs)
if base is None:
self.nodes = nodes
@@ -67,9 +90,33 @@ class Relation(OSMObject):
member type should be a single character 'n', 'w' or 'r'.
"""
- def __init__(self, base=None, members=None, **attrs):
+ def __init__(self, base: Optional['RelationLike'] = None,
+ members: Optional['MemberSequence'] = None, **attrs: Any) -> None:
OSMObject.__init__(self, base=base, **attrs)
if base is None:
self.members = members
else:
self.members = members if members is not None else base.members
+
+
+def create_mutable_node(node: 'NodeLike', **args: Any) -> Node:
+ """ Create a mutable node 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 Node(base=node, **args)
+
+def create_mutable_way(way: 'WayLike', **args: Any) -> Way:
+ """ Create a mutable way 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 Way(base=way, **args)
+
+def create_mutable_relation(rel: 'RelationLike', **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)
+
=====================================
src/osmium/py.typed
=====================================
=====================================
src/osmium/replication/_replication.pyi
=====================================
@@ -0,0 +1,3 @@
+import datetime
+
+def newest_change_from_file(filename: str) -> datetime.datetime: ...
=====================================
src/osmium/replication/server.py
=====================================
@@ -1,6 +1,6 @@
""" Helper functions to communicate with replication servers.
"""
-
+from typing import NamedTuple, Optional, Any, Iterator, cast, Mapping, Tuple
import requests
import urllib.request as urlrequest
from urllib.error import URLError
@@ -9,7 +9,7 @@ from collections import namedtuple
from contextlib import contextmanager
from math import ceil
-from osmium import MergeInputReader
+from osmium import MergeInputReader, BaseHandler
from osmium import io as oio
from osmium import version
@@ -18,75 +18,85 @@ import logging
LOG = logging.getLogger('pyosmium')
LOG.addHandler(logging.NullHandler())
-OsmosisState = namedtuple('OsmosisState', ['sequence', 'timestamp'])
-DownloadResult = namedtuple('DownloadResult', ['id', 'reader', 'newest'])
+class OsmosisState(NamedTuple):
+ sequence: int
+ timestamp: dt.datetime
+
+class DownloadResult(NamedTuple):
+ id: int
+ reader: MergeInputReader
+ newest: int
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
downloading the full dataset again.
+ `url` contains the base URL of the replication service. This is the
+ directory that contains the state file with the current state. If the
+ replication service serves something other than osc.gz files, set
+ the `diff_type` to the given file suffix.
+
ReplicationServer may be used as a context manager. In this case, it
internally keeps a connection to the server making downloads faster.
"""
- def __init__(self, url, diff_type='osc.gz'):
+ def __init__(self, url: str, diff_type: str = 'osc.gz') -> None:
self.baseurl = url
self.diff_type = diff_type
- self.session = None
+ self.extra_request_params: Mapping[str, Any] = dict(timeout=60, stream=True)
+ self.session: Optional[requests.Session] = None
- def close(self):
+ def close(self) -> None:
""" Close any open connection to the replication server.
"""
if self.session is not None:
self.session.close()
self.session = None
- def __enter__(self):
+ def __enter__(self) -> 'ReplicationServer':
self.session = requests.Session()
return self
- def __exit__(self, exc_type, exc_value, traceback):
+ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
self.close()
- def make_request(self, url):
- headers = {"User-Agent" : "pyosmium/{}".format(version.pyosmium_release)}
+ def set_request_parameter(self, key: str, value: Any) -> None:
+ """ Set a parameter which will be handed to the requests library
+ when calling `requests.get()`. This
+ may be used to set custom headers, timeouts and similar parameters.
+ See the `requests documentation <https://requests.readthedocs.io/en/latest/api/?highlight=get#requests.request>`_
+ for possible parameters. Per default, a timeout of 60 sec is set
+ and streaming download enabled.
+ """
+ self.extra_request_params[key] = value
+
+ def make_request(self, url: str) -> urlrequest.Request:
+ headers = {"User-Agent" : f"pyosmium/{version.pyosmium_release}"}
return urlrequest.Request(url, headers=headers)
- def open_url(self, url):
+ def open_url(self, url: urlrequest.Request) -> Any:
""" Download a resource from the given URL and return a byte sequence
of the content.
-
- This method has no support for cookies or any special authentication
- methods. If you need these, you have to provide your own custom URL
- opener. Overwrite open_url() with a method that receives an
- urllib.Request object and returns a ByteIO-like object or a
- requests.Response.
-
- Example::
-
- opener = urlrequest.build_opener()
- opener.addheaders = [('X-Fancy-Header', 'important_content')]
-
- svr = ReplicationServer()
- svr.open_url = opener.open
"""
- headers = dict()
- for h in url.header_items():
- headers[h[0]] = h[1]
+ if 'headers' in self.extra_request_params:
+ 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()}
if self.session is not None:
- return self.session.get(url.get_full_url(), headers=headers, stream=True)
+ return self.session.get(url.get_full_url(), **get_params)
@contextmanager
- def _get_url_with_session():
+ def _get_url_with_session() -> Iterator[requests.Response]:
with requests.Session() as session:
- request = session.get(url.get_full_url(), headers=headers, stream=True)
+ request = session.get(url.get_full_url(), **get_params)
yield request
return _get_url_with_session()
- def collect_diffs(self, start_id, max_size=1024):
+ def collect_diffs(self, start_id: int, max_size: int = 1024) -> Optional[DownloadResult]:
""" Create a MergeInputReader and download diffs starting with sequence
id `start_id` into it. `max_size`
restricts the number of diffs that are downloaded. The download
@@ -131,7 +141,9 @@ class ReplicationServer:
return DownloadResult(current_id - 1, rd, newest.sequence)
- def apply_diffs(self, handler, start_id, max_size=1024, idx="", simplify=True):
+ def apply_diffs(self, handler: BaseHandler, start_id: int,
+ max_size: int = 1024, idx: str = "",
+ simplify: bool = True) -> Optional[int]:
""" Download diffs starting with sequence id `start_id`, merge them
together and then apply them to handler `handler`. `max_size`
restricts the number of diffs that are downloaded. The download
@@ -165,9 +177,11 @@ class ReplicationServer:
return diffs.id
- def apply_diffs_to_file(self, infile, outfile, start_id, max_size=1024,
- set_replication_header=True, extra_headers=None,
- outformat=None):
+ def apply_diffs_to_file(self, infile: str, outfile: str,
+ start_id: int, max_size: int = 1024,
+ set_replication_header: bool = True,
+ extra_headers: Optional[Mapping[str, str]] = None,
+ outformat: Optional[str] = None) -> Optional[Tuple[int, int]]:
""" Download diffs starting with sequence id `start_id`, merge them
with the data from the OSM file named `infile` and write the result
into a file with the name `outfile`. The output file must not yet
@@ -230,7 +244,8 @@ class ReplicationServer:
return (diffs.id, diffs.newest)
- def timestamp_to_sequence(self, timestamp, balanced_search=False):
+ 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
given timestamp. The search algorithm is optimised for replication
servers that publish updates in regular intervals. For servers
@@ -312,7 +327,7 @@ class ReplicationServer:
return lower.sequence
- def get_state_info(self, seq=None, retries=2):
+ 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
`sequence` and `timestamp` is returned, otherwise the function
@@ -359,7 +374,7 @@ class ReplicationServer:
return None
- def get_diff_block(self, seq):
+ def get_diff_block(self, seq: int) -> str:
""" Downloads the diff with the given sequence number and returns
it as a byte sequence. Throws a :code:`urllib.error.HTTPError`
if the file cannot be downloaded.
@@ -367,13 +382,13 @@ class ReplicationServer:
with self.open_url(self.make_request(self.get_diff_url(seq))) as resp:
if hasattr(resp, 'content'):
# generated by requests
- return resp.content
+ return cast(str, resp.content)
# generated by urllib.request
- return resp.read()
+ return cast(str, resp.read())
- def get_state_url(self, seq):
+ def get_state_url(self, seq: Optional[int]) -> str:
""" Returns the URL of the state.txt files for a given sequence id.
If seq is `None` the URL for the latest state info is returned,
@@ -387,7 +402,7 @@ class ReplicationServer:
(self.baseurl, seq / 1000000, (seq % 1000000) / 1000, seq % 1000)
- def get_diff_url(self, seq):
+ def get_diff_url(self, seq: int) -> str:
""" Returns the URL to the diff file for the given sequence id.
"""
return '%s/%03i/%03i/%03i.%s' % \
=====================================
src/osmium/replication/utils.py
=====================================
@@ -1,5 +1,5 @@
""" Helper functions for change file handling. """
-
+from typing import NamedTuple, Optional
import logging
import datetime as dt
from collections import namedtuple
@@ -8,10 +8,13 @@ from osmium.osm import NOTHING
LOG = logging.getLogger('pyosmium')
-ReplicationHeader = namedtuple('ReplicationHeader',
- ['url', 'sequence', 'timestamp'])
+class ReplicationHeader(NamedTuple):
+ url: Optional[str]
+ sequence: Optional[int]
+ timestamp: Optional[dt.datetime]
+
-def get_replication_header(fname):
+def get_replication_header(fname: str) -> ReplicationHeader:
""" Scans the given file for an Osmosis replication header. It returns
a namedtuple with `url`, `sequence` and `timestamp`. Each or all fields
may be None, if the piece of information is not avilable. If any of
@@ -24,20 +27,21 @@ def get_replication_header(fname):
r = oreader(fname, NOTHING)
h = r.header()
- ts = h.get("osmosis_replication_timestamp")
- url = h.get("osmosis_replication_base_url")
+ tsstr = h.get("osmosis_replication_timestamp")
+ url: Optional[str] = h.get("osmosis_replication_base_url")
- if url or ts:
+ if url or tsstr:
LOG.debug("Replication information found in OSM file header.")
if url:
LOG.debug("Replication URL: %s", url)
# the sequence ID is only considered valid, if an URL is given
- seq = h.get("osmosis_replication_sequence_number")
- if seq:
- LOG.debug("Replication sequence: %s", seq)
+ seqstr = h.get("osmosis_replication_sequence_number")
+ seq: Optional[int]
+ if seqstr:
+ LOG.debug("Replication sequence: %s", seqstr)
try:
- seq = int(seq)
+ seq = int(seqstr)
if seq < 0:
LOG.warning("Sequence id '%d' in OSM file header is negative. Ignored.", seq)
seq = None
@@ -50,10 +54,10 @@ def get_replication_header(fname):
url = None
seq = None
- if ts:
- LOG.debug("Replication timestamp: %s", ts)
+ if tsstr:
+ LOG.debug("Replication timestamp: %s", tsstr)
try:
- ts = dt.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ")
+ ts = dt.datetime.strptime(tsstr, "%Y-%m-%dT%H:%M:%SZ")
ts = ts.replace(tzinfo=dt.timezone.utc)
except ValueError:
=====================================
src/osmium/version.py
=====================================
@@ -3,13 +3,13 @@ Version information.
"""
# the major version
-pyosmium_major = '3.4'
+pyosmium_major = '3.5'
# current release (Pip version)
-pyosmium_release = '3.4.1'
+pyosmium_release = '3.5.0'
# libosmium version shipped with the Pip release
libosmium_version = '2.18.0'
# protozero version shipped with the Pip release
protozero_version = '1.7.1'
# pybind11 version shipped with the Pip release
-pybind11_version = '2.10.0'
+pybind11_version = '2.10.1'
=====================================
test/helpers.py
=====================================
@@ -7,9 +7,6 @@ import osmium
def mkdate(*args):
return datetime(*args, tzinfo=timezone.utc)
-def check_repr(o):
- return not str(o).startswith('<') and not repr(o).startswith('<')
-
class CountingHandler(osmium.SimpleHandler):
def __init__(self):
=====================================
test/test_osm.py
=====================================
@@ -3,10 +3,11 @@
# This file is part of Pyosmium.
#
# Copyright (C) 2022 Sarah Hoffmann.
+import re
from itertools import count
import pytest
-from helpers import check_repr, mkdate
+from helpers import mkdate
import osmium as o
@@ -62,7 +63,9 @@ def area_importer(request, tmp_path, to_opl):
def test_invalid_location():
loc = o.osm.Location()
assert not loc.valid()
- assert check_repr(loc)
+ assert str(loc) == 'invalid'
+ assert repr(loc) == 'osmium.osm.Location()'
+
with pytest.raises(o.InvalidLocationError):
lat = loc.lat
with pytest.raises(o.InvalidLocationError):
@@ -73,12 +76,13 @@ def test_invalid_location():
def test_valid_location():
- loc = o.osm.Location(1,10)
- assert loc.lon == pytest.approx(1)
+ loc = o.osm.Location(-1, 10)
+ assert loc.lon == pytest.approx(-1)
assert loc.lat == pytest.approx(10)
- assert loc.x == 10000000
+ assert loc.x == -10000000
assert loc.y == 100000000
- assert check_repr(loc)
+ assert re.fullmatch('-1.0*/10.0*', str(loc))
+ assert repr(loc) == 'osmium.osm.Location(x=-10000000, y=100000000)'
def test_node_attributes(test_importer):
@@ -92,7 +96,8 @@ def test_node_attributes(test_importer):
assert n.timestamp == mkdate(2014, 1, 31, 6, 23, 35)
assert n.user == u'änonymous'
assert n.positive_id() == 1
- assert check_repr(n)
+ assert str(n) == 'n1: location=invalid tags={}'
+ assert repr(n) == "osmium.osm.Node(id=1, deleted=False, visible=True, version=5, changeset=58674, uid=42, timestamp=datetime.datetime(2014, 1, 31, 6, 23, 35, tzinfo=datetime.timezone.utc), user='änonymous', tags=osmium.osm.TagList({}), location=osmium.osm.Location())"
assert 1 == test_importer('n1 v5 c58674 t2014-01-31T06:23:35Z i42 uänonymous',
node=node)
@@ -125,8 +130,12 @@ def test_way_attributes(test_importer):
assert not o.is_closed()
assert not o.ends_have_same_id()
assert not o.ends_have_same_location()
- assert check_repr(o)
- assert check_repr(o.nodes)
+
+ assert str(o) == 'w1: nodes=[1 at 0.0000000/0.0000000,2,3 at 1.0000000/1.0000000] tags={}'
+ assert repr(o) == "osmium.osm.Way(id=1, deleted=False, visible=True, version=5, changeset=58674, uid=42, timestamp=datetime.datetime(2014, 1, 31, 6, 23, 35, tzinfo=datetime.timezone.utc), user='anonymous', tags=osmium.osm.TagList({}), nodes=osmium.osm.WayNodeList([osmium.osm.NodeRef(ref=1, location=osmium.osm.Location(x=0, y=0)), osmium.osm.NodeRef(ref=2, location=osmium.osm.Location()), osmium.osm.NodeRef(ref=3, location=osmium.osm.Location(x=10000000, y=10000000))]))"
+
+ assert str(o.nodes) == '[1 at 0.0000000/0.0000000,2,3 at 1.0000000/1.0000000]'
+ assert repr(o.nodes) == "osmium.osm.WayNodeList([osmium.osm.NodeRef(ref=1, location=osmium.osm.Location(x=0, y=0)), osmium.osm.NodeRef(ref=2, location=osmium.osm.Location()), osmium.osm.NodeRef(ref=3, location=osmium.osm.Location(x=10000000, y=10000000))])"
assert 1 == test_importer(['n1 x0 y0', 'n3 x1 y1',
'w1 v5 c58674 t2014-01-31T06:23:35Z i42 uanonymous Nn1,n2,n3'],
@@ -145,8 +154,12 @@ def test_relation_attributes(test_importer):
assert o.timestamp == mkdate(2014, 1, 31, 6, 23, 35)
assert o.user == ' anonymous'
assert o.positive_id() == 1
- assert check_repr(o)
- assert check_repr(o.members)
+
+ assert str(o) == 'r1: members=[w1], tags={}'
+ assert repr(o) == "osmium.osm.Relation(id=1, deleted=False, visible=True, version=5, changeset=58674, uid=42, timestamp=datetime.datetime(2014, 1, 31, 6, 23, 35, tzinfo=datetime.timezone.utc), user=' anonymous', tags=osmium.osm.TagList({}), members=osmium.osm.RelationMemberList([osmium.osm.RelationMember(ref=1, type='w', role='')]))"
+
+ assert str(o.members) == '[w1]'
+ assert repr(o.members) == "osmium.osm.RelationMemberList([osmium.osm.RelationMember(ref=1, type='w', role='')])"
assert 1 == test_importer('r1 v5 c58674 t2014-01-31T06:23:35Z i42 u%20%anonymous Mw1@',
relation=relation)
@@ -230,7 +243,8 @@ def test_changest_attributes(area_importer):
assert 515288620 == c.bounds.top_right.y
assert -1465242 == c.bounds.bottom_left.x
assert 515288506 == c.bounds.bottom_left.y
- assert check_repr(c)
+ assert str(c) == 'c34: closed_at=2005-04-09 20:54:39+00:00, bounds=(-0.1465242/51.5288506 -0.1464925/51.5288620), tags={}'
+ assert repr(c) == "osmium.osm.Changeset(id=34, uid=1, created_at=datetime.datetime(2005, 4, 9, 19, 54, 13, tzinfo=datetime.timezone.utc), closed_at=datetime.datetime(2005, 4, 9, 20, 54, 39, tzinfo=datetime.timezone.utc), open=False, num_changes=2, bounds=osmium.osm.Box(bottom_left=osmium.osm.Location(x=-1465242, y=515288506), top_right=osmium.osm.Location(x=-1464925, y=515288620)), user='Steve', tags=osmium.osm.TagList({}))"
assert 1 == area_importer('c34 k2 s2005-04-09T19:54:13Z e2005-04-09T20:54:39Z '
'd34 i1 uSteve x-0.1465242 y51.5288506 X-0.1464925 Y51.5288620',
=====================================
test/test_pyosmium_get_changes.py
=====================================
@@ -48,13 +48,6 @@ class TestPyosmiumGetChanges:
monkeypatch.setattr(osmium.replication.server.requests.Session, "get", mock_get)
- @pytest.fixture
- def mock_urllib(self, monkeypatch):
- def mock_get(_, url, **kwargs):
- return BytesIO(self.urls[url.get_full_url()])
- monkeypatch.setattr(self.script['urlrequest'].OpenerDirector, "open", mock_get)
-
-
def url(self, url, result):
self.urls[url] = dedent(result).encode()
@@ -67,7 +60,7 @@ class TestPyosmiumGetChanges:
output = capsys.readouterr().out.strip()
- assert output == '454'
+ assert output == '453'
def test_init_date(self, capsys, mock_requests):
@@ -85,14 +78,14 @@ class TestPyosmiumGetChanges:
output = capsys.readouterr().out.strip()
- assert output == '1'
+ assert output == '-1'
def test_init_to_file(self, tmp_path):
fname = tmp_path / 'db.seq'
assert 0 == self.main('-I', '453', '-f', str(fname))
- assert fname.read_text() == '454'
+ assert fname.read_text() == '453'
def test_init_from_seq_file(self, tmp_path):
@@ -100,10 +93,10 @@ class TestPyosmiumGetChanges:
fname.write_text('453')
assert 0 == self.main('-f', str(fname))
- assert fname.read_text() == '454'
+ assert fname.read_text() == '453'
- def test_init_date_with_cookie(self, capsys, tmp_path, mock_urllib):
+ def test_init_date_with_cookie(self, capsys, tmp_path, mock_requests):
self.url('https://planet.osm.org/replication/minute//state.txt',
"""\
sequenceNumber=100
@@ -123,4 +116,4 @@ class TestPyosmiumGetChanges:
output = capsys.readouterr().out.strip()
- assert output == '1'
+ assert output == '-1'
=====================================
test/test_taglist.py
=====================================
@@ -5,8 +5,6 @@
# Copyright (C) 2022 Sarah Hoffmann.
import pytest
-from helpers import check_repr
-
import osmium as o
@pytest.fixture
@@ -19,7 +17,6 @@ def tag_handler(simple_handler):
del tags[None]
tags.update(n.tags)
tests(n)
- assert check_repr(n.tags)
simple_handler(data, node=node)
@@ -32,6 +29,8 @@ def test_empty_taglist_length(tag_handler):
def tests(n):
assert 0 == len(n.tags)
assert not n.tags
+ assert str(n.tags) == '{}'
+ assert repr(n.tags) == 'osmium.osm.TagList({})'
tags = tag_handler("n234 x1 y2", tests)
assert tags == {}
@@ -91,6 +90,8 @@ def test_taglist_contains(tag_handler):
assert "x" not in n.tags
assert None not in n.tags
assert "" not in n.tags
+ assert str(n.tags) == '{abba=x,2=vvv,xx=abba}'
+ assert repr(n.tags) == "osmium.osm.TagList({'abba': 'x', '2': 'vvv', 'xx': 'abba'})"
tags = tag_handler("n234 Tabba=x,2=vvv,xx=abba", tests)
=====================================
test/test_writer.py
=====================================
@@ -246,3 +246,75 @@ def test_add_relation_after_close(tmp_path, simple_handler):
with pytest.raises(RuntimeError, match='closed'):
simple_handler(node_opl, relation=lambda o: writer.add_relation(o))
+
+
+ at pytest.mark.parametrize("final_item", (True, False))
+def test_catch_errors_in_add_node(tmp_path, final_item):
+ test_file = tmp_path / 'test.opl'
+
+ writer = o.SimpleWriter(str(test_file), 4000)
+
+ try:
+ writer.add_node(o.osm.mutable.Node(id=123))
+ with pytest.raises(TypeError):
+ writer.add_node(o.osm.mutable.Node(id=124, tags=34))
+ if not final_item:
+ writer.add_node(o.osm.mutable.Node(id=125))
+ finally:
+ writer.close()
+
+ output = test_file.read_text()
+
+ expected = 'n123 v0 dV c0 t i0 u T x y\n'
+ if not final_item:
+ expected += 'n125 v0 dV c0 t i0 u T x y\n'
+
+ assert output == expected
+
+
+ at pytest.mark.parametrize("final_item", (True, False))
+def test_catch_errors_in_add_way(tmp_path, final_item):
+ test_file = tmp_path / 'test.opl'
+
+ writer = o.SimpleWriter(str(test_file), 4000)
+
+ try:
+ writer.add_way(o.osm.mutable.Way(id=123, nodes=[1, 2, 3]))
+ with pytest.raises(TypeError):
+ writer.add_way(o.osm.mutable.Way(id=124, nodes=34))
+ if not final_item:
+ writer.add_way(o.osm.mutable.Way(id=125, nodes=[11, 12]))
+ finally:
+ writer.close()
+
+ output = test_file.read_text()
+
+ expected = 'w123 v0 dV c0 t i0 u T Nn1,n2,n3\n'
+ if not final_item:
+ expected += 'w125 v0 dV c0 t i0 u T Nn11,n12\n'
+
+ assert output == expected
+
+
+ at pytest.mark.parametrize("final_item", (True, False))
+def test_catch_errors_in_add_relation(tmp_path, final_item):
+ test_file = tmp_path / 'test.opl'
+
+ writer = o.SimpleWriter(str(test_file), 4000)
+
+ try:
+ writer.add_relation(o.osm.mutable.Relation(id=123))
+ with pytest.raises(TypeError):
+ writer.add_relation(o.osm.mutable.Relation(id=124, members=34))
+ if not final_item:
+ writer.add_relation(o.osm.mutable.Relation(id=125))
+ finally:
+ writer.close()
+
+ output = test_file.read_text()
+
+ expected = 'r123 v0 dV c0 t i0 u T M\n'
+ if not final_item:
+ expected += 'r125 v0 dV c0 t i0 u T M\n'
+
+ assert output == expected
=====================================
tools/pyosmium-get-changes
=====================================
@@ -27,18 +27,14 @@ cookies to the server and will save received cookies to the jar file.
from argparse import ArgumentParser, RawDescriptionHelpFormatter, ArgumentTypeError
import datetime as dt
-import socket
+import http.cookiejar
+
from osmium.replication import server as rserv
from osmium.replication import newest_change_from_file
from osmium.replication.utils import get_replication_header
from osmium.version import pyosmium_release
from osmium import SimpleHandler, WriteHandler
-try:
- import http.cookiejar as cookiejarlib
-except ImportError:
- import cookielib as cookiejarlib
-import urllib.request as urlrequest
import re
import sys
@@ -62,18 +58,16 @@ class ReplicationStart(object):
return self.seq_id + 1
log.debug("Looking up sequence ID for timestamp %s" % self.date)
- seq = svr.timestamp_to_sequence(self.date)
-
- return seq + 1 if seq is not None else None
+ return svr.timestamp_to_sequence(self.date)
@staticmethod
def from_id(idstr):
try:
- seq_id=int(idstr)
+ seq_id = int(idstr)
except ValueError:
raise ArgumentTypeError("Sequence id '%s' is not a number" % idstr)
- if seq_id < 0:
+ if seq_id < -1:
raise ArgumentTypeError("Sequence id '%s' is negative" % idstr)
return ReplicationStart(seq_id=seq_id)
@@ -213,15 +207,13 @@ def main(args):
or 'https://planet.osm.org/replication/minute/'
logging.info("Using replication server at %s" % url)
- socket.setdefaulttimeout(options.socket_timeout)
-
with rserv.ReplicationServer(url) as svr:
+ svr.set_request_parameter('timeout', options.socket_timeout or None)
+
if options.cookie is not None:
- # According to the documentation, the cookie jar loads the file only if FileCookieJar.load is called.
- cookie_jar = cookiejarlib.MozillaCookieJar(options.cookie)
+ cookie_jar = http.cookiejar.MozillaCookieJar(options.cookie)
cookie_jar.load(options.cookie)
- opener = urlrequest.build_opener(urlrequest.HTTPCookieProcessor(cookie_jar))
- svr.open_url = opener.open
+ svr.set_request_parameter('cookies', cookie_jar)
startseq = options.start.get_sequence(svr)
if startseq is None:
@@ -229,7 +221,7 @@ def main(args):
return 1
if options.outfile is None:
- write_end_sequence(options.seq_file, startseq)
+ write_end_sequence(options.seq_file, startseq - 1)
return 0
log.debug("Starting download at ID %d (max %d MB)" % (startseq, options.outsize))
@@ -247,6 +239,7 @@ def main(args):
cookie_jar.save(options.cookie)
if endseq is None:
+ log.error("Error while downloading diffs.")
return 3
if options.outfile != '-' or options.seq_file is not None:
=====================================
tools/pyosmium-up-to-date
=====================================
@@ -35,7 +35,7 @@ import re
import sys
import traceback
import logging
-import socket
+import http.cookiejar
from argparse import ArgumentParser, RawDescriptionHelpFormatter
import datetime as dt
@@ -47,15 +47,6 @@ from textwrap import dedent as msgfmt
from tempfile import mktemp
import os.path
-try:
- import http.cookiejar as cookiejarlib
-except ImportError:
- import cookielib as cookiejarlib
-try:
- import urllib.request as urlrequest
-except ImportError:
- import urllib2 as urlrequest
-
log = logging.getLogger()
def update_from_osm_server(ts, options):
@@ -71,14 +62,14 @@ def update_from_osm_server(ts, options):
def update_from_custom_server(url, seq, ts, options):
"""Update from a custom URL, simply using the diff sequence as is."""
with rserv.ReplicationServer(url, "osc.gz") as svr:
+ log.info("Using replication service at %s", url)
+
+ svr.set_request_parameter('timeout', options.socket_timeout or None)
+
if options.cookie is not None:
- # According to the documentation, the cookie jar loads the file only if FileCookieJar.load is called.
- cookie_jar = cookiejarlib.MozillaCookieJar(options.cookie)
+ cookie_jar = http.cookiejar.MozillaCookieJar(options.cookie)
cookie_jar.load(options.cookie)
- opener = urlrequest.build_opener(urlrequest.HTTPCookieProcessor(cookie_jar))
- svr.open_url = opener.open
-
- log.info("Using replication service at %s", url)
+ svr.set_request_parameter('cookies', cookie_jar)
current = svr.get_state_info()
if current is None:
@@ -244,8 +235,6 @@ if __name__ == '__main__':
options = get_arg_parser(from_main=True).parse_args()
log.setLevel(max(3 - options.loglevel, 0) * 10)
- socket.setdefaulttimeout(options.socket_timeout)
-
try:
url, seq, ts = compute_start_point(options)
except RuntimeError as e:
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyosmium/-/compare/a766f912b5d3ad23e0726e0c827f01142bc68c99...3b61a766f1b9a0aeabc549b2d7135841b6af0e63
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyosmium/-/compare/a766f912b5d3ad23e0726e0c827f01142bc68c99...3b61a766f1b9a0aeabc549b2d7135841b6af0e63
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20221117/9de08003/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list