From 13ea02843bd5698cd598ace187e9033ff3406ea5 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@debian.org>
Date: Sun, 29 Nov 2020 21:06:58 +0100
Subject: [PATCH] New upstream version 0.7.2

---
 MANIFEST.in                  |   9 +-
 Makefile                     |  47 +++-
 NEWS                         |  38 +++
 PKG-INFO                     |  16 +-
 README.md                    |  68 +++++
 README.rst                   |  41 ---
 doc/conf.py                  |  13 +-
 doc/index.rst                |   5 +-
 doc/news.rst                 | 204 ---------------
 pyxattr.egg-info/PKG-INFO    |  16 +-
 pyxattr.egg-info/SOURCES.txt |   7 +-
 setup.py                     |  21 +-
 test/test_xattr.py           | 463 ---------------------------------
 {test => tests}/__init__.py  |   0
 tests/test_xattr.py          | 489 +++++++++++++++++++++++++++++++++++
 xattr.c                      |  97 +++----
 16 files changed, 727 insertions(+), 807 deletions(-)
 create mode 100644 README.md
 delete mode 100644 README.rst
 delete mode 100644 doc/news.rst
 delete mode 100644 test/test_xattr.py
 rename {test => tests}/__init__.py (100%)
 create mode 100644 tests/test_xattr.py

diff --git a/MANIFEST.in b/MANIFEST.in
index 6c709af..4960670 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,10 +1,11 @@
 include COPYING
 include NEWS
-include README.rst
+include README.md
 include Makefile
 include doc/conf.py
