Repository: omarkohl/pytest-datafiles

Issue:
`py.path` vs `pathlib.Path`
It seems that the `datafiles` fixture is a `py.path` (well, a `LocalPath`) - does this have any functionality that `pathlib.Path` does not? If not, I think using `pathlib`'s `Path` would be nice. The only function I've missed so far is `is_file`, but I haven't tested this much. Additionally, the [`py` website][py website] says

> Note: The ‘py’ library is in “maintenance mode” and so is not recommended for new projects. Please check out pathlib or pathlib2 for path operations.

[py website]: https://py.readthedocs.io/en/latest/path.html

Discussion:
omarkohl: Wow, thanks, that's a very thorough PR, updating the changelog and all :heart_eyes: That certainly makes my life easier :slightly_smiling_face: . I will have a look at the code. If you have time, could you do the following?

* Check that all the examples in the README.md still work
* Add a section in README.md documenting how people have to modify their code to upgrade to version 3.0.0
hans-d: For the README.rst:
- i will move the examples to their own files (makes life easier to test them)
- and generate a full README.rst from the that

Additional changes are expected as well in tests (also getting rid of py.path there)

Patch:
diff --git a/.gitignore b/.gitignore
index 25f71b9..6b9d3d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,6 @@ build/
 dist/
 htmlcov/
 pytest_datafiles.egg-info/
+.doctrees/
+.idea/
+.venv
diff --git a/.travis.yml b/.travis.yml
index 432e8dc..6b5b370 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,12 +2,12 @@
 
 language: python
 python:
-  - 2.7
-  - 3.4
-  - 3.5
   - 3.6
   - 3.7
   - 3.8
+  - 3.9
+  - 3.10
+  - 3.11
   - pypy
 
 install:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6fdf021..24c3b85 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -17,6 +17,14 @@ Removed
 Fixed
 Security
 
+`3.0`_
+~~~~~~
+
+Changed
+
+* BREAKING CHANGE: Using `pathlib.Path` instead of `py.path` (`#7`_)
+* BREAKING CHANGE: Removed support for Python 2.7 and Python 3 <= 3.6
+
 `2.0.1`_
 ~~~~~~~~
 
@@ -74,7 +82,8 @@ Added
   function
 
 
-.. _`Unreleased`: https://github.com/omarkohl/pytest-datafiles/compare/2.0.1...master
+.. _`Unreleased`: https://github.com/omarkohl/pytest-datafiles/compare/3.0...master
+.. _`3.0`: https://github.com/omarkohl/pytest-datafiles/compare/2.0.1...3.0
 .. _`2.0.1`: https://github.com/omarkohl/pytest-datafiles/compare/2.0...2.0.1
 .. _`2.0`: https://github.com/omarkohl/pytest-datafiles/compare/1.0...2.0
 .. _`1.0`: https://github.com/omarkohl/pytest-datafiles/compare/0.2...1.0
@@ -84,6 +93,7 @@ Added
 
 .. _`#1`: https://github.com/omarkohl/pytest-datafiles/issues/1
 .. _`#2`: https://github.com/omarkohl/pytest-datafiles/issues/2
+.. _`#7`: https://github.com/omarkohl/pytest-datafiles/issues/7
 .. _`#18`: https://github.com/omarkohl/pytest-datafiles/issues/18
 
 
diff --git a/Makefile b/Makefile
index 01bab1f..dd27bd7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,13 +1,16 @@
-.PHONY: clean-pyc clean-build docs clean
+.PHONY: all clean clean-build clean-pyc clean-test lint test test-quick test-example test-all coverage dist
 
 help:
+	@echo "all          Lint, test, check coverage and package"
 	@echo "clean        Remove all build, test, coverage and Python artifacts"
 	@echo "clean-build  Remove build artifacts"
 	@echo "clean-pyc    Remove Python file artifacts"
 	@echo "clean-test   Remove test and coverage artifacts"
 	@echo "lint         Check style with Flake8 and Pylint"
-	@echo "test         Run tests quickly with Python 3.5"
-	@echo "test-all     Run tests on every Python version with tox"
+	@echo "test         Run quick tests and those for the example code"
+	@echo "test-quick   Run tests quickly (main code only)with Python 3.11"
+	@echo "test-example Run tests for the example code"
+	@echo "test-all     Run tests on every Python version with tox and the example"
 	@echo "coverage     Check code coverage quickly with Python 3"
 	@echo "dist         Package"
 
@@ -31,18 +34,27 @@ clean-test:
 	rm -f .coverage
 	rm -fr htmlcov/
 
+all: lint test-all coverage dist
+
 lint:
 	tox -e lint
 
-test:
-	tox -e py35
+test: test-example | test-quick
+
+test-quick:
+	tox -e py311
 
-test-all:
+test-all: test-example | test-all-env
+
+test-all-env:
 	tox
 
+test-example:
+	tox -e examples
+
 coverage:
 	tox -e coverage
-	xdg-open htmlcov/index.html
+	command -v xdg-open && xdg-open htmlcov/index.html || true
 
 dist: clean
 	python3 setup.py sdist
diff --git a/README.rst b/README.rst
index fe7f95d..98ac931 100644
--- a/README.rst
+++ b/README.rst
@@ -3,23 +3,23 @@ pytest-datafiles
 ================
 
 .. image:: https://img.shields.io/travis/omarkohl/pytest-datafiles.svg
-        :target: https://travis-ci.org/omarkohl/pytest-datafiles
+    :target: https://travis-ci.org/omarkohl/pytest-datafiles
 
 
 .. image:: https://coveralls.io/repos/omarkohl/pytest-datafiles/badge.svg?branch=master&service=github
-  :target: https://coveralls.io/github/omarkohl/pytest-datafiles?branch=master
+    :target: https://coveralls.io/github/omarkohl/pytest-datafiles?branch=master
 
 
 .. image:: https://img.shields.io/pypi/v/pytest-datafiles.svg
-	:target: https://pypi.python.org/pypi/pytest-datafiles
+    :target: https://pypi.python.org/pypi/pytest-datafiles
 
 
 .. image:: https://codeclimate.com/github/omarkohl/pytest-datafiles/badges/gpa.svg
-   :target: https://codeclimate.com/github/omarkohl/pytest-datafiles
-   :alt: Code Climate
+    :target: https://codeclimate.com/github/omarkohl/pytest-datafiles
+    :alt: Code Climate
 
 
-`pytest`_ plugin to create a `tmpdir`_ containing a preconfigured set of
+`pytest`_ plugin to create a `tmp_path`_ containing a preconfigured set of
 files and/or directories.
 
 **Note about maintenance:** This project is maintained and bug reports or pull
@@ -30,19 +30,19 @@ Features
 --------
 
 This plugin allows you to specify one or several files/directories that are
-copied to a temporary directory (`tmpdir`_) before the execution of the test.
+copied to a temporary directory (`tmp_path`_) before the execution of the test.
 This means the original files are not modified and every test runs on its own
 version of the same files.
 
-Files/directories can be specified either as *strings* or as *py.path* objects.
+Files/directories can be specified either as *strings* or as *pathlib.Path* objects.
 
 To take advantage of the *datafiles* fixture in a test function, add
 *datafiles* as one of the test function parameters (per usual with `pytest`_
 fixtures) and decorate the test function with *@pytest.mark.datafiles(file1,
 file2, dir1, dir2, ...)*. See the examples below.
 
