Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-xattr for openSUSE:Factory 
checked in at 2025-10-18 14:35:56
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-xattr (Old)
 and      /work/SRC/openSUSE:Factory/.python-xattr.new.18484 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-xattr"

Sat Oct 18 14:35:56 2025 rev:29 rq:1311668 version:1.3.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-xattr/python-xattr.changes        
2025-08-27 21:34:41.578716893 +0200
+++ /work/SRC/openSUSE:Factory/.python-xattr.new.18484/python-xattr.changes     
2025-10-18 14:36:49.399930446 +0200
@@ -1,0 +2,32 @@
+Thu Oct 16 10:04:18 UTC 2025 - Daniel Garcia <[email protected]>
+
+- Update to 1.3.0:
+  - Update pyproject.toml license identifier and update CI workflows
+    for Python 3.14, dropping support for Python 3.8. #134
+- 1.2.0
+  - Support optional default value in xattr.get() #130
+  - Ignore macOS 13 SIP attribute 'com.apple.provenance' in tests #131
+- 1.1.4
+  - Update GitHub actions to support upload-artifact@v4 (with the
+    side-effect of building wheels for Python v3.13)
+- 1.1.3
+  - Update GitHub actions to support upload-artifact@v4 #126 #125
+  - Update GitHub actions (with the side-effect of building wheels
+    for Python v3.13) #123 #124
+- 1.1.2
+  - Update GitHub actions to support upload-artifact@v4 #125
+- 1.1.1
+  - Update GitHub actions (with the side-effect of building wheels for
+    Python v3.13) #123 #124
+- 1.1.0
+  - Improve FreeBSD compatibility by stripping "user." prefix on
+    attribute names, but add them when listing attributes so that it
+    behaves similarly to Linux (the xattr.pyxattr_compat module does
+    not add them). Also adds FreeBSD with its ports build of Python
+    3.9 to the CI test suite. #116 #117
+- 1.0.0
+  - Update test & build matrix and use Github Actions as a Trusted
+    Publisher. Drop support for Python 3.7 and earlier (including
+    Python 2). Move tests out of package. #115
+
+-------------------------------------------------------------------

Old:
----
  xattr-0.10.1.tar.gz

New:
----
  xattr-1.3.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-xattr.spec ++++++
--- /var/tmp/diff_new_pack.zZgZkB/_old  2025-10-18 14:36:49.839948853 +0200
+++ /var/tmp/diff_new_pack.zZgZkB/_new  2025-10-18 14:36:49.839948853 +0200
@@ -23,7 +23,7 @@
 %endif
 %{?sle15_python_module_pythons}
 Name:           python-xattr
-Version:        0.10.1
+Version:        1.3.0
 Release:        0
 Summary:        Python wrapper for extended filesystem attributes
 License:        MIT
@@ -32,6 +32,7 @@
 BuildRequires:  %{python_module cffi}
 BuildRequires:  %{python_module devel}
 BuildRequires:  %{python_module pip}
+BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module wheel}
 BuildRequires:  fdupes
@@ -73,7 +74,7 @@
 
 %check
 export LC_ALL=en_US.utf-8
-%pyunittest discover -v
+%pytest_arch tests
 
 %post
 %python_install_alternative xattr