-include doc/*.rst
+include doc/index.rst
+include doc/module.rst
 include setup.cfg
-include test/test_xattr.py
-include test/__init__.py
+include tests/test_xattr.py
+include tests/__init__.py
 include xattr.c
diff --git a/Makefile b/Makefile
index 141685f..f57b8bf 100644
--- a/Makefile
+++ b/Makefile
@@ -1,19 +1,23 @@
+PYTHON        = python3
 SPHINXOPTS    = -W
-SPHINXBUILD   = sphinx-build
+SPHINXBUILD   = $(PYTHON) -m sphinx
 DOCDIR        = doc
 DOCHTML       = $(DOCDIR)/html
 DOCTREES      = $(DOCDIR)/doctrees
 ALLSPHINXOPTS = -d $(DOCTREES) $(SPHINXOPTS) $(DOCDIR)
+VERSION       = 0.7.2
+FULLVER       = pyxattr-$(VERSION)
+DISTFILE      = $(FULLVER).tar.gz
 
 MODNAME = xattr.so
-RSTFILES = doc/index.rst doc/module.rst NEWS README.rst doc/conf.py
-PYVERS = 2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7
+RSTFILES = doc/index.rst doc/module.rst doc/news.rst doc/readme.md doc/conf.py
+PYVERS = 3.4 3.5 3.6 3.7 3.8 3.9
 REPS = 5
 
 all: doc test
 
 $(MODNAME): xattr.c
-	./setup.py build_ext --inplace
+	$(PYTHON) ./setup.py build_ext --inplace
 
 $(DOCHTML)/index.html: $(MODNAME) $(RSTFILES)
 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(DOCHTML)
@@ -21,23 +25,43 @@ $(DOCHTML)/index.html: $(MODNAME) $(RSTFILES)
 
 doc: $(DOCHTML)/index.html
 
+doc/readme.md: README.md
+	ln -s ../README.md doc/readme.md
+
+doc/news.rst: NEWS
+	ln -s ../NEWS doc/news.rst
+
 dist:
-	fakeroot ./setup.py sdist
+	fakeroot $(PYTHON) ./setup.py sdist
+
+distcheck: dist
+	set -e; \
+	TDIR=$$(mktemp -d) && \
+	trap "rm -rf $$TDIR" EXIT; \
+	tar xzf dist/$(DISTFILE) -C $$TDIR && \
+	(cd $$TDIR/$(FULLVER) && make doc && make test && make dist) && \
+	echo "All good, you can upload $(DISTFILE)!"
 
 test:
 	@for ver in $(PYVERS); do \
 	  for flavour in "" "-dbg"; do \
 	    if type python$$ver$$flavour >/dev/null; then \
 	      echo Testing with python$$ver$$flavour; \
-	      python$$ver$$flavour ./setup.py test -q; \
+	      python$$ver$$flavour setup.py build_ext -i; \
+	      python$$ver$$flavour -m pytest tests; \
 	    fi; \
 	  done; \
 	done;
-	@if type pypy >/dev/null; then \
-	  echo Testing with pypy; \
-	  pypy ./setup.py test -q; \
+	@if type pypy3 >/dev/null; then \
+	  echo Testing with pypy3; \
+	  pypy3 setup.py build_ext -i; \
+	  pypy3 -m pytest tests; \
 	fi
 
+fast-test:
+	python3 setup.py build_ext -i
+	python3 -m pytest tests -v
+
 benchmark: $(MODNAME)
 	@set -e; \
 	TESTFILE=`mktemp`;\
@@ -60,13 +84,14 @@ benchmark: $(MODNAME)
 coverage:
 	$(MAKE) clean
 	$(MAKE) test CFLAGS="-coverage"
-	lcov --capture --directory . --output-file coverage.info
+	lcov --capture --directory . --no-external --output-file coverage.info
 	genhtml coverage.info --output-directory out
 
 clean:
 	rm -rf $(DOCHTML) $(DOCTREES)
+	rm -f doc/readme.md doc/news.rst
 	rm -f $(MODNAME)
 	rm -f *.so
 	rm -rf build
 
-.PHONY: doc test clean dist coverage
+.PHONY: doc test fast-test clean dist distcheck coverage
diff --git a/NEWS b/NEWS
index 6bee508..87caf52 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,44 @@
 News
 ====
 
+Version 0.7.2
+-------------
+
+*Sun, 29 Nov 2020*
+
+Minor release:
+
+* Expand testing by adding better mixed-access checks (e.g. set via
+  symlink and read on file) and by not leaking resources during tests.
+* Enable testing with Python 3.9 and confirm compatibility with it.
+* Fix documentation building with Sphinx 3.0+.
+
+Version 0.7.1
+-------------
+
+*released Tue, 26 Nov 2019*
+
+Typo fix release in the bug tracker link :/
+
+Version 0.7.0
+-------------
+
+*released Tue, 26 Nov 2019*
+
+Major change: drop compatibility with Python 2, which allows significant
+code cleanups.
+
+Other changes:
+
+* Switch internal implementation of argument parsing to a built-in one
+  (`PyUnicode_FSConverter`), which brings automatic support for
+  path-like objects in Python 3.6+ (#20), and also a more uniform
+  handling of Unicode path arguments with respect to other Python code.
+* Fix missing error check in list operations in `get_all` (#17).
+* Switch test library to pytest; not that a reasonable recent version is
+  needed. Additionally, expand test coverage, although not directly
+  visible in actual coverage reports…
+
 Version 0.6.1
 -------------
 
diff --git a/PKG-INFO b/PKG-INFO
index 525c3b3..efe5a0c 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,13 +1,25 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.2
 Name: pyxattr
-Version: 0.6.1
+Version: 0.7.2
 Summary: Filesystem extended attributes for python
 Home-page: http://pyxattr.k1024.org/
 Author: Iustin Pop
 Author-email: iustin@k1024.org
 License: LGPL
 Download-URL: http://pyxattr.k1024.org/downloads/
+Project-URL: Bug Tracker, https://github.com/iustin/pyxattr/issues
 Description: This is a C extension module for Python which
         implements extended attributes manipulation. It is a wrapper on top
         of the attr C library - see attr(5).
 Platform: Linux
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: System :: Filesystems
+Requires-Python: >=3.4
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..693be67
--- /dev/null
+++ b/README.md
@@ -0,0 +1,68 @@
+# pyxattr
+
+This is the pyxattr module, a Python extension module which gives access
+to the extended attributes for filesystem objects available in some
+operating systems.
+
+[![Travis](https://img.shields.io/travis/iustin/pyxattr)](https://travis-ci.org/iustin/pyxattr)
+[![Codecov](https://img.shields.io/codecov/c/github/iustin/pyxattr)](https://codecov.io/gh/iustin/pyxattr)
+[![Read the Docs](https://img.shields.io/readthedocs/pyxattr)](http://pyxattr.readthedocs.io/en/latest/?badge=latest)
+[![GitHub issues](https://img.shields.io/github/issues/iustin/pyxattr)](https://github.com/iustin/pyxattr/issues)
+![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/iustin/pyxattr)
+[![GitHub release (latest by date)](https://img.shields.io/github/v/release/iustin/pyxattr)](https://github.com/iustin/pyxattr/releases)
+[![PyPI](https://img.shields.io/pypi/v/pyxattr)](https://pypi.org/project/pyxattr/)
+![Debian package](https://img.shields.io/debian/v/python-pyxattr)
+![Ubuntu package](https://img.shields.io/ubuntu/v/python-pyxattr)
+![GitHub Release Date](https://img.shields.io/github/release-date/iustin/pyxattr)
+![GitHub commits since latest release](https://img.shields.io/github/commits-since/iustin/pyxattr/latest)
+![GitHub last commit](https://img.shields.io/github/last-commit/iustin/pyxattr)
+
+Downloads: go to <https://pyxattr.k1024.org/downloads/>. The source
+repository is either at <http://git.k1024.org/pyxattr.git> or at
+<https://github.com/iustin/pyxattr>.
+
+## Requirements
+
+The current supported Python versions are 3.4+ (tested up to 3.9).
+
+The library has been written and tested on Linux, kernel v2.4 or
+later, with XFS and ext2/ext3/ext3 file systems. If any other platform
+implements the same behaviour, pyxattr could be used.
+
+You need to have the setuptools tool installed in order to build and
+install the module, and for building the documentation you need to
+have Sphinx installed.
+
+Alternatively, you can install directly from pip:
+
+    $ pip install pyxattr
+
+Or from your distribution, e.g. in Debian:
+
+    $ sudo apt install python3-pyxattr
+
+## Basic example
+
+    >>> import xattr
+    >>> xattr.listxattr("file.txt")
+    ['user.mime_type']
+    >>> xattr.getxattr("file.txt", "user.mime_type")
+    'text/plain'
+    >>> xattr.setxattr("file.txt", "user.comment", "Simple text file")
+    >>> xattr.listxattr("file.txt")
+    ['user.mime_type', 'user.comment']
+    >>> xattr.removexattr ("file.txt", "user.comment")
+
+## License
+
+pyxattr is Copyright 2002-2008, 2012-2015 Iustin Pop.
+
+pyxattr is free software; you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option) any
+later version. See the COPYING file for the full license terms.
+
+Note that previous versions had different licenses: version 0.3 was licensed
+under LGPL version 3 (which, I realized later, is not compatible with GPLv2,
+hence the change to LGPL 2.1), and even older versions were licensed under GPL
+v2 or later.
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 033d597..0000000
--- a/README.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-pyxattr
-=======
-
-This is the pyxattr module, a Python extension module which gives access
-to the extended attributes for filesystem objects available in some
-operating systems.
-
-Downloads: go to https://pyxattr.k1024.org/downloads/. Latest
-version is 0.6.1. The source repository is either at
-http://git.k1024.org/pyxattr.git or at
-https://github.com/iustin/pyxattr.
-
-Requirements
-------------
-
-pyxattr has been written and tested on Linux, kernel v2.4 or later, with
-XFS filesystems; ext2/ext3 should work also. If any other platform
-implements the same behavior, pyxattr could be used.
-
-You need to have the setuptools tool installed in order to build and
-install the module.
-
-License
--------
-
-pyxattr is Copyright 2002-2008, 2012-2015 Iustin Pop.
-
-pyxattr is free software; you can redistribute it and/or modify it under the
-terms of the GNU Lesser General Public License as published by the Free
-Software Foundation; either version 2.1 of the License, or (at your option) any
-later version. See the COPYING file for the full license terms.
-
-Note that previous versions had different licenses: version 0.3 was licensed
-under LGPL version 3 (which, I realized later, is not compatible with GPLv2,
-hence the change to LGPL 2.1), and even older versions were licensed under GPL
-v2 or later.
-
-.. Local Variables:
-.. mode: rst
-.. fill-column: 72
-.. End:
diff --git a/doc/conf.py b/doc/conf.py
index a2ac56a..c48b6d7 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -25,13 +25,13 @@ sys.path.insert(0, os.path.abspath('../'))
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo']
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'recommonmark']
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
 
 # The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ['.rst', '.md']
 
 # The encoding of source files.
 #source_encoding = 'utf-8-sig'
@@ -48,9 +48,9 @@ copyright = u'2002, 2003, 2006, 2008, 2012, 2013, 2014, 2015, Iustin Pop'
 # built documents.
 #
 # The short X.Y version.
-version = '0.6.1'
+version = '0.7.2'
 # The full version, including alpha/beta/rc tags.
-release = '0.6.1'
+release = '0.7.2'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -90,6 +90,11 @@ pygments_style = 'sphinx'
 
 keep_warnings = True
 
+# Note: this is still needed in Sphinx 1.8 with recommonmark 0.4.0
+# (https://github.com/readthedocs/recommonmark/issues/119):
+source_parsers = {
+   '.md': 'recommonmark.parser.CommonMarkParser',
+}
 
 # -- Options for HTML output ---------------------------------------------------
 
diff --git a/doc/index.rst b/doc/index.rst
index 6032182..a918c2e 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -2,8 +2,8 @@
  Welcome to pyxattr's documentation!
 ======================================
 
-.. include:: ../README.rst
-   :start-line: 2
+See the :doc:`README <readme>` for start, or the detailed :doc:`module
+<module>` information.
 
 Contents
 --------
@@ -11,6 +11,7 @@ Contents
 .. toctree::
    :maxdepth: 2
 
+   readme.md
    module.rst
    news.rst
 
diff --git a/doc/news.rst b/doc/news.rst
deleted file mode 100644
index 6bee508..0000000
--- a/doc/news.rst
+++ /dev/null
@@ -1,204 +0,0 @@
-News
-====
-
-Version 0.6.1
--------------
-
-*released Tue, 24 Jul 2018*
-
-Minor bugfix, performance and compatibility release.
-
-* Minor compatibility fix: on Linux, drop the use of the `attr` library,
-  and instead switch to the glibc header `sys/xattr.h`, which is
-  provided for a really long time (since glibc 2.3). The formerly used
-  header `attr/xattr.h` has been removed from the `attr` library in
-  version 2.4.48. Fix provided by Lars Wendler, many thanks!
-* Release the GIL when performing I/O. Patch proposed by xwhuang, many
-  thanks. I tested this a long while back it seemed to impact
-  performance on local filesystems, but upon further inspection, the
-  downsides are minor (between 0 and 5%, in many cases negligible). For
-  remote or slow filesystems, this should allow much increased
-  parallelism.
-* Fix symlink set operation on MacOS X; bugfix provided by adamlin, much
-  appreciated! This also uncovered testing problems related to symlinks,
-  which are now fixed (the bug would be caught by the updated tests).
-
-Version 0.6.0
--------------
-
-*released Mon, 23 Jan 2017*
-
-Bugfix and feature release (hence the version bump).
-
-The main change is to the implementation of how attributes are listed
-and read. This was done due to existing race issues when attributes are
-modified while being read (github issue #12), but basically all various
-internal paths that dealt with retrieving an attribute value or listing
-attributes were unified in a single helper function that does handle
-such concurrent modifications. As a side effect, the size of the buffers
-used for such reads have changed, which (depending on attribute value)
-might change the trade-off between number of syscalls done and memory
-usage.
-
-As feature release, OSX support was contributed by Adam Knight
-<adam@movq.us>, thanks a lot! I don't have access to OSX so the testing
-for it is done via Travis builds; please report any issues.
-
-Version 0.5.6
--------------
-
-*released Sat, 09 Apr 2016*
-
-Small bugfix release:
-
-* Fixes some sign-compare warnings
-* Fixes potential name truncation in merge_ns()
-* Fixes building on systems which don't have ENODATA
-
-Tested with Python 2.7.11, Python 3.5.1 and PyPy 5.0.1.
-
-Version 0.5.5
--------------
-
-*released Fri, 01 May 2015*
-
-Bugfix release:
-
-* fixes some more memory leaks when handling out-of-memory in get_all()
-  function
-* improve error reporting when an attribute disappears after we asked
-  for its length but before we managed to read it
-* fix int/size_t issues found by RedHat/Fedora,
-  https://bugzilla.redhat.com/show_bug.cgi?id=1127310; the fix is
-  different than their fix, but it should accomplish the same thing
-* convert all code to only do explicit casts after checking boundaries,
-  making the code `-Wconversion`-clean (although that warning is not
-  enabled by default)
-
-Version 0.5.4
--------------
-
-*released Thu, 30 Apr 2015*
-
-Fix memory leaks on some of the error-handling paths of the `get()`
-function.
-
-Version 0.5.3
--------------
-
-*released Fri, 23 May 2014*
-
-Small optimisations release:
-
-* ari edelkind contributed a speed-up optimisation for handling of files
-  without xattrs (which is, in general, the expected case)
-* Jonas Borgström contributed a behaviour change to the handling of file
-  names: under Python 3 and up, unicode paths are encoded/decoded using
-  the 'surogatee' handler, instead of the 'strict' handler; while this
-  can hide encoding errors, it mirrors what Python libraries do
-  (e.g. see os.fsencode/fsdecode)
-* Sean Patrick Santos contributed improvements to the test suite so that
-  it can be used even on files systems which have built-in attributes
-  (e.g. when using SELinux, or NFSv4); to enable this, define the
-  attributes in the TEST_IGNORE_XATTRS environment variable
-
-Version 0.5.2
--------------
-
-*released Thu, 03 Jan 2013*
-
-Bug-fix release. Thanks to Michał Górny, it looked like the library had
-problem running under pypy, but actually there was a bug in the
-PyArg_ParseTuple use of et# (signed vs. unsigned, and lack of compiler
-warnings). This was fixed, and now the test suite passed with many
-CPython versions and PyPy (version 1.9).
-
-Version 0.5.1
--------------
-
-*released Wed, 16 May 2012*
-
-Bug-fix release. Thanks to Dave Malcolm and his cpychecker tool, a
-number of significant bugs (refcount leaks and potential NULL-pointer
-dereferences) have been fixed.
-
-Furthermore, compatibility with Python 3 has been improved; this however
-required changing the meaning of the ``namespace`` argument to the
-functions: if passed, None is no longer a valid value; pass an empty
-string if (due to the structure of your program) you have to pass this
-argument but want to specify no namespace.
-
-Also, the project home page has changed from SourceForge to GitHub, and
-the documentation has been converted from epydoc-based to sphinx.
-
-
-Version 0.5
------------
-
-*released Sun, 27 Dec 2009*
-
-Implemented support for Python 3. This required a significant change to
-the C module, hence the new version number.
-
-Version 0.4
------------
-
-*released Mon, 30 Jun 2008*
-
-API
-~~~
-
-The old functions ({get,set,list,remove}xattr) are deprecated and replaced with
-a new API that is namespace-aware and hopefully will allow other OSes (e.g.
-FreeBSD) to be supported more naturally.
-
-Both the old and the new API are supported in the 0.4 versions, however users
-are encouraged to migrate to the new API.
-
-New features
-~~~~~~~~~~~~
-
-A new bulk get function called get_all() has been added that should be somewhat
-faster in case of querying files which have many attributes.
-
-License
-~~~~~~~
-
-Since LGPLv3 is not compatible with GPLv2 (which unfortunately I didn't realize
-before), the license was changed to LGPLv2.1 or later.
-
-Internals
-~~~~~~~~~
-
-Unittest coverage was improved.
-
-Version 0.3
------------
-
-*released Sun, 09 Mar 2008*
-
-* changed licence from GPL to LGPL (3 or later)
-* changed listxattr return type from tuple to a list
-* developer-related: added unittests
-
-Version 0.2.2
--------------
-
-*released Sun, 01 Jul 2007*
-
-* fixed listing symlink xattrs
-
-Version 0.2.1
--------------
-
-*released Sat, 11 Feb 2006*
-
-* fixed a bug when reading symlink EAs (you weren't able to
-  do it, actually)
-* fixed a possible memory leak when the actual read of the EA
-  failed but the call to get the length of the EA didn't
-
-.. Local Variables:
-.. mode: rst
-.. fill-column: 72
-.. End:
diff --git a/pyxattr.egg-info/PKG-INFO b/pyxattr.egg-info/PKG-INFO
index 525c3b3..efe5a0c 100644
--- a/pyxattr.egg-info/PKG-INFO
+++ b/pyxattr.egg-info/PKG-INFO
@@ -1,13 +1,25 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.2
 Name: pyxattr
-Version: 0.6.1
+Version: 0.7.2
 Summary: Filesystem extended attributes for python
 Home-page: http://pyxattr.k1024.org/
 Author: Iustin Pop
 Author-email: iustin@k1024.org
 License: LGPL
 Download-URL: http://pyxattr.k1024.org/downloads/
+Project-URL: Bug Tracker, https://github.com/iustin/pyxattr/issues
 Description: This is a C extension module for Python which
         implements extended attributes manipulation. It is a wrapper on top
         of the attr C library - see attr(5).
 Platform: Linux
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: System :: Filesystems
+Requires-Python: >=3.4
diff --git a/pyxattr.egg-info/SOURCES.txt b/pyxattr.egg-info/SOURCES.txt
index 55eaa50..f9162da 100644
--- a/pyxattr.egg-info/SOURCES.txt
+++ b/pyxattr.egg-info/SOURCES.txt
@@ -2,17 +2,16 @@ COPYING
 MANIFEST.in
 Makefile
 NEWS
-README.rst
+README.md
 setup.cfg
 setup.py
 xattr.c
 doc/conf.py
 doc/index.rst
 doc/module.rst
-doc/news.rst
 pyxattr.egg-info/PKG-INFO
 pyxattr.egg-info/SOURCES.txt
 pyxattr.egg-info/dependency_links.txt
 pyxattr.egg-info/top_level.txt
-test/__init__.py
-test/test_xattr.py
\ No newline at end of file
+tests/__init__.py
+tests/test_xattr.py
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 4cd944b..ac5c82b 100755
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
 
 import distutils
 import platform
@@ -10,7 +10,7 @@ except ImportError:
 long_desc = """This is a C extension module for Python which
 implements extended attributes manipulation. It is a wrapper on top
 of the attr C library - see attr(5)."""
-version = "0.6.1"
+version = "0.7.2"
 author = "Iustin Pop"
 author_email = "iustin@k1024.org"
 libraries = []
@@ -33,6 +33,21 @@ setup(name = "pyxattr",
                                define_macros=macros,
                                extra_compile_args=["-Wall", "-Werror", "-Wsign-compare"],
                                )],
-      test_suite = "test",
       platforms = ["Linux"],
+      python_requires = ">=3.4",
+      project_urls={
+        "Bug Tracker": "https://github.com/iustin/pyxattr/issues",
+      },
+      classifiers = [
+        "Development Status :: 5 - Production/Stable",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)",
+        "Programming Language :: Python :: 3 :: Only",
+        "Programming Language :: Python :: Implementation :: CPython",
+        "Programming Language :: Python :: Implementation :: PyPy",
+        "Operating System :: MacOS :: MacOS X",
+        "Operating System :: POSIX :: Linux",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        "Topic :: System :: Filesystems",
+      ]
       )
diff --git a/test/test_xattr.py b/test/test_xattr.py
deleted file mode 100644
index 7b14eec..0000000
--- a/test/test_xattr.py
+++ /dev/null
@@ -1,463 +0,0 @@
-#
-#
-
-import sys
-import unittest
-import tempfile
-import os
-import errno
-
-import xattr
-from xattr import NS_USER, XATTR_CREATE, XATTR_REPLACE
-
-NAMESPACE = os.environ.get("NAMESPACE", NS_USER)
-
-if sys.hexversion >= 0x03000000:
-    PY3K = True
-    EMPTY_NS = bytes()
-else:
-    PY3K = False
-    EMPTY_NS = ''
-
-TEST_DIR = os.environ.get("TEST_DIR", ".")
-TEST_IGNORE_XATTRS = os.environ.get("TEST_IGNORE_XATTRS", "")
-if TEST_IGNORE_XATTRS == "":
-    TEST_IGNORE_XATTRS = []
-else:
-    TEST_IGNORE_XATTRS = TEST_IGNORE_XATTRS.split(",")
-    # The following has to be a list comprehension, not a generator, to
-    # avoid weird consequences of lazy evaluation.
-    TEST_IGNORE_XATTRS.extend([a.encode() for a in TEST_IGNORE_XATTRS])
-
-class xattrTest(unittest.TestCase):
-    USER_NN = "test"
-    USER_ATTR = NAMESPACE.decode() + "." + USER_NN
-    USER_VAL = "abc"
-    EMPTY_VAL = ""
-    LARGE_VAL = "x" * 2048
-    MANYOPS_COUNT = 131072
-
-    if PY3K:
-        USER_NN = USER_NN.encode()
-        USER_VAL = USER_VAL.encode()
-        USER_ATTR = USER_ATTR.encode()
-        EMPTY_VAL = EMPTY_VAL.encode()
-        LARGE_VAL = LARGE_VAL.encode()
-
-    @staticmethod
-    def _ignore_tuples(attrs):
-        """Remove ignored attributes from the output of xattr.get_all."""
-        return [attr for attr in attrs
-                if attr[0] not in TEST_IGNORE_XATTRS]
-
-    @staticmethod
-    def _ignore(attrs):
-        """Remove ignored attributes from the output of xattr.list"""
-        return [attr for attr in attrs
-                if attr not in TEST_IGNORE_XATTRS]
-
-    def checkList(self, attrs, value):
-        """Helper to check list equivalence, skipping TEST_IGNORE_XATTRS."""
-        self.assertEqual(self._ignore(attrs), value)
-
-    def checkTuples(self, attrs, value):
-        """Helper to check list equivalence, skipping TEST_IGNORE_XATTRS."""
-        self.assertEqual(self._ignore_tuples(attrs), value)
-
-    def setUp(self):
-        """set up function"""
-        self.rmfiles = []
-        self.rmdirs = []
-
-    def tearDown(self):
-        """tear down function"""
-        for fname in self.rmfiles:
-            try:
-                os.unlink(fname)
-            except EnvironmentError:
-                continue
-        for dname in self.rmdirs:
-            try:
-                os.rmdir(dname)
-            except EnvironmentError:
-                continue
-
-    def _getfile(self):
-        """create a temp file"""
-        fh, fname = tempfile.mkstemp(".test", "xattr-", TEST_DIR)
-        self.rmfiles.append(fname)
-        return fh, fname
-
-    def _getdir(self):
-        """create a temp dir"""
-        dname = tempfile.mkdtemp(".test", "xattr-", TEST_DIR)
-        self.rmdirs.append(dname)
-        return dname
-
-    def _getsymlink(self, dangling=True):
-        """create a symlink"""
-        fh, fname = self._getfile()
-        os.close(fh)
-        if dangling:
-            os.unlink(fname)
-        sname = fname + ".symlink"
-        os.symlink(fname, sname)
-        self.rmfiles.append(sname)
-        return fname, sname
-
-    def _checkDeprecated(self, item, symlink=False):
-        """check deprecated list, set, get operations against an item"""
-        self.checkList(xattr.listxattr(item, symlink), [])
-        self.assertRaises(EnvironmentError, xattr.setxattr, item,
-                          self.USER_ATTR, self.USER_VAL,
-                          XATTR_REPLACE, symlink)
-        try:
-            xattr.setxattr(item, self.USER_ATTR, self.USER_VAL, 0, symlink)
-        except IOError:
-            err = sys.exc_info()[1]
-            if symlink and (err.errno == errno.EPERM or
-                            err.errno == errno.ENOENT):
-                # symlinks may fail, in which case we abort the rest
-                # of the test for this case (Linux returns EPERM; OS X
-                # returns ENOENT)
-                return
-            raise
-        self.assertRaises(EnvironmentError, xattr.setxattr, item,
-                          self.USER_ATTR, self.USER_VAL, XATTR_CREATE, symlink)
-        self.checkList(xattr.listxattr(item, symlink), [self.USER_ATTR])
-        self.assertEqual(xattr.getxattr(item, self.USER_ATTR, symlink),
-                         self.USER_VAL)
-        self.checkTuples(xattr.get_all(item, nofollow=symlink),
-                          [(self.USER_ATTR, self.USER_VAL)])
-        xattr.removexattr(item, self.USER_ATTR, symlink)
-        self.checkList(xattr.listxattr(item, symlink), [])
-        self.checkTuples(xattr.get_all(item, nofollow=symlink),
-                         [])
-        self.assertRaises(EnvironmentError, xattr.removexattr,
-                          item, self.USER_ATTR, symlink)
-
-    def _checkListSetGet(self, item, symlink=False, use_ns=False):
-        """check list, set, get operations against an item"""
-        self.checkList(xattr.list(item, symlink), [])
-        self.assertRaises(EnvironmentError, xattr.set, item,
-                          self.USER_ATTR, self.USER_VAL,
-                          flags=XATTR_REPLACE,
-                          nofollow=symlink)
-        self.assertRaises(EnvironmentError, xattr.set, item,
-                          self.USER_NN, self.USER_VAL,
-                          flags=XATTR_REPLACE,
-                          namespace=NAMESPACE,
-                          nofollow=symlink)
-        try:
-            if use_ns:
-                xattr.set(item, self.USER_NN, self.USER_VAL,
-                          namespace=NAMESPACE,
-                          nofollow=symlink)
-            else:
-                xattr.set(item, self.USER_ATTR, self.USER_VAL,
-                          nofollow=symlink)
-        except IOError:
-            err = sys.exc_info()[1]
-            if symlink and (err.errno == errno.EPERM or
-                            err.errno == errno.ENOENT):
-                # symlinks may fail, in which case we abort the rest
-                # of the test for this case (Linux returns EPERM; OS X
-                # returns ENOENT)
-                return
-            raise
-        self.assertRaises(EnvironmentError, xattr.set, item,
-                          self.USER_ATTR, self.USER_VAL,
-                          flags=XATTR_CREATE,
-                          nofollow=symlink)
-        self.assertRaises(EnvironmentError, xattr.set, item,
-                          self.USER_NN, self.USER_VAL,
-                          flags=XATTR_CREATE,
-                          namespace=NAMESPACE,
-                          nofollow=symlink)
-        self.checkList(xattr.list(item, nofollow=symlink), [self.USER_ATTR])
-        self.checkList(xattr.list(item, nofollow=symlink,
-                                  namespace=EMPTY_NS),
-                       [self.USER_ATTR])
-        self.assertEqual(xattr.list(item, namespace=NAMESPACE, nofollow=symlink),
-                         [self.USER_NN])
-        self.assertEqual(xattr.get(item, self.USER_ATTR, nofollow=symlink),
-                         self.USER_VAL)
-        self.assertEqual(xattr.get(item, self.USER_NN, nofollow=symlink,
-                                   namespace=NAMESPACE), self.USER_VAL)
-        self.checkTuples(xattr.get_all(item, nofollow=symlink),
-                         [(self.USER_ATTR, self.USER_VAL)])
-        self.assertEqual(xattr.get_all(item, nofollow=symlink,
-                                       namespace=NAMESPACE),
-                         [(self.USER_NN, self.USER_VAL)])
-        if use_ns:
-            xattr.remove(item, self.USER_NN, namespace=NAMESPACE, nofollow=symlink)
-        else:
-            xattr.remove(item, self.USER_ATTR, nofollow=symlink)
-        self.checkList(xattr.list(item, nofollow=symlink), [])
-        self.checkTuples(xattr.get_all(item, nofollow=symlink),
-                         [])
-        self.assertRaises(EnvironmentError, xattr.remove,
-                          item, self.USER_ATTR, nofollow=symlink)
-        self.assertRaises(EnvironmentError, xattr.remove, item,
-                          self.USER_NN, namespace=NAMESPACE, nofollow=symlink)
-
-    def testNoXattrDeprecated(self):
-        """test no attributes (deprecated functions)"""
-        fh, fname = self._getfile()
-        self.checkList(xattr.listxattr(fname), [])
-        self.checkTuples(xattr.get_all(fname), [])
-        self.assertRaises(EnvironmentError, xattr.getxattr, fname,
-                              self.USER_ATTR)
-        dname = self._getdir()
-        self.checkList(xattr.listxattr(dname), [])
-        self.checkTuples(xattr.get_all(dname), [])
-        self.assertRaises(EnvironmentError, xattr.getxattr, dname,
-                              self.USER_ATTR)
-        _, sname = self._getsymlink()
-        self.checkList(xattr.listxattr(sname, True), [])
-        self.checkTuples(xattr.get_all(sname, nofollow=True), [])
-        self.assertRaises(EnvironmentError, xattr.getxattr, fname,
-                              self.USER_ATTR, True)
-
-
-    def testNoXattr(self):
-        """test no attributes"""
-        fh, fname = self._getfile()
-        self.checkList(xattr.list(fname), [])
-        self.assertEqual(xattr.list(fname, namespace=NAMESPACE), [])
-        self.checkTuples(xattr.get_all(fname), [])
-        self.assertEqual(xattr.get_all(fname, namespace=NAMESPACE), [])
-        self.assertRaises(EnvironmentError, xattr.get, fname,
-                              self.USER_NN, namespace=NAMESPACE)
-        dname = self._getdir()
-        self.checkList(xattr.list(dname), [])
-        self.assertEqual(xattr.list(dname, namespace=NAMESPACE), [])
-        self.checkTuples(xattr.get_all(dname), [])
-        self.assertEqual(xattr.get_all(dname, namespace=NAMESPACE), [])
-        self.assertRaises(EnvironmentError, xattr.get, dname,
-                              self.USER_NN, namespace=NAMESPACE)
-        _, sname = self._getsymlink()
-        self.checkList(xattr.list(sname, nofollow=True), [])
-        self.assertEqual(xattr.list(sname, nofollow=True,
-                                    namespace=NAMESPACE), [])
-        self.checkTuples(xattr.get_all(sname, nofollow=True), [])
-        self.assertEqual(xattr.get_all(sname, nofollow=True,
-                                           namespace=NAMESPACE), [])
-        self.assertRaises(EnvironmentError, xattr.get, sname,
-                              self.USER_NN, namespace=NAMESPACE, nofollow=True)
-
-    def testFileByNameDeprecated(self):
-        """test set and retrieve one attribute by file name (deprecated)"""
-        fh, fname = self._getfile()
-        self._checkDeprecated(fname)
-        os.close(fh)
-
-    def testFileByName(self):
-        """test set and retrieve one attribute by file name"""
-        fh, fname = self._getfile()
-        self._checkListSetGet(fname)
-        self._checkListSetGet(fname, use_ns=True)
-        os.close(fh)
-
-    def testFileByDescriptorDeprecated(self):
-        """test file descriptor operations (deprecated functions)"""
-        fh, fname = self._getfile()
-        self._checkDeprecated(fh)
-        os.close(fh)
-
-    def testFileByDescriptor(self):
-        """test file descriptor operations"""
-        fh, fname = self._getfile()
-        self._checkListSetGet(fh)
-        self._checkListSetGet(fh, use_ns=True)
-        os.close(fh)
-
-    def testFileByObjectDeprecated(self):
-        """test file descriptor operations (deprecated functions)"""
-        fh, fname = self._getfile()
-        fo = os.fdopen(fh)
-        self._checkDeprecated(fo)
-        fo.close()
-
-    def testFileByObject(self):
-        """test file descriptor operations"""
-        fh, fname = self._getfile()
-        fo = os.fdopen(fh)
-        self._checkListSetGet(fo)
-        self._checkListSetGet(fo, use_ns=True)
-        fo.close()
-
-    def testMixedAccessDeprecated(self):
-        """test mixed access to file (deprecated functions)"""
-        fh, fname = self._getfile()
-        fo = os.fdopen(fh)
-        self.checkList(xattr.listxattr(fname), [])
-        xattr.setxattr(fname, self.USER_ATTR, self.USER_VAL)
-        self.checkList(xattr.listxattr(fh), [self.USER_ATTR])
-        self.assertEqual(xattr.getxattr(fo, self.USER_ATTR), self.USER_VAL)
-        self.checkTuples(xattr.get_all(fo), [(self.USER_ATTR, self.USER_VAL)])
-        self.checkTuples(xattr.get_all(fname),
-                         [(self.USER_ATTR, self.USER_VAL)])
-        fo.close()
-
-    def testMixedAccess(self):
-        """test mixed access to file"""
-        fh, fname = self._getfile()
-        fo = os.fdopen(fh)
-        self.checkList(xattr.list(fname), [])
-        xattr.set(fname, self.USER_ATTR, self.USER_VAL)
-        self.checkList(xattr.list(fh), [self.USER_ATTR])
-        self.assertEqual(xattr.list(fh, namespace=NAMESPACE), [self.USER_NN])
-        self.assertEqual(xattr.get(fo, self.USER_ATTR), self.USER_VAL)
-        self.assertEqual(xattr.get(fo, self.USER_NN, namespace=NAMESPACE),
-                         self.USER_VAL)
-        self.checkTuples(xattr.get_all(fo),
-                         [(self.USER_ATTR, self.USER_VAL)])
-        self.assertEqual(xattr.get_all(fo, namespace=NAMESPACE),
-                         [(self.USER_NN, self.USER_VAL)])
-        self.checkTuples(xattr.get_all(fname),
-                         [(self.USER_ATTR, self.USER_VAL)])
-        self.assertEqual(xattr.get_all(fname, namespace=NAMESPACE),
-                         [(self.USER_NN, self.USER_VAL)])
-        fo.close()
-
-    def testDirOpsDeprecated(self):
-        """test attribute setting on directories (deprecated functions)"""
-        dname = self._getdir()
-        self._checkDeprecated(dname)
-
-    def testDirOps(self):
-        """test attribute setting on directories"""
-        dname = self._getdir()
-        self._checkListSetGet(dname)
-        self._checkListSetGet(dname, use_ns=True)
-
-    def testSymlinkOpsDeprecated(self):
-        """test symlink operations (deprecated functions)"""
-        _, sname = self._getsymlink()
-        self.assertRaises(EnvironmentError, xattr.listxattr, sname)
-        self._checkDeprecated(sname, symlink=True)
-        target, sname = self._getsymlink(dangling=False)
-        xattr.setxattr(target, self.USER_ATTR, self.USER_VAL)
-        self.checkList(xattr.listxattr(target), [self.USER_ATTR])
-        self.checkList(xattr.listxattr(sname, True), [])
-        self.assertRaises(EnvironmentError, xattr.removexattr, sname,
-                          self.USER_ATTR, True)
-        xattr.removexattr(sname, self.USER_ATTR, False)
-
-    def testSymlinkOps(self):
-        """test symlink operations"""
-        _, sname = self._getsymlink()
-        self.assertRaises(EnvironmentError, xattr.list, sname)
-        self._checkListSetGet(sname, symlink=True)
-        self._checkListSetGet(sname, symlink=True, use_ns=True)
-        target, sname = self._getsymlink(dangling=False)
-        xattr.set(target, self.USER_ATTR, self.USER_VAL)
-        self.checkList(xattr.list(target), [self.USER_ATTR])
-        self.checkList(xattr.list(sname, nofollow=True), [])
-        self.assertRaises(EnvironmentError, xattr.remove, sname,
-                          self.USER_ATTR, nofollow=True)
-        xattr.remove(sname, self.USER_ATTR, nofollow=False)
-
-    def testBinaryPayloadDeprecated(self):
-        """test binary values (deprecated functions)"""
-        fh, fname = self._getfile()
-        os.close(fh)
-        BINVAL = "abc" + '\0' + "def"
-        if PY3K:
-            BINVAL = BINVAL.encode()
-        xattr.setxattr(fname, self.USER_ATTR, BINVAL)
-        self.checkList(xattr.listxattr(fname), [self.USER_ATTR])
-        self.assertEqual(xattr.getxattr(fname, self.USER_ATTR), BINVAL)
-        self.checkTuples(xattr.get_all(fname), [(self.USER_ATTR, BINVAL)])
-        xattr.removexattr(fname, self.USER_ATTR)
-
-    def testBinaryPayload(self):
-        """test binary values"""
-        fh, fname = self._getfile()
-        os.close(fh)
-        BINVAL = "abc" + '\0' + "def"
-        if PY3K:
-            BINVAL = BINVAL.encode()
-        xattr.set(fname, self.USER_ATTR, BINVAL)
-        self.checkList(xattr.list(fname), [self.USER_ATTR])
-        self.assertEqual(xattr.list(fname, namespace=NAMESPACE), [self.USER_NN])
-        self.assertEqual(xattr.get(fname, self.USER_ATTR), BINVAL)
-        self.assertEqual(xattr.get(fname, self.USER_NN,
-                                   namespace=NAMESPACE), BINVAL)
-        self.checkTuples(xattr.get_all(fname), [(self.USER_ATTR, BINVAL)])
-        self.assertEqual(xattr.get_all(fname, namespace=NAMESPACE),
-                         [(self.USER_NN, BINVAL)])
-        xattr.remove(fname, self.USER_ATTR)
-
-    def testManyOpsDeprecated(self):
-        """test many ops (deprecated functions)"""
-        fh, fname = self._getfile()
-        xattr.setxattr(fh, self.USER_ATTR, self.USER_VAL)
-        VL = [self.USER_ATTR]
-        for i in range(self.MANYOPS_COUNT):
-            self.checkList(xattr.listxattr(fh), VL)
-        for i in range(self.MANYOPS_COUNT):
-            self.assertEqual(xattr.getxattr(fh, self.USER_ATTR), self.USER_VAL)
-        for i in range(self.MANYOPS_COUNT):
-            self.checkTuples(xattr.get_all(fh),
-                             [(self.USER_ATTR, self.USER_VAL)])
-
-    def testManyOps(self):
-        """test many ops"""
-        fh, fname = self._getfile()
-        xattr.set(fh, self.USER_ATTR, self.USER_VAL)
-        VL = [self.USER_ATTR]
-        VN = [self.USER_NN]
-        for i in range(self.MANYOPS_COUNT):
-            self.checkList(xattr.list(fh), VL)
-            self.checkList(xattr.list(fh, namespace=EMPTY_NS), VL)
-            self.assertEqual(xattr.list(fh, namespace=NAMESPACE), VN)
-        for i in range(self.MANYOPS_COUNT):
-            self.assertEqual(xattr.get(fh, self.USER_ATTR), self.USER_VAL)
-            self.assertEqual(xattr.get(fh, self.USER_NN, namespace=NAMESPACE),
-                             self.USER_VAL)
-        for i in range(self.MANYOPS_COUNT):
-            self.checkTuples(xattr.get_all(fh),
-                             [(self.USER_ATTR, self.USER_VAL)])
-            self.assertEqual(xattr.get_all(fh, namespace=NAMESPACE),
-                             [(self.USER_NN, self.USER_VAL)])
-
-    def testNoneNamespace(self):
-        fh, fname = self._getfile()
-        self.assertRaises(TypeError, xattr.get, fh, self.USER_ATTR,
-                          namespace=None)
-
-    def testEmptyValue(self):
-        fh, fname = self._getfile()
-        xattr.set(fh, self.USER_ATTR, self.EMPTY_VAL)
-        self.assertEqual(xattr.get(fh, self.USER_ATTR), self.EMPTY_VAL)
-
-    def testWrongCall(self):
-       for call in [xattr.get,
-                    xattr.list, xattr.listxattr,
-                    xattr.remove, xattr.removexattr,
-                    xattr.set, xattr.setxattr,
-                    xattr.get, xattr.getxattr]:
-           self.assertRaises(TypeError, call)
-
-    def testWrongType(self):
-        self.assertRaises(TypeError, xattr.get, object(), self.USER_ATTR)
-        for call in [xattr.listxattr, xattr.list]:
-            self.assertRaises(TypeError, call, object())
-        for call in [xattr.remove, xattr.removexattr,
-                     xattr.get, xattr.getxattr]:
-            self.assertRaises(TypeError, call, object(), self.USER_ATTR)
-        for call in [xattr.set, xattr.setxattr]:
-            self.assertRaises(TypeError, call, object(), self.USER_ATTR, self.USER_VAL)
-
-
-    def testLargeAttribute(self):
-        fh, fname = self._getfile()
-
-        xattr.set(fh, self.USER_ATTR, self.LARGE_VAL)
-        self.assertEqual(xattr.get(fh, self.USER_ATTR), self.LARGE_VAL)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/test/__init__.py b/tests/__init__.py
similarity index 100%
rename from test/__init__.py
rename to tests/__init__.py
diff --git a/tests/test_xattr.py b/tests/test_xattr.py
new file mode 100644
index 0000000..4f3e663
--- /dev/null
+++ b/tests/test_xattr.py
@@ -0,0 +1,489 @@
+#
+#
+
+import sys
+import tempfile
+import os
+import errno
+import pytest
+import pathlib
+import platform
+import io
+import contextlib
+
+import xattr
+from xattr import NS_USER, XATTR_CREATE, XATTR_REPLACE
+
+NAMESPACE = os.environ.get("NAMESPACE", NS_USER)
+
+EMPTY_NS = bytes()
+
+TEST_DIR = os.environ.get("TEST_DIR", ".")
+TEST_IGNORE_XATTRS = os.environ.get("TEST_IGNORE_XATTRS", "")
+if TEST_IGNORE_XATTRS == "":
+    TEST_IGNORE_XATTRS = []
+else:
+    TEST_IGNORE_XATTRS = TEST_IGNORE_XATTRS.split(",")
+    # The following has to be a list comprehension, not a generator, to
+    # avoid weird consequences of lazy evaluation.
+    TEST_IGNORE_XATTRS.extend([a.encode() for a in TEST_IGNORE_XATTRS])
+
+USER_NN = "test"
+USER_ATTR = NAMESPACE.decode() + "." + USER_NN
+USER_VAL = "abc"
+EMPTY_VAL = ""
+LARGE_VAL = "x" * 2048
+MANYOPS_COUNT = 16384
+
+USER_NN = USER_NN.encode()
+USER_VAL = USER_VAL.encode()
+USER_ATTR = USER_ATTR.encode()
+EMPTY_VAL = EMPTY_VAL.encode()
+LARGE_VAL = LARGE_VAL.encode()
+
+# Helper functions
+
+def ignore_tuples(attrs):
+    """Remove ignored attributes from the output of xattr.get_all."""
+    return [attr for attr in attrs
+            if attr[0] not in TEST_IGNORE_XATTRS]
+
+def ignore(attrs):
+    """Remove ignored attributes from the output of xattr.list"""
+    return [attr for attr in attrs
+            if attr not in TEST_IGNORE_XATTRS]
+
+def lists_equal(attrs, value):
+    """Helper to check list equivalence, skipping TEST_IGNORE_XATTRS."""
+    assert ignore(attrs) == value
+
+def tuples_equal(attrs, value):
+    """Helper to check list equivalence, skipping TEST_IGNORE_XATTRS."""
+    assert ignore_tuples(attrs) == value
+
+# Fixtures and helpers
+
+@pytest.fixture
+def testdir():
+    """per-test temp dir based in TEST_DIR"""
+    with tempfile.TemporaryDirectory(dir=TEST_DIR) as dname:
+        yield dname
+
+def get_file(path):
+    fh, fname = tempfile.mkstemp(".test", "xattr-", path)
+    return fh, fname
+
+@contextlib.contextmanager
+def get_file_name(path):
+    fh, fname = get_file(path)
+    os.close(fh)
+    yield fname
+
+@contextlib.contextmanager
+def get_file_fd(path):
+    fd = get_file(path)[0]
+    yield fd
+    os.close(fd)
+
+@contextlib.contextmanager
+def get_file_object(path):
+    fd = get_file(path)[0]
+    with os.fdopen(fd) as f:
+        yield f
+
+@contextlib.contextmanager
+def get_dir(path):
+    yield tempfile.mkdtemp(".test", "xattr-", path)
+
+def get_symlink(path, dangling=True):
+    """create a symlink"""
+    fh, fname = get_file(path)
+    os.close(fh)
+    if dangling:
+        os.unlink(fname)
+    sname = fname + ".symlink"
+    os.symlink(fname, sname)
+    return fname, sname
+
+@contextlib.contextmanager
+def get_valid_symlink(path):
+    yield get_symlink(path, dangling=False)[1]
+
+@contextlib.contextmanager
+def get_dangling_symlink(path):
+    yield get_symlink(path, dangling=True)[1]
+
+@contextlib.contextmanager
+def get_file_and_symlink(path):
+    yield get_symlink(path, dangling=False)
+
+@contextlib.contextmanager
+def get_file_and_fobject(path):
+    fh, fname = get_file(path)
+    with os.fdopen(fh) as fo:
+        yield fname, fo
+
+# Wrappers that build upon existing values
+
+def as_wrapper(call, fn, closer=None):
+    @contextlib.contextmanager
+    def f(path):
+        with call(path) as r:
+            val = fn(r)
+            yield val
+            if closer is not None:
+                closer(val)
+    return f
+
+def as_bytes(call):
+    return as_wrapper(call, lambda r: r.encode())
+
+def as_fspath(call):
+    return as_wrapper(call, pathlib.PurePath)
+
+def as_iostream(call):
+    opener = lambda f: io.open(f, "r")
+    closer = lambda r: r.close()
+    return as_wrapper(call, opener, closer)
+
+NOT_BEFORE_36 = pytest.mark.xfail(condition="sys.version_info < (3,6)",
+                                  strict=True)
+NOT_PYPY = pytest.mark.xfail(condition="platform.python_implementation() == 'PyPy'",
+                                  strict=False)
+
+# Note: user attributes are only allowed on files and directories, so
+# we have to skip the symlinks here. See xattr(7).
+ITEMS_P = [
+    (get_file_name, False),
+    (as_bytes(get_file_name), False),
+    pytest.param((as_fspath(get_file_name), False),
+                 marks=[NOT_BEFORE_36, NOT_PYPY]),
+    (get_file_fd, False),
+    (get_file_object, False),
+    (as_iostream(get_file_name), False),
+    (get_dir, False),
+    (as_bytes(get_dir), False),
+    pytest.param((as_fspath(get_dir), False),
+                 marks=[NOT_BEFORE_36, NOT_PYPY]),
+    (get_valid_symlink, False),
+    (as_bytes(get_valid_symlink), False),
+    pytest.param((as_fspath(get_valid_symlink), False),
+                 marks=[NOT_BEFORE_36, NOT_PYPY]),
+]
+
+ITEMS_D = [
+    "file name",
+    "file name (bytes)",
+    "file name (path)",
+    "file FD",
+    "file object",
+    "file io stream",
+    "directory",
+    "directory (bytes)",
+    "directory (path)",
+    "file via symlink",
+    "file via symlink (bytes)",
+    "file via symlink (path)",
+]
+
+ALL_ITEMS_P = ITEMS_P + [
+    (get_valid_symlink, True),
+    (as_bytes(get_valid_symlink), True),
+    (get_dangling_symlink, True),
+    (as_bytes(get_dangling_symlink), True),
+]
+
+ALL_ITEMS_D = ITEMS_D + [
+    "valid symlink",
+    "valid symlink (bytes)",
+    "dangling symlink",
+    "dangling symlink (bytes)"
+]
+
+@pytest.fixture(params=ITEMS_P, ids=ITEMS_D)
+def subject(testdir, request):
+    with request.param[0](testdir) as value:
+        yield value, request.param[1]
+
+@pytest.fixture(params=ALL_ITEMS_P, ids=ALL_ITEMS_D)
+def any_subject(testdir, request):
+    with request.param[0](testdir) as value:
+        yield value, request.param[1]
+
+@pytest.fixture(params=[True, False], ids=["with namespace", "no namespace"])
+def use_ns(request):
+    return request.param
+
+@pytest.fixture(params=[True, False], ids=["dangling", "valid"])
+def use_dangling(request):
+    return request.param
+
+### Test functions
+
+def test_empty_value(subject):
+    item, nofollow = subject
+    xattr.set(item, USER_ATTR, EMPTY_VAL, nofollow=nofollow)
+    assert xattr.get(item, USER_ATTR, nofollow=nofollow) == EMPTY_VAL
+
+def test_large_value(subject):
+    item, nofollow = subject
+    xattr.set(item, USER_ATTR, LARGE_VAL)
+    assert xattr.get(item, USER_ATTR, nofollow=nofollow) == LARGE_VAL
+
+@pytest.mark.parametrize(
+    "gen", [ get_file_and_symlink, get_file_and_fobject ])
+def test_mixed_access(testdir, gen):
+    """test mixed access to file"""
+    with gen(testdir) as (a, b):
+        # Check empty
+        lists_equal(xattr.list(a), [])
+        lists_equal(xattr.listxattr(b), [])
+
+        # Check value
+        xattr.set(a, USER_ATTR, USER_VAL)
+        for i in [a, b]:
+            # Deprecated functions
+            lists_equal(xattr.listxattr(i), [USER_ATTR])
+            assert xattr.getxattr(i, USER_ATTR) == USER_VAL
+            tuples_equal(xattr.get_all(i), [(USER_ATTR, USER_VAL)])
+            # Current functions
+            lists_equal(xattr.list(i), [USER_ATTR])
+            assert xattr.list(i, namespace=NAMESPACE) == [USER_NN]
+            assert xattr.get(i, USER_ATTR) == USER_VAL
+            assert xattr.get(i, USER_NN, namespace=NAMESPACE) == USER_VAL
+            tuples_equal(xattr.get_all(i),
+                         [(USER_ATTR, USER_VAL)])
+            assert xattr.get_all(i, namespace=NAMESPACE) == \
+                [(USER_NN, USER_VAL)]
+
+        # Overwrite
+        xattr.set(b, USER_ATTR, LARGE_VAL, flags=xattr.XATTR_REPLACE)
+        assert xattr.get(a, USER_ATTR) == LARGE_VAL
+        assert xattr.getxattr(a, USER_ATTR) == LARGE_VAL
+        xattr.removexattr(b, USER_ATTR)
+        assert xattr.get_all(a, namespace=NAMESPACE) == []
+        assert xattr.get_all(b, namespace=NAMESPACE) == []
+
+def test_replace_on_missing(subject, use_ns):
+    item = subject[0]
+    lists_equal(xattr.list(item), [])
+    with pytest.raises(EnvironmentError):
+        if use_ns:
+            xattr.set(item, USER_NN, USER_VAL, flags=XATTR_REPLACE,
+                      namespace=NAMESPACE)
+        else:
+            xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_REPLACE)
+
+def test_create_on_existing(subject, use_ns):
+    item = subject[0]
+    lists_equal(xattr.list(item), [])
+    if use_ns:
+        xattr.set(item, USER_NN, USER_VAL,
+                  namespace=NAMESPACE)
+    else:
+        xattr.set(item, USER_ATTR, USER_VAL)
+    with pytest.raises(EnvironmentError):
+        if use_ns:
+            xattr.set(item, USER_NN, USER_VAL,
+                      flags=XATTR_CREATE, namespace=NAMESPACE)
+        else:
+            xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_CREATE)
+
+def test_remove_on_missing(any_subject, use_ns):
+    item, nofollow = any_subject
+    lists_equal(xattr.list(item, nofollow=nofollow), [])
+    with pytest.raises(EnvironmentError):
+        if use_ns:
+            xattr.remove(item, USER_NN, namespace=NAMESPACE,
+                         nofollow=nofollow)
+        else:
+            xattr.remove(item, USER_ATTR, nofollow=nofollow)
+
+def test_set_get_remove(subject, use_ns):
+    item = subject[0]
+    lists_equal(xattr.list(item), [])
+    if use_ns:
+        xattr.set(item, USER_NN, USER_VAL,
+                  namespace=NAMESPACE)
+    else:
+        xattr.set(item, USER_ATTR, USER_VAL)
+    if use_ns:
+        assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
+    else:
+        lists_equal(xattr.list(item), [USER_ATTR])
+        lists_equal(xattr.list(item, namespace=EMPTY_NS),
+                    [USER_ATTR])
+    if use_ns:
+        assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
+    else:
+        assert xattr.get(item, USER_ATTR) == USER_VAL
+    if use_ns:
+        assert xattr.get_all(item, namespace=NAMESPACE) == \
+            [(USER_NN, USER_VAL)]
+    else:
+        tuples_equal(xattr.get_all(item),
+                     [(USER_ATTR, USER_VAL)])
+    if use_ns:
+        xattr.remove(item, USER_NN, namespace=NAMESPACE)
+    else:
+        xattr.remove(item, USER_ATTR)
+    lists_equal(xattr.list(item), [])
+    tuples_equal(xattr.get_all(item), [])
+
+def test_replace_on_missing_deprecated(subject):
+    item = subject[0]
+    lists_equal(xattr.listxattr(item), [])
+    with pytest.raises(EnvironmentError):
+        xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_REPLACE)
+
+def test_create_on_existing_deprecated(subject):
+    item = subject[0]
+    lists_equal(xattr.listxattr(item), [])
+    xattr.setxattr(item, USER_ATTR, USER_VAL, 0)
+    with pytest.raises(EnvironmentError):
+        xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_CREATE)
+
+def test_remove_on_missing_deprecated(any_subject):
+    """check deprecated list, set, get operations against an item"""
+    item, nofollow = any_subject
+    lists_equal(xattr.listxattr(item, nofollow), [])
+    with pytest.raises(EnvironmentError):
+        xattr.removexattr(item, USER_ATTR)
+
+def test_set_get_remove_deprecated(subject):
+    """check deprecated list, set, get operations against an item"""
+    item = subject[0]
+    lists_equal(xattr.listxattr(item), [])
+    xattr.setxattr(item, USER_ATTR, USER_VAL, 0)
+    lists_equal(xattr.listxattr(item), [USER_ATTR])
+    assert xattr.getxattr(item, USER_ATTR) == USER_VAL
+    tuples_equal(xattr.get_all(item), [(USER_ATTR, USER_VAL)])
+    xattr.removexattr(item, USER_ATTR)
+    lists_equal(xattr.listxattr(item), [])
+    tuples_equal(xattr.get_all(item), [])
+
+def test_many_ops(subject):
+    """test many ops"""
+    item = subject[0]
+    xattr.set(item, USER_ATTR, USER_VAL)
+    VL = [USER_ATTR]
+    VN = [USER_NN]
+    for i in range(MANYOPS_COUNT):
+        lists_equal(xattr.list(item), VL)
+        lists_equal(xattr.list(item, namespace=EMPTY_NS), VL)
+        assert xattr.list(item, namespace=NAMESPACE) == VN
+    for i in range(MANYOPS_COUNT):
+        assert xattr.get(item, USER_ATTR) == USER_VAL
+        assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
+    for i in range(MANYOPS_COUNT):
+        tuples_equal(xattr.get_all(item),
+                     [(USER_ATTR, USER_VAL)])
+        assert xattr.get_all(item, namespace=NAMESPACE) == \
+            [(USER_NN, USER_VAL)]
+
+def test_many_ops_deprecated(subject):
+    """test many ops (deprecated functions)"""
+    item = subject[0]
+    xattr.setxattr(item, USER_ATTR, USER_VAL)
+    VL = [USER_ATTR]
+    for i in range(MANYOPS_COUNT):
+        lists_equal(xattr.listxattr(item), VL)
+    for i in range(MANYOPS_COUNT):
+        assert xattr.getxattr(item, USER_ATTR) == USER_VAL
+    for i in range(MANYOPS_COUNT):
+        tuples_equal(xattr.get_all(item),
+                     [(USER_ATTR, USER_VAL)])
+
+def test_no_attributes_deprecated(any_subject):
+    """test no attributes (deprecated functions)"""
+    item, nofollow = any_subject
+    lists_equal(xattr.listxattr(item, True), [])
+    tuples_equal(xattr.get_all(item, True), [])
+    with pytest.raises(EnvironmentError):
+        xattr.getxattr(item, USER_ATTR, True)
+
+def test_no_attributes(any_subject):
+    """test no attributes"""
+    item, nofollow = any_subject
+    lists_equal(xattr.list(item, nofollow=nofollow), [])
+    assert xattr.list(item, nofollow=nofollow,
+                      namespace=NAMESPACE) == []
+    tuples_equal(xattr.get_all(item, nofollow=nofollow), [])
+    assert xattr.get_all(item, nofollow=nofollow,
+                         namespace=NAMESPACE) == []
+    with pytest.raises(EnvironmentError):
+        xattr.get(item, USER_NN, nofollow=nofollow,
+                  namespace=NAMESPACE)
+
+def test_binary_payload_deprecated(subject):
+    """test binary values (deprecated functions)"""
+    item = subject[0]
+    BINVAL = b"abc\0def"
+    xattr.setxattr(item, USER_ATTR, BINVAL)
+    lists_equal(xattr.listxattr(item), [USER_ATTR])
+    assert xattr.getxattr(item, USER_ATTR) == BINVAL
+    tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
+    xattr.removexattr(item, USER_ATTR)
+
+def test_binary_payload(subject):
+    """test binary values"""
+    item = subject[0]
+    BINVAL = b"abc\0def"
+    xattr.set(item, USER_ATTR, BINVAL)
+    lists_equal(xattr.list(item), [USER_ATTR])
+    assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
+    assert xattr.get(item, USER_ATTR) == BINVAL
+    assert xattr.get(item, USER_NN, namespace=NAMESPACE) == BINVAL
+    tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
+    assert xattr.get_all(item, namespace=NAMESPACE) == [(USER_NN, BINVAL)]
+    xattr.remove(item, USER_ATTR)
+
+def test_symlinks_user_fail(testdir, use_dangling):
+    _, sname = get_symlink(testdir, dangling=use_dangling)
+    with pytest.raises(IOError):
+        xattr.set(sname, USER_ATTR, USER_VAL, nofollow=True)
+    with pytest.raises(IOError):
+        xattr.set(sname, USER_NN, USER_VAL, namespace=NAMESPACE,
+                  nofollow=True)
+    with pytest.raises(IOError):
+        xattr.setxattr(sname, USER_ATTR, USER_VAL, XATTR_CREATE, True)
+
+@pytest.mark.parametrize(
+    "call, args", [(xattr.get, [USER_ATTR]),
+                   (xattr.list, []),
+                   (xattr.remove, [USER_ATTR]),
+                   (xattr.get, [USER_ATTR]),
+                   (xattr.set, [USER_ATTR, USER_VAL])])
+def test_none_namespace(testdir, call, args):
+    # Don't want to use subject, since that would prevent xfail test
+    # on path objects (due to hiding the exception here).
+    f = get_file_name(testdir)
+    with pytest.raises(TypeError):
+        call(f, *args, namespace=None)
+    fd = get_file_fd(testdir)
+    with pytest.raises(TypeError):
+        call(fd, *args, namespace=None)
+
+@pytest.mark.parametrize(
+    "call",
+    [xattr.get, xattr.list, xattr.listxattr,
+     xattr.remove, xattr.removexattr,
+     xattr.set, xattr.setxattr,
+     xattr.get, xattr.getxattr])
+def test_wrong_call(call):
+    with pytest.raises(TypeError):
+        call()
+
+@pytest.mark.parametrize(
+    "call, args", [(xattr.get, [USER_ATTR]),
+                   (xattr.listxattr, []),
+                   (xattr.list, []),
+                   (xattr.remove, [USER_ATTR]),
+                   (xattr.removexattr, [USER_ATTR]),
+                   (xattr.get, [USER_ATTR]),
+                   (xattr.getxattr, [USER_ATTR]),
+                   (xattr.set, [USER_ATTR, USER_VAL]),
+                   (xattr.setxattr, [USER_ATTR, USER_VAL])])
+def test_wrong_argument_type(call, args):
+    with pytest.raises(TypeError):
+        call(object(), *args)
diff --git a/xattr.c b/xattr.c
index 0087b7e..e118f79 100644
--- a/xattr.c
+++ b/xattr.c
@@ -28,30 +28,10 @@
 #endif
 #include <stdio.h>
 
-/* Compatibility with python 2.4 regarding python size type (PEP 353) */
-#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
-typedef int Py_ssize_t;
-#define PY_SSIZE_T_MAX INT_MAX
-#define PY_SSIZE_T_MIN INT_MIN
-#endif
-
-#if PY_MAJOR_VERSION >= 3
-#define IS_PY3K
-#define BYTES_CHAR "y"
-#define BYTES_TUPLE "yy#"
-#else
-#define BYTES_CHAR "s"
-#define BYTES_TUPLE "ss#"
-#define PyBytes_Check PyString_Check
-#define PyBytes_AS_STRING PyString_AS_STRING
-#define PyBytes_FromStringAndSize PyString_FromStringAndSize
-#define PyBytes_FromString PyString_FromString
-#endif
-
 #define ITEM_DOC \