-The *datafiles* variable in your test function is a py.path object
-(`tmpdir`_) where the copied files are located. Under Linux systems this
+The *datafiles* variable in your test function is a pathlib.Path object
+(`tmp_path`_) where the copied files are located. Under Linux systems this
 will most likely be some subdirectory of */tmp/*.
 
 
@@ -79,9 +79,21 @@ Installation
     pip install pytest-datafiles
 
 
+Upgrade to 3.0
+--------------
+
+Version 3 now uses `tmp_path`_, resulting in `pathlib.Path` objects
+instead of `py.path`.
+
+Your tests may need to be adjusted. In `examples/example_upgradev3.py` you see some possible
+variations.
+
+
 Usage
 -----
 
+The full code with more details for the examples can be found in `examples/`.
+
 Example 1
 ~~~~~~~~~
 
@@ -93,19 +105,12 @@ data files in your test method as follows:
 
 .. code-block:: python
 
-    import os
-    import pytest
+    # more details in `examples/example_1.py`
 
     @pytest.mark.datafiles('/opt/big_files/film1.mp4')
     def test_fast_forward(datafiles):
-        path = str(datafiles)  # Convert from py.path object to path (str)
-        assert len(os.listdir(path)) == 1
-        assert os.path.isfile(os.path.join(path, 'film1.mp4'))
-        #assert some_operation(os.path.join(path, 'film1.mp4')) == expected_result
+        # ...
 
-        # Using py.path syntax
-        assert len(datafiles.listdir()) == 1
-        assert (datafiles / 'film1.mp4').check(file=1)
 
 Example 2
 ~~~~~~~~~
@@ -117,32 +122,16 @@ modified by every test.
 
 .. code-block:: python
 
-    import os
-    import pytest
-
-    FIXTURE_DIR = os.path.join(
-        os.path.dirname(os.path.realpath(__file__)),
-        'test_files',
-        )
+    # more details in `examples/example_2.py`
 
     @pytest.mark.datafiles(
-        os.path.join(FIXTURE_DIR, 'img1.jpg'),
-        os.path.join(FIXTURE_DIR, 'img2.jpg'),
-        os.path.join(FIXTURE_DIR, 'img3.jpg'),
-        )
+        FIXTURE_DIR / 'img1.jpg',
+        FIXTURE_DIR / 'img2.jpg',
+        FIXTURE_DIR / 'img3.jpg',
+    )
     def test_find_borders(datafiles):
-        for img in datafiles.listdir():
-            print(img)
-            #assert process(img) == some_expected_value
+        # ...
 
-    @pytest.mark.datafiles(
-        os.path.join(FIXTURE_DIR, 'img4.jpg'),
-        os.path.join(FIXTURE_DIR, 'img5.jpg'),
-        )
-    def test_brightness(datafiles):
-        for img in datafiles.listdir():
-            print(img)
-            #assert process(img) == some_expected_value
 
 Example 3
 ~~~~~~~~~
@@ -152,36 +141,18 @@ define one decorator beforehand and apply it to every test like this example:
 
 .. code-block:: python
 
-    import os
-    import pytest
-
-    FIXTURE_DIR = os.path.join(
-        os.path.dirname(os.path.realpath(__file__)),
-        'test_files',
-        )
+    # more details in `examples/example_3.py`
 
     ALL_IMGS = pytest.mark.datafiles(
-        os.path.join(FIXTURE_DIR, 'img1.jpg'),
-        os.path.join(FIXTURE_DIR, 'img2.jpg'),
-        os.path.join(FIXTURE_DIR, 'img3.jpg'),
-        os.path.join(FIXTURE_DIR, 'img4.jpg'),
-        os.path.join(FIXTURE_DIR, 'img5.jpg'),
-        os.path.join(FIXTURE_DIR, 'img6.jpg'),
-        os.path.join(FIXTURE_DIR, 'img7.jpg'),
-        os.path.join(FIXTURE_DIR, 'img8.jpg'),
-        )
+        FIXTURE_DIR / 'img1.jpg',
+        FIXTURE_DIR / 'img2.jpg',
+        FIXTURE_DIR / 'img3.jpg',
+    )
 
     @ALL_IMGS
     def test_something1(datafiles):
-        for img in datafiles.listdir():
-            print(img)
-            #assert process(img) == some_expected_value
+        # ...
 
-    @ALL_IMGS
-    def test_something2(datafiles):
-        for img in datafiles.listdir():
-            print(img)
-            #assert process(img) == some_expected_value
 
 Example 4
 ~~~~~~~~~
@@ -191,79 +162,23 @@ Imagine you have 3 directories (*dir1*, *dir2*, *dir3*) each containing the file
 
 This example clarifies the options **on_duplicate** and **keep_top_dir**.
 
-.. code-block:: python
-
-    import os
-    import pytest
-
-    FIXTURE_DIR = os.path.join(
-        os.path.dirname(os.path.realpath(__file__)),
-        '_fixture_files',
-        )
-
-    @pytest.mark.datafiles(
-        os.path.join(FIXTURE_DIR, 'dir1'),
-        os.path.join(FIXTURE_DIR, 'dir2'),
-        os.path.join(FIXTURE_DIR, 'dir3'),
-        on_duplicate='ignore',
-        )
-    def test_dir_ignore(datafiles):
-        # datafiles.listdir() will list fileA and fileB originally from dir1
-        pass
-
-    @pytest.mark.datafiles(
-        os.path.join(FIXTURE_DIR, 'dir1'),
-        os.path.join(FIXTURE_DIR, 'dir2'),
-        os.path.join(FIXTURE_DIR, 'dir3'),
-        on_duplicate='replace',
-        )
-    def test_dir_replace(datafiles):
-        # datafiles.listdir() will list fileA and fileB originally from dir3
-        pass
-
-    @pytest.mark.datafiles(
-        os.path.join(FIXTURE_DIR, 'dir1'),
-        os.path.join(FIXTURE_DIR, 'dir2'),
-        os.path.join(FIXTURE_DIR, 'dir3'),
-        # on_duplicate='exception' is the default and does not need to be
-        # specified
-        )
-    def test_dir_exception(datafiles):
-        # An exception will be raised because of duplicate filename fileA
-        pass
-
-    @pytest.mark.datafiles(
-        os.path.join(FIXTURE_DIR, 'dir1'),
-        os.path.join(FIXTURE_DIR, 'dir2'),
-        os.path.join(FIXTURE_DIR, 'dir3'),
-        keep_top_dir=True,
-        )
-    def test_dir_keep_top_dir(datafiles):
-        # datafiles.listdir() will list dir1, dir2 and dir3 (each containing
-        # fileA and fileB)
-        pass
 
 Example 5
 ~~~~~~~~~
 
-You can also use a py.path object instead of str paths.
+You can also use a str paths.
 
 .. code-block:: python
 
-    import os
-    import py
-    import pytest
-
-    _dir = os.path.dirname(os.path.realpath(__file__))
-    FIXTURE_DIR = py.path.local(_dir) / 'test_files'
+    # more details in `examples/example_5.py`
 
     @pytest.mark.datafiles(
-        FIXTURE_DIR / 'img1.jpg',
-        FIXTURE_DIR / 'img2.jpg',
-        FIXTURE_DIR / 'img3.jpg',
-        )
-    def test_fast_forward(datafiles):
-        assert len(datafiles.listdir()) == 3
+        os.path.join(FIXTURE_DIR, 'img1.jpg'),
+        os.path.join(FIXTURE_DIR, 'img2.jpg'),
+        os.path.join(FIXTURE_DIR, 'img3.jpg'),
+    )
+    def test_str(datafiles):
+        # ...
 
 
 Contributing
@@ -288,7 +203,7 @@ To create and upload a new package first update the version number and then:
     # Create some test_example.py (e.g. with one of the examples above)
     test-venv/bin/pytest test_example.py
     # Set the git tag for final release
-    git tag -a 2.0
+    git tag -a 3.0
     git push --tags
     # Upload the package for final release
     twine upload dist/*
@@ -324,7 +239,7 @@ templates `cookiecutter-pypackage`_ and `cookiecutter-pytest-plugin`_.
 
 
 .. _`pytest`: https://docs.pytest.org/en/latest/contents.html
-.. _`tmpdir`: https://docs.pytest.org/en/latest/tmpdir.html
+.. _`tmp_path`: https://docs.pytest.org/en/latest/tmp_path.html
 .. _`tox`: https://tox.readthedocs.org/en/latest/
 .. _`MIT License`: http://opensource.org/licenses/MIT
 .. _`file an issue`: https://github.com/omarkohl/pytest-datafiles/issues
diff --git a/examples/_fixture_files/dir1/fileA b/examples/_fixture_files/dir1/fileA
new file mode 100644
index 0000000..a8994dc
--- /dev/null
+++ b/examples/_fixture_files/dir1/fileA
@@ -0,0 +1,1 @@
+1a
diff --git a/examples/_fixture_files/dir1/fileB b/examples/_fixture_files/dir1/fileB
new file mode 100644
index 0000000..6f8bafa
--- /dev/null
+++ b/examples/_fixture_files/dir1/fileB
@@ -0,0 +1,1 @@
+1b
diff --git a/examples/_fixture_files/dir2/fileA b/examples/_fixture_files/dir2/fileA
new file mode 100644
index 0000000..94226da
--- /dev/null
+++ b/examples/_fixture_files/dir2/fileA
@@ -0,0 +1,1 @@
+2a
diff --git a/examples/_fixture_files/dir2/fileB b/examples/_fixture_files/dir2/fileB
new file mode 100644
index 0000000..b8a4cf4
--- /dev/null
+++ b/examples/_fixture_files/dir2/fileB
@@ -0,0 +1,1 @@
+2b
diff --git a/examples/_fixture_files/dir3/fileA b/examples/_fixture_files/dir3/fileA
new file mode 100644
index 0000000..31cd755
--- /dev/null
+++ b/examples/_fixture_files/dir3/fileA
@@ -0,0 +1,1 @@
+3a
diff --git a/examples/_fixture_files/dir3/fileB b/examples/_fixture_files/dir3/fileB
new file mode 100644
index 0000000..f6fc7b7
--- /dev/null
+++ b/examples/_fixture_files/dir3/fileB
@@ -0,0 +1,1 @@
+3b
diff --git a/examples/example_1.py b/examples/example_1.py
new file mode 100644
index 0000000..e107c13
--- /dev/null
+++ b/examples/example_1.py
@@ -0,0 +1,27 @@
+"""Example: Reference files anywhere.
+
+One possible use case is when you are running tests on very big files that are
+not included or packaged with your tests. For example, your test files are
+large video files stored under */opt/big_files/* . You don't want your tests
+modifying the original files, but the files are required by the tests.
+
+"""
+import os
+import pytest
+
+
+@pytest.mark.datafiles('/opt/big_files/film1.mp4')
+def test_fast_forward(datafiles):
+    """Work with a copy of the big file."""
+    assert len(list(datafiles.iterdir())) == 1
+    assert (datafiles / 'film1.mp4').is_file()
+    # assert some_operation(datafiles / 'film1.mp4') == expected_result
+
+
+@pytest.mark.datafiles('/opt/big_files/film1.mp4')
+def test_fast_forward_alternative(datafiles):
+    """Work with a copy of the bigfile, using `str`."""
+    path = str(datafiles)  # Convert from py.path object to path (str)
+    assert len(os.listdir(path)) == 1
+    assert os.path.isfile(os.path.join(path, 'film1.mp4'))
+    # assert some_operation(os.path.join(path, 'film1.mp4')) == expected_result
diff --git a/examples/example_2.py b/examples/example_2.py
new file mode 100644
index 0000000..1044fcf
--- /dev/null
+++ b/examples/example_2.py
@@ -0,0 +1,57 @@
+"""Example: make a subset of the files in a dir available.
+
+Now for another use case: let's say in the directory where your tests are
+located, you place a directory named *test_files*. Here you have a lot of
+images you want to run tests on. By using this plugin, you make sure the
+original files under *test_files* are not modified by every test.
+
+"""
+from pathlib import Path
+
+import pytest
+
+# Dir containing 8 files
+FIXTURE_DIR = Path(__file__).parent.resolve() / 'test_files'
+
+
+@pytest.mark.datafiles(
+    FIXTURE_DIR / 'img1.jpg',
+    FIXTURE_DIR / 'img2.jpg',
+    FIXTURE_DIR / 'img3.jpg',
+)
+def test_find_borders(datafiles):
+    """Work with a copy of only 3 files."""
+    for img in datafiles.iterdir():
+        print(img)
+        # assert process(img) == some_expected_value
+
+        # and only the referenced files are available
+        assert img.name != 'img4.jpg'
+
+    assert len(list(datafiles.iterdir())) == 3
+
+
+@pytest.mark.datafiles(
+    FIXTURE_DIR / 'img4.jpg',
+    FIXTURE_DIR / 'img5.jpg',
+)
+def test_brightness(datafiles):
+    """Work with a copy of only 2 files."""
+    for img in datafiles.iterdir():
+        print(img)
+        # assert process(img) == some_expected_value
+
+        # and only the referenced files are available
+        assert img.name != 'img3.jpg'
+
+    assert len(list(datafiles.iterdir())) == 2
+
+
+@pytest.mark.datafiles(FIXTURE_DIR)
+def test_resize(datafiles):
+    """Work with a copy of all files."""
+    for img in datafiles.iterdir():
+        print(img)
+        # assert process(img) == some_expected_value
+
+    assert len(list(datafiles.iterdir())) == 8
diff --git a/examples/example_3.py b/examples/example_3.py
new file mode 100644
index 0000000..13fb982
--- /dev/null
+++ b/examples/example_3.py
@@ -0,0 +1,49 @@
+"""Example: re-use file selection.
+
+If all (or many) of your tests rely on the same files it can be easier to
+define one decorator beforehand and apply it to every test.
+"""
+from pathlib import Path
+
+import pytest
+
+FIXTURE_DIR = Path(__file__).parent.resolve() / 'test_files'
+
+ALL_IMGS = pytest.mark.datafiles(
+    FIXTURE_DIR / 'img1.jpg',
+    FIXTURE_DIR / 'img2.jpg',
+    FIXTURE_DIR / 'img3.jpg',
+    FIXTURE_DIR / 'img4.jpg',
+    FIXTURE_DIR / 'img5.jpg',
+    FIXTURE_DIR / 'img6.jpg',
+    FIXTURE_DIR / 'img7.jpg',
+    FIXTURE_DIR / 'img8.jpg',
+)
+
+
+@ALL_IMGS
+def test_something1(datafiles):
+    """Work with copy of all files."""
+    for img in datafiles.iterdir():
+        print(img)
+        # assert process(img) == some_expected_value
+
+    assert len(list(datafiles.iterdir())) == 8
+
+    # we can do something destructive
+    (datafiles / 'img3.jpg').unlink()
+    assert len(list(datafiles.iterdir())) == 7
+
+
+@ALL_IMGS
+def test_something2(datafiles):
+    """Work with copy of all files."""
+    for img in datafiles.iterdir():
+        print(img)
+        # assert process(img) == some_expected_value
+
+    assert len(list(datafiles.iterdir())) == 8
+
+    # we can do something destructive
+    (datafiles / 'img1.jpg').unlink()
+    assert len(list(datafiles.iterdir())) == 7
diff --git a/examples/example_4.py b/examples/example_4.py
new file mode 100644
index 0000000..52085c9
--- /dev/null
+++ b/examples/example_4.py
@@ -0,0 +1,78 @@
+"""Example: files with same names.
+
+Imagine you have 3 directories (*dir1*, *dir2*, *dir3*) each containing
+the files (*fileA* and *fileB*).
+
+This example clarifies the options **on_duplicate** and **keep_top_dir**.
+"""
+from pathlib import Path
+
+import pytest
+
+FIXTURE_DIR = Path(__file__).parent.resolve() / '_fixture_files'
+
+
+@pytest.mark.datafiles(
+    FIXTURE_DIR / 'dir1',
+    FIXTURE_DIR / 'dir2',
+    FIXTURE_DIR / 'dir3',
+    on_duplicate='ignore',
+)
+def test_dir_ignore(datafiles):
+    """Use files from dir1 (first dir added)."""
+    assert len(list(datafiles.iterdir())) == 2
+    assert (datafiles / 'fileA').exists()
+    assert (datafiles / 'fileA').read_text() == '1a\n'
+
+
+@pytest.mark.datafiles(
+    FIXTURE_DIR / 'dir2',
+    FIXTURE_DIR / 'dir1',
+    FIXTURE_DIR / 'dir3',
+    on_duplicate='ignore',
+)
+def test_dir_ignore2(datafiles):
+    """Use files from dir2 (first dir added)."""
+    assert len(list(datafiles.iterdir())) == 2
+    assert (datafiles / 'fileA').exists()
+    assert (datafiles / 'fileA').read_text() == '2a\n'
+
+
+@pytest.mark.datafiles(
+    FIXTURE_DIR / 'dir1',
+    FIXTURE_DIR / 'dir2',
+    FIXTURE_DIR / 'dir3',
+    on_duplicate='overwrite',
+)
+def test_dir_overwrite(datafiles):
+    """Use files from dir3 (last dir added)."""
+    assert len(list(datafiles.iterdir())) == 2
+    assert (datafiles / 'fileA').exists()
+    assert (datafiles / 'fileA').read_text() == '3a\n'
+
+
+@pytest.mark.datafiles(
+    FIXTURE_DIR / 'dir1',
+    FIXTURE_DIR / 'dir2',
+    FIXTURE_DIR / 'dir3',
+    # on_duplicate='exception' is the default
+)
+def test_dir_exception(datafiles):  # pylint: disable=W0613
+    """Raise exception because of duplicate filename fileA."""
+    assert False
+
+
+@pytest.mark.datafiles(
+    FIXTURE_DIR / 'dir1',
+    FIXTURE_DIR / 'dir2',
+    FIXTURE_DIR / 'dir3',
+    keep_top_dir=True,
+)
+def test_dir_keep_top_dir(datafiles):
+    """Use all files."""
+    # 3 subdirs
+    assert len(list(datafiles.iterdir())) == 3
+    # 3 subdirs with each 2 files: 3 + 3*2
+    assert len(list(datafiles.glob('**/*'))) == 9
+    assert (datafiles / 'dir3' / 'fileA').exists()
+    assert (datafiles / 'dir3' / 'fileA').read_text() == '3a\n'
diff --git a/examples/example_5.py b/examples/example_5.py
new file mode 100644
index 0000000..0e030a3
--- /dev/null
+++ b/examples/example_5.py
@@ -0,0 +1,18 @@
+"""Example: use `str` paths."""
+import os
+import pytest
+
+FIXTURE_DIR = os.path.join(
+    os.path.dirname(os.path.realpath(__file__)),
+    'test_files'
+)
+
+
+@pytest.mark.datafiles(
+    os.path.join(FIXTURE_DIR, 'img1.jpg'),
+    os.path.join(FIXTURE_DIR, 'img2.jpg'),
+    os.path.join(FIXTURE_DIR, 'img3.jpg'),
+)
+def test_str(datafiles):
+    """Work with `str`."""
+    assert len(list(datafiles.iterdir())) == 3
diff --git a/examples/example_upgradev3.py b/examples/example_upgradev3.py
new file mode 100644
index 0000000..9e8d093
--- /dev/null
+++ b/examples/example_upgradev3.py
@@ -0,0 +1,63 @@
+"""Upgrade to v3."""
+import os
+from pathlib import Path
+
+import pytest
+import py
+
+FIXTURE_DIR = os.path.join(
+    os.path.dirname(os.path.realpath(__file__)),
+    'test_files',
+)
+
+FIXTURE_DIR_NEW_STYLE = Path(__file__).parent.resolve() / 'test_files'
+
+
+@pytest.mark.datafiles(os.path.join(FIXTURE_DIR, 'img1.jpg'))
+def test_convert_to_pypath(datafiles):
+    """
+    If your tests are taking advantage of py.path the simplest change
+    you can make is at the top of each test convert the pathlib.Path
+    object back to a py.path .
+
+    Note that it would be better to migrate to pathlib.Path, as shown in
+    the next example, because py.path is deprecated.
+    """
+
+    # changing the type back to py.path as a quick fix
+    datafiles = py.path.local(str(datafiles))
+
+    # code remains as before
+    assert len(datafiles.listdir()) == 1
+    assert (datafiles / 'img1.jpg').check(file=1)
+
+
+@pytest.mark.datafiles(FIXTURE_DIR_NEW_STYLE / 'img1.jpg')
+def test_update_to_pathlib(datafiles):
+    """Upgrade code to use `pathlib`.
+
+    Some examples showing `pathlib` style.
+    """
+
+    # For demo purpose: this is the old-style
+    pypath_datafiles = py.path.local(str(datafiles))
+
+    # old style
+    assert len(pypath_datafiles.listdir()) == 1
+    assert (pypath_datafiles / 'img1.jpg').check(file=1)
+
+    # new style  # pylint: disable=R0801
+    assert len(list(datafiles.iterdir())) == 1
+    assert (datafiles / 'img1.jpg').is_file()
+
+
+@pytest.mark.datafiles(os.path.join(FIXTURE_DIR, 'img1.jpg'))
+def test_keep_using_str(datafiles):
+    """Keep using plain `os`.
+
+    Nothing changes.
+    """
+
+    path = str(datafiles)
+    assert len(os.listdir(path)) == 1
+    assert os.path.isfile(os.path.join(path, 'img1.jpg'))
diff --git a/setup.py b/setup.py
index 1a9229d..af8d395 100755
--- a/setup.py
+++ b/setup.py
@@ -12,17 +12,15 @@ def _read(fname):
 
 
 DEPENDENCIES = [
-    'py',
     'pytest>=3.6',
     ]
 
