Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pathlib Path and py.test LocalPath

I have started using pathlib.Path some time ago and I like using it. Now that I have gotten used to it, I have gotten sloppy and forget to cast arguments to str.

This often happens when using tox + py.test with temporary directories based on tmpdir (which is a py._path.local.LocalPath):

from pathlib import Path
import pytest

def test_tmpdir(tmpdir):
    p = Path(tmpdir) / 'testfile.csv'

Instead of inserting str() every time, I looked at solving this more generally, but could not.

First I tried to make my own Path class that has an adapted _parse_args:

import pytest
from py._path.local import LocalPath
from pathlib import Path, PurePath

def Path(Path):
    @classmethod
    def _parse_args(cls, args):
        parts = []
        for a in args:
            if isinstance(a, PurePath):
                parts += a._parts
            elif isinstance(a, str):
                # Force-cast str subclasses to str (issue #21127)
                parts.append(str(a))
            elif isinstance(a, LocalPath):
                parts.append(str(a))
            else:
                raise TypeError(
                    "argument should be a path or str object, not %r"
                    % type(a))
        return cls._flavour.parse_parts(parts)

def test_subclass(tmpdir):
    p = Path(tmpdir) / 'testfile.csv'

This throws a TypeError: unsupported operand type(s) for /: 'NoneType' and 'str' (tried with PosixPath as well, same result, would prefer not to be Linux specific).

The I tried to monkey-patch Path:

import pytest
from pathlib import Path

def add_tmpdir():
    from py._path.local import LocalPath

    org_attr = '_parse_args'
    stow_attr = '_org_parse_args'

    def parse_args_localpath(cls, args):
        args = list(args)
        for idx, a in enumerate(args):
            if isinstance(a, LocalPath):
                args[idx] = str(a)
        return getattr(cls, stow_attr)(args)

    if hasattr(Path, stow_attr):
        return  # already done
    setattr(Path, stow_attr, getattr(Path, org_attr))
    setattr(Path, org_attr, parse_args_localpath)

add_tmpdir()

def test_monkeypatch_path(tmpdir):
    p = Path(tmpdir) / 'testfile.csv'

This throws a AttributeError: type object 'Path' has no attribute '_flavour' (also when monkey-patching PurePath).

And finally I tried just wrapping Path:

import pytest
import pathlib

def Path(*args):
    from py._path.local import LocalPath
    args = list(args)
    for idx, a in enumerate(args):
        if isinstance(a, LocalPath):
            args[idx] = str(a)
    return pathlib.Path(*args)

def test_tmpdir_path(tmpdir):
    p = Path(tmpdir) / 'testfile.csv'

Which also gives the AttributeError: type object 'Path' has no attribute '_flavour'

I thought at some point this last one worked, but I cannot reproduce that.
Am I doing something wrong? Why is this so hard?

like image 834
Alois Avatar asked Nov 24 '16 11:11

Alois


People also ask

What does Pathlib path () do?

The pathlib is a Python module which provides an object API for working with files and directories. The pathlib is a standard module. Path is the core object to work with files.

Is Pathlib better than OS path?

The pathlib module provides an easier method to interact with the filesystem no matter what the operating system is. It allows a more intuitive, more pythonic way to interface with file paths (the name of a file including any of its directories and subdirectories).

What is path () in Python?

PYTHONPATH is an environment variable which the user can set to add additional directories that the user wants Python to add to the sys. path directory list. In short, we can say that it is an environment variable that you set before running the Python interpreter.

Is Pathlib newer than OS?

pathlib was introduced in Python 3.4 and offers a different set of abstractions for working with paths. However, just because it is newer, that doesn't mean it's better.


2 Answers

In case anyone else is researching whether pytest's tmpdir paths play nicely with pathlib.Path:

Using python 3.6.5 and pytest 3.2.1, the code posted in the question works perfectly fine without explicitly casting to str:

from pathlib import Path

def test_tmpdir(tmpdir):
    p = Path(tmpdir) / 'testfile.csv'
like image 167
Felix Avatar answered Sep 19 '22 01:09

Felix


That last one (wrapping) should work, I suspect you actually test all of these in one py.test/tox run and that monkey-patch is still in effect (that might explain why it worked at some point, the order of the test files etc matters if you start to change things on global classes).

That this is hard, is because of Path essentially being a generator, that on the fly decides whether you are on Windows or Linux, and creates a WindowsPath resp. PosixPath accordingly.

BDFL Guido van Rossum already indicated in May 2015:

It does sound like subclassing Path should be made easier.

but nothing happened. Support for pathlib in 3.6 within other standard libraries has increased, but pathlib itself still has the same problems.

like image 41
Anthon Avatar answered Sep 21 '22 01:09

Anthon