-    ":param item: a string representing a file-name, or a file-like\n" \
-    "    object, or a file descriptor; this represents the file on \n" \
-    "    which to act\n"
+    ":param item: a string representing a file-name, a file-like\n" \
+    "    object, a file descriptor, or (in Python 3.6+) a path-like\n" \
+    "    object; this represents the file on which to act\n"
 
 #define NOFOLLOW_DOC \
     ":param nofollow: if true and if\n" \
@@ -150,33 +130,28 @@ static int merge_ns(const char *ns, const char *name,
 static int convert_obj(PyObject *myobj, target_t *tgt, int nofollow) {
     int fd;
     tgt->tmp = NULL;
-    if(PyBytes_Check(myobj)) {
-        tgt->type = nofollow ? T_LINK : T_PATH;
-        tgt->name = PyBytes_AS_STRING(myobj);
-    } else if(PyUnicode_Check(myobj)) {
-        tgt->type = nofollow ? T_LINK : T_PATH;
-        tgt->tmp = \
-            PyUnicode_AsEncodedString(myobj,
-                                      Py_FileSystemDefaultEncoding,
-#ifdef IS_PY3K
-                                      "surrogateescape"
-#else
-                                      "strict"
-#endif
-                                      );
-        if(tgt->tmp == NULL)
-            return -1;
-        tgt->name = PyBytes_AS_STRING(tgt->tmp);
-    } else if((fd = PyObject_AsFileDescriptor(myobj)) != -1) {
+    if((fd = PyObject_AsFileDescriptor(myobj)) != -1) {
         tgt->type = T_FD;
         tgt->fd = fd;
+        return 0;
+    }
+    // PyObject_AsFileDescriptor sets an error when failing, so clear
+    // it such that further code works; some method lookups fail if an
+    // error already occured when called, which breaks at least
+    // PyOS_FSPath (called by FSConverter).
+    PyErr_Clear();
+
+    if(PyUnicode_FSConverter(myobj, &(tgt->tmp))) {
+        tgt->type = nofollow ? T_LINK : T_PATH;
+        tgt->name = PyBytes_AS_STRING(tgt->tmp);
+        return 0;
     } else {
-        PyErr_SetString(PyExc_TypeError, "argument must be string or int");
+        // Don't set our own exception type, since we'd ignore the
+        // FSConverter-generated one.
         tgt->type = T_PATH;
         tgt->name = NULL;
         return -1;
     }
-    return 0;
 }
 
 /* Combine a namespace string and an attribute name into a
@@ -537,7 +512,7 @@ xattr_get(PyObject *self, PyObject *args, PyObject *keywds)
     static char *kwlist[] = {"item", "name", "nofollow", "namespace", NULL};
 
     /* Parse the arguments */
-    if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oet|i" BYTES_CHAR, kwlist,
+    if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oet|iy", kwlist,
                                      &myarg, NULL, &attrname, &nofollow, &ns))
         return NULL;
     res = NULL;