-DESCRIPTION = ("py.test plugin to create a 'tmpdir' containing predefined "
-	       "files/directories.")
+DESCRIPTION = "py.test plugin to create a 'tmp_path' containing predefined files/directories."
 LONG_DESCRIPTION = _read('README.rst') + '\n\n' + _read('CHANGELOG.rst')
 
 setup(
     name='pytest-datafiles',
-    version='2.0.1',
+    version='3.0.0',
     py_modules=['pytest_datafiles'],
     url='https://github.com/omarkohl/pytest-datafiles',
     license='MIT',
@@ -36,21 +34,21 @@ setup(
     long_description_content_type='text/x-rst',
     entry_points={
         'pytest11': ['pytest_datafiles = pytest_datafiles'],
-	},
-    keywords='pytest datafiles tmpdir',
+    },
+    keywords='pytest datafiles tmp_path',
     classifiers=[
         'Development Status :: 5 - Production/Stable',
         'Framework :: Pytest',
         'Intended Audience :: Developers',
         'License :: OSI Approved :: MIT License',
         'Operating System :: OS Independent',
-        'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.4',
-        'Programming Language :: Python :: 3.5',
         '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',
         'Topic :: Software Development :: Testing',
         ]
     )
diff --git a/tox.ini b/tox.ini
index e9cd71f..c5ea0f5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,9 +1,17 @@
 [tox]
