Skip to content

Commit

Permalink
consistently allow str, Path, File and FileBuffer for file input
Browse files Browse the repository at this point in the history
Uses built-in support for Path from pybind11 which has only been
added in version 2.7 and needs C++17.
  • Loading branch information
lonvia committed Sep 16, 2024
1 parent 9809288 commit 4666326
Show file tree
Hide file tree
Showing 16 changed files with 227 additions and 77 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.8.0)
project(pyosmium VERSION 3.6.0)

option(WITH_LZ4 "Build with lz4 support for PBF files" ON)
Expand Down Expand Up @@ -41,7 +41,7 @@ message(STATUS "Building in C++${CMAKE_CXX_STANDARD} mode")
if(PYBIND11_PREFIX)
add_subdirectory(${PYBIND11_PREFIX} contrib/pybind11)
else()
find_package(pybind11 2.2 REQUIRED)
find_package(pybind11 2.7 REQUIRED)
endif()

find_package(Boost 1.41 REQUIRED COMPONENTS)
Expand Down
29 changes: 23 additions & 6 deletions lib/id_tracker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* For a full list of authors see the git log.
*/
#include <pybind11/pybind11.h>
#include <pybind11/stl/filesystem.h>

#include <osmium/osm.hpp>
#include <osmium/io/any_input.hpp>
Expand All @@ -15,6 +16,8 @@
#include "base_filter.h"
#include "osmium_module.h"

#include <filesystem>

namespace py = pybind11;

namespace {
Expand Down Expand Up @@ -95,7 +98,7 @@ class IdTracker
}