@@ -624,7 +599,7 @@ get_all(PyObject *self, PyObject *args, PyObject *keywds)
     int io_errno;
 
     /* Parse the arguments */
-    if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|i" BYTES_CHAR, kwlist,
+    if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iy", kwlist,
                                      &myarg, &nofollow, &ns))
         return NULL;
     if(convert_obj(myarg, &tgt, nofollow) < 0)
@@ -665,12 +640,15 @@ get_all(PyObject *self, PyObject *args, PyObject *keywds)
             goto free_buf_val;
           }
         }
-        my_tuple = Py_BuildValue(BYTES_TUPLE, name, buf_val, nval);
+        my_tuple = Py_BuildValue("yy#", name, buf_val, nval);
         if (my_tuple == NULL) {
           Py_DECREF(mylist);
           goto free_buf_val;
         }
-        PyList_Append(mylist, my_tuple);
+        if(PyList_Append(mylist, my_tuple) < 0) {
+            Py_DECREF(mylist);
+            goto free_buf_val;
+        }
         Py_DECREF(my_tuple);
     }
 
@@ -807,7 +785,7 @@ xattr_set(PyObject *self, PyObject *args, PyObject *keywds)
                              "nofollow", "namespace", NULL};
 
     /* Parse the arguments */
-    if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oetet#|ii" BYTES_CHAR,
+    if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oetet#|iiy",
                                      kwlist, &myarg, NULL, &attrname, NULL,
                                      &buf, &bufsize_s, &flags, &nofollow, &ns))
         return NULL;