-envlist = py27, py34, py35, py36, py37, py38
+envlist = py36, py37, py38, py39, py310, py311
 
 [testenv]
-commands = py.test tests/test_pytest_datafiles.py
-deps = pytest
+deps =
+    pytest
+commands =
+    pytest tests/test_pytest_datafiles.py
+
+[testenv:examples]
+deps =
+    pytest
+commands =
+    pytest tests/test_examples.py
 
 [testenv:coverage]
 usedevelop = True
@@ -20,5 +28,5 @@ deps =
     pylint
     flake8
 commands =
-    flake8 --max-complexity=10 pytest_datafiles.py tests/test_pytest_datafiles.py
-    pylint pytest_datafiles.py tests/test_pytest_datafiles.py
+    flake8 --max-complexity=10 pytest_datafiles.py tests/ examples/
+    pylint pytest_datafiles.py tests/ examples/


Test patch:
diff --git a/examples/test_files/img1.jpg b/examples/test_files/img1.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/examples/test_files/img2.jpg b/examples/test_files/img2.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/examples/test_files/img3.jpg b/examples/test_files/img3.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/examples/test_files/img4.jpg b/examples/test_files/img4.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/examples/test_files/img5.jpg b/examples/test_files/img5.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/examples/test_files/img6.jpg b/examples/test_files/img6.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/examples/test_files/img7.jpg b/examples/test_files/img7.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/examples/test_files/img8.jpg b/examples/test_files/img8.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/pytest_datafiles.py b/pytest_datafiles.py
index 7e3a008..331c6ba 100644
--- a/pytest_datafiles.py
+++ b/pytest_datafiles.py
@@ -1,64 +1,50 @@
 """
 Module containing a 'datafiles' fixture for pytest Tests.
 """