++++++ xattr-0.10.1.tar.gz -> xattr-1.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/CHANGES.txt new/xattr-1.3.0/CHANGES.txt
--- old/xattr-0.10.1/CHANGES.txt        2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/CHANGES.txt 2025-10-13 23:46:56.000000000 +0200
@@ -1,3 +1,42 @@
+Version 1.3.0 released 2025-10-13
+
+* Update pyproject.toml license identifier and update CI workflows for
+  Python 3.14, dropping support for Python 3.8.
+  https://github.com/xattr/xattr/pull/134
+
+Version 1.2.0 released 2025-07-13
+
+* Support optional default value in xattr.get()
+  https://github.com/xattr/xattr/pull/130
+* Ignore macOS 13 SIP attribute 'com.apple.provenance' in tests
+  https://github.com/xattr/xattr/pull/131
+
+Version 1.1.4 released 2025-01-06
+
+* Update GitHub actions to support upload-artifact@v4 (with the side-effect of
+  building wheels for Python v3.13)
+  https://github.com/xattr/xattr/issues/123
+  https://github.com/xattr/xattr/pull/124
+  https://github.com/xattr/xattr/pull/125
+  https://github.com/xattr/xattr/pull/126
+  https://github.com/xattr/xattr/pull/127
+
+Version 1.1.0 released 2024-02-01
+
+* Improve FreeBSD compatibility by stripping "user." prefix on attribute
+  names, but add them when listing attributes so that it behaves similarly
+  to Linux (the xattr.pyxattr_compat module does not add them).
+  Also adds FreeBSD with its ports build of Python 3.9 to the CI test suite.
+  https://github.com/xattr/xattr/issues/116
+  https://github.com/xattr/xattr/pull/117
+
+Version 1.0.0 released 2023-11-19
+
+* Update test & build matrix and use Github Actions as a Trusted Publisher.
+  Drop support for Python 3.7 and earlier (including Python 2).
+  Move tests out of package.
+  https://github.com/xattr/xattr/issues/115
+
 Version 0.10.1 released 2022-12-03
 
 * Update github actions
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/MANIFEST.in new/xattr-1.3.0/MANIFEST.in
--- old/xattr-0.10.1/MANIFEST.in        2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/MANIFEST.in 2025-10-13 23:46:56.000000000 +0200
@@ -3,4 +3,3 @@
 include xattr/*.h
 include *.txt
 include MANIFEST.in
-include xattr/tests/*.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/PKG-INFO new/xattr-1.3.0/PKG-INFO
--- old/xattr-0.10.1/PKG-INFO   2022-12-04 01:11:27.651918000 +0100
+++ new/xattr-1.3.0/PKG-INFO    2025-10-13 23:47:12.821641700 +0200
@@ -1,28 +1,45 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: xattr
-Version: 0.10.1
+Version: 1.3.0
 Summary: Python wrapper for extended filesystem attributes
-Home-page: http://github.com/xattr/xattr
-Author: Bob Ippolito
-Author-email: [email protected]
-License: MIT License
+Author-email: Bob Ippolito <[email protected]>
+Maintainer-email: Bob Ippolito <[email protected]>
+License-Expression: MIT
+Project-URL: Homepage, https://github.com/xattr/xattr
+Project-URL: Repository, https://github.com/xattr/xattr
+Keywords: xattr
 Platform: MacOS X
 Platform: Linux
 Platform: FreeBSD
 Platform: Solaris
 Classifier: Environment :: Console
 Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
 Classifier: Natural Language :: English
 Classifier: Operating System :: MacOS :: MacOS X
 Classifier: Operating System :: POSIX :: Linux
 Classifier: Operating System :: POSIX :: BSD :: FreeBSD
+Classifier: Operating System :: POSIX :: SunOS/Solaris
 Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2
 Classifier: Programming Language :: Python :: 3
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Requires-Python: >=3.9
+Description-Content-Type: text/x-rst
 License-File: LICENSE.txt
+Requires-Dist: cffi>=1.16.0
+Provides-Extra: test
+Requires-Dist: pytest; extra == "test"
+Dynamic: license-file
+Dynamic: platform
 
+xattr
+-----
+
+xattr is a Python wrapper for extended filesystem attributes.
+
+xattr also ships with an `xattr` command line tool for viewing and
+editing extended filesystem attributes. On platforms that support or
+ship with the attr package, you may prefer to use the `getfattr`
+and `setfattr` command line tools from the attr package.
 
 Extended attributes extend the basic attributes of files and directories
 in the file system.  They are stored as name:data pairs associated with
@@ -30,3 +47,19 @@
 
 Extended attributes are currently only available on Darwin 8.0+ (Mac OS X 10.4)
 and Linux 2.6+. Experimental support is included for Solaris and FreeBSD.
+
+Python 3.9+ is required as of v1.3.0.
+
+Versions older than v1.0.0 are no longer supported, but are
+available for use. v0.10.1 is the last version to support older versions
+of Python (including 2.7).
+
+Note: On Linux, custom xattr keys need to be prefixed with the `user`
+namespace, ie: `user.your_attr`.
+
+Note: If you need to read or write Spotlight metadata attributes on macOS,
+see osxmetadata_ which provides a native macOS means to do so without
+directly manipulating extended attributes. osxmetadata also provides access
+to other macOS metadata attributes and extended attributes via xattr.
+
+.. _osxmetadata: https://github.com/RhetTbull/osxmetadata
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/README.rst new/xattr-1.3.0/README.rst
--- old/xattr-0.10.1/README.rst 2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/README.rst  2025-10-13 23:46:56.000000000 +0200
@@ -1,9 +1,6 @@
 xattr
 -----
 
-.. image:: https://travis-ci.org/xattr/xattr.svg?branch=master
-    :target: https://travis-ci.org/xattr/xattr
-
 xattr is a Python wrapper for extended filesystem attributes.
 
 xattr also ships with an `xattr` command line tool for viewing and
@@ -18,6 +15,12 @@
 Extended attributes are currently only available on Darwin 8.0+ (Mac OS X 10.4)
 and Linux 2.6+. Experimental support is included for Solaris and FreeBSD.
 
+Python 3.9+ is required as of v1.3.0.
+
+Versions older than v1.0.0 are no longer supported, but are
+available for use. v0.10.1 is the last version to support older versions
+of Python (including 2.7).
+
 Note: On Linux, custom xattr keys need to be prefixed with the `user`
 namespace, ie: `user.your_attr`.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/TODO.txt new/xattr-1.3.0/TODO.txt
--- old/xattr-0.10.1/TODO.txt   2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/TODO.txt    1970-01-01 01:00:00.000000000 +0100
@@ -1,2 +0,0 @@
-* Write tests
-* Write examples
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/pyproject.toml 
new/xattr-1.3.0/pyproject.toml
--- old/xattr-0.10.1/pyproject.toml     2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/pyproject.toml      2025-10-13 23:46:56.000000000 +0200
@@ -1,6 +1,57 @@
+[build-system]
+requires = [
+    "setuptools>=77.0.3",
+    "wheel",
+    "cffi>=1.16.0",
+]
+build-backend = "setuptools.build_meta"
+
+[project]
+requires-python = ">= 3.9"
+name = "xattr"
+version = "1.3.0"
+readme = "README.rst"
+dependencies = [
+    "cffi>=1.16.0",
+]
+authors = [
+    {name = "Bob Ippolito", email = "[email protected]"},
+]
+maintainers = [
+    {name = "Bob Ippolito", email = "[email protected]"},
+]
+description="Python wrapper for extended filesystem attributes"
+license = "MIT"
+keywords = ["xattr"]
+classifiers = [
+    "Environment :: Console",
+    "Intended Audience :: Developers",
+    "Natural Language :: English",
+    "Operating System :: MacOS :: MacOS X",
+    "Operating System :: POSIX :: Linux",
+    "Operating System :: POSIX :: BSD :: FreeBSD",
+    "Operating System :: POSIX :: SunOS/Solaris",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 3",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+]
+
+[project.urls]
+Homepage = "https://github.com/xattr/xattr";
+Repository = "https://github.com/xattr/xattr";
+
+[project.optional-dependencies]
+test = ["pytest"]
+
+[project.scripts]
+xattr = "xattr.tool:main"
+
+[tool.setuptools]
+packages = ["xattr"]
+
 [tool.cibuildwheel]
 test-requires = "pytest"
-test-command = "pytest {project}/xattr/tests"
+test-command = "pytest {project}/tests"
 
 [tool.cibuildwheel.linux]
 before-all = "yum install -y libffi-devel"
@@ -11,4 +62,4 @@
 
 [[tool.cibuildwheel.overrides]]
 select = "*-musllinux*"
-before-all = "apk add libffi-dev"
\ No newline at end of file
+before-all = "apk add libffi-dev"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/requirements.txt 
new/xattr-1.3.0/requirements.txt
--- old/xattr-0.10.1/requirements.txt   2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/requirements.txt    1970-01-01 01:00:00.000000000 +0100
@@ -1 +0,0 @@
-cffi>=1.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/setup.py new/xattr-1.3.0/setup.py
--- old/xattr-0.10.1/setup.py   2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/setup.py    2025-10-13 23:46:56.000000000 +0200
@@ -1,58 +1,10 @@
 #!/usr/bin/env python
 
-import os
-import sys
-
 from setuptools import setup
 
-VERSION = '0.10.1'
-DESCRIPTION = "Python wrapper for extended filesystem attributes"
-LONG_DESCRIPTION = """
-Extended attributes extend the basic attributes of files and directories
-in the file system.  They are stored as name:data pairs associated with
-file system objects (files, directories, symlinks, etc).
-
-Extended attributes are currently only available on Darwin 8.0+ (Mac OS X 10.4)
-and Linux 2.6+. Experimental support is included for Solaris and FreeBSD.
-"""
-
-CLASSIFIERS = list(filter(bool, map(str.strip,
-"""
-Environment :: Console
-Intended Audience :: Developers
-License :: OSI Approved :: MIT License
-Natural Language :: English
-Operating System :: MacOS :: MacOS X
-Operating System :: POSIX :: Linux
-Operating System :: POSIX :: BSD :: FreeBSD
-Programming Language :: Python
-Programming Language :: Python :: 2
-Programming Language :: Python :: 3
-Topic :: Software Development :: Libraries :: Python Modules
-""".splitlines())))
-
 setup(
-    name="xattr",
-    version=VERSION,
-    description=DESCRIPTION,
-    long_description=LONG_DESCRIPTION,
-    classifiers=CLASSIFIERS,
-    author="Bob Ippolito",
-    author_email="[email protected]",
-    url="http://github.com/xattr/xattr";,
-    license="MIT License",
-    packages=['xattr'],
     ext_package='xattr',
     platforms=['MacOS X', 'Linux', 'FreeBSD', 'Solaris'],
-    entry_points={
-        'console_scripts': [
-            "xattr = xattr.tool:main",
-        ],
-    },
-    # Keep this in sync with pyproject.toml
-    install_requires=["cffi>=1.0"],
-    setup_requires=["cffi>=1.0"],
     cffi_modules=["xattr/lib_build.py:ffi"],
-    test_suite="xattr.tests.all_tests_suite",
     zip_safe=False,
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/tests/test_tool.py 
new/xattr-1.3.0/tests/test_tool.py
--- old/xattr-0.10.1/tests/test_tool.py 1970-01-01 01:00:00.000000000 +0100
+++ new/xattr-1.3.0/tests/test_tool.py  2025-10-13 23:46:56.000000000 +0200
@@ -0,0 +1,117 @@
+import contextlib
+import errno
+import io
+import os
+import shutil
+import sys
+import tempfile
+import unittest
+import uuid
+
+import xattr
+import xattr.tool
+
+
+class TestTool(unittest.TestCase):
+    def setUp(self):
+        self.test_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, self.test_dir)
+
+        orig_stdout = sys.stdout
+
+        def unpatch_stdout(sys=sys, orig_stdout=orig_stdout):
+            sys.stdout = orig_stdout
+
+        self.addCleanup(unpatch_stdout)
+        sys.stdout = self.mock_stdout = io.StringIO()
+
+    def getoutput(self):
+        value = self.mock_stdout.getvalue()
+        self.mock_stdout.seek(0)
+        self.mock_stdout.truncate(0)
+        return value
+
+    @contextlib.contextmanager
+    def temp_file(self):
+        test_file = os.path.join(self.test_dir, str(uuid.uuid4()))
+        fd = os.open(test_file, os.O_CREAT | os.O_WRONLY)
+        try:
+            yield test_file, fd
+        finally:
+            os.close(fd)
+
+    def set_xattr(self, fd, name, value):
+        try:
+            xattr.setxattr(fd, name, value)
+        except OSError as e:
+            if e.errno == errno.ENOTSUP:
+                raise unittest.SkipTest('xattrs are not supported')
+            raise
+
+    def test_utf8(self):
+        with self.temp_file() as (file_path, fd):
+            self.set_xattr(fd, 'user.test-utf8',
+                           u'\N{SNOWMAN}'.encode('utf8'))
+            self.set_xattr(fd, 'user.test-utf8-and-nul',
+                           u'\N{SNOWMAN}\0'.encode('utf8'))
+
+        xattr.tool.main(['prog', '-p', 'user.test-utf8', file_path])
+        self.assertEqual(u'\N{SNOWMAN}\n', self.getoutput())
+
+        xattr.tool.main(['prog', '-p', 'user.test-utf8-and-nul', file_path])
+        self.assertEqual(u'''
+0000   E2 98 83 00                                        ....
+
+'''.lstrip(), self.getoutput())
+
+        xattr.tool.main(['prog', '-l', file_path])
+        output = self.getoutput()
+        self.assertIn(u'user.test-utf8: \N{SNOWMAN}\n', output)
+        self.assertIn(u'''
+user.test-utf8-and-nul:
+0000   E2 98 83 00                                        ....
+
+'''.lstrip(), output)
+
+    def test_non_utf8(self):
+        with self.temp_file() as (file_path, fd):
+            self.set_xattr(fd, 'user.test-not-utf8', b'cannot\xffdecode')
+
+        xattr.tool.main(['prog', '-p', 'user.test-not-utf8', file_path])
+        self.assertEqual(u'''
+0000   63 61 6E 6E 6F 74 FF 64 65 63 6F 64 65             cannot.decode
+
+'''.lstrip(), self.getoutput())
+
+        xattr.tool.main(['prog', '-l', file_path])
+        self.assertIn(u'''
+user.test-not-utf8:
+0000   63 61 6E 6E 6F 74 FF 64 65 63 6F 64 65             cannot.decode
+
+'''.lstrip(), self.getoutput())
+
+    def test_nul(self):
+        with self.temp_file() as (file_path, fd):
+            self.set_xattr(fd, 'user.test', b'foo\0bar')
+            self.set_xattr(fd, 'user.test-long',
+                           b'some rather long value with\0nul\0chars in it')
+
+        xattr.tool.main(['prog', '-p', 'user.test', file_path])
+        self.assertEqual(u'''
+0000   66 6F 6F 00 62 61 72                               foo.bar
+
+'''.lstrip(), self.getoutput())
+
+        xattr.tool.main(['prog', '-p', 'user.test-long', file_path])
+        self.assertEqual(u'''
+0000   73 6F 6D 65 20 72 61 74 68 65 72 20 6C 6F 6E 67    some rather long
+0010   20 76 61 6C 75 65 20 77 69 74 68 00 6E 75 6C 00     value with.nul.
+0020   63 68 61 72 73 20 69 6E 20 69 74                   chars in it
+
+'''.lstrip(), self.getoutput())
+
+        xattr.tool.main(['prog', '-l', file_path])
+        self.assertIn(u'''
+0000   66 6F 6F 00 62 61 72                               foo.bar
+
+'''.lstrip(), self.getoutput())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/tests/test_xattr.py 
new/xattr-1.3.0/tests/test_xattr.py
--- old/xattr-0.10.1/tests/test_xattr.py        1970-01-01 01:00:00.000000000 
+0100
+++ new/xattr-1.3.0/tests/test_xattr.py 2025-10-13 23:46:56.000000000 +0200
@@ -0,0 +1,178 @@
+import os
+import sys
+import unittest
+from unittest import TestCase
+from tempfile import mkdtemp, NamedTemporaryFile
+
+import xattr
+from xattr import pyxattr_compat
+
+
+class BaseTestXattr(object):
+    # TESTDIR for temporary files usually defaults to "/tmp",
+    # which may not have XATTR support (e.g. tmpfs);
+    # manual override here.
+    TESTDIR = None
+
+    def test_update(self):
+        x = xattr.xattr(self.tempfile)
+        attrs = {
+            'user.test.key1': b'test_value1',
+            'user.test.key2': b'test_value2'
+        }
+        x.update(attrs)
+        for k, v in attrs.items():
+            self.assertEqual(x[k], v)
+
+    def test_attr(self):
+        x = xattr.xattr(self.tempfile)
+
+        # Solaris 11 and forward contain system attributes (file flags) in
+        # extended attributes present on all files, so cons them up into a
+        # comparison dict.
+        d = {}
+        if sys.platform == 'sunos5' and 'SUNWattr_ro' in x:
+            d['SUNWattr_ro'] = x['SUNWattr_ro']
+            d['SUNWattr_rw'] = x['SUNWattr_rw']
+
+        # macOS 13.x SIP adds this attribute to all files
+        # POSIX platforms may have system.posix_acl_default
+        # SELinux systems use an attribute which must be accounted for
+        IGNORE_KEYS = ['com.apple.provenance', 'system.posix_acl_default', 
'security.selinux']
+        for k in IGNORE_KEYS:
+            if x.has_key(k):
+                d[k] = x[k]
+
+        self.assertEqual(list(x.keys()), list(d.keys()))
+        self.assertEqual(list(x.list()), list(d.keys()))
+        self.assertEqual(dict(x), d)
+
+        x['user.sopal'] = b'foo'
+        x['user.sop.foo'] = b'bar'
+        x[u'user.\N{SNOWMAN}'] = b'not a snowman'
+        del x
+
+        x = xattr.xattr(self.tempfile)
+        attrs = set(x.list())
+        self.assertTrue('user.sopal' in x)
+        self.assertTrue(u'user.sopal' in attrs)
+        self.assertEqual(x['user.sopal'], b'foo')
+        self.assertTrue('user.sop.foo' in x)
+        self.assertTrue(u'user.sop.foo' in attrs)
+        self.assertEqual(x['user.sop.foo'], b'bar')
+        self.assertTrue(u'user.\N{SNOWMAN}' in x)
+        self.assertTrue(u'user.\N{SNOWMAN}' in attrs)
+        self.assertEqual(x[u'user.\N{SNOWMAN}'],
+                         b'not a snowman')
+
+        del x[u'user.\N{SNOWMAN}']
+        del x['user.sop.foo']
+        del x
+
+        x = xattr.xattr(self.tempfile)
+        self.assertTrue('user.sop.foo' not in x)
+
+    def test_setxattr_unicode_error(self):
+        x = xattr.xattr(self.tempfile)
+        def assign():
+            x['abc'] = u'abc'
+        self.assertRaises(TypeError, assign)
+
+        msg = "Value must be bytes, str was passed."
+
+        try:
+            assign()
+        except TypeError:
+            e = sys.exc_info()[1]
+            self.assertEqual(str(e), msg)
+
+    def test_symlink_attrs(self):
+        symlinkPath = self.tempfilename + '.link'
+        os.symlink(self.tempfilename, symlinkPath)
+        try:
+            symlink = xattr.xattr(symlinkPath, options=xattr.XATTR_NOFOLLOW)
+            realfile = xattr.xattr(self.tempfilename)
+            try:
+                symlink['user.islink'] = b'true'
+            except IOError:
+                # Solaris, Linux don't support extended attributes on symlinks
+                raise unittest.SkipTest("XATTRs on symlink not allowed"
+                                        " on filesystem/platform")
+            realfile_xattrs = dict(realfile)
+            realfile_xattrs.pop('com.apple.provenance', None)
+            self.assertEqual(realfile_xattrs, {})
+            self.assertEqual(symlink['user.islink'], b'true')
+        finally:
+            os.remove(symlinkPath)
+
+    def test_freebsd_compat_prefix(self):
+        if not sys.platform.startswith('freebsd'):
+            raise unittest.SkipTest("FreeBSD only")
+        x = xattr.xattr(self.tempfile)
+        # Test setting and getting without prefix
+        x['test'] = b'test'
+        self.assertEqual(x['test'], b'test')
+        self.assertEqual(x['user.test'], b'test')
+        self.assertEqual(x['user.user.test'], b'test')
+        # Test setting with prefix
+        x['user.test'] = b'test2'
+        self.assertEqual(x['test'], b'test2')
+        self.assertEqual(x['user.test'], b'test2')
+        # Test that listing returns the prefixed attribute
+        self.assertEqual(x.list(), ['user.test'])
+        # Test that listing with pyxattr_compat does not prefix or decode
+        self.assertEqual(pyxattr_compat.list(self.tempfile), [b'test'])
+
+    def test_get_with_default(self):
+        x = xattr.xattr(self.tempfile)
+
+        # Test backwards compatibility - exception raised when no default
+        with self.assertRaises(OSError):
+            x.get('user.nonexistent')
+
+        # Test default=None returns None
+        result = x.get('user.nonexistent', default=None)
+        self.assertIsNone(result)
+
+        # Test default parameter with non-None values
+        result = x.get('user.nonexistent', default=b'default_value')
+        self.assertEqual(result, b'default_value')
+
+        # Test default parameter with empty string
+        result = x.get('user.nonexistent', default=b'')
+        self.assertEqual(result, b'')
+
+        # Test that existing attributes ignore default
+        x['user.existing'] = b'real_value'
+        result = x.get('user.existing', default=b'should_be_ignored')
+        self.assertEqual(result, b'real_value')
+
+        # Test that default is keyword-only (positional should fail)
+        with self.assertRaises(TypeError):
+            x.get('user.nonexistent', 0, b'default_value')
+
+
+class TestFile(TestCase, BaseTestXattr):
+    def setUp(self):
+        self.tempfile = NamedTemporaryFile(dir=self.TESTDIR)
+        self.tempfilename = self.tempfile.name
+
+    def tearDown(self):
+        self.tempfile.close()
+
+
+class TestDir(TestCase, BaseTestXattr):
+    def setUp(self):
+        self.tempfile = mkdtemp(dir=self.TESTDIR)
+        self.tempfilename = self.tempfile
+
+    def tearDown(self):
+        os.rmdir(self.tempfile)
+
+
+class TestFileWithSurrogates(TestFile):
+    def setUp(self):
+        if not (sys.platform.startswith('linux') or 
sys.platform.startswith('freebsd')):
+            raise unittest.SkipTest('Files with invalid encoded names are only 
supported under Linux and FreeBSD')
+        self.tempfile = 
NamedTemporaryFile(prefix=b'invalid-\xe9'.decode('utf8', 'surrogateescape'), 
dir=self.TESTDIR)
+        self.tempfilename = self.tempfile.name
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr/__init__.py 
new/xattr-1.3.0/xattr/__init__.py
--- old/xattr-0.10.1/xattr/__init__.py  2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/xattr/__init__.py   2025-10-13 23:46:56.000000000 +0200
@@ -7,12 +7,14 @@
 that exposes these extended attributes.
 """
 
-__version__ = '0.10.1'
+__version__ = '1.3.0'
+
+import errno
 
-from .compat import integer_types
 from .lib import (XATTR_NOFOLLOW, XATTR_CREATE, XATTR_REPLACE,
     XATTR_NOSECURITY, XATTR_MAXNAMELEN, XATTR_FINDERINFO_NAME,
-    XATTR_RESOURCEFORK_NAME, _getxattr, _fgetxattr, _setxattr, _fsetxattr,
+    XATTR_RESOURCEFORK_NAME, XATTR_COMPAT_USER_PREFIX,
+    _getxattr, _fgetxattr, _setxattr, _fsetxattr,
     _removexattr, _fremovexattr, _listxattr, _flistxattr)
 
 
@@ -23,6 +25,14 @@
 ]
 
 
+_SENTINEL_MISSING = object()
+ERRNO_MISSING = set()
+try:
+    ERRNO_MISSING.add(errno.ENOATTR)
+except AttributeError:
+    ERRNO_MISSING.add(errno.ENODATA)
+
+
 class xattr(object):
     """
     A wrapper for paths or file descriptors to access
@@ -47,26 +57,31 @@
             self.value = obj
 
     def __repr__(self):
-        if isinstance(self.value, integer_types):
+        if isinstance(self.value, int):
             flavor = "fd"
         else:
             flavor = "file"
         return "<%s %s=%r>" % (type(self).__name__, flavor, self.value)
 
     def _call(self, name_func, fd_func, *args):
-        if isinstance(self.value, integer_types):
+        if isinstance(self.value, int):
             return fd_func(self.value, *args)
         else:
             return name_func(self.value, *args)
 
-    def get(self, name, options=0):
+    def get(self, name, options=0, *, default=_SENTINEL_MISSING):
         """
         Retrieve the extended attribute ``name`` as a ``str``.
-        Raises ``IOError`` on failure.
+        Raises ``IOError`` on failure unless default is provided.
 
         See x-man-page://2/getxattr for options and possible errors.
         """
-        return self._call(_getxattr, _fgetxattr, name, 0, 0, options | 
self.options)
+        try:
+            return self._call(_getxattr, _fgetxattr, name, 0, 0, options | 
self.options)
+        except OSError as e:
+            if default is not _SENTINEL_MISSING and e.errno in ERRNO_MISSING:
+                return default
+            raise
 
     def set(self, name, value, options=0):
         """
@@ -95,7 +110,7 @@
         """
         res = self._call(_listxattr, _flistxattr, options | 
self.options).split(b'\x00')
         res.pop()
-        return [s.decode('utf-8') for s in res]
+        return [XATTR_COMPAT_USER_PREFIX + s.decode('utf-8') for s in res]
 
     # dict-like methods
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr/compat.py 
new/xattr-1.3.0/xattr/compat.py
--- old/xattr-0.10.1/xattr/compat.py    2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/xattr/compat.py     1970-01-01 01:00:00.000000000 +0100
@@ -1,31 +0,0 @@
-"""Python 3 compatibility shims
-"""
-import os
-import sys
-import codecs
-
-if sys.version_info[0] < 3:
-    integer_types = (int, long)
-    text_type = unicode
-    binary_type = str
-else:
-    integer_types = (int,)
-    text_type = str
-    binary_type = bytes
-
-fs_encoding = sys.getfilesystemencoding()
-fs_errors = 'strict'
-if fs_encoding != 'mbcs':
-    try:
-        codecs.lookup('surrogateescape')
-        fs_errors = 'surrogateescape'
-    except LookupError:
-        pass
-try:
-    fs_encode = os.fsencode
-except AttributeError:
-    def fs_encode(val):
-        if not isinstance(val, bytes):
-            return val.encode(fs_encoding, fs_errors)
-        else:
-            return val
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr/lib.py 
new/xattr-1.3.0/xattr/lib.py
--- old/xattr-0.10.1/xattr/lib.py       2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/xattr/lib.py        2025-10-13 23:46:56.000000000 +0200
@@ -1,19 +1,14 @@
 import os
 import sys
 
-from .compat import fs_encode
-
-try:
-    from ._lib import lib, ffi
-except ImportError:
-    from .lib_build import ffi, c_source
-    lib = ffi.verify(c_source)
+from ._lib import lib, ffi
 
 XATTR_NOFOLLOW = lib.XATTR_XATTR_NOFOLLOW
 XATTR_CREATE = lib.XATTR_XATTR_CREATE
 XATTR_REPLACE = lib.XATTR_XATTR_REPLACE
 XATTR_NOSECURITY = lib.XATTR_XATTR_NOSECURITY
 XATTR_MAXNAMELEN = lib.XATTR_MAXNAMELEN
+XATTR_COMPAT_USER_PREFIX = lib.XATTR_COMPAT_ADD_USER_PREFIX and "user." or ""
 
 XATTR_FINDERINFO_NAME = "com.apple.FinderInfo"
 XATTR_RESOURCEFORK_NAME = "com.apple.ResourceFork"
@@ -39,8 +34,8 @@
     """
     getxattr(path, name, size=0, position=0, options=0) -> str
     """
-    path = fs_encode(path)
-    name = fs_encode(name)
+    path = os.fsencode(path)
+    name = os.fsencode(name)
     if size == 0:
         res = lib.xattr_getxattr(path, name, ffi.NULL, 0, position, options)
         if res == -1:
@@ -57,7 +52,7 @@
     """
     fgetxattr(fd, name, size=0, position=0, options=0) -> str
     """
-    name = fs_encode(name)
+    name = os.fsencode(name)
     if size == 0:
         res = lib.xattr_fgetxattr(fd, name, ffi.NULL, 0, position, options)
         if res == -1:
@@ -75,8 +70,8 @@
     setxattr(path, name, value, position=0, options=0) -> None
     """
     _check_bytes(value)
-    path = fs_encode(path)
-    name = fs_encode(name)
+    path = os.fsencode(path)
+    name = os.fsencode(name)
     res = lib.xattr_setxattr(path, name, value, len(value), position, options)
     if res:
         raise error(path)
@@ -87,7 +82,7 @@
     fsetxattr(fd, name, value, position=0, options=0) -> None
     """
     _check_bytes(value)
-    name = fs_encode(name)
+    name = os.fsencode(name)
     res = lib.xattr_fsetxattr(fd, name, value, len(value), position, options)
     if res:
         raise error()
@@ -97,8 +92,8 @@
     """
     removexattr(path, name, options=0) -> None
     """
-    path = fs_encode(path)
-    name = fs_encode(name)
+    path = os.fsencode(path)
+    name = os.fsencode(name)
     res = lib.xattr_removexattr(path, name, options)
     if res:
         raise error(path)
@@ -108,7 +103,7 @@
     """
     fremovexattr(fd, name, options=0) -> None
     """
-    name = fs_encode(name)
+    name = os.fsencode(name)
     res = lib.xattr_fremovexattr(fd, name, options)
     if res:
         raise error()
@@ -118,7 +113,7 @@
     """
     listxattr(path, options=0) -> str
     """
-    path = fs_encode(path)
+    path = os.fsencode(path)
     res = lib.xattr_listxattr(path, ffi.NULL, 0, options)
     if res == -1:
         raise error(path)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr/lib_build.c 
new/xattr-1.3.0/xattr/lib_build.c
--- old/xattr-0.10.1/xattr/lib_build.c  2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/xattr/lib_build.c   2025-10-13 23:46:56.000000000 +0200
@@ -14,7 +14,22 @@
 
 #ifdef __FreeBSD__
 
+#define XATTR_COMPAT_ADD_USER_PREFIX 1
+
 /* FreeBSD compatibility API */