@@ -938,7 +916,7 @@ xattr_remove(PyObject *self, PyObject *args, PyObject *keywds)
     static char *kwlist[] = {"item", "name", "nofollow", "namespace", NULL};
 
     /* Parse the arguments */
-    if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oet|i" BYTES_CHAR, kwlist,
+    if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oet|iy", kwlist,
                                      &myarg, NULL, &attrname, &nofollow, &ns))
         return NULL;
 
@@ -1085,7 +1063,7 @@ xattr_list(PyObject *self, PyObject *args, PyObject *keywds)
     static char *kwlist[] = {"item", "nofollow", "namespace", NULL};
 
     /* Parse the arguments */
-    if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|i" BYTES_CHAR, kwlist,
+    if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iy", kwlist,
                                      &myarg, &nofollow, &ns))
         return NULL;
     res = NULL;
@@ -1184,7 +1162,7 @@ static char __xattr_doc__[] = \
     "   a :exc:`EnvironmentError`; under\n"
     "   Linux, the following ``errno`` values are used:\n"
     "\n"
-    "   - ``ENODATA`` means that the attribute name is\n invalid\n"
+    "   - ``ENODATA`` means that the attribute name is invalid\n"
     "   - ``ENOTSUP`` and ``EOPNOTSUPP`` mean that the filesystem does not\n"
     "     support extended attributes, or that the namespace is invalid\n"
     "   - ``E2BIG`` mean that the attribute value is too big\n"