+import shutil
+from pathlib import Path
+from typing import List
 
 import pytest
-from py import path  # pylint: disable=E0611
 
 from _pytest.config import Config
 
 
 def pytest_configure(config: Config) -> None:
+    """Perform initial configuration."""
     config.addinivalue_line(
         "markers",
-        "datafiles(path, ..., *, keep_top_dir=False, on_duplicate='exception'): "
-        "Paths to copy to tmpdir before the test. 'keep_top_dir': For all "
-        "parameters that represent directories, keep that directory instead of "
-        "only (recursively) copying its content (default is False). Use the "
-        "option 'on_duplicate' to specify the action to take when duplicate "
-        "files/directories are found. Possible values are: exception, ignore and "
-        "replace. The default value is exception.",
+        "datafiles(path, ..., *, keep_top_dir=False, "
+        "on_duplicate='exception'): Paths to copy to tmpdir before the test. "
+        "'keep_top_dir': For all parameters that represent directories, keep "
+        "that directory instead of only (recursively) copying its content "
+        "(default is False). Use the option 'on_duplicate' to specify the "
+        "action to take when duplicate files/directories are found. Possible "
+        "values are: exception, ignore and replace. The default value is "
+        "exception.",
     )
 
 
-def _copy(src, target):
-    """
-    Copies a single entry (file, dir) named 'src' to 'target'. Softlinks are
-    processed properly as well.
-    """
-    if not src.exists():
-        raise ValueError("'%s' does not exist!" % src)
-
-    if src.isdir():
-        src.copy(target / src.basename)
-    elif src.islink():
-        (target / src.basename).mksymlinkto(src.realpath())
-    else:  # file
-        src.copy(target)
-
-
-def _copy_all(entry_list, target_dir, on_duplicate):
+def _copy_all(entry_list: List[Path], target_dir: Path, on_duplicate: str):
     """
     Copies all entries (files, dirs) from 'entry_list' to 'target_dir' taking
     into account the 'on_duplicate' option (which defines what should happen if
     an entry already exists: raise an exception, overwrite it or ignore it).
     """
     for entry in entry_list:
-        target_entry = target_dir / entry.basename
+        target_entry = target_dir / entry.name
         if not target_entry.exists() or on_duplicate == 'overwrite':
-            _copy(entry, target_dir)
+            if entry.is_file():
+                shutil.copy(entry, target_entry, follow_symlinks=False)
+            else:
+                shutil.copytree(entry, target_entry, symlinks=True)
         elif on_duplicate == 'exception':
-            raise ValueError(
-                "'%s' already exists (src %s)" % (
-                    target_entry,
-                    entry,
-                    )
-                )
+            raise ValueError(f"'{target_entry}' already exists (src {entry})")
         else:  # ignore
             continue
 
 
-def _get_all_entries(entry_list, keep_top_dir):
+def _get_all_entries(entry_list: List[str], keep_top_dir: bool) -> List[Path]:
     """
     Returns a list of all entries (files, directories) that should be copied.
     The main purpose of this function is to evaluate 'keep_top_dir' and in case
@@ -66,23 +52,23 @@ def _get_all_entries(entry_list, keep_top_dir):
     """
     all_files = []
 
-    entry_list = [path.local(entry) for entry in entry_list]
+    entry_list = [Path(entry) for entry in entry_list]
 
     if keep_top_dir:
-        all_files = entry_list
-    else:
-        for entry in entry_list:
-            if entry.isdir():
-                all_files.extend(entry.listdir())
-            else:
-                all_files.append(entry)
+        return entry_list
+
+    for entry in entry_list:
+        if entry.is_dir():
+            all_files.extend(entry.iterdir())
+        else:
+            all_files.append(entry)
     return all_files
 
 
 @pytest.fixture
-def datafiles(request, tmpdir):
+def datafiles(request, tmp_path: Path) -> Path:
     """
-    pytest fixture to define a 'tmpdir' containing files or directories
+    pytest fixture to define a 'tmp_path' containing files or directories
     specified with a 'datafiles' mark.
     """
     entry_list = []
@@ -100,9 +86,9 @@ def datafiles(request, tmpdir):
     if keep_top_dir not in (True, False):
         raise ValueError("'keep_top_dir' must be True or False")
     if on_duplicate not in ('exception', 'ignore', 'overwrite'):
-        raise ValueError("'on_duplicate' must be 'exception', 'ignore' or "
-                         "'overwrite'")
+        raise ValueError(f"'on_duplicate' must be 'exception', 'ignore' or "
+                         f"'overwrite', got '{on_duplicate}'")
 
     all_entries = _get_all_entries(entry_list, keep_top_dir)