+
+/* FreeBSD specifies the namespace separately from the attribute name.
+ * Hence when we set names in the "user" namespace, we should
+ * strip the "user." prefix off said name.
+ *
+ * Normally, this would just be an asthetic difference, but recent versions
+ * of ZFS now refuse to set on FreeBSD any attribute name starting with the
+ * "user." prefix (this is to allow filesystems to be compatible across
+ * FreeBSD and Linux systems without ambiguity.)
+ *
+ * More details here: 
https://github.com/openzfs/zfs/commit/5c0061345b824eebe7a6578528f873ffcaae1cdd
+ */
+
 #define XATTR_XATTR_NOFOLLOW 0x0001
 #define XATTR_XATTR_CREATE 0x0002
 #define XATTR_XATTR_REPLACE 0x0004
@@ -23,17 +38,30 @@
 #define XATTR_CREATE 0x1
 #define XATTR_REPLACE 0x2
 
-/* Converts a freebsd format attribute list into a NULL terminated list.
+static const char *strip_user_prefix(const char *name)
+{
+    char *stripped_name = (char *)name;
+
+    while (!strncmp(stripped_name, "user.", 5)) {
+        stripped_name += 5;
+    }
+
+    return stripped_name;
+}
+
+/* Converts a FreeBSD format attribute list into a NULL terminated list.
  * The first byte is the length of the following attribute.
+ * The "user." prefix is added for compatibility with other platforms in the
+ * Python interface.
  */
 static void convert_bsd_list(char *namebuf, size_t size)
 {
     size_t offset = 0;
-    while(offset < size) {
-        int length = (int) (unsigned char)namebuf[offset];
-        memmove(namebuf+offset, namebuf+offset+1, length);
-        namebuf[offset+length] = '\0';
-        offset += length+1;
+    while (offset < size) {
+        size_t length = (size_t)(unsigned char)namebuf[offset];
+        memmove(namebuf + offset, namebuf + offset + 1, length);
+        namebuf[offset + length] = '\0';
+        offset += length + 1;
     }
 }
 
@@ -48,11 +76,11 @@
 
     if (options & XATTR_XATTR_NOFOLLOW) {
         return extattr_get_link(path, EXTATTR_NAMESPACE_USER,
-                                name, value, size);
+                                strip_user_prefix(name), value, size);
     }
     else {
         return extattr_get_file(path, EXTATTR_NAMESPACE_USER,
-                                name, value, size);
+                                strip_user_prefix(name), value, size);
     }
 }
 
