diff --git a/.coveragerc b/.coveragerc
deleted file mode 100644
index 738d86fa..00000000
--- a/.coveragerc
+++ /dev/null
@@ -1,27 +0,0 @@
-[run]
-branch = True
-source =
- ldap
- ldif
- ldapurl
- slapdtest
-
-[paths]
-source =
- Lib/
- .tox/*/lib/python*/site-packages/
-
-[report]
-ignore_errors = False
-precision = 1
-exclude_lines =
- pragma: no cover
- raise NotImplementedError
- if 0:
- if __name__ == .__main__.:
- if PY2
- if not PY2
-
-[html]
-directory = build/htmlcov
-title = python-ldap coverage report
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 37843f31..bcfbafc3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,18 +20,16 @@ jobs:
fail-fast: false
matrix:
python-version:
- - "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
+ - "3.13"
- "pypy3.9"
+ - "pypy3.10"
image:
- "ubuntu-22.04"
- include:
- - python-version: "3.6"
- image: "ubuntu-20.04"
steps:
- name: Checkout
uses: "actions/checkout@v4"
@@ -43,7 +41,7 @@ jobs:
- name: Disable AppArmor
run: sudo aa-disable /usr/sbin/slapd
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml
index b86303fe..a8145a59 100644
--- a/.github/workflows/tox-fedora.yml
+++ b/.github/workflows/tox-fedora.yml
@@ -9,7 +9,7 @@ jobs:
tox_test:
name: Tox env "${{matrix.tox_env}}" on Fedora
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Run Tox tests
uses: fedora-python/tox-github-action@main
with:
@@ -17,23 +17,20 @@ jobs:
dnf_install: >
@c-development openldap-devel python3-devel
openldap-servers openldap-clients lcov clang-analyzer valgrind
- enchant
+ enchant python3-setuptools
strategy:
matrix:
tox_env:
- - py36
- - py37
- py38
- py39
- py310
- py311
- py312
- - c90-py36
- - c90-py37
+ - py313
- py3-nosasltls
- py3-trace
- pypy3
- doc
# Use GitHub's Linux Docker host
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
diff --git a/.gitignore b/.gitignore
index bab21878..75a13538 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,6 @@
*.pyc
__pycache__/
.tox
-.coverage*
-!.coveragerc
/.cache
/.pytest_cache
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 00000000..91fb6028
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,22 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the version of Python and other tools you might need
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.11"
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+ configuration: Doc/conf.py
+
+# We recommend specifying your dependencies to enable reproducible builds:
+# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
+python:
+ install:
+ - requirements: Doc/requirements.txt
diff --git a/CHANGES b/CHANGES
index 500fa1e7..05fcf4b6 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,49 @@
+Released 3.4.5 2025-10-10
+
+Security fixes:
+* CVE-2025-61911 (GHSA-r7r6-cc7p-4v5m): Enforce ``str`` input in
+ ``ldap.filter.escape_filter_chars`` with ``escape_mode=1``; ensure proper
+ escaping. (thanks to lukas-eu)
+* CVE-2025-61912 (GHSA-p34h-wq7j-h5v6): Correct NUL escaping in
+ ``ldap.dn.escape_dn_chars`` to ``\00`` per RFC 4514. (thanks to aradona91)
+
+Fixes:
+* ReconnectLDAPObject now properly reconnects on UNAVAILABLE, CONNECT_ERROR
+ and TIMEOUT exceptions (previously only SERVER_DOWN), fixing reconnection
+ issues especially during server restarts
+* Fixed syncrepl.py to use named constants instead of raw decimal values
+ for result types
+* Fixed error handling in SearchNoOpMixIn to prevent a undefined variable error
+
+Tests:
+* Added comprehensive reconnection test cases including concurrent operation
+ handling and server restart scenarios
+
+Doc/
+* Updated installation docs and fixed various documentation typos
+* Added ReadTheDocs configuration file
+
+Infrastructure:
+* Add testing and document support for Python 3.13
+
+----------------------------------------------------------------
+Released 3.4.4 2022-11-17
+
+Fixes:
+* Reconnect race condition in ReconnectLDAPObject is now fixed
+* Socket ownership is now claimed once we've passed it to libldap
+* LDAP_set_option string formats are now compatible with Python 3.12
+
+Doc/
+* Security Policy was created
+* Broken article links are fixed now
+* Bring Conscious Language improvements
+
+Infrastructure:
+* Add testing and document support for Python 3.10, 3.11, and 3.12
+
+
+----------------------------------------------------------------
Released 3.4.3 2022-09-15
This is a minor release to bring back the removed OPT_X_TLS option.
diff --git a/Demo/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py
index f1f24e19..754b237a 100644
--- a/Demo/pyasn1/syncrepl.py
+++ b/Demo/pyasn1/syncrepl.py
@@ -76,7 +76,7 @@ def syncrepl_entry(self, dn, attributes, uuid):
logger.debug('Detected %s of entry %r', change_type, dn)
# If we have a cookie then this is not our first time being run,
# so it must be a change
- if 'ldap_cookie' in self.__data:
+ if 'cookie' in self.__data:
self.perform_application_sync(dn, attributes, previous_attributes)
def syncrepl_delete(self,uuids):
@@ -98,7 +98,7 @@ def syncrepl_present(self,uuids,refreshDeletes=False):
deletedEntries = [
uuid
for uuid in self.__data.keys()
- if uuid not in self.__presentUUIDs and uuid != 'ldap_cookie'
+ if uuid not in self.__presentUUIDs and uuid != 'cookie'
]
self.syncrepl_delete( deletedEntries )
# Phase is now completed, reset the list
diff --git a/Doc/contributing.rst b/Doc/contributing.rst
index bbaab491..6ef8a5a8 100644
--- a/Doc/contributing.rst
+++ b/Doc/contributing.rst
@@ -19,7 +19,7 @@ Communication
Always keep in mind that python-ldap is developed and maintained by volunteers.
We're happy to share our work, and to work with you to make the library better,
-but (until you pay someone), there's obligation to provide assistance.
+but (until you pay someone), there's no obligation to provide assistance.
So, keep it friendly, respectful, and supportive!
diff --git a/Doc/installing.rst b/Doc/installing.rst
index e4518c11..1c7ec8c3 100644
--- a/Doc/installing.rst
+++ b/Doc/installing.rst
@@ -63,8 +63,8 @@ to get up to date information which versions are available.
Windows
-------
-Unofficial packages for Windows are available on
-`Christoph Gohlke's page `_.
+Unofficial binary builds for Windows are provided by Christoph Gohlke, available at
+`python-ldap-build `_.
`FreeBSD `_
@@ -76,12 +76,23 @@ The CVS repository of FreeBSD contains the package
macOS
-----
-You can install directly with pip::
+You can install directly with pip. First install Xcode command line tools::
$ xcode-select --install
- $ pip install python-ldap \
- --global-option=build_ext \
- --global-option="-I$(xcrun --show-sdk-path)/usr/include/sasl"
+
+Then install python-ldap::
+
+ $ pip install python-ldap
+
+For custom installations, you may need to set environment variables::
+
+ $ export CPPFLAGS="-I$(xcrun --show-sdk-path)/usr/include/sasl"
+ $ pip install python-ldap
+
+If using Homebrew::
+
+ $ brew install openldap
+ $ pip install python-ldap
.. _install-source:
@@ -90,11 +101,14 @@ Installing from Source
======================
-python-ldap is built and installed using the Python setuptools.
-From a source repository::
+python-ldap is built and installed using modern Python packaging standards
+with pyproject.toml configuration. From a source repository::
- $ python -m pip install setuptools
- $ python setup.py install
+ $ pip install .
+
+For development installation with editable mode::
+
+ $ pip install -e .
If you have more than one Python interpreter installed locally, you should
use the same one you plan to use python-ldap with.
@@ -143,10 +157,15 @@ Packages for building::
Debian
------
+Packages for building::
+
+ # apt-get install build-essential ldap-utils \
+ libldap2-dev libsasl2-dev
+
Packages for building and testing::
- # apt-get install build-essential python3-dev \
- libldap2-dev libsasl2-dev slapd ldap-utils tox \
+ # apt-get install build-essential ldap-utils \
+ libldap2-dev libsasl2-dev slapd python3-dev tox \
lcov valgrind
.. note::
diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst
index d059dfa4..c518571e 100644
--- a/Doc/reference/ldap.rst
+++ b/Doc/reference/ldap.rst
@@ -604,13 +604,13 @@ The module defines the following exceptions:
.. py:exception:: COMPARE_FALSE
A compare operation returned false.
- (This exception should only be seen asynchronous operations, because
+ (This exception should only be seen in asynchronous operations, because
:py:meth:`~LDAPObject.compare_s()` returns a boolean result.)
.. py:exception:: COMPARE_TRUE
A compare operation returned true.
- (This exception should only be seen asynchronous operations, because
+ (This exception should only be seen in asynchronous operations, because
:py:meth:`~LDAPObject.compare_s()` returns a boolean result.)
.. py:exception:: CONFIDENTIALITY_REQUIRED
@@ -1364,7 +1364,7 @@ and wait for and return with the server's result, or with
This synchronous method implements the LDAP "Who Am I?"
extended operation.
- It is useful for finding out to find out which identity
+ It is useful for finding out which identity
is assumed by the LDAP server after a SASL bind.
.. seealso::
diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt
index e6c2aedd..42a85406 100644
--- a/Doc/spelling_wordlist.txt
+++ b/Doc/spelling_wordlist.txt
@@ -25,6 +25,7 @@ changeNumber
changesOnly
changeType
changeTypes
+Christoph
cidict
clientctrls
conf
@@ -56,8 +57,10 @@ filterstr
filterStr
formatOID
func
+Gohlke
GPG
Heimdal
+Homebrew
hostport
hrefTarget
hrefText
@@ -105,6 +108,7 @@ previousDN
processResultsCount
Proxied
py
+pyproject
pytest
rdn
readthedocs
@@ -145,6 +149,7 @@ syncrepl
syntaxes
timelimit
TLS
+toml
tracebacks
tuple
tuples
@@ -161,5 +166,6 @@ userPassword
usr
uuids
Valgrind
+Xcode
whitespace
workflow
diff --git a/INSTALL b/INSTALL
index b9b13d2d..224df4a4 100644
--- a/INSTALL
+++ b/INSTALL
@@ -1,8 +1,7 @@
Quick build instructions:
edit setup.cfg (see Build/ for platform-specific examples)
- python setup.py build
- python setup.py install
+ pip install .
Detailed instructions are in Doc/installing.rst, or online at:
diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py
index f846fd29..65041e0a 100644
--- a/Lib/ldap/cidict.py
+++ b/Lib/ldap/cidict.py
@@ -85,7 +85,7 @@ def strlist_minus(a,b):
a,b are supposed to be lists of case-insensitive strings.
"""
warnings.warn(
- "strlist functions are deprecated and will be removed in 3.5",
+ "strlist functions are deprecated and will be removed in 4.0",
category=DeprecationWarning,
stacklevel=2,
)
@@ -105,7 +105,7 @@ def strlist_intersection(a,b):
Return intersection of two lists of case-insensitive strings a,b.
"""
warnings.warn(
- "strlist functions are deprecated and will be removed in 3.5",
+ "strlist functions are deprecated and will be removed in 4.0",
category=DeprecationWarning,
stacklevel=2,
)
@@ -125,7 +125,7 @@ def strlist_union(a,b):
Return union of two lists of case-insensitive strings a,b.
"""
warnings.warn(
- "strlist functions are deprecated and will be removed in 3.5",
+ "strlist functions are deprecated and will be removed in 4.0",
category=DeprecationWarning,
stacklevel=2,
)
diff --git a/Lib/ldap/controls/openldap.py b/Lib/ldap/controls/openldap.py
index 24040ed7..26c76868 100644
--- a/Lib/ldap/controls/openldap.py
+++ b/Lib/ldap/controls/openldap.py
@@ -51,6 +51,7 @@ class SearchNoOpMixIn:
"""
def noop_search_st(self,base,scope=ldap.SCOPE_SUBTREE,filterstr='(objectClass=*)',timeout=-1):
+ msg_id = None
try:
msg_id = self.search_ext(
base,
@@ -66,9 +67,10 @@ def noop_search_st(self,base,scope=ldap.SCOPE_SUBTREE,filterstr='(objectClass=*)
ldap.TIMELIMIT_EXCEEDED,
ldap.SIZELIMIT_EXCEEDED,
ldap.ADMINLIMIT_EXCEEDED
- ) as e:
- self.abandon(msg_id)
- raise e
+ ):
+ if msg_id is not None:
+ self.abandon(msg_id)
+ raise
else:
noop_srch_ctrl = [
c
diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py
index a9d96846..8d406733 100644
--- a/Lib/ldap/dn.py
+++ b/Lib/ldap/dn.py
@@ -26,7 +26,8 @@ def escape_dn_chars(s):
s = s.replace('>' ,'\\>')
s = s.replace(';' ,'\\;')
s = s.replace('=' ,'\\=')
- s = s.replace('\000' ,'\\\000')
+ # RFC 4514 requires NULL (U+0000) to be escaped as hex pair "\00"
+ s = s.replace('\x00' ,'\\00')
if s[-1]==' ':
s = ''.join((s[:-1],'\\ '))
if s[0]=='#' or s[0]==' ':
diff --git a/Lib/ldap/filter.py b/Lib/ldap/filter.py
index 782737aa..5bd41b21 100644
--- a/Lib/ldap/filter.py
+++ b/Lib/ldap/filter.py
@@ -24,6 +24,8 @@ def escape_filter_chars(assertion_value,escape_mode=0):
If 1 all NON-ASCII chars are escaped.
If 2 all chars are escaped.
"""
+ if not isinstance(assertion_value, str):
+ raise TypeError("assertion_value must be of type str.")
if escape_mode:
r = []
if escape_mode==1:
diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py
index 7a9c17f6..a5902ea3 100644
--- a/Lib/ldap/ldapobject.py
+++ b/Lib/ldap/ldapobject.py
@@ -521,7 +521,7 @@ def result(self,msgid=ldap.RES_ANY,all=1,timeout=None):
The method returns a tuple of the form (result_type,
result_data). The result_type is one of the constants RES_*.
- See search() for a description of the search result's
+ See search_ext() for a description of the search result's
result_data, otherwise the result_data is normally meaningless.
The result() method will block for timeout seconds, or
@@ -588,7 +588,7 @@ def search_ext(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverct
values are stored in a list as dictionary value.
The DN in dn is extracted using the underlying ldap_get_dn(),
- which may raise an exception of the DN is malformed.
+ which may raise an exception if the DN is malformed.
If attrsonly is non-zero, the values of attrs will be
meaningless (they are not transmitted in the result).
@@ -820,8 +820,7 @@ def get_naming_contexts(self):
class ReconnectLDAPObject(SimpleLDAPObject):
"""
:py:class:`SimpleLDAPObject` subclass whose synchronous request methods
- automatically reconnect and re-try in case of server failure
- (:exc:`ldap.SERVER_DOWN`).
+ automatically reconnect and re-try in case of server failure.
The first arguments are same as for the :py:func:`~ldap.initialize()`
function.
@@ -833,6 +832,10 @@ class ReconnectLDAPObject(SimpleLDAPObject):
* retry_delay: specifies the time in seconds between reconnect attempts.
This class also implements the pickle protocol.
+
+ .. versionadded:: 3.4.5
+ The exceptions :py:exc:`ldap.SERVER_DOWN`, :py:exc:`ldap.UNAVAILABLE`, :py:exc:`ldap.CONNECT_ERROR` and
+ :py:exc:`ldap.TIMEOUT` (configurable via :py:attr:`_reconnect_exceptions`) now trigger a reconnect.
"""
__transient_attrs__ = {
@@ -842,6 +845,7 @@ class ReconnectLDAPObject(SimpleLDAPObject):
'_reconnect_lock',
'_last_bind',
}
+ _reconnect_exceptions = (ldap.SERVER_DOWN, ldap.UNAVAILABLE, ldap.CONNECT_ERROR, ldap.TIMEOUT)
def __init__(
self,uri,
@@ -970,7 +974,7 @@ def _apply_method_s(self,func,*args,**kwargs):
self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay,force=False)
try:
return func(self,*args,**kwargs)
- except ldap.SERVER_DOWN:
+ except self._reconnect_exceptions:
# Try to reconnect
self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay,force=True)
# Re-try last operation
diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py
index 026e9101..2ac6852d 100644
--- a/Lib/ldap/pkginfo.py
+++ b/Lib/ldap/pkginfo.py
@@ -1,6 +1,6 @@
"""
meta attributes for packaging which does not import any dependencies
"""
-__version__ = '3.4.3'
+__version__ = '3.4.5'
__author__ = 'python-ldap project'
__license__ = 'Python style'
diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py
index 1708b468..fd0c1285 100644
--- a/Lib/ldap/syncrepl.py
+++ b/Lib/ldap/syncrepl.py
@@ -12,6 +12,7 @@
from ldap.pkginfo import __version__, __author__, __license__
from ldap.controls import RequestControl, ResponseControl, KNOWN_RESPONSE_CONTROLS
+from ldap import RES_SEARCH_RESULT, RES_SEARCH_ENTRY, RES_INTERMEDIATE
__all__ = [
'SyncreplConsumer',
@@ -407,7 +408,7 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0):
all=0,
)
- if type == 101:
+ if type == RES_SEARCH_RESULT:
# search result. This marks the end of a refreshOnly session.
# look for a SyncDone control, save the cookie, and if necessary
# delete non-present entries.
@@ -420,7 +421,7 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0):
return False
- elif type == 100:
+ elif type == RES_SEARCH_ENTRY:
# search entry with associated SyncState control
for m in msg:
dn, attrs, ctrls = m
@@ -439,7 +440,7 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0):
self.syncrepl_set_cookie(c.cookie)
break
- elif type == 121:
+ elif type == RES_INTERMEDIATE:
# Intermediate message. If it is a SyncInfoMessage, parse it
for m in msg:
rname, resp, ctrls = m
diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py
index 964076d3..57900028 100644
--- a/Lib/ldapurl.py
+++ b/Lib/ldapurl.py
@@ -4,7 +4,7 @@
See https://www.python-ldap.org/ for details.
"""
-__version__ = '3.4.3'
+__version__ = '3.4.5'
__all__ = [
# constants
diff --git a/Lib/ldif.py b/Lib/ldif.py
index ae1d643d..7bfe5b4c 100644
--- a/Lib/ldif.py
+++ b/Lib/ldif.py
@@ -3,7 +3,7 @@
See https://www.python-ldap.org/ for details.
"""
-__version__ = '3.4.3'
+__version__ = '3.4.5'
__all__ = [
# constants
diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py
index 7ab7d2bd..0fabc4c4 100644
--- a/Lib/slapdtest/__init__.py
+++ b/Lib/slapdtest/__init__.py
@@ -4,7 +4,7 @@
See https://www.python-ldap.org/ for details.
"""
-__version__ = '3.4.3'
+__version__ = '3.4.5'
from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler
from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls
diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py
index 36841110..4110d945 100644
--- a/Lib/slapdtest/_slapdtest.py
+++ b/Lib/slapdtest/_slapdtest.py
@@ -467,8 +467,16 @@ def restart(self):
"""
Restarts the slapd server with same data
"""
- self._proc.terminate()
+ self.terminate()
self.wait()
+ self.resume()
+
+ def terminate(self):
+ """Terminate slapd server"""
+ self._proc.terminate()
+
+ def resume(self):
+ """Start slapd server"""
self._start_slapd()
def wait(self):
diff --git a/MANIFEST.in b/MANIFEST.in
index 687d2b0c..bedea8d6 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,5 @@
include MANIFEST.in Makefile CHANGES INSTALL LICENCE README TODO
-include tox.ini .coveragerc
+include tox.ini
include Modules/*.c Modules/*.h
recursive-include Build *.cfg*
recursive-include Lib *.py
diff --git a/Makefile b/Makefile
index 577ba883..2c8efdb5 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,6 @@ Modules/constants_generated.h: Lib/ldap/constants.py
.PHONY: clean
clean:
rm -rf build dist *.egg-info .tox MANIFEST
- rm -f .coverage .coverage.*
find . \( -name '*.py[co]' -or -name '*.so*' -or -name '*.dylib' \) \
-delete
find . -depth -name __pycache__ -exec rm -rf {} \;
diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py
index 86d36403..14e5999c 100644
--- a/Tests/t_ldap_dn.py
+++ b/Tests/t_ldap_dn.py
@@ -49,7 +49,7 @@ def test_escape_dn_chars(self):
self.assertEqual(ldap.dn.escape_dn_chars(' '), '\\ ')
self.assertEqual(ldap.dn.escape_dn_chars(' '), '\\ \\ ')
self.assertEqual(ldap.dn.escape_dn_chars('foobar '), 'foobar\\ ')
- self.assertEqual(ldap.dn.escape_dn_chars('f+o>o,bo\\,b\\o,bo\\,b\\=3.6"
+keywords = ["ldap", "directory", "authentication"]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "Intended Audience :: System Administrators",
+ "Operating System :: OS Independent",
+ "Operating System :: MacOS :: MacOS X",
+ "Operating System :: Microsoft :: Windows",
+ "Operating System :: POSIX",
+ "Programming Language :: C",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Topic :: Database",
+ "Topic :: Internet",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP",
+ "License :: OSI Approved :: Python Software Foundation License",
+]
+dependencies = [
+ "pyasn1 >= 0.3.7",
+ "pyasn1_modules >= 0.1.5",
+]
+
+[project.urls]
+Homepage = "https://www.python-ldap.org/"
+Documentation = "https://python-ldap.readthedocs.io/"
+Repository = "https://github.com/python-ldap/python-ldap"
+Download = "https://pypi.org/project/python-ldap/"
+Changelog = "https://github.com/python-ldap/python-ldap/blob/main/CHANGES"
+
+
+
+[tool.setuptools]
+zip-safe = false
+include-package-data = true
+license-files = ["LICENCE", "LICENCE.MIT"]
+# Explicitly list all Python modules
+py-modules = ["ldapurl", "ldif"]
+
+[tool.setuptools.dynamic]
+version = {attr = "ldap.pkginfo.__version__"}
+
+[tool.setuptools.packages.find]
+where = ["Lib"]
+
+[tool.setuptools.package-dir]
+"" = "Lib"
[tool.isort]
-line_length=88
-known_first_party=['ldap', '_ldap', 'ldapurl', 'ldif', 'slapdtest']
-sections=['FUTURE', 'STDLIB', 'THIRDPARTY', 'FIRSTPARTY', 'LOCALFOLDER']
+line_length = 88
+known_first_party = ["ldap", "_ldap", "ldapurl", "ldif", "slapdtest"]
+sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
+
+[tool.coverage.run]
+branch = true
+source = [
+ "ldap",
+ "ldif",
+ "ldapurl",
+ "slapdtest",
+]
+
+[tool.coverage.paths]
+source = [
+ "Lib/",
+ ".tox/*/lib/python*/site-packages/",
+]
+
+[tool.coverage.report]
+ignore_errors = false
+precision = 1
+exclude_lines = [
+ "pragma: no cover",
+ "raise NotImplementedError",
+ "if 0:",
+ "if __name__ == .__main__.:",
+ "if PY2",
+ "if not PY2",
+]
+
+[tool.coverage.html]
+directory = "build/htmlcov"
+title = "python-ldap coverage report"
diff --git a/setup.py b/setup.py
index 6da3f491..9ad6996e 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,9 @@
"""
-setup.py - Setup package with the help Python's DistUtils
+setup.py - C extension module configuration for python-ldap
See https://www.python-ldap.org/ for details.
+This file handles only the C extension modules (_ldap) configuration,
+while pyproject.toml handles all project metadata, dependencies, and other settings.
"""
import sys,os
@@ -54,54 +56,8 @@ class OpenLDAP2:
LDAP_CLASS.extra_link_args.append('-pg')
LDAP_CLASS.libs.append('gcov')
-#-- Let distutils/setuptools do the rest
-name = 'python-ldap'
-
+#-- C extension modules configuration only
setup(
- #-- Package description
- name = name,
- license=pkginfo.__license__,
- version=pkginfo.__version__,
- description = 'Python modules for implementing LDAP clients',
- long_description = """python-ldap:
- python-ldap provides an object-oriented API to access LDAP directory servers
- from Python programs. Mainly it wraps the OpenLDAP 2.x libs for that purpose.
- Additionally the package contains modules for other LDAP-related stuff
- (e.g. processing LDIF, LDAPURLs, LDAPv3 schema, LDAPv3 extended operations
- and controls, etc.).
- """,
- author = 'python-ldap project',
- author_email = 'python-ldap@python.org',
- url = 'https://www.python-ldap.org/',
- download_url = 'https://pypi.org/project/python-ldap/',
- classifiers = [
- 'Development Status :: 5 - Production/Stable',
- 'Intended Audience :: Developers',
- 'Intended Audience :: System Administrators',
- 'Operating System :: OS Independent',
- 'Operating System :: MacOS :: MacOS X',
- 'Operating System :: Microsoft :: Windows',
- 'Operating System :: POSIX',
- 'Programming Language :: C',
-
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7',
- 'Programming Language :: Python :: 3.8',
- 'Programming Language :: Python :: 3.9',
- 'Programming Language :: Python :: 3.10',
- 'Programming Language :: Python :: 3.11',
- 'Programming Language :: Python :: 3.12',
- # Note: when updating Python versions, also change tox.ini and .github/workflows/*
-
- 'Topic :: Database',
- 'Topic :: Internet',
- 'Topic :: Software Development :: Libraries :: Python Modules',
- 'Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP',
- 'License :: OSI Approved :: Python Software Foundation License',
- ],
- #-- C extension modules
ext_modules = [
Extension(
'_ldap',
@@ -144,28 +100,4 @@ class OpenLDAP2:
]
),
],
- #-- Python "stand alone" modules
- py_modules = [
- 'ldapurl',
- 'ldif',
-
- ],
- packages = [
- 'ldap',
- 'ldap.controls',
- 'ldap.extop',
- 'ldap.schema',
- 'slapdtest',
- 'slapdtest.certs',
- ],
- package_dir = {'': 'Lib',},
- data_files = LDAP_CLASS.extra_files,
- include_package_data=True,
- install_requires=[
- 'pyasn1 >= 0.3.7',
- 'pyasn1_modules >= 0.1.5',
- ],
- zip_safe=False,
- python_requires='>=3.6',
- test_suite = 'Tests',
)
diff --git a/tox.ini b/tox.ini
index beade024..d0cc0ad9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -5,22 +5,22 @@
[tox]
# Note: when updating Python versions, also change setup.py and .github/worlflows/*
-envlist = py{36,37,38,39,310,311,312},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3.9
+envlist = py{38,39,310,311,312,313},py3-nosasltls,doc,py3-trace,pypy3.9
minver = 1.8
[gh-actions]
python =
- 3.6: py36
- 3.7: py37
3.8: py38, doc, py3-nosasltls
3.9: py39, py3-trace
3.10: py310
3.11: py311
3.12: py312
+ 3.13: py313
pypy3.9: pypy3.9
+ pypy3.10: pypy3.10
[testenv]
-deps =
+deps = setuptools
passenv = WITH_GCOV
# - Enable BytesWarning
# - Turn all warnings into exceptions.
@@ -34,6 +34,7 @@ commands = {envpython} -bb -Werror \
setenv =
CFLAGS=-Wno-int-in-bool-context -Werror -std=c99
+
[testenv:py3-nosasltls]
basepython = python3
# don't install, install dependencies manually
@@ -98,6 +99,7 @@ deps =
markdown
sphinx
sphinxcontrib-spelling
+ setuptools
commands =
{envpython} setup.py check --restructuredtext --metadata --strict
{envpython} -m markdown README -f {envtmpdir}/README.html