Browse Source

python: handle Windows path insanities in the bindings directly.

Otherwise people wouldn't be able to conveniently use os.path, which is
an undesirable pain point. And do this for all currently exposed APIs,
not just the AbstractImporter that caused a problem.

This reverts commit 28497ec2ca.
next
Vladimír Vondruš 3 years ago
parent
commit
a45fbb84da
  1. 5
      doc/python/magnum.text.rst
  2. 15
      doc/python/magnum.trade.rst
  3. 60
      src/python/magnum/test/test_trade.py
  4. 18
      src/python/magnum/text.cpp
  5. 40
      src/python/magnum/trade.cpp

5
doc/python/magnum.text.rst

@ -53,6 +53,11 @@
.. py:function:: magnum.text.AbstractFont.open_file .. py:function:: magnum.text.AbstractFont.open_file
:raise RuntimeError: If file opening fails :raise RuntimeError: If file opening fails
For compatibility with :ref:`os.path`, on Windows this function converts
all backslashes in :p:`filename` to forward slashes before passing it to
:dox:`Text::AbstractFont::openFile()`, which expects forward slashes as
directory separators on all platforms.
.. py:property:: magnum.text.AbstractFont.size .. py:property:: magnum.text.AbstractFont.size
:raise AssertionError: If no file is opened :raise AssertionError: If no file is opened
.. py:property:: magnum.text.AbstractFont.ascent .. py:property:: magnum.text.AbstractFont.ascent

15
doc/python/magnum.trade.rst

@ -189,6 +189,11 @@
.. py:function:: magnum.trade.AbstractImporter.open_file .. py:function:: magnum.trade.AbstractImporter.open_file
:raise RuntimeError: If file opening fails :raise RuntimeError: If file opening fails
For compatibility with :ref:`os.path`, on Windows this function converts
all backslashes in :p:`filename` to forward slashes before passing it to
:dox:`Trade::AbstractImporter::openFile()`, which expects forward slashes
as directory separators on all platforms.
.. py:property:: magnum.trade.AbstractImporter.mesh_count .. py:property:: magnum.trade.AbstractImporter.mesh_count
:raise AssertionError: If no file is opened :raise AssertionError: If no file is opened
.. py:function:: magnum.trade.AbstractImporter.mesh_level_count .. py:function:: magnum.trade.AbstractImporter.mesh_level_count
@ -297,6 +302,11 @@
.. py:function:: magnum.trade.AbstractImageConverter.convert_to_file .. py:function:: magnum.trade.AbstractImageConverter.convert_to_file
:raise RuntimeError: If image conversion fails :raise RuntimeError: If image conversion fails
For compatibility with :ref:`os.path`, on Windows this function converts
all backslashes in :p:`filename` to forward slashes before passing it to
:dox:`Trade::AbstractImageConverter::convertToFile()`, which expects
forward slashes as directory separators on all platforms.
.. py:class:: magnum.trade.SceneConverterManager .. py:class:: magnum.trade.SceneConverterManager
:summary: Manager for :ref:`AbstractSceneConverter` plugin instances :summary: Manager for :ref:`AbstractSceneConverter` plugin instances
@ -324,3 +334,8 @@
.. py:function:: magnum.trade.AbstractSceneConverter.convert_to_file .. py:function:: magnum.trade.AbstractSceneConverter.convert_to_file
:raise RuntimeError: If scene conversion fails :raise RuntimeError: If scene conversion fails
For compatibility with :ref:`os.path`, on Windows this function converts
all backslashes in :p:`filename` to forward slashes before passing it to
:dox:`Trade::AbstractSceneConverter::convertToFile()`, which expects
forward slashes as directory separators on all platforms.

60
src/python/magnum/test/test_trade.py