@@ -60,7 +88,7 @@
                               void *value, ssize_t size, u_int32_t position,
                               int options)
 {
-    int rv = 0;
+    size_t rv = 0;
     int nofollow;
 
     if (position != 0) {
@@ -83,14 +111,14 @@
 
     if (nofollow) {
         rv = extattr_set_link(path, EXTATTR_NAMESPACE_USER,
-                                name, value, size);
+                                strip_user_prefix(name), value, size);
     }
     else {
         rv = extattr_set_file(path, EXTATTR_NAMESPACE_USER,
-                                name, value, size);
+                                strip_user_prefix(name), value, size);
     }
 
-    /* freebsd returns the written length on success, not zero. */
+    /* FreeBSD returns the written length on success, not zero. */
     if (rv >= 0) {
         return 0;
     }
@@ -108,14 +136,13 @@
     }
 
     if (options & XATTR_XATTR_NOFOLLOW) {
-        return extattr_delete_link(path, EXTATTR_NAMESPACE_USER, name);
+        return extattr_delete_link(path, EXTATTR_NAMESPACE_USER, 
strip_user_prefix(name));
     }
     else {
-        return extattr_delete_file(path, EXTATTR_NAMESPACE_USER, name);
+        return extattr_delete_file(path, EXTATTR_NAMESPACE_USER, 
strip_user_prefix(name));
     }
 }
 