@@ -1197,8 +1175,6 @@ static char __xattr_doc__[] = \
     "\n"
     ;
 
-#ifdef IS_PY3K
-
 static struct PyModuleDef xattrmodule = {
     PyModuleDef_HEAD_INIT,
     "xattr",
@@ -1212,23 +1188,14 @@ static struct PyModuleDef xattrmodule = {
 PyMODINIT_FUNC
 PyInit_xattr(void)
 
-#else
-#define INITERROR return
-void
-initxattr(void)
-#endif
 {
     PyObject *ns_security = NULL;
     PyObject *ns_system   = NULL;
     PyObject *ns_trusted  = NULL;
     PyObject *ns_user     = NULL;
-#ifdef IS_PY3K
     PyObject *m = PyModule_Create(&xattrmodule);
-#else
-    PyObject *m = Py_InitModule3("xattr", xattr_methods, __xattr_doc__);
-#endif
     if (m==NULL)
-        INITERROR;
+        return NULL;
 
     PyModule_AddStringConstant(m, "__author__", _XATTR_AUTHOR);
     PyModule_AddStringConstant(m, "__contact__", _XATTR_EMAIL);
@@ -1262,11 +1229,7 @@ initxattr(void)
         goto err_out;
     ns_user = NULL;
 
-#ifdef IS_PY3K
     return m;
-#else
-    return;
-#endif
 
  err_out:
     Py_XDECREF(ns_user);
-- 
2.39.5