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