-
 static ssize_t xattr_listxattr(const char *path, char *namebuf,
                                size_t size, int options)
 {
@@ -151,14 +178,14 @@
         return -1;
     }
     else {
-        return extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, name, value, size);
+        return extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, 
strip_user_prefix(name), value, size);
     }
 }
 
 static ssize_t xattr_fsetxattr(int fd, const char *name, void *value,
                                ssize_t size, u_int32_t position, int options)
 {
-    int rv = 0;
+    size_t rv = 0;
     int nofollow;
 
     if (position != 0) {
@@ -170,7 +197,7 @@
 
     if (options == XATTR_XATTR_CREATE ||
         options == XATTR_XATTR_REPLACE) {
-        /* freebsd noop */
+        /* FreeBSD noop */
     }
     else if (options != 0) {
         return -1;
@@ -181,10 +208,10 @@
     }
     else {
         rv = extattr_set_fd(fd, EXTATTR_NAMESPACE_USER,
-                            name, value, size);
+                            strip_user_prefix(name), value, size);
     }
 
-    /* freebsd returns the written length on success, not zero. */
+    /* FreeBSD returns the written length on success, not zero. */
     if (rv >= 0) {
         return 0;
     }
@@ -205,7 +232,7 @@
         return -1;
     }
     else {
-        return extattr_delete_fd(fd, EXTATTR_NAMESPACE_USER, name);
+        return extattr_delete_fd(fd, EXTATTR_NAMESPACE_USER, 
strip_user_prefix(name));
     }
 }
 