@ -103,8 +103,7 @@ class ImageData(unittest.TestCase):
class MeshData(unittest.TestCase): class MeshData(unittest.TestCase):
def test(self): def test(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES)
@ -197,8 +196,7 @@ class MeshData(unittest.TestCase):
def test_index_data_access(self): def test_index_data_access(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
mesh_refcount = sys.getrefcount(mesh) mesh_refcount = sys.getrefcount(mesh)
@ -221,8 +219,7 @@ class MeshData(unittest.TestCase):
def test_vertex_data_access(self): def test_vertex_data_access(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
mesh_refcount = sys.getrefcount(mesh) mesh_refcount = sys.getrefcount(mesh)
@ -245,8 +242,7 @@ class MeshData(unittest.TestCase):
def test_indices_access(self): def test_indices_access(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
mesh_refcount = sys.getrefcount(mesh) mesh_refcount = sys.getrefcount(mesh)
@ -275,8 +271,7 @@ class MeshData(unittest.TestCase):
def test_attribute_access(self): def test_attribute_access(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
mesh_refcount = sys.getrefcount(mesh) mesh_refcount = sys.getrefcount(mesh)
@ -336,8 +331,7 @@ class MeshData(unittest.TestCase):
def test_mutable_index_data_access(self): def test_mutable_index_data_access(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
self.assertEqual(mesh.index_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) self.assertEqual(mesh.index_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE)
@ -354,8 +348,7 @@ class MeshData(unittest.TestCase):
def test_mutable_vertex_data_access(self): def test_mutable_vertex_data_access(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
self.assertEqual(mesh.vertex_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) self.assertEqual(mesh.vertex_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE)
@ -372,8 +365,7 @@ class MeshData(unittest.TestCase):
def test_mutable_indices_access(self): def test_mutable_indices_access(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
self.assertEqual(mesh.index_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) self.assertEqual(mesh.index_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE)
@ -388,8 +380,7 @@ class MeshData(unittest.TestCase):
def test_mutable_attributes_access(self): def test_mutable_attributes_access(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
self.assertEqual(mesh.index_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) self.assertEqual(mesh.index_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE)
@ -431,8 +422,7 @@ class MeshData(unittest.TestCase):
def test_nonindexed(self): def test_nonindexed(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(1) mesh = importer.mesh(1)
self.assertFalse(mesh.is_indexed) self.assertFalse(mesh.is_indexed)
@ -456,8 +446,7 @@ class MeshData(unittest.TestCase):
def test_attribute_oob(self): def test_attribute_oob(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
@ -513,8 +502,7 @@ class MeshData(unittest.TestCase):
def test_attribute_access_array(self): def test_attribute_access_array(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
joint_ids_id = mesh.attribute_id(trade.MeshAttribute.JOINT_IDS) joint_ids_id = mesh.attribute_id(trade.MeshAttribute.JOINT_IDS)
@ -530,8 +518,7 @@ class MeshData(unittest.TestCase):
def test_attribute_access_unsupported_format(self): def test_attribute_access_unsupported_format(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(0) mesh = importer.mesh(0)
custom_attribute_id = mesh.attribute_id(importer.mesh_attribute_for_name("_CUSTOM_ATTRIBUTE")) custom_attribute_id = mesh.attribute_id(importer.mesh_attribute_for_name("_CUSTOM_ATTRIBUTE"))
@ -669,8 +656,7 @@ class Importer(unittest.TestCase):
self.assertIsNone(importer.mesh_attribute_name(trade.MeshAttribute(32768 + 7))) self.assertIsNone(importer.mesh_attribute_name(trade.MeshAttribute(32768 + 7)))
self.assertIsNone(importer.mesh_attribute_for_name("_CUSTOM_ATTRIBUTE")) self.assertIsNone(importer.mesh_attribute_for_name("_CUSTOM_ATTRIBUTE"))
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
self.assertEqual(importer.mesh_count, 2) self.assertEqual(importer.mesh_count, 2)
self.assertEqual(importer.mesh_level_count(0), 1) self.assertEqual(importer.mesh_level_count(0), 1)
self.assertEqual(importer.mesh_name(0), 'Indexed mesh') self.assertEqual(importer.mesh_name(0), 'Indexed mesh')
@ -693,32 +679,28 @@ class Importer(unittest.TestCase):
def test_mesh_level_oob(self): def test_mesh_level_oob(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
with self.assertRaises(IndexError): with self.assertRaises(IndexError):
importer.mesh(0, 1) importer.mesh(0, 1)
def test_mesh_by_name(self): def test_mesh_by_name(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh('Non-indexed mesh') mesh = importer.mesh('Non-indexed mesh')
self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES)
def test_mesh_by_name_not_found(self): def test_mesh_by_name_not_found(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
with self.assertRaises(KeyError): with self.assertRaises(KeyError):
importer.mesh('Nonexistent') importer.mesh('Nonexistent')
def test_mesh_by_name_level_oob(self): def test_mesh_by_name_level_oob(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
with self.assertRaises(IndexError): with self.assertRaises(IndexError):
importer.mesh('Non-indexed mesh', 1) importer.mesh('Non-indexed mesh', 1)
@ -798,8 +780,7 @@ class ImageConverter(unittest.TestCase):
class SceneConverter(unittest.TestCase): class SceneConverter(unittest.TestCase):
def test_mesh(self): def test_mesh(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(1) mesh = importer.mesh(1)
converter = trade.SceneConverterManager().load_and_instantiate('StanfordSceneConverter') converter = trade.SceneConverterManager().load_and_instantiate('StanfordSceneConverter')
@ -810,8 +791,7 @@ class SceneConverter(unittest.TestCase):
def test_mesh_failed(self): def test_mesh_failed(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
# TODO figure out a less silly way to get forward slashes on Windows importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf').replace('\\', '/'))
mesh = importer.mesh(1) mesh = importer.mesh(1)
converter = trade.SceneConverterManager().load_and_instantiate('AnySceneConverter') converter = trade.SceneConverterManager().load_and_instantiate('AnySceneConverter')

18
src/python/magnum/text.cpp

@ -33,6 +33,13 @@
#include "corrade/pluginmanager.h" #include "corrade/pluginmanager.h"
#include "magnum/bootstrap.h" #include "magnum/bootstrap.h"
#ifdef CORRADE_TARGET_WINDOWS
/* To allow people to conveniently use Python's os.path, we need to convert
backslashes to forward slashes as all Corrade and Magnum APIs expect
forward */
#include <Corrade/Utility/Path.h>
#endif
namespace magnum { namespace magnum {
namespace { namespace {
@ -109,7 +116,16 @@ void text(py::module_& m) {
.def("open_file", [](Text::AbstractFont& self, const std::string& filename, Float size) { .def("open_file", [](Text::AbstractFont& self, const std::string& filename, Float size) {
/** @todo log redirection -- but we'd need assertions to not be /** @todo log redirection -- but we'd need assertions to not be
part of that so when it dies, the user can still see why */ part of that so when it dies, the user can still see why */
if(self.openFile(filename, size)) return; if(self.openFile(
#ifdef CORRADE_TARGET_WINDOWS
/* To allow people to conveniently use Python's os.path, we
need to convert backslashes to forward slashes as all
Corrade and Magnum APIs expect forward */
Utility::Path::fromNativeSeparators(filename)
#else
filename
#endif
, size)) return;
PyErr_Format(PyExc_RuntimeError, "opening %s failed", filename.data()); PyErr_Format(PyExc_RuntimeError, "opening %s failed", filename.data());
throw py::error_already_set{}; throw py::error_already_set{};

40
src/python/magnum/trade.cpp

@ -46,6 +46,13 @@
#include "corrade/pluginmanager.h" #include "corrade/pluginmanager.h"
#include "magnum/bootstrap.h" #include "magnum/bootstrap.h"
#ifdef CORRADE_TARGET_WINDOWS
/* To allow people to conveniently use Python's os.path, we need to convert
backslashes to forward slashes as all Corrade and Magnum APIs expect
forward */
#include <Corrade/Utility/Path.h>
#endif
namespace magnum { namespace magnum {
namespace { namespace {
@ -282,7 +289,16 @@ template<class R, Containers::Optional<R>(Trade::AbstractImporter::*f)(UnsignedI
template<class T, bool(Trade::AbstractImageConverter::*f)(const T&, Containers::StringView)> void checkImageConverterResult(Trade::AbstractImageConverter& self, const T& image, const std::string& filename) { template<class T, bool(Trade::AbstractImageConverter::*f)(const T&, Containers::StringView)> void checkImageConverterResult(Trade::AbstractImageConverter& self, const T& image, const std::string& filename) {
/** @todo log redirection -- but we'd need assertions to not be part of /** @todo log redirection -- but we'd need assertions to not be part of
that so when it dies, the user can still see why */ that so when it dies, the user can still see why */
bool out = (self.*f)(image, filename); bool out = (self.*f)(image,
#ifdef CORRADE_TARGET_WINDOWS
/* To allow people to conveniently use Python's os.path, we need to
convert backslashes to forward slashes as all Corrade and Magnum
APIs expect forward */
Utility::Path::fromNativeSeparators(filename)
#else
filename
#endif
);
if(!out) { if(!out) {
PyErr_SetString(PyExc_RuntimeError, "conversion failed"); PyErr_SetString(PyExc_RuntimeError, "conversion failed");
throw py::error_already_set{}; throw py::error_already_set{};
@ -295,7 +311,16 @@ template<class T, bool(Trade::AbstractImageConverter::*f)(const T&, Containers::
template<class T, bool(Trade::AbstractSceneConverter::*f)(const T&, Containers::StringView)> void checkSceneConverterResult(Trade::AbstractSceneConverter& self, const T& mesh, const std::string& filename) { template<class T, bool(Trade::AbstractSceneConverter::*f)(const T&, Containers::StringView)> void checkSceneConverterResult(Trade::AbstractSceneConverter& self, const T& mesh, const std::string& filename) {
/** @todo log redirection -- but we'd need assertions to not be part of /** @todo log redirection -- but we'd need assertions to not be part of
that so when it dies, the user can still see why */ that so when it dies, the user can still see why */
bool out = (self.*f)(mesh, filename); bool out = (self.*f)(mesh,
#ifdef CORRADE_TARGET_WINDOWS
/* To allow people to conveniently use Python's os.path, we need to
convert backslashes to forward slashes as all Corrade and Magnum
APIs expect forward */
Utility::Path::fromNativeSeparators(filename)
#else
filename
#endif
);
if(!out) { if(!out) {
PyErr_SetString(PyExc_RuntimeError, "conversion failed"); PyErr_SetString(PyExc_RuntimeError, "conversion failed");
throw py::error_already_set{}; throw py::error_already_set{};
@ -691,7 +716,16 @@ void trade(py::module_& m) {
.def("open_file", [](Trade::AbstractImporter& self, const std::string& filename) { .def("open_file", [](Trade::AbstractImporter& self, const std::string& filename) {
/** @todo log redirection -- but we'd need assertions to not be /** @todo log redirection -- but we'd need assertions to not be
part of that so when it dies, the user can still see why */ part of that so when it dies, the user can still see why */
if(self.openFile(filename)) return; if(self.openFile(
#ifdef CORRADE_TARGET_WINDOWS
/* To allow people to conveniently use Python's os.path, we
need to convert backslashes to forward slashes as all
Corrade and Magnum APIs expect forward */
Utility::Path::fromNativeSeparators(filename)
#else
filename
#endif
)) return;
PyErr_Format(PyExc_RuntimeError, "opening %s failed", filename.data()); PyErr_Format(PyExc_RuntimeError, "opening %s failed", filename.data());
throw py::error_already_set{}; throw py::error_already_set{};

Loading…
Cancel
Save