void complete_backward_references(std::string const &file, int relation_depth)
void complete_backward_references(osmium::io::File file, int relation_depth)
{
// first pass: relations
while (relation_depth > 0 && !m_ids.relations().empty()) {
Expand Down Expand Up @@ -136,7 +139,7 @@ class IdTracker
}


void complete_forward_references(std::string const &file, int relation_depth)
void complete_forward_references(osmium::io::File file, int relation_depth)
{
// standard pass: find directly referenced ways and relations
{
Expand Down Expand Up @@ -290,14 +293,28 @@ void init_id_tracker(pybind11::module &m)
.def("add_relation", [](IdTracker &self, IdType id) { self.relation_ids().set(id); })
.def("add_references", &IdTracker::add_references)
.def("contains_any_references", &IdTracker::contains_any_references)
.def("complete_backward_references", &IdTracker::complete_backward_references,
py::arg("fname"), py::arg("relation_depth") = 0)
.def("complete_backward_references",
[](IdTracker &self, char const *fname, int relation_depth) {
self.complete_backward_references(osmium::io::File{fname}, relation_depth);
},
py::arg("fname"), py::arg("relation_depth") = 0)
.def("complete_backward_references",
[](IdTracker &self, py::object const &fname, int relation_depth) {
self.complete_backward_references(py::str(fname), relation_depth);
[](IdTracker &self, std::filesystem::path const &fname, int relation_depth) {
self.complete_backward_references(osmium::io::File{fname}, relation_depth);
},
py::arg("fname"), py::arg("relation_depth") = 0)
.def("complete_forward_references", &IdTracker::complete_forward_references,
py::arg("fname"), py::arg("relation_depth") = 0)
.def("complete_forward_references",
[](IdTracker &self, char const *fname, int relation_depth) {
self.complete_forward_references(osmium::io::File{fname}, relation_depth);
},
py::arg("fname"), py::arg("relation_depth") = 0)
.def("complete_forward_references",
[](IdTracker &self, py::object const &fname, int relation_depth) {
self.complete_forward_references(py::str(fname), relation_depth);
[](IdTracker &self, std::filesystem::path const &fname, int relation_depth) {
self.complete_forward_references(osmium::io::File{fname.c_str()}, relation_depth);
},
py::arg("fname"), py::arg("relation_depth") = 0)
.def("id_filter",
Expand Down
26 changes: 20 additions & 6 deletions lib/io.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
* For a full list of authors see the git log.
*/
#include <pybind11/pybind11.h>
#include <pybind11/stl/filesystem.h>

#include <osmium/io/any_input.hpp>
#include <osmium/io/any_output.hpp>

#include <filesystem>

namespace py = pybind11;

namespace {
Expand All @@ -27,15 +30,21 @@ PYBIND11_MODULE(io, m)
py::class_<osmium::io::File>(m, "File")
.def(py::init<std::string>())
.def(py::init<std::string, std::string>())
.def(py::init<>([] (std::filesystem::path const &file) {
return new osmium::io::File(file.c_str());
}))
.def(py::init<>([] (std::filesystem::path const &file, const char *format) {
return new osmium::io::File(file.c_str(), format);
}))
.def_property("has_multiple_object_versions",
&osmium::io::File::has_multiple_object_versions,
&osmium::io::File::set_has_multiple_object_versions)
.def("parse_format", &osmium::io::File::parse_format)
;


py::class_<FileBuffer>(m, "FileBuffer")
.def(py::init<>([] (pybind11::buffer const &buf, std::string const &format) {
py::class_<FileBuffer, osmium::io::File>(m, "FileBuffer")
.def(py::init<>([] (py::buffer const &buf, std::string const &format) {
pybind11::buffer_info info = buf.request();
return new FileBuffer(reinterpret_cast<const char *>(info.ptr),
static_cast<size_t>(info.size), format.c_str());
Expand Down Expand Up @@ -68,10 +77,12 @@ PYBIND11_MODULE(io, m)
py::class_<osmium::io::Reader>(m, "Reader")
.def(py::init<std::string>())
.def(py::init<std::string, osmium::osm_entity_bits::type>())
.def(py::init<FileBuffer>(),
py::keep_alive<1, 2>())
.def(py::init<FileBuffer, osmium::osm_entity_bits::type>(),
py::keep_alive<1, 2>())
.def(py::init<>([] (std::filesystem::path const &file) {
return new osmium::io::Reader(file.c_str());
}))
.def(py::init<>([] (std::filesystem::path const &file, osmium::osm_entity_bits::type etype) {
return new osmium::io::Reader(file.c_str(), etype);
}))
.def(py::init<osmium::io::File>(),
py::keep_alive<1, 2>())
.def(py::init<osmium::io::File, osmium::osm_entity_bits::type>(),
Expand All @@ -85,6 +96,9 @@ PYBIND11_MODULE(io, m)

py::class_<osmium::io::Writer>(m, "Writer")
.def(py::init<std::string>())
.def(py::init<>([] (std::filesystem::path const &file) {
return new osmium::io::Writer(file.c_str());
}))
.def(py::init<osmium::io::File>())
.def(py::init<std::string, osmium::io::Header>())
.def(py::init<osmium::io::File, osmium::io::Header>())
Expand Down
32 changes: 30 additions & 2 deletions lib/osmium.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
* Copyright (C) 2024 Sarah Hoffmann <[email protected]> and others.
* For a full list of authors see the git log.
*/
#include <vector>

#include <pybind11/pybind11.h>
#include <pybind11/stl/filesystem.h>

#include <osmium/osm.hpp>
#include <osmium/handler.hpp>
Expand All @@ -22,6 +21,9 @@
#include "handler_chain.h"
#include "buffer_iterator.h"

#include <vector>
#include <filesystem>

namespace py = pybind11;

void pyosmium::apply_item(osmium::OSMEntity &obj, pyosmium::BaseHandler &handler)
Expand Down Expand Up @@ -102,6 +104,32 @@ PYBIND11_MODULE(_osmium, m) {
pyosmium::apply(rd, handler);
},
py::arg("filename"));
m.def("apply", [](std::filesystem::path const &fn, pyosmium::BaseHandler &h)
{
osmium::io::Reader rd{fn};
pyosmium::apply(rd, h);
},
py::arg("filename"), py::arg("handler"));
m.def("apply", [](std::filesystem::path const &fn, py::args args)
{
pyosmium::HandlerChain handler{args};
osmium::io::Reader rd{fn};
pyosmium::apply(rd, handler);
},
py::arg("filename"));
m.def("apply", [](osmium::io::File fn, pyosmium::BaseHandler &h)
{
osmium::io::Reader rd{fn};
pyosmium::apply(rd, h);
},
py::arg("filename"), py::arg("handler"));
m.def("apply", [](osmium::io::File fn, py::args args)
{
pyosmium::HandlerChain handler{args};
osmium::io::Reader rd{fn};
pyosmium::apply(rd, handler);
},
py::arg("filename"));

py::class_<pyosmium::BaseHandler>(m, "BaseHandler");
py::class_<pyosmium::BaseFilter, pyosmium::BaseHandler>(m, "BaseFilter")
Expand Down
23 changes: 23 additions & 0 deletions lib/simple_writer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* For a full list of authors see the git log.
*/
#include <pybind11/pybind11.h>
#include <pybind11/stl/filesystem.h>

#include <osmium/osm.hpp>
#include <osmium/io/any_output.hpp>
Expand All @@ -18,6 +19,8 @@
#include "osm_base_objects.h"
#include "base_handler.h"

#include <filesystem>

namespace py = pybind11;

namespace {
Expand All @@ -37,6 +40,15 @@ class SimpleWriter : public pyosmium::BaseHandler
buffer_size(buffer.capacity()) // same rounding to BUFFER_WRAP
{}

SimpleWriter(osmium::io::File file, size_t bufsz, osmium::io::Header const *header,
bool overwrite)
: writer(file, header ? *header : osmium::io::Header(),
overwrite ? osmium::io::overwrite::allow : osmium::io::overwrite::no),
buffer(bufsz < 2 * BUFFER_WRAP ? 2 * BUFFER_WRAP : bufsz,
osmium::memory::Buffer::auto_grow::yes),
buffer_size(buffer.capacity()) // same rounding to BUFFER_WRAP
{}

virtual ~SimpleWriter()
{ close(); }

Expand Down Expand Up @@ -344,6 +356,17 @@ void init_simple_writer(pybind11::module &m)
py::arg("header") = nullptr,
py::arg("overwrite") = false,
py::arg("filetype") = "")
.def(py::init<>([] (std::filesystem::path const &file, unsigned long bufsz,
osmium::io::Header const *header, bool overwrite) {
return new SimpleWriter(file.c_str(), bufsz, header, overwrite, "");
}),
py::arg("filename"), py::arg("bufsz") = 4096*1024,
py::arg("header") = nullptr,
py::arg("overwrite") = false)
.def(py::init<osmium::io::File, unsigned long, osmium::io::Header const *, bool>(),
py::arg("filename"), py::arg("bufsz") = 4096*1024,
py::arg("header") = nullptr,
py::arg("overwrite") = false)
.def("add_node", &SimpleWriter::add_node, py::arg("node"))
.def("add_way", &SimpleWriter::add_way, py::arg("way"))
.def("add_relation", &SimpleWriter::add_relation, py::arg("relation"))
Expand Down
18 changes: 8 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,6 @@ def run(self):
raise RuntimeError("CMake must be installed to build the following extensions: " +
", ".join(e.name for e in self.extensions))

if platform.system() == "Windows":
cmake_version = Version(re.search(r'version\s*([\d.]+)', out.decode()).group(1))
if cmake_version < Version('3.1.0'):
raise RuntimeError("CMake >= 3.1.0 is required on Windows")

for ext in self.extensions:
self.build_extension(ext)

Expand Down Expand Up @@ -138,8 +133,8 @@ def build_extension(self, ext):

versions = get_versions()

if sys.version_info < (3,6):
raise RuntimeError("Python 3.6 or larger required.")
if sys.version_info < (3,7):
raise RuntimeError("Python 3.7 or larger required.")

with open('README.rst', 'r') as descfile:
long_description = descfile.read()
Expand All @@ -159,13 +154,16 @@ def build_extension(self, ext):
license='BSD',
scripts=['tools/pyosmium-get-changes', 'tools/pyosmium-up-to-date'],
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: C++",
],
Expand All @@ -176,7 +174,7 @@ def build_extension(self, ext):
package_data = { 'osmium': ['py.typed', '*.pyi',
'replication/_replication.pyi',
'osm/_osm.pyi']},
python_requires = ">=3.6",
python_requires = ">=3.7",
install_requires = ['requests'],
extras_require = {
'tests': ['pytest', 'pytest-httpserver', 'werkzeug'],
Expand Down
38 changes: 24 additions & 14 deletions src/osmium/_osmium.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import os
from .osm import osm_entity_bits
from .osm.types import OSMEntity
from .index import LocationTable, IdSet
from .io import Reader, Writer, Header

StrPath = Union[str, 'os.PathLike[str]']
from .io import Reader, Writer, Header, File, FileBuffer

# Placeholder for more narrow type definition to come
HandlerLike = object
Expand Down Expand Up @@ -128,14 +126,23 @@ class SimpleWriter:
don't use it in a `with` context, don't forget to call `close()`,
when writing is finished.
"""
def __init__(self, filename: str, bufsz: int= ...,
def __init__(self, file: Union[str, 'os.PathLike[str]', File],
bufsz: int= ...,
header: Optional[Header]= ..., overwrite: bool= ...,
filetype: str= ...) -> None:
""" Initiate a new writer for the file _filename_. The writer will
""" Initiate a new writer for the given file. The writer will
refuse to overwrite an already existing file unless _overwrite_
is explicitly set to `True`. The file type is usually determined
from the file extension. It can also be set explicitly with the
_filetype_ parameter.
is explicitly set to `True`.
The file type is usually determined from the file extension.
If you want to explicitly set the filetype (for example, when
writing to standard output '-'), then use a File object.
Using the _filetype_ parameter to set the file type is deprecated
and only works when the file is a string.
The _header_ parameter can be used to set a custom header in
the output file. What kind of information can be written into
the file header depends on the file type.
The optional parameter _bufsz_ sets the size of the buffers used
for collecting the data before they are written out. The default
Expand Down Expand Up @@ -271,7 +278,8 @@ class IdTracker:
equivalent content. All other object kinds will return
`False`.
"""
def complete_backward_references(self, filename: str, relation_depth: int = ...) -> None:
def complete_backward_references(self, filename: Union[str, 'os.PathLike[str]', File, FileBuffer],
relation_depth: int = ...) -> None:
""" Make the IDs in the tracker reference-complete by adding
all referenced IDs for objects whose IDs are already tracked.
Expand All @@ -289,7 +297,8 @@ class IdTracker:
depth up to _relation_depth_ are guaranteed to be included.
Relations that are nested more deeply, may or may not appear.
"""
def complete_forward_references(self, filename: str, relation_depth: int = ...) -> None:
def complete_forward_references(self, filename: Union[str, 'os.PathLike[str]', File, FileBuffer],
relation_depth: int = ...) -> None:
""" Add to the tracker all IDs of object that reference any ID already
tracked.
Expand Down Expand Up @@ -341,10 +350,11 @@ class IdTracker:
on it, which then have a direct effect on the tracker.
"""

def apply(reader: Union[Reader | str], *handlers: HandlerLike) -> None:
def apply(reader: Union[Reader, str, 'os.PathLike[str]', File, FileBuffer],
*handlers: HandlerLike) -> None:
""" Apply a chain of handlers to the given input source. The input
source may be given either as a [osmium.io.Reader][] or
as a simple file name. If one of the handlers is a
source may be a [osmium.io.Reader][], a file or a file buffer.
If one of the handlers is a
[filter][osmium.BaseFilter], then processing of the
object will be stopped if it does not pass the filter.
object will be stopped when it does not pass the filter.
"""
Loading

0 comments on commit 4666326

Please sign in to comment.