@@ -563,3 +590,7 @@
 #ifndef XATTR_MAXNAMELEN
 #define XATTR_MAXNAMELEN 127
 #endif
+
+#ifndef XATTR_COMPAT_ADD_USER_PREFIX
+#define XATTR_COMPAT_ADD_USER_PREFIX 0
+#endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr/lib_build.h 
new/xattr-1.3.0/xattr/lib_build.h
--- old/xattr-0.10.1/xattr/lib_build.h  2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/xattr/lib_build.h   2025-10-13 23:46:56.000000000 +0200
@@ -3,6 +3,7 @@
 #define XATTR_XATTR_REPLACE ...
 #define XATTR_XATTR_NOSECURITY ...
 #define XATTR_MAXNAMELEN ...
+#define XATTR_COMPAT_ADD_USER_PREFIX ...
 
 ssize_t xattr_getxattr(const char *, const char *, void *, ssize_t, uint32_t, 
int);
 ssize_t xattr_fgetxattr(int, const char *, void *, ssize_t, uint32_t, int);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr/pyxattr_compat.py 
new/xattr-1.3.0/xattr/pyxattr_compat.py
--- old/xattr-0.10.1/xattr/pyxattr_compat.py    2022-12-04 01:11:15.000000000 
+0100
+++ new/xattr-1.3.0/xattr/pyxattr_compat.py     2025-10-13 23:46:56.000000000 
+0200
@@ -8,7 +8,6 @@
 
 import sys
 