-    _copy_all(all_entries, tmpdir, on_duplicate)
-    return tmpdir
+    _copy_all(all_entries, tmp_path, on_duplicate)
+    return tmp_path
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..810b03b
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,3 @@
+# pylint: skip-file
+
+pytest_plugins = ['pytester']
diff --git a/tests/test_examples.py b/tests/test_examples.py
new file mode 100644
index 0000000..165324b
--- /dev/null
+++ b/tests/test_examples.py
@@ -0,0 +1,71 @@
+"""
+Tests for the examples.
+"""
+import shutil
+from pathlib import Path
+
+import pytest
+from _pytest.config import ExitCode
+from _pytest.pytester import Pytester
+
+EXAMPLE_DIR = Path(__file__).parent.parent.resolve() / 'examples'
+
+
+@pytest.fixture
+def example_files(pytester: Pytester):
+    """Get all examples and dependencies into the testing env."""
+    shutil.copytree(EXAMPLE_DIR, pytester.path, dirs_exist_ok=True)
+    return pytester
+
+
+@pytest.mark.parametrize("example,expected", [
+    ('example_1', ExitCode.TESTS_FAILED),
+    ('example_2', ExitCode.OK),
+    ('example_3', ExitCode.OK),
+    ('example_4', ExitCode.TESTS_FAILED),
+    ('example_5', ExitCode.OK),
+    ('example_upgradev3', ExitCode.OK),
+])
+def test_examples(example_files, example, expected):  # pylint: disable=W0621
+    """
+    Smoke test all provided examples.
+
+    For those with expected failures, additional tests have been
+    created to validate the correctness of the failure.
+    """
+    result = example_files.runpytest(f'{example}.py')
+    assert result.ret == expected
+
+
+def test_example_1_ok(example_files):  # pylint: disable=W0621
+    """
+    Example 1 is referring to a file in '/opt'.
+
+    For the smoke test, just use a local existing file.
+    """
+    target_file = example_files.path / 'film1.mp4'
+    target_file.write_text('dummy')
+
+    example = (example_files.path / 'example_1.py').read_text().replace(
+        '/opt/big_files/film1.mp4', str(target_file)
+    )
+
+    new_example = example_files.makepyfile(example)
+    result = example_files.runpytest(new_example)
+    assert result.ret == ExitCode.OK
+
+
+def test_example_4_ok(example_files):  # pylint: disable=W0621
+    """
+    Example 4 contains an expected failure during test setup, that cannot
+    be captured in a nice way without disturbing the example.
+    """
+    result = example_files.runpytest('example_4.py')
+
+    assert result.ret == ExitCode.TESTS_FAILED
+    result.assert_outcomes(passed=4, errors=1)
+
+    result.stdout.fnmatch_lines([
+        '*ERROR at setup of test_dir_exception*',
+        'E*ValueError: *already exists*',
+    ])
diff --git a/tests/test_pytest_datafiles.py b/tests/test_pytest_datafiles.py
index 4af5f15..2618cc8 100644
--- a/tests/test_pytest_datafiles.py
+++ b/tests/test_pytest_datafiles.py
@@ -1,19 +1,19 @@
 """
 Tests for the pytest-datafiles pytest plugin
 """
-
 import os
-from py import path  # pylint: disable=E0611
+from pathlib import Path
+
 import pytest
 
 
 pytest_plugins = 'pytester'  # pylint: disable=C0103
 
 