-from .compat import (binary_type, integer_types, text_type)
 from .lib import (XATTR_NOFOLLOW, XATTR_CREATE, XATTR_REPLACE,
     XATTR_NOSECURITY, XATTR_MAXNAMELEN, XATTR_FINDERINFO_NAME,
     XATTR_RESOURCEFORK_NAME, _getxattr, _fgetxattr, _setxattr, _fsetxattr,
@@ -30,13 +29,13 @@
 _fsencoding = sys.getfilesystemencoding()
 
 def _call(item, name_func, fd_func, *args):
-    if isinstance(item, integer_types):
+    if isinstance(item, int):
         return fd_func(item, *args)
     elif hasattr(item, 'fileno'):
         return fd_func(item.fileno(), *args)
-    elif isinstance(item, binary_type):
+    elif isinstance(item, bytes):
         return name_func(item, *args)
-    elif isinstance(item, text_type):
+    elif isinstance(item, str):
         item = item.encode(_fsencoding)
         return name_func(item, *args)
     else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr/tests/__init__.py 
new/xattr-1.3.0/xattr/tests/__init__.py
--- old/xattr-0.10.1/xattr/tests/__init__.py    2022-12-04 01:11:15.000000000 
+0100
+++ new/xattr-1.3.0/xattr/tests/__init__.py     1970-01-01 01:00:00.000000000 
+0100
@@ -1,22 +0,0 @@
-import os
-import sys
-import unittest
-
-
-def all_tests_suite():
-    suite = unittest.TestLoader().loadTestsFromNames([
-        'xattr.tests.test_xattr',
-        'xattr.tests.test_tool',
-    ])
-    return suite
-
-
-def main():
-    runner = unittest.TextTestRunner()
-    suite = all_tests_suite()
-    runner.run(suite)
-
-
-if __name__ == '__main__':
-    sys.path.insert(0, 
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
-    main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr/tests/test_tool.py 
new/xattr-1.3.0/xattr/tests/test_tool.py
--- old/xattr-0.10.1/xattr/tests/test_tool.py   2022-12-04 01:11:15.000000000 
+0100
+++ new/xattr-1.3.0/xattr/tests/test_tool.py    1970-01-01 01:00:00.000000000 
+0100
@@ -1,117 +0,0 @@
-import contextlib
-import errno
-import io
-import os
-import shutil
-import sys
-import tempfile
-import unittest
-import uuid
-
-import xattr
-import xattr.tool
-
-
-class TestTool(unittest.TestCase):
-    def setUp(self):
-        self.test_dir = tempfile.mkdtemp()
-        self.addCleanup(shutil.rmtree, self.test_dir)
-
-        orig_stdout = sys.stdout
-
-        def unpatch_stdout(sys=sys, orig_stdout=orig_stdout):
-            sys.stdout = orig_stdout
-
-        self.addCleanup(unpatch_stdout)
-        sys.stdout = self.mock_stdout = io.StringIO()
-
-    def getoutput(self):
-        value = self.mock_stdout.getvalue()
-        self.mock_stdout.seek(0)
-        self.mock_stdout.truncate(0)
-        return value
-
-    @contextlib.contextmanager
-    def temp_file(self):
-        test_file = os.path.join(self.test_dir, str(uuid.uuid4()))
-        fd = os.open(test_file, os.O_CREAT | os.O_WRONLY)
-        try:
-            yield test_file, fd
-        finally:
-            os.close(fd)
-
-    def set_xattr(self, fd, name, value):
-        try:
-            xattr.setxattr(fd, name, value)
-        except OSError as e:
-            if e.errno == errno.ENOTSUP:
-                raise unittest.SkipTest('xattrs are not supported')
-            raise
-
-    def test_utf8(self):
-        with self.temp_file() as (file_path, fd):
-            self.set_xattr(fd, 'user.test-utf8',
-                           u'\N{SNOWMAN}'.encode('utf8'))
-            self.set_xattr(fd, 'user.test-utf8-and-nul',
-                           u'\N{SNOWMAN}\0'.encode('utf8'))
-
-        xattr.tool.main(['prog', '-p', 'user.test-utf8', file_path])
-        self.assertEqual(u'\N{SNOWMAN}\n', self.getoutput())
-
-        xattr.tool.main(['prog', '-p', 'user.test-utf8-and-nul', file_path])
-        self.assertEqual(u'''
-0000   E2 98 83 00                                        ....
-
-'''.lstrip(), self.getoutput())
-
-        xattr.tool.main(['prog', '-l', file_path])
-        output = self.getoutput()
-        self.assertIn(u'user.test-utf8: \N{SNOWMAN}\n', output)
-        self.assertIn(u'''
-user.test-utf8-and-nul:
-0000   E2 98 83 00                                        ....
-
-'''.lstrip(), output)
-
-    def test_non_utf8(self):
-        with self.temp_file() as (file_path, fd):
-            self.set_xattr(fd, 'user.test-not-utf8', b'cannot\xffdecode')
-
-        xattr.tool.main(['prog', '-p', 'user.test-not-utf8', file_path])
-        self.assertEqual(u'''
-0000   63 61 6E 6E 6F 74 FF 64 65 63 6F 64 65             cannot.decode
-
-'''.lstrip(), self.getoutput())
-
-        xattr.tool.main(['prog', '-l', file_path])
-        self.assertIn(u'''
-user.test-not-utf8:
-0000   63 61 6E 6E 6F 74 FF 64 65 63 6F 64 65             cannot.decode
-
-'''.lstrip(), self.getoutput())
-
-    def test_nul(self):
-        with self.temp_file() as (file_path, fd):
-            self.set_xattr(fd, 'user.test', b'foo\0bar')
-            self.set_xattr(fd, 'user.test-long',
-                           b'some rather long value with\0nul\0chars in it')
-
-        xattr.tool.main(['prog', '-p', 'user.test', file_path])
-        self.assertEqual(u'''
-0000   66 6F 6F 00 62 61 72                               foo.bar
-
-'''.lstrip(), self.getoutput())
-
-        xattr.tool.main(['prog', '-p', 'user.test-long', file_path])
-        self.assertEqual(u'''
-0000   73 6F 6D 65 20 72 61 74 68 65 72 20 6C 6F 6E 67    some rather long
-0010   20 76 61 6C 75 65 20 77 69 74 68 00 6E 75 6C 00     value with.nul.
-0020   63 68 61 72 73 20 69 6E 20 69 74                   chars in it
-
-'''.lstrip(), self.getoutput())
-
-        xattr.tool.main(['prog', '-l', file_path])
-        self.assertIn(u'''
-0000   66 6F 6F 00 62 61 72                               foo.bar
-
-'''.lstrip(), self.getoutput())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr/tests/test_xattr.py 
new/xattr-1.3.0/xattr/tests/test_xattr.py
--- old/xattr-0.10.1/xattr/tests/test_xattr.py  2022-12-04 01:11:15.000000000 
+0100
+++ new/xattr-1.3.0/xattr/tests/test_xattr.py   1970-01-01 01:00:00.000000000 
+0100
@@ -1,151 +0,0 @@
-import os
-import sys
-import unittest
-from unittest import TestCase
-from tempfile import mkdtemp, NamedTemporaryFile
-
-import xattr
-
-
-class BaseTestXattr(object):
-    # TESTDIR for temporary files usually defaults to "/tmp",
-    # which may not have XATTR support (e.g. tmpfs);
-    # manual override here.
-    TESTDIR = None
-
-    def test_attr_fs_encoding_unicode(self):
-        # Not using setlocale(LC_ALL, ..) to set locale because
-        # sys.getfilesystemencoding() implementation falls back
-        # to user's preferred locale by calling setlocale(LC_ALL, '').
-        xattr.compat.fs_encoding = 'UTF-8'
-        self._test_attr()
-
-    def test_attr_fs_encoding_ascii(self):
-        xattr.compat.fs_encoding = 'US-ASCII'
-        if sys.version_info[0] < 3:
-            with self.assertRaises(UnicodeEncodeError):
-                self._test_attr()
-        else:
-            self._test_attr()
-
-    def test_update(self):
-        x = xattr.xattr(self.tempfile)
-        attrs = {
-            'user.test.key1': b'test_value1',
-            'user.test.key2': b'test_value2'
-        }
-        x.update(attrs)
-        for k, v in attrs.items():
-            self.assertEqual(x[k], v)
-
-    def _test_attr(self):
-        x = xattr.xattr(self.tempfile)
-
-        # Solaris 11 and forward contain system attributes (file flags) in
-        # extended attributes present on all files, so cons them up into a
-        # comparison dict.
-        d = {}
-        if sys.platform == 'sunos5' and 'SUNWattr_ro' in x:
-            d['SUNWattr_ro'] = x['SUNWattr_ro']
-            d['SUNWattr_rw'] = x['SUNWattr_rw']
-
-        # SELinux systems use an attribute which must be accounted for
-        if sys.platform.startswith('linux') and 'security.selinux' in x:
-            d['security.selinux'] = x['security.selinux']
-
-        self.assertEqual(list(x.keys()), list(d.keys()))
-        self.assertEqual(list(x.list()), list(d.keys()))
-        self.assertEqual(dict(x), d)
-
-        x['user.sopal'] = b'foo'
-        x['user.sop.foo'] = b'bar'
-        x[u'user.\N{SNOWMAN}'] = b'not a snowman'
-        del x
-
-        x = xattr.xattr(self.tempfile)
-        attrs = set(x.list())
-        self.assertTrue('user.sopal' in x)
-        self.assertTrue(u'user.sopal' in attrs)
-        self.assertEqual(x['user.sopal'], b'foo')
-        self.assertTrue('user.sop.foo' in x)
-        self.assertTrue(u'user.sop.foo' in attrs)
-        self.assertEqual(x['user.sop.foo'], b'bar')
-        self.assertTrue(u'user.\N{SNOWMAN}' in x)
-        self.assertTrue(u'user.\N{SNOWMAN}' in attrs)
-        self.assertEqual(x[u'user.\N{SNOWMAN}'],
-                         b'not a snowman')
-
-        del x[u'user.\N{SNOWMAN}']
-        del x['user.sop.foo']
-        del x
-
-        x = xattr.xattr(self.tempfile)
-        self.assertTrue('user.sop.foo' not in x)
-
-    def test_setxattr_unicode_error(self):
-        x = xattr.xattr(self.tempfile)
-        def assign():
-            x['abc'] = u'abc'
-        self.assertRaises(TypeError, assign)
-
-        if sys.version_info[0] >= 3:
-            msg = "Value must be bytes, str was passed."
-        else:
-            msg = "Value must be bytes, unicode was passed."
-
-        try:
-            assign()
-        except TypeError:
-            e = sys.exc_info()[1]
-            self.assertEqual(str(e), msg)
-
-    def test_symlink_attrs(self):
-        symlinkPath = self.tempfilename + '.link'
-        os.symlink(self.tempfilename, symlinkPath)
-        try:
-            symlink = xattr.xattr(symlinkPath, options=xattr.XATTR_NOFOLLOW)
-            realfile = xattr.xattr(self.tempfilename)
-            try:
-                symlink['user.islink'] = b'true'
-            except IOError:
-                # Solaris, Linux don't support extended attributes on symlinks
-                raise unittest.SkipTest("XATTRs on symlink not allowed"
-                                        " on filesystem/platform")
-            self.assertEqual(dict(realfile), {})
-            self.assertEqual(symlink['user.islink'], b'true')
-        finally:
-            os.remove(symlinkPath)
-
-
-class TestFile(TestCase, BaseTestXattr):
-    def setUp(self):
-        self.tempfile = NamedTemporaryFile(dir=self.TESTDIR)
-        self.tempfilename = self.tempfile.name
-
-    def tearDown(self):
-        self.tempfile.close()
-
-
-class TestDir(TestCase, BaseTestXattr):
-    def setUp(self):
-        self.tempfile = mkdtemp(dir=self.TESTDIR)
-        self.tempfilename = self.tempfile
-
-    def tearDown(self):
-        os.rmdir(self.tempfile)
-
-
-try:
-    # SkipTest is only available in Python 2.7+
-    unittest.SkipTest
-except AttributeError:
-    pass
-else:
-    class TestFileWithSurrogates(TestFile):
-        def setUp(self):
-            if sys.platform not in ('linux', 'linux2'):
-                raise unittest.SkipTest('Files with invalid encoded names are 
only supported under linux')
-            if sys.version_info[0] < 3:
-                raise unittest.SkipTest('Test is only available on Python3') # 
surrogateescape not avail in py2
-            self.tempfile = 
NamedTemporaryFile(prefix=b'invalid-\xe9'.decode('utf8','surrogateescape'), 
dir=self.TESTDIR)
-            self.tempfilename = self.tempfile.name
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr/tool.py 
new/xattr-1.3.0/xattr/tool.py
--- old/xattr-0.10.1/xattr/tool.py      2022-12-04 01:11:15.000000000 +0100
+++ new/xattr-1.3.0/xattr/tool.py       2025-10-13 23:46:56.000000000 +0200
@@ -67,14 +67,7 @@
         return 0
 
 
-if sys.version_info < (3,):
-    ascii = repr
-    uchr = unichr
-else:
-    uchr = chr
-
-
-_FILTER = u''.join([(len(ascii(chr(x))) == 3) and uchr(x) or u'.' for x in 
range(256)])
+_FILTER = u''.join([(len(ascii(chr(x))) == 3) and chr(x) or u'.' for x in 
range(256)])
 
 
 def _dump(src, length=16):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr.egg-info/PKG-INFO 
new/xattr-1.3.0/xattr.egg-info/PKG-INFO
--- old/xattr-0.10.1/xattr.egg-info/PKG-INFO    2022-12-04 01:11:27.000000000 
+0100
+++ new/xattr-1.3.0/xattr.egg-info/PKG-INFO     2025-10-13 23:47:12.000000000 
+0200
@@ -1,28 +1,45 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: xattr
-Version: 0.10.1
+Version: 1.3.0
 Summary: Python wrapper for extended filesystem attributes
-Home-page: http://github.com/xattr/xattr
-Author: Bob Ippolito
-Author-email: [email protected]
-License: MIT License
+Author-email: Bob Ippolito <[email protected]>
+Maintainer-email: Bob Ippolito <[email protected]>
+License-Expression: MIT
+Project-URL: Homepage, https://github.com/xattr/xattr
+Project-URL: Repository, https://github.com/xattr/xattr
+Keywords: xattr
 Platform: MacOS X
 Platform: Linux
 Platform: FreeBSD
 Platform: Solaris
 Classifier: Environment :: Console
 Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
 Classifier: Natural Language :: English
 Classifier: Operating System :: MacOS :: MacOS X
 Classifier: Operating System :: POSIX :: Linux
 Classifier: Operating System :: POSIX :: BSD :: FreeBSD
+Classifier: Operating System :: POSIX :: SunOS/Solaris
 Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2
 Classifier: Programming Language :: Python :: 3
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Requires-Python: >=3.9
+Description-Content-Type: text/x-rst
 License-File: LICENSE.txt
+Requires-Dist: cffi>=1.16.0
+Provides-Extra: test
+Requires-Dist: pytest; extra == "test"
+Dynamic: license-file
+Dynamic: platform
 
+xattr
+-----
+
+xattr is a Python wrapper for extended filesystem attributes.
+
+xattr also ships with an `xattr` command line tool for viewing and
+editing extended filesystem attributes. On platforms that support or
+ship with the attr package, you may prefer to use the `getfattr`
+and `setfattr` command line tools from the attr package.
 
 Extended attributes extend the basic attributes of files and directories
 in the file system.  They are stored as name:data pairs associated with
@@ -30,3 +47,19 @@
 
 Extended attributes are currently only available on Darwin 8.0+ (Mac OS X 10.4)
 and Linux 2.6+. Experimental support is included for Solaris and FreeBSD.
+
+Python 3.9+ is required as of v1.3.0.
+
+Versions older than v1.0.0 are no longer supported, but are
+available for use. v0.10.1 is the last version to support older versions
+of Python (including 2.7).
+
+Note: On Linux, custom xattr keys need to be prefixed with the `user`
+namespace, ie: `user.your_attr`.
+
+Note: If you need to read or write Spotlight metadata attributes on macOS,
+see osxmetadata_ which provides a native macOS means to do so without
+directly manipulating extended attributes. osxmetadata also provides access
+to other macOS metadata attributes and extended attributes via xattr.
+
+.. _osxmetadata: https://github.com/RhetTbull/osxmetadata
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr.egg-info/SOURCES.txt 
new/xattr-1.3.0/xattr.egg-info/SOURCES.txt
--- old/xattr-0.10.1/xattr.egg-info/SOURCES.txt 2022-12-04 01:11:27.000000000 
+0100
+++ new/xattr-1.3.0/xattr.egg-info/SOURCES.txt  2025-10-13 23:47:12.000000000 
+0200
@@ -3,12 +3,11 @@
 LICENSE.txt
 MANIFEST.in
 README.rst
-TODO.txt
 pyproject.toml
-requirements.txt
 setup.py
+tests/test_tool.py
+tests/test_xattr.py
 xattr/__init__.py
-xattr/compat.py
 xattr/lib.py
 xattr/lib_build.c
 xattr/lib_build.h
@@ -21,7 +20,4 @@
 xattr.egg-info/entry_points.txt
 xattr.egg-info/not-zip-safe
 xattr.egg-info/requires.txt
-xattr.egg-info/top_level.txt
-xattr/tests/__init__.py
-xattr/tests/test_tool.py
-xattr/tests/test_xattr.py
\ No newline at end of file
+xattr.egg-info/top_level.txt
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xattr-0.10.1/xattr.egg-info/requires.txt 
new/xattr-1.3.0/xattr.egg-info/requires.txt
--- old/xattr-0.10.1/xattr.egg-info/requires.txt        2022-12-04 
01:11:27.000000000 +0100
+++ new/xattr-1.3.0/xattr.egg-info/requires.txt 2025-10-13 23:47:12.000000000 
+0200
@@ -1 +1,4 @@
-cffi>=1.0
+cffi>=1.16.0
+
+[test]
+pytest

Reply via email to