-TEST_DIR = os.path.dirname(os.path.realpath(__file__))
-FIXTURE_DIR = os.path.join(TEST_DIR, '_fixture_files')
+TEST_DIR = Path(__file__).parent.resolve()
+FIXTURE_DIR = TEST_DIR / '_fixture_files'
 FIXTURE_FILES = [
-    os.path.join(FIXTURE_DIR, name)
+    FIXTURE_DIR / name
     for name in [
         'huckleberry.txt',
         'random.bin',
@@ -22,50 +22,44 @@ FIXTURE_FILES = [
     ]
 
 
-@pytest.mark.datafiles(
-    path.local(
-        FIXTURE_FILES[0],  # huckleberry.txt
-        )
-    )
-def test_single_file_pypath(datafiles):
+@pytest.mark.datafiles(FIXTURE_FILES[0])  # huckleberry.txt
+def test_single_file_path(datafiles):
     """
-    Verify that a single file (py.path.local) is copied correctly
+    Verify that a single file (pathlib.Path) is copied correctly
     """
-    assert (datafiles / 'huckleberry.txt').check(file=1)
+    assert (datafiles / 'huckleberry.txt').is_file()
     assert 'Mark Twain' in (datafiles / 'huckleberry.txt').read_text('utf-8')
 
 
-@pytest.mark.datafiles(FIXTURE_FILES[0])  # huckleberry.txt
+@pytest.mark.datafiles(str(FIXTURE_FILES[0]))  # huckleberry.txt
 def test_single_file_str(datafiles):
     """
     Verify that a single file (str) is copied correctly
     """
-    assert (datafiles / 'huckleberry.txt').check(file=1)
+    assert (datafiles / 'huckleberry.txt').is_file()
     assert 'Mark Twain' in (datafiles / 'huckleberry.txt').read_text('utf-8')
 
 
-@pytest.mark.datafiles(
-    *[path.local(p) for p in FIXTURE_FILES]
-    )
-def test_multiple_files_pypath(datafiles):
+@pytest.mark.datafiles(*FIXTURE_FILES)
+def test_multiple_files_path(datafiles):
     """
-    Verify multiple files (py.path.local) are copied correctly
+    Verify multiple files (pathlib.Path) are copied correctly
     """
-    assert (datafiles / 'huckleberry.txt').check(file=1)
-    assert (datafiles / 'random.bin').check(file=1)
-    assert (datafiles / 'sparrow.jpg').check(file=1)
-    assert len((datafiles).listdir()) == 3
+    assert (datafiles / 'huckleberry.txt').is_file()
+    assert (datafiles / 'random.bin').is_file()
+    assert (datafiles / 'sparrow.jpg').is_file()
+    assert len(list(datafiles.iterdir())) == 3
 
 
-@pytest.mark.datafiles(*FIXTURE_FILES)
+@pytest.mark.datafiles(*[str(f) for f in FIXTURE_FILES])
 def test_multiple_files_str(datafiles):
     """
     Verify multiple files (str) are copied correctly
     """
-    assert (datafiles / 'huckleberry.txt').check(file=1)
-    assert (datafiles / 'random.bin').check(file=1)
-    assert (datafiles / 'sparrow.jpg').check(file=1)
-    assert len((datafiles).listdir()) == 3
+    assert (datafiles / 'huckleberry.txt').is_file()
+    assert (datafiles / 'random.bin').is_file()
+    assert (datafiles / 'sparrow.jpg').is_file()
+    assert len(list(datafiles.iterdir())) == 3
 
 
 @pytest.mark.datafiles(FIXTURE_FILES[0])
@@ -75,10 +69,10 @@ def test_multiple_marks(datafiles):
     """
     Verify multiple marks are combined
     """
-    assert (datafiles / 'huckleberry.txt').check(file=1)
-    assert (datafiles / 'random.bin').check(file=1)
-    assert (datafiles / 'sparrow.jpg').check(file=1)
-    assert len((datafiles).listdir()) == 3
+    assert (datafiles / 'huckleberry.txt').is_file()
+    assert (datafiles / 'random.bin').is_file()
+    assert (datafiles / 'sparrow.jpg').is_file()
+    assert len(list(datafiles.iterdir())) == 3
 
 
 @pytest.mark.datafiles
@@ -86,32 +80,32 @@ def test_no_files1(datafiles):
     """
     Verify if datafiles marker is set but empty the directory is empty
     """
-    assert not datafiles.listdir()
+    assert not list(datafiles.iterdir())
 
 
 def test_no_files2(datafiles):
     """
     Verify if datafiles marker is not set the directory is empty
     """
-    assert not datafiles.listdir()
+    assert not list(datafiles.iterdir())
 
 
-@pytest.mark.datafiles(os.path.join(FIXTURE_DIR, 'dir1'))
+@pytest.mark.datafiles(os.path.join(str(FIXTURE_DIR), 'dir1'))
 def test_single_dir_str(datafiles):
     """
     Verify that a single directory (specified as a string) is copied correctly.
 
     The content of the directory is copied.
     """
-    assert len(datafiles.listdir()) == 3
-    assert (datafiles / 'file1').check(file=1)
-    assert (datafiles / 'file2').check(file=1)
-    assert (datafiles / 'file3').check(file=1)
+    assert len(list(datafiles.iterdir())) == 3
+    assert (datafiles / 'file1').is_file()
+    assert (datafiles / 'file2').is_file()
+    assert (datafiles / 'file3').is_file()
 
 
 @pytest.mark.datafiles(
-    os.path.join(FIXTURE_DIR, 'dir1'),
-    os.path.join(FIXTURE_DIR, 'dir2'),
+    os.path.join(str(FIXTURE_DIR), 'dir1'),
+    os.path.join(str(FIXTURE_DIR), 'dir2'),
     )
 def test_multi_dir_str(datafiles):
     """
@@ -119,58 +113,58 @@ def test_multi_dir_str(datafiles):
 
     The content of the directories is copied.
     """
-    assert len(datafiles.listdir()) == 6
+    assert len(list(datafiles.iterdir())) == 6
     # files from dir1
-    assert (datafiles / 'file1').check(file=1)
-    assert (datafiles / 'file2').check(file=1)
-    assert (datafiles / 'file3').check(file=1)
+    assert (datafiles / 'file1').is_file()
+    assert (datafiles / 'file2').is_file()
+    assert (datafiles / 'file3').is_file()
     # files from dir2
-    assert (datafiles / 'file4').check(file=1)
-    assert (datafiles / 'file5').check(file=1)
-    assert (datafiles / 'file6').check(file=1)
+    assert (datafiles / 'file4').is_file()
+    assert (datafiles / 'file5').is_file()
+    assert (datafiles / 'file6').is_file()
 
 
 @pytest.mark.datafiles(
-    path.local(FIXTURE_DIR) / 'dir1',
-    path.local(FIXTURE_FILES[0]),  # huckleberry.txt
-    path.local(FIXTURE_DIR) / 'dir4',
-    path.local(FIXTURE_FILES[1]),  # random.bin
+    FIXTURE_DIR / 'dir1',
+    FIXTURE_FILES[0],  # huckleberry.txt
+    FIXTURE_DIR / 'dir4',
+    FIXTURE_FILES[1],  # random.bin
     )
 def test_multiple(datafiles):
     """
     Verify multiple files and directories (with subdirectories) are copied
     """
-    assert len(datafiles.listdir()) == 7
-    assert (datafiles / 'huckleberry.txt').check(file=1)
-    assert (datafiles / 'random.bin').check(file=1)
-    assert (datafiles / 'file1').check(file=1)
-    assert (datafiles / 'file2').check(file=1)
-    assert (datafiles / 'file3').check(file=1)
-    assert (datafiles / 'subdir1').check(dir=1)
-    assert (datafiles / 'subdir2').check(dir=1)
-    assert (datafiles / 'subdir1' / 'file1').check(file=1)
-    assert (datafiles / 'subdir1' / 'file1').check(file=1)
-    assert (datafiles / 'subdir2' / 'file2').check(file=1)
-    assert (datafiles / 'subdir2' / 'file2').check(file=1)
+    assert len(list(datafiles.iterdir())) == 7
+    assert (datafiles / 'huckleberry.txt').is_file()
+    assert (datafiles / 'random.bin').is_file()
+    assert (datafiles / 'file1').is_file()
+    assert (datafiles / 'file2').is_file()
+    assert (datafiles / 'file3').is_file()
+    assert (datafiles / 'subdir1').is_dir()
+    assert (datafiles / 'subdir2').is_dir()
+    assert (datafiles / 'subdir1' / 'file1').is_file()
+    assert (datafiles / 'subdir1' / 'file1').is_file()
+    assert (datafiles / 'subdir2' / 'file2').is_file()
+    assert (datafiles / 'subdir2' / 'file2').is_file()
 
 
 @pytest.mark.datafiles(
-    path.local(FIXTURE_DIR) / 'dir1',
-    path.local(FIXTURE_DIR) / 'dir2',
+    FIXTURE_DIR / 'dir1',
+    FIXTURE_DIR / 'dir2',
     keep_top_dir=True,
     )
 def test_keep_top_dir(datafiles):
     """
     Verify top level directory is kept (instead of only copying the content)
     """
-    assert len(datafiles.listdir()) == 2
-    assert (datafiles / 'dir1').check(dir=1)
+    assert len(list(datafiles.iterdir())) == 2
+    assert (datafiles / 'dir1').is_dir()
 
 
 @pytest.mark.datafiles(
-    path.local(FIXTURE_DIR) / 'dir1',
-    path.local(FIXTURE_DIR) / 'dir2',
-    path.local(FIXTURE_DIR) / 'dir3',
+    FIXTURE_DIR / 'dir1',
+    FIXTURE_DIR / 'dir2',
+    FIXTURE_DIR / 'dir3',
     on_duplicate='ignore',
     )
 def test_on_duplicate_ignore_dir_with_file(datafiles):
@@ -178,18 +172,18 @@ def test_on_duplicate_ignore_dir_with_file(datafiles):
     Verify duplicate files are ignored (i.e. the first one is kept)
 
     If duplicate files appear (to be copied) then the duplicates are ignored
-    and the first occurence is kept. In this example the file 'file1' appears
+    and the first occurrence is kept. In this example the file 'file1' appears
     both in dir1 and dir3 and 'file4' appears both in dir2 and dir3.
     """
-    assert len(datafiles.listdir()) == 6
-    assert (datafiles / 'file1').read() == "dir1\n123\n"
-    assert (datafiles / 'file4').read() == "dir2\n101112\n"
+    assert len(list(datafiles.iterdir())) == 6
+    assert (datafiles / 'file1').read_text() == "dir1\n123\n"
+    assert (datafiles / 'file4').read_text() == "dir2\n101112\n"
 
 
 @pytest.mark.datafiles(
-    path.local(FIXTURE_DIR) / 'dir1',
-    path.local(FIXTURE_DIR) / 'dir2',
-    path.local(FIXTURE_DIR) / 'dir3',
+    FIXTURE_DIR / 'dir1',
+    FIXTURE_DIR / 'dir2',
+    FIXTURE_DIR / 'dir3',
     on_duplicate='overwrite',
     )
 def test_on_duplicate_overwrite(datafiles):
@@ -200,9 +194,9 @@ def test_on_duplicate_overwrite(datafiles):
     overwritten and the first last is kept. In this example the file 'file1'
     appears both in dir1 and dir3 and 'file4' appears both in dir2 and dir3.
     """
-    assert len(datafiles.listdir()) == 6
-    assert (datafiles / 'file1').read() == "dir3\n123\n"
-    assert (datafiles / 'file4').read() == "dir3\n101112\n"
+    assert len(list(datafiles.iterdir())) == 6
+    assert (datafiles / 'file1').read_text() == "dir3\n123\n"
+    assert (datafiles / 'file4').read_text() == "dir3\n101112\n"
 
 
 def test_on_duplicate_exception(testdir):
@@ -214,19 +208,19 @@ def test_on_duplicate_exception(testdir):
     If duplicate files appear (to be copied) then a ValueError is raised. In
     this example the file 'file1' appears both in dir1 and dir3.
     """
-    testdir.makepyfile('''
+    testdir.makepyfile(f'''
         import pytest
-        from py import path
+        from pathlib import Path
 
-        FIXTURE_DIR = '{0}'
+        FIXTURE_DIR = Path('{FIXTURE_DIR}')
 
         @pytest.mark.datafiles(
-            path.local(FIXTURE_DIR) / 'dir1',
-            path.local(FIXTURE_DIR) / 'dir3',
+            FIXTURE_DIR / 'dir1',
+            FIXTURE_DIR / 'dir3',
             )
         def test_ode(datafiles):
-            assert len(datafiles.listdir()) == 6
-    '''.format(FIXTURE_DIR))
+            assert len(list(datafiles.iterdir())) == 6
+    ''')
     result = testdir.runpytest('-s')
     result.stdout.fnmatch_lines(["E*ValueError:*file1'*already exists*"])
 
@@ -235,19 +229,19 @@ def test_on_duplicate_exception2(testdir):
     """
     Verify that a ValueError is raised when duplicate files appear
     """
-    testdir.makepyfile('''
+    testdir.makepyfile(f'''
         import pytest
-        from py import path
+        from pathlib import Path
 
-        FIXTURE_DIR = '{0}'
+        FIXTURE_DIR = Path('{FIXTURE_DIR}')
 
         @pytest.mark.datafiles(
-            path.local(FIXTURE_DIR) / 'dir1' / 'file1',
-            path.local(FIXTURE_DIR) / 'dir3' / 'file1',
+            FIXTURE_DIR / 'dir1' / 'file1',
+            FIXTURE_DIR / 'dir3' / 'file1',
             )
         def test_ode(datafiles):
-            assert len(datafiles.listdir()) == 2
-    '''.format(FIXTURE_DIR))
+            assert len(list(datafiles.iterdir())) == 2
+    ''')
     result = testdir.runpytest('-s')
     result.stdout.fnmatch_lines(["E*ValueError:*file1'*already exists*"])
 
@@ -261,27 +255,27 @@ def test_on_duplicate_exception_dir(testdir):
     If duplicate files appear (to be copied) then a ValueError is raised. In
     this example the directory 'subdir1' appears twice.
     """
-    testdir.makepyfile('''
+    testdir.makepyfile(f'''
         import pytest
-        from py import path
+        from pathlib import Path
 
-        FIXTURE_DIR = '{0}'
+        FIXTURE_DIR = Path('{FIXTURE_DIR}')
 
         @pytest.mark.datafiles(
-            path.local(FIXTURE_DIR) / 'dir4' / 'subdir1',
-            path.local(FIXTURE_DIR) / 'dir5' / 'subdir1',
+            FIXTURE_DIR / 'dir4' / 'subdir1',
+            FIXTURE_DIR / 'dir5' / 'subdir1',
             keep_top_dir=True,
             )
         def test_duplicate_dir(datafiles):
             assert True
-    '''.format(FIXTURE_DIR))
+    ''')
     result = testdir.runpytest('-s')
     result.stdout.fnmatch_lines(["E*ValueError:*subdir1'*already exists*"])
 
 
 @pytest.mark.datafiles(
-    path.local(FIXTURE_DIR) / 'dir4' / 'subdir1',
-    path.local(FIXTURE_DIR) / 'dir5' / 'subdir1',
+    FIXTURE_DIR / 'dir4' / 'subdir1',
+    FIXTURE_DIR / 'dir5' / 'subdir1',
     keep_top_dir=True,
     on_duplicate='ignore',
     )
@@ -289,41 +283,41 @@ def test_on_duplicate_ignore_dir(datafiles):
     """
     Verify that the second (duplicate) directory is ignored.
     """
-    assert (datafiles / 'subdir1' / 'file1').check(file=1)
+    assert (datafiles / 'subdir1' / 'file1').is_file()
 
 
 @pytest.mark.datafiles(
-    path.local(FIXTURE_DIR) / 'dir1' / 'file1',
-    path.local(FIXTURE_DIR) / 'dir3' / 'file1',
+    FIXTURE_DIR / 'dir1' / 'file1',
+    FIXTURE_DIR / 'dir3' / 'file1',
     on_duplicate='ignore',
     )
 def test_on_duplicate_ignore_file(datafiles):
     """
     Verify on_duplicate=ignore causes the first file to be used
     """
-    assert (datafiles / 'file1').check(file=1)
-    assert (datafiles / 'file1').read() == "dir1\n123\n"
+    assert (datafiles / 'file1').is_file()
+    assert (datafiles / 'file1').read_text() == "dir1\n123\n"
 
 
 def test_non_existing_file(testdir):
     """
     Verify exception is raised if file doesn't exist.
     """
-    testdir.makepyfile('''
+    testdir.makepyfile(f'''
         import pytest
-        from py import path
+        from pathlib import Path
 
-        FIXTURE_DIR = '{0}'
+        FIXTURE_DIR = Path('{FIXTURE_DIR}')
 
         @pytest.mark.datafiles(
-            path.local(FIXTURE_DIR) / 'fileZZ',
+            FIXTURE_DIR / 'fileZZ',
             )
         def test_ode(datafiles):
-            assert len(datafiles.listdir()) == 1
-    '''.format(FIXTURE_DIR))
+            assert len(list(datafiles.iterdir())) == 1
+    ''')
     result = testdir.runpytest('-s')
     result.stdout.fnmatch_lines([
-        "E*ValueError:*fileZZ'*does not exist!*",
+        "E*FileNotFoundError:*fileZZ'",
         ])
 
 
@@ -331,74 +325,74 @@ def test_invalid_keep_top_dir(testdir):
     """
     Verify ValueError is raised if parameter isn't boolean
     """
-    testdir.makepyfile('''
+    testdir.makepyfile(f'''
         import pytest
-        from py import path
+        from pathlib import Path
 
-        FIXTURE_DIR = '{0}'
+        FIXTURE_DIR = Path('{FIXTURE_DIR}')
 
         @pytest.mark.datafiles(
-            path.local(FIXTURE_DIR) / 'fileZZ',
+            FIXTURE_DIR / 'fileZZ',
             keep_top_dir='invalid-value',
             )
         def test_invalid_param(datafiles):
             assert True
-    '''.format(FIXTURE_DIR))
+    ''')
     result = testdir.runpytest('-s')
     result.stdout.fnmatch_lines([
         "E*ValueError: 'keep_top_dir' must be True or False*",
-        ])
+    ])
 
 
 def test_invalid_on_duplicate(testdir):
     """
     Verify ValueError is raised if parameter isn't boolean
     """
-    testdir.makepyfile('''
+    testdir.makepyfile(f'''
         import pytest
-        from py import path
+        from pathlib import Path
 
-        FIXTURE_DIR = '{0}'
+        FIXTURE_DIR = Path('{FIXTURE_DIR}')
 
         @pytest.mark.datafiles(
-            path.local(FIXTURE_DIR) / 'fileZZ',
+            FIXTURE_DIR / 'fileZZ',
             on_duplicate='invalid-value',
             )
         def test_invalid_param(datafiles):
             assert True
-    '''.format(FIXTURE_DIR))
+    ''')
     result = testdir.runpytest('-s')
     result.stdout.fnmatch_lines([
         "E*ValueError: 'on_duplicate' must be 'exception', 'ignore' or *",
         ])
 
 
-@pytest.mark.datafiles(os.path.join(FIXTURE_DIR, 'sparrow_link.jpg'))
+@pytest.mark.datafiles(FIXTURE_DIR / 'sparrow_link.jpg')
 def test_copy_symlink(datafiles):
     """
     Verify that symlinks are copied and not their targets.
     """
-    assert len(datafiles.listdir()) == 1
-    assert (datafiles / 'sparrow_link.jpg').check(link=1)
+    assert len(list(datafiles.iterdir())) == 1
+    assert (datafiles / 'sparrow_link.jpg').is_symlink()
 
 
-@pytest.mark.datafiles(os.path.join(FIXTURE_DIR, 'dir6'))
+@pytest.mark.datafiles(FIXTURE_DIR / 'dir6')
 def test_copy_symlink_in_dir(datafiles):
     """
     Verify that symlinks are copied and not their targets.
     """
-    assert len(datafiles.listdir()) == 1
-    assert (datafiles / 'sparrow_link.jpg').check(link=1)
+    assert len(list(datafiles.iterdir())) == 1
+    assert (datafiles / 'sparrow_link.jpg').is_symlink()
 
 
 @pytest.mark.datafiles(
-    os.path.join(FIXTURE_DIR, 'dir6'),
+    FIXTURE_DIR / 'dir6',
     keep_top_dir=True,
     )
 def test_copy_symlink_in_dir2(datafiles):
     """
     Verify that symlinks are copied and not their targets.
     """
-    assert len(datafiles.listdir()) == 1
-    assert len((datafiles / 'dir6').listdir()) == 1
-    assert (datafiles / 'dir6' / 'sparrow_link.jpg').check(link=1)
+    assert len(list(datafiles.iterdir())) == 1
+    assert len(list((datafiles / 'dir6').iterdir())) == 1
+    assert (datafiles / 'dir6' / 'sparrow_link.jpg').is_symlink()