From 607bf2ddd3474575e598bcc778e65375fb41ca95 Mon Sep 17 00:00:00 2001
From: Julian Rother <julian@jrother.eu>
Date: Thu, 25 Nov 2021 22:43:01 +0100
Subject: [PATCH] Entry naming cleanup (Object -> ObjectEntry)

---
 docs/api.rst                               |  9 ++-
 docs/conf.py                               |  1 -
 examples/passwd.py                         |  6 +-
 ldapserver/__init__.py                     |  2 +-
 ldapserver/{objects.py => entries.py}      | 75 +++++++++++-------
 ldapserver/server.py                       | 32 ++++----
 tests/{test_objects.py => test_entries.py} | 92 +++++++++++-----------
 7 files changed, 116 insertions(+), 101 deletions(-)
 rename ldapserver/{objects.py => entries.py} (90%)
 rename tests/{test_objects.py => test_entries.py} (90%)

diff --git a/docs/api.rst b/docs/api.rst
index 6aeda77..3284158 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -29,13 +29,16 @@ Distinguished Names
 	:members:
 	:special-members: __str__
 
-Objects
+Entries
 -------
 
+An LDAP server provides access to a tree of directory entries (the DIT).
+There are different kinds of entries: Object entries holding actual data objects (see :class:`ObjectEntry`), alias entries providing alternative naming for other entries (not supported) and subentries holding administrative/operational information (see :class:`SubschemaSubentry` and :class:`RootDSE`).
+
 .. autoclass:: ldapserver.AttributeDict
 	:members:
 
-.. autoclass:: ldapserver.Object
+.. autoclass:: ldapserver.Entry
 	:members:
 
 .. autoclass:: ldapserver.RootDSE
@@ -47,7 +50,7 @@ Objects
 .. autodata:: ldapserver.WILDCARD_VALUE
 	:no-value:
 
-.. autoclass:: ldapserver.ObjectTemplate
+.. autoclass:: ldapserver.EntryTemplate
 	:members:
 
 Schema
diff --git a/docs/conf.py b/docs/conf.py
index 74e1d91..066df16 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -57,7 +57,6 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
-html_theme = 'alabaster'
 html_theme = 'sphinx_rtd_theme'
 
 # Add any paths that contain custom static files (such as style sheets) here,
diff --git a/examples/passwd.py b/examples/passwd.py
index 8e746cf..ce024a3 100644
--- a/examples/passwd.py
+++ b/examples/passwd.py
@@ -12,14 +12,14 @@ class RequestHandler(ldapserver.LDAPRequestHandler):
 
 	def do_search(self, basedn, scope, filterobj):
 		yield from super().do_search(basedn, scope, filterobj)
-		yield self.subschema.Object('dc=example,dc=com', **{
+		yield self.subschema.ObjectEntry('dc=example,dc=com', **{
 			'objectClass': ['top', 'dcObject', 'organization'],
 			'structuralObjectClass': ['organization'],
 		})
 		user_gids = {}
 		for user in pwd.getpwall():
 			user_gids[user.pw_gid] = user_gids.get(user.pw_gid, set()) | {user.pw_name}
-			yield self.subschema.Object(self.subschema.DN('ou=users,dc=example,dc=com', uid=user.pw_name), **{
+			yield self.subschema.ObjectEntry(self.subschema.DN('ou=users,dc=example,dc=com', uid=user.pw_name), **{
 				'objectClass': ['top', 'organizationalperson', 'person', 'posixaccount'],
 				'structuralObjectClass': ['organizationalperson'],
 				'uid': [user.pw_name],
@@ -29,7 +29,7 @@ class RequestHandler(ldapserver.LDAPRequestHandler):
 			})
 		for group in grp.getgrall():
 			members = set(group.gr_mem) | user_gids.get(group.gr_gid, set())
-			yield self.subschema.Object(self.subschema.DN('ou=groups,dc=example,dc=com', cn=group.gr_name), **{
+			yield self.subschema.ObjectEntry(self.subschema.DN('ou=groups,dc=example,dc=com', cn=group.gr_name), **{
 				'objectClass': ['top', 'groupOfUniqueNames', 'posixGroup'],
 				'structuralObjectClass': ['groupOfUniqueNames'],
 				'cn': [group.gr_name],
diff --git a/ldapserver/__init__.py b/ldapserver/__init__.py
index c749ca5..ceeec72 100644
--- a/ldapserver/__init__.py
+++ b/ldapserver/__init__.py
@@ -4,5 +4,5 @@ from . import schema
 from . import rfc4518_stringprep
 
 from .dn import *
-from .objects import *
+from .entries import *
 from .server import *
diff --git a/ldapserver/objects.py b/ldapserver/entries.py
similarity index 90%
rename from ldapserver/objects.py
rename to ldapserver/entries.py
index a8c0a55..5a6c931 100644
--- a/ldapserver/objects.py
+++ b/ldapserver/entries.py
@@ -4,7 +4,7 @@ import enum
 from . import ldap, exceptions
 from .dn import DN, RDN, RDNAssertion
 
-__all__ = ['AttributeDict', 'Object', 'RootDSE', 'WILDCARD_VALUE', 'ObjectTemplate', 'SubschemaSubentry']
+__all__ = ['AttributeDict', 'Entry', 'ObjectEntry', 'RootDSE', 'WILDCARD_VALUE', 'EntryTemplate', 'SubschemaSubentry']
 
 class TypeKeysView(collections.abc.Set):
 	def __init__(self, attributes):
@@ -110,10 +110,18 @@ class FilterResult(enum.Enum):
 	FALSE = enum.auto()
 	UNDEFINED = enum.auto()
 
-class Object(AttributeDict):
+class Entry(AttributeDict):
+	'''Base class for all directory entries
+
+	All entries implement the methods :any:`Entry.search` and
+	:any:`Entry.compare`. :class:`LDAPRequestHandler` uses them to process the
+	corresponding LDAP requests.
+
+	Entries also provide dict-like access to attributes. See
+	:class:`AttributeDict`.'''
 	def __init__(self, schema, dn, **attributes):
 		super().__init__(schema, **attributes)
-		#: Objects distinguished name (:class:`DN`)
+		#: Entry's distinguished name (:class:`DN`)
 		self.dn = DN(schema, dn)
 
 	def __search_match_dn(self, basedn, scope):
@@ -220,10 +228,10 @@ class Object(AttributeDict):
 		       self.__search_match_filter(filter_obj) == FilterResult.TRUE
 
 	def search(self, base_obj, scope, filter_obj, attributes, types_only):
-		'''Return SEARCH result for the object if it matches the operation
+		'''Return SEARCH result for the entry if it matches the operation
 		parameters
 
-		:param base_obj: DN of the base object
+		:param base_obj: DN of the base entry
 		:type base_obj: str
 		:param scope: Scope of base_obj
 		:type scope: ldap.SearchScope
@@ -233,7 +241,7 @@ class Object(AttributeDict):
 		:type attributes: list of str
 		:param types_only: Omit values in :class:`ldap.PartialAttribute`
 		:type types_only: bool
-		:returns: SEARCH result for the object if it matches the operation
+		:returns: SEARCH result for the entry if it matches the operation
 		          parameters, None otherwise
 		:rtype: ldap.SearchResultEntry or None
 
@@ -259,15 +267,15 @@ class Object(AttributeDict):
 		return ldap.SearchResultEntry(str(self.dn), partial_attributes)
 
 	def compare(self, dn, attribute, value):
-		'''Return the result of the COMPARE operation applied to the object
+		'''Return the result of the COMPARE operation applied to the entry
 
-		:param dn: DN of the object to be compared
+		:param dn: DN of the entry to be compared
 		:type dn: str
 		:param attribute: Attribute OID or short descriptive name
 		:type attribute: str
 		:param value: Assertion value
 		:type value: bytes
-		:raises exceptions.LDAPNoSuchObject: if dn does not refer to the object
+		:raises exceptions.LDAPNoSuchObject: if dn does not refer to the entry
 		:raises exceptions.LDAPError: if operation results to anything other than
 		                              TRUE/FALSE
 		:return: True/False if COMPARE operation evaluates to TRUE/FALSE
@@ -287,7 +295,14 @@ class Object(AttributeDict):
 			raise exceptions.LDAPUndefinedAttributeType() from exc
 		return attribute_type.match_equal(self.get(attribute_type, subtypes=True), value)
 
-class RootDSE(Object):
+class ObjectEntry(Entry):
+	'''Regular object entry'''
+
+class RootDSE(Entry):
+	'''Root DSA-specific (server-specific) Entry
+
+	Root of the Directory Information Tree (DIT). It's always identified by the
+	emtpy DN. Provides information about the server, like supported features.'''
 	def __init__(self, schema, **attributes):
 		super().__init__(schema, DN(schema), **attributes)
 		self.setdefault('objectClass', ['top'])
@@ -300,7 +315,7 @@ class RootDSE(Object):
 class WildcardValue:
 	pass
 
-#: Special wildcard value for :class:`ObjectTemplate`
+#: Special wildcard value for :class:`EntryTemplate`
 WILDCARD_VALUE = WildcardValue()
 
 class TemplateFilterResult(enum.Enum):
@@ -309,7 +324,7 @@ class TemplateFilterResult(enum.Enum):
 	UNDEFINED = enum.auto()
 	MAYBE_TRUE = enum.auto()
 
-class ObjectTemplate(AttributeDict):
+class EntryTemplate(AttributeDict):
 	def __init__(self, schema, parent_dn, rdn_attribute, **attributes):
 		super().__init__(schema, **attributes)
 		self.parent_dn = DN(schema, parent_dn)
@@ -332,7 +347,7 @@ class ObjectTemplate(AttributeDict):
 			return False, AttributeDict(self.schema)
 
 	def __search_match_dn(self, basedn, scope):
-		'''Return whether objects from this template might match the provided parameters'''
+		'''Return whether entries from this template might match the provided parameters'''
 		return self.__match_extract_dn_constraints(basedn, scope)[0]
 
 	def __extract_dn_constraints(self, basedn, scope):
@@ -467,15 +482,15 @@ class ObjectTemplate(AttributeDict):
 		return AttributeDict(self.schema)
 
 	def match_search(self, base_obj, scope, filter_obj):
-		'''Return whether objects based on this template might match the search parameters
+		'''Return whether entries based on this template might match the search parameters
 
-		:param base_obj: DN of the base object
+		:param base_obj: DN of the base entry
 		:type base_obj: str
 		:param scope: Scope of base_obj
 		:type scope: ldap.SearchScope
 		:param filter_obj: Search filter
 		:type filter_obj: ldap.Filter
-		:returns: True if objects based on this template might match the SEARCH
+		:returns: True if entries based on this template might match the SEARCH
 		          parameters or False if they do not match
 		:rtype: bool'''
 		return self.__search_match_dn(DN.from_str(self.schema, base_obj), scope) and \
@@ -483,21 +498,21 @@ class ObjectTemplate(AttributeDict):
 		                                                  TemplateFilterResult.MAYBE_TRUE)
 
 	def extract_search_constraints(self, base_obj, scope, filter_obj):
-		'''Return approximate value constraints for objects that match SEARCH parameters
+		'''Return approximate value constraints for entries that match SEARCH parameters
 
-		:returns: :class:`AttributeDict` with values that any object must have
+		:returns: :class:`AttributeDict` with values that any entry must have
 		          to match potentially match SEARCH parameters
 		:rtype: AttributeDict
 
 		Example:
 
 			>>> subschema = SubschemaSubentry(schema.RFC4519_SCHEMA, 'cn=Subschema')
-			>>> template = subschema.ObjectTemplate('ou=users,dc=example,dc=com', 'cn', objectclass=['person', 'top'], sn=[WILDCARD_VALUE])
+			>>> template = subschema.EntryTemplate('ou=users,dc=example,dc=com', 'cn', objectclass=['person', 'top'], sn=[WILDCARD_VALUE])
 			>>> template.extract_search_constraints('cn=test,ou=users,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterEqual('sn', b'foobar'))
 			subschema.AttributeDict(cn=['test'], sn=['foobar'])
 
 		Note that the results are an approximation (i.e. some constrains may be
-		missing) and the quality may improve over time. Also note that every object
+		missing) and the quality may improve over time. Also note that every entry
 		that matches the SEARCH parameters also matches the constrains, but not
 		vice versa.'''
 		constraints = self.__extract_filter_constraints(filter_obj)
@@ -505,17 +520,17 @@ class ObjectTemplate(AttributeDict):
 			constraints[key] += values
 		return constraints
 
-	def create_object(self, rdn_value, **attributes):
-		'''Instanciate :class:`Object` based on template
+	def create_entry(self, rdn_value, **attributes):
+		'''Instanciate :class:`Entry` based on template
 
 		:param rdn_value: RDN value for DN construction
 		:type rdn_value: any
-		:rtype: Object
+		:rtype: Entry
 
 		Template attributes set to :any:`WILDCARD_VALUE` are stripped. Only
 		template attributes set to :any:`WILDCARD_VALUE` may be overwritten
 		with `attributes`.'''
-		obj = Object(self.schema, DN(self.schema, self.parent_dn, **{self.rdn_attribute: rdn_value}))
+		obj = Entry(self.schema, DN(self.schema, self.parent_dn, **{self.rdn_attribute: rdn_value}))
 		for key, values in attributes.items():
 			if WILDCARD_VALUE not in self[key]:
 				raise ValueError(f'Cannot set attribute "{key}" that is not set to [WILDCARD_VALUE] in the template')
@@ -525,8 +540,8 @@ class ObjectTemplate(AttributeDict):
 				obj[attribute_type] = values
 		return obj
 
-class SubschemaSubentry(Object):
-	'''Special :class:`Object` providing information on a Schema'''
+class SubschemaSubentry(Entry):
+	'''Subentry providing information on a :class:`Schema`'''
 	def __init__(self, schema, dn, **attributes):
 		super().__init__(schema, dn, **attributes)
 		self['subschemaSubentry'] = [self.dn]
@@ -540,12 +555,12 @@ class SubschemaSubentry(Object):
 		# pylint: disable=invalid-name
 		#: Shorthand for :class:`AttributeDict`
 		self.AttributeDict = lambda **attributes: AttributeDict(schema, **attributes)
-		#: Shorthand for :class:`Object`
-		self.Object = lambda *args, **attributes: Object(schema, *args, subschemaSubentry=[self.dn], **attributes)
+		#: Shorthand for :class:`ObjectEntry`
+		self.ObjectEntry = lambda *args, **attributes: ObjectEntry(schema, *args, subschemaSubentry=[self.dn], **attributes)
 		#: Shorthand for :class:`RootDSE`
 		self.RootDSE = lambda **attributes: RootDSE(schema, subschemaSubentry=[self.dn], **attributes)
-		#: Shorthand for :class:`ObjectTemplate`
-		self.ObjectTemplate = lambda *args, **kwargs: ObjectTemplate(schema, *args, subschemaSubentry=[self.dn], **kwargs)
+		#: Shorthand for :class:`EntryTemplate`
+		self.EntryTemplate = lambda *args, **kwargs: EntryTemplate(schema, *args, subschemaSubentry=[self.dn], **kwargs)
 		class Wrapper:
 			def __init__(self, cls, schema):
 				self.cls = cls
diff --git a/ldapserver/server.py b/ldapserver/server.py
index 1c58592..f9923cc 100644
--- a/ldapserver/server.py
+++ b/ldapserver/server.py
@@ -8,7 +8,7 @@ import random
 import string
 import itertools
 
-from . import asn1, exceptions, ldap, schema, objects
+from . import asn1, exceptions, ldap, schema, entries
 
 __all__ = ['BaseLDAPRequestHandler', 'LDAPRequestHandler']
 
@@ -168,7 +168,7 @@ class BaseLDAPRequestHandler(socketserver.BaseRequestHandler):
 class LDAPRequestHandler(BaseLDAPRequestHandler):
 	#: :class:`SubschemaSubentry` object that describes the schema. Default
 	#: value uses :any:`schema.RFC4519_SCHEMA`. Returned by :any:`do_search`.
-	subschema = objects.SubschemaSubentry(schema.RFC4519_SCHEMA, 'cn=Subschema', cn=['Subschema'])
+	subschema = entries.SubschemaSubentry(schema.RFC4519_SCHEMA, 'cn=Subschema', cn=['Subschema'])
 
 	#: :class:`RootDSE` object containing information about the server, such
 	#: as supported extentions and SASL authentication mechansims. Content is
@@ -437,11 +437,11 @@ class LDAPRequestHandler(BaseLDAPRequestHandler):
 				yield ldap.SearchResultDone(ldap.LDAPResultCode.success), [build_control()]
 				return
 		is_last = True
-		entries = 0
+		result_count = 0
 		time_start = time.perf_counter()
 		for entry, is_last in itertools.islice(iterator, 0, paged_control.size):
 			self.logger.debug('SEARCH entry %r', entry)
-			entries += 1
+			result_count += 1
 			yield entry
 		cookie = b''
 		if not is_last:
@@ -450,8 +450,8 @@ class LDAPRequestHandler(BaseLDAPRequestHandler):
 			self.__paged_searches[cookie] = iterator, op
 		yield ldap.SearchResultDone(ldap.LDAPResultCode.success), [build_control(cookie=cookie)]
 		time_end = time.perf_counter()
-		self.logger.info('SEARCH dn=%r dn_scope=%s filter=%s attributes=%r page_cookie=%r entries=%d duration_seconds=%.3f',
-		                 op.baseObject, op.scope.name, op.filter, ' '.join(op.attributes), cookie, entries, time_end - time_start)
+		self.logger.info('SEARCH dn=%r dn_scope=%s filter=%s attributes=%r page_cookie=%r result_count=%d duration_seconds=%.3f',
+		                 op.baseObject, op.scope.name, op.filter, ' '.join(op.attributes), cookie, result_count, time_end - time_start)
 
 	def handle_search(self, op, controls=None):
 		self.logger.debug('SEARCH request dn=%r dn_scope=%s filter=%r attributes=%r',
@@ -463,18 +463,18 @@ class LDAPRequestHandler(BaseLDAPRequestHandler):
 		if paged_control:
 			yield from self.__handle_search_paged(op, paged_control, controls)
 			return
-		entries = 0
+		result_count = 0
 		time_start = time.perf_counter()
 		for obj in self.do_search(op.baseObject, op.scope, op.filter):
 			entry = obj.search(op.baseObject, op.scope, op.filter, op.attributes, op.typesOnly)
 			if entry:
 				self.logger.debug('SEARCH entry %r', entry)
-				entries += 1
+				result_count += 1
 				yield entry
 		yield ldap.SearchResultDone(ldap.LDAPResultCode.success)
 		time_end = time.perf_counter()
-		self.logger.info('SEARCH dn=%r dn_scope=%s filter=\'%s\' attributes=%r entries=%d duration_seconds=%.3f',
-		                 op.baseObject, op.scope.name, op.filter, ' '.join(op.attributes), entries, time_end - time_start)
+		self.logger.info('SEARCH dn=%r dn_scope=%s filter=\'%s\' attributes=%r result_count=%d duration_seconds=%.3f',
+		                 op.baseObject, op.scope.name, op.filter, ' '.join(op.attributes), result_count, time_end - time_start)
 
 	def do_search(self, baseobj, scope, filterobj):
 		'''Return result candidates for a SEARCH operation
@@ -487,16 +487,16 @@ class LDAPRequestHandler(BaseLDAPRequestHandler):
 		:param filterobj: Filter object
 		:type filterobj: ldap.Filter
 		:raises exceptions.LDAPError: on error
-		:returns: All LDAP objects that might match the parameters of the SEARCH
+		:returns: All entries that might match the parameters of the SEARCH
 		          operation.
-		:rtype: Iterable of :class:`Object`
+		:rtype: Iterable of :class:`Entry`
 
 		The default implementation yields :any:`rootdse` and :any:`subschema`.
 		Both are importent for feature detection, so make sure to also return
 		them (e.g. with ``yield from super().do_search(...)``).
 
-		For every returned object :any:`Object.search` is called to filter out
-		non-matching objects and to construct the response.
+		For every returned object :any:`Entry.search` is called to filter out
+		non-matching entries and to construct the response.
 
 		Note that if this method is as an iterator, its execution may be paused
 		for extended periods of time or aborted prematurly.'''
@@ -525,10 +525,10 @@ class LDAPRequestHandler(BaseLDAPRequestHandler):
 		:param value: Attribute value
 		:type value: bytes
 		:raises exceptions.LDAPError: on error
-		:returns: `Object` or None
+		:returns: `Entry` or None
 
 		The default implementation calls `do_search` and returns the first object
-		for which :any:`Object.compare` does not raise
+		for which :any:`Entry.compare` does not raise
 		:any:`exceptions.LDAPNoSuchObject` (i.e. the object has the requested DN).'''
 		objs = self.do_search(dn, ldap.SearchScope.baseObject, ldap.FilterPresent(attribute='objectClass'))
 		for obj in objs:
diff --git a/tests/test_objects.py b/tests/test_entries.py
similarity index 90%
rename from tests/test_objects.py
rename to tests/test_entries.py
index cf29d6e..e1d7529 100644
--- a/tests/test_objects.py
+++ b/tests/test_entries.py
@@ -2,9 +2,7 @@ import unittest
 import datetime
 
 import ldapserver
-from ldapserver.objects import AttributeDict, Object, RootDSE, SubschemaSubentry, ObjectTemplate, WILDCARD_VALUE
-from ldapserver.dn import DN
-from ldapserver import ldap
+from ldapserver import DN, AttributeDict, ObjectEntry, RootDSE, SubschemaSubentry, EntryTemplate, WILDCARD_VALUE, ldap
 
 schema = ldapserver.schema.RFC4519_SCHEMA
 
@@ -95,15 +93,15 @@ class TestAttributeDict(unittest.TestCase):
 		self.assertEqual(attrs.setdefault('c', ['default']), ['default'])
 		self.assertEqual(attrs['c'], ['default'])
 
-class TestObject(unittest.TestCase):
+class TestObjectEntry(unittest.TestCase):
 	def test_init(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[])
 		self.assertEqual(obj.dn, DN.from_str(schema, 'cn=foo,dc=example,dc=com'))
 		self.assertEqual(obj['cn'], ['foo', 'bar'])
 		self.assertEqual(obj['uid'], [])
 
 	def test_match_search_dn(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', objectclass=['top'])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', objectclass=['top'])
 		true_filter = ldap.FilterPresent('objectClass')
 
 		scope = ldap.SearchScope.baseObject
@@ -128,7 +126,7 @@ class TestObject(unittest.TestCase):
 		self.assertFalse(obj.match_search('cn=test,cn=foo,dc=example,dc=com', scope, true_filter))
 
 	def test_match_search_filter_present(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
 		dn = 'cn=foo,dc=example,dc=com'
 		scope = ldap.SearchScope.baseObject
 		# True
@@ -141,7 +139,7 @@ class TestObject(unittest.TestCase):
 		self.assertFalse(obj.match_search(dn, scope, ldap.FilterPresent('undefined')))
 
 	def test_match_search_filter_not(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
 		dn = 'cn=foo,dc=example,dc=com'
 		scope = ldap.SearchScope.baseObject
 		# Not True = False
@@ -154,7 +152,7 @@ class TestObject(unittest.TestCase):
 		self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterPresent('undefined'))))
 
 	def test_match_search_filter_and(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
 		dn = 'cn=foo,dc=example,dc=com'
 		scope = ldap.SearchScope.baseObject
 		true = ldap.FilterPresent('objectclass')
@@ -187,7 +185,7 @@ class TestObject(unittest.TestCase):
 		self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([true, undefined]))))
 
 	def test_match_search_filter_or(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
 		dn = 'cn=foo,dc=example,dc=com'
 		scope = ldap.SearchScope.baseObject
 		true = ldap.FilterPresent('objectclass')
@@ -220,7 +218,7 @@ class TestObject(unittest.TestCase):
 		self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([false, undefined]))))
 
 	def test_match_search_filter_equal(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
 		dn = 'cn=foo,dc=example,dc=com'
 		scope = ldap.SearchScope.baseObject
 		# True
@@ -243,7 +241,7 @@ class TestObject(unittest.TestCase):
 		self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('telexNumber', b'foo')))) # no EQUALITY
 
 	def test_match_search_filter_substr(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foobar', 'test'], uid=[], objectclass=['top'])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foobar', 'test'], uid=[], objectclass=['top'])
 		dn = 'cn=foo,dc=example,dc=com'
 		scope = ldap.SearchScope.baseObject
 		self.assertTrue(obj.match_search(dn, scope, ldap.FilterSubstrings('cn', [ldap.InitialSubstring(b'foo')])))
@@ -251,7 +249,7 @@ class TestObject(unittest.TestCase):
 		self.assertTrue(obj.match_search(dn, scope, ldap.FilterSubstrings('cn', [ldap.InitialSubstring(b'foo'), ldap.AnySubstring(b'b'), ldap.AnySubstring(b'a'), ldap.FinalSubstring(b'r')])))
 
 	def test_match_search_filter_le(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo'], objectclass=['top'], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo'], objectclass=['top'], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)])
 		dn = 'cn=foo,dc=example,dc=com'
 		scope = ldap.SearchScope.baseObject
 		self.assertTrue(obj.match_search(dn, scope, ldap.FilterLessOrEqual('createTimestamp', b'199412161032Z')))
@@ -261,7 +259,7 @@ class TestObject(unittest.TestCase):
 		self.assertTrue(obj.match_search(dn, scope, ldap.FilterLessOrEqual('cn', b'foo')))
 
 	def test_match_search_filter_ge(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo'], objectclass=['top'], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo'], objectclass=['top'], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)])
 		dn = 'cn=foo,dc=example,dc=com'
 		scope = ldap.SearchScope.baseObject
 		self.assertTrue(obj.match_search(dn, scope, ldap.FilterGreaterOrEqual('createTimestamp', b'199412161032Z')))
@@ -272,7 +270,7 @@ class TestObject(unittest.TestCase):
 		self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterGreaterOrEqual('cn', b'foo'))))
 
 	def test_match_search_filter_extensible_attribute_type(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'test'], uid=['foobar'], objectclass=['top'])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'test'], uid=['foobar'], objectclass=['top'])
 		dn = 'cn=foo,dc=example,dc=com'
 		scope = ldap.SearchScope.baseObject
 		# True
@@ -295,7 +293,7 @@ class TestObject(unittest.TestCase):
 		self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('generalizedTimeMatch', 'cn', b'199412161032Z', False))))
 
 	def test_match_search_filter_extensible_no_attribute_type(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'test'], uid=['foobar'], objectclass=['top'])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'test'], uid=['foobar'], objectclass=['top'])
 		dn = 'cn=foo,dc=example,dc=com'
 		scope = ldap.SearchScope.baseObject
 		# True
@@ -314,10 +312,10 @@ class TestObject(unittest.TestCase):
 		self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('octetStringOrderingMatch', None, b'someoctetstring', False))))
 
 	def test_match_search_filter_extensible_dn(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'test'], uid=['foobar'], objectclass=['top'])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'test'], uid=['foobar'], objectclass=['top'])
 		dn = 'cn=foo,dc=example,dc=com'
 		scope = ldap.SearchScope.baseObject
-		obj = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar'])
+		obj = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar'])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		# True
@@ -338,17 +336,17 @@ class TestObject(unittest.TestCase):
 		self.assertFalse(obj.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('generalizedTimeMatch', 'dc', b'example', False))))
 
 	def test_search(self):
-		class TrueObject(Object):
+		class TrueEntry(ObjectEntry):
 			def match_search(self, base_obj, scope, filter_obj):
 				return True
 
-		class FalseObject(Object):
+		class FalseEntry(ObjectEntry):
 			def match_search(self, base_obj, scope, filter_obj):
 				return False
 
-		obj = FalseObject(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'], subschemaSubentry=[DN(schema, 'cn=subschema')])
+		obj = FalseEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'], subschemaSubentry=[DN(schema, 'cn=subschema')])
 		self.assertIsNone(obj.search('cn=foo,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectclass'), [], False))
-		obj = TrueObject(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'], subschemaSubentry=[DN(schema, 'cn=subschema')])
+		obj = TrueEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'], subschemaSubentry=[DN(schema, 'cn=subschema')])
 		result = obj.search('cn=foo,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectclass'), [], False)
 		self.assertEqual(result.objectName, 'cn=foo,dc=example,dc=com')
 		self.assertEqual(len(result.attributes), 2)
@@ -374,7 +372,7 @@ class TestObject(unittest.TestCase):
 		                 {'cn': [], 'subschemaSubentry': []})
 
 	def test_compare(self):
-		obj = Object(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
+		obj = ObjectEntry(schema, 'cn=foo,dc=example,dc=com', cn=['foo', 'bar'], uid=[], objectclass=['top'])
 		self.assertTrue(obj.compare('cn=foo,dc=example,dc=com', 'cn', b'bar'))
 		self.assertFalse(obj.compare('cn=foo,dc=example,dc=com', 'cn', b'test'))
 		with self.assertRaises(ldapserver.exceptions.LDAPUndefinedAttributeType):
@@ -400,12 +398,12 @@ class TestRootDSE(unittest.TestCase):
 		self.assertFalse(obj.match_search('', ldap.SearchScope.wholeSubtree, ldap.FilterPresent('objectclass')))
 		self.assertFalse(obj.match_search('', ldap.SearchScope.baseObject, ldap.FilterPresent('cn')))
 
-class TestObjectTemplate(unittest.TestCase):
+class TestEntryTemplate(unittest.TestCase):
 	def test_init(self):
-		obj = ObjectTemplate(schema, 'ou=users,dc=example,dc=com', 'uid', cn=['foo', 'bar'], uid=[])
+		obj = EntryTemplate(schema, 'ou=users,dc=example,dc=com', 'uid', cn=['foo', 'bar'], uid=[])
 
 	def test_match_search_dn(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
 		true_filter = ldap.FilterPresent('objectClass')
 
 		scope = ldap.SearchScope.baseObject
@@ -427,7 +425,7 @@ class TestObjectTemplate(unittest.TestCase):
 		self.assertFalse(template.match_search('cn=test,cn=foo,dc=example,dc=com', scope, true_filter))
 
 	def test_match_search_filter_present(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		# True
@@ -441,7 +439,7 @@ class TestObjectTemplate(unittest.TestCase):
 		# We verify in ..._filter_not that Undefined/Maybe are not just False/True
 
 	def test_match_search_filter_not(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		# Not True = False
@@ -454,7 +452,7 @@ class TestObjectTemplate(unittest.TestCase):
 		self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterPresent('cn'))))
 
 	def test_match_search_filter_and(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		true = ldap.FilterPresent('objectclass')
@@ -501,7 +499,7 @@ class TestObjectTemplate(unittest.TestCase):
 		self.assertFalse(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterAnd([undefined, maybe]))))
 
 	def test_match_search_filter_or(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		true = ldap.FilterPresent('objectclass')
@@ -548,7 +546,7 @@ class TestObjectTemplate(unittest.TestCase):
 		self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterOr([undefined, maybe]))))
 
 	def test_match_search_filter_equal(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		# True
@@ -575,7 +573,7 @@ class TestObjectTemplate(unittest.TestCase):
 		self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterEqual('name', b'bar')))) # subtype
 
 	def test_match_search_filter_substr(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar', 'test'])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar', 'test'])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		# True
@@ -596,7 +594,7 @@ class TestObjectTemplate(unittest.TestCase):
 		self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterSubstrings('cn', [ldap.InitialSubstring(b'foo')]))))
 
 	def test_match_search_filter_le(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)], modifyTimestamp=[WILDCARD_VALUE])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)], modifyTimestamp=[WILDCARD_VALUE])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		# True
@@ -621,7 +619,7 @@ class TestObjectTemplate(unittest.TestCase):
 		self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterLessOrEqual('modifyTimestamp', b'199412161032Z'))))
 
 	def test_match_search_filter_ge(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)], modifyTimestamp=[WILDCARD_VALUE])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], createTimestamp=[datetime.datetime(1994, 12, 16, 10, 32, tzinfo=datetime.timezone.utc)], modifyTimestamp=[WILDCARD_VALUE])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		# True
@@ -646,7 +644,7 @@ class TestObjectTemplate(unittest.TestCase):
 		self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterGreaterOrEqual('modifyTimestamp', b'199412161032Z'))))
 
 	def test_match_search_filter_extensible_attribute_type(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar'])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar'])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		# True
@@ -673,7 +671,7 @@ class TestObjectTemplate(unittest.TestCase):
 		self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'cn', b'Foobar', False))))
 
 	def test_match_search_filter_extensible_no_attribute_type(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar'])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar'])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		# True
@@ -696,7 +694,7 @@ class TestObjectTemplate(unittest.TestCase):
 		self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', None, b'foo', False))))
 
 	def test_match_search_filter_extensible_dn(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar'])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar'])
 		dn = 'dc=example,dc=com'
 		scope = ldap.SearchScope.wholeSubtree
 		# True
@@ -721,21 +719,21 @@ class TestObjectTemplate(unittest.TestCase):
 		self.assertTrue(template.match_search(dn, scope, ldap.FilterNot(ldap.FilterExtensibleMatch('caseIgnoreMatch', 'cn', b'foo', True))))
 
 	def test_extract_search_constraints(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar'])
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], uid=['foobar'])
 		self.assertEqual(dict(template.extract_search_constraints('dc=exapmle,dc=com', ldap.SearchScope.wholeSubtree, ldap.FilterEqual('cn', b'foo')).items()), {'cn': ['foo']})
 		self.assertEqual(dict(template.extract_search_constraints('dc=exapmle,dc=com', ldap.SearchScope.wholeSubtree, ldap.FilterAnd([ldap.FilterEqual('objectclass', b'top'), ldap.FilterEqual('cn', b'foo')])).items()), {'cn': ['foo'], 'objectClass': ['top']})
 		self.assertEqual(dict(template.extract_search_constraints('cn=foo,dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectClass')).items()), {'cn': ['foo']})
 		self.assertEqual(dict(template.extract_search_constraints('dc=example,dc=com', ldap.SearchScope.baseObject, ldap.FilterPresent('objectClass')).items()), {})
 
-	def test_create_object(self):
-		template = ObjectTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], c=[WILDCARD_VALUE], uid=['foobar'])
-		obj = template.create_object('foo', cn=['foo', 'bar'], c=['DE'])
+	def test_create_entry(self):
+		template = EntryTemplate(schema, 'dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE], c=[WILDCARD_VALUE], uid=['foobar'])
+		obj = template.create_entry('foo', cn=['foo', 'bar'], c=['DE'])
 		self.assertEqual(obj.dn, DN(schema, 'cn=foo,dc=example,dc=com'))
 		self.assertEqual(dict(obj.items()), {'cn': ['foo', 'bar'], 'uid': ['foobar'], 'c': ['DE'], 'objectClass': ['top']})
-		obj = template.create_object('foo', cn=['foo', 'bar'])
+		obj = template.create_entry('foo', cn=['foo', 'bar'])
 		self.assertEqual(dict(obj.items()), {'cn': ['foo', 'bar'], 'uid': ['foobar'], 'objectClass': ['top']})
 		with self.assertRaises(ValueError):
-			template.create_object('foo', cn=['foo', 'bar'], c=['DE'], description=['foo bar'])
+			template.create_entry('foo', cn=['foo', 'bar'], c=['DE'], description=['foo bar'])
 
 class TestSubschemaSubentry(unittest.TestCase):
 	def test_init(self):
@@ -756,8 +754,8 @@ class TestSubschemaSubentry(unittest.TestCase):
 		self.assertIsInstance(attrs, AttributeDict)
 		self.assertIs(attrs.schema, subschema.schema)
 		self.assertEqual(attrs['cn'], ['foo'])
-		obj = subschema.Object('cn=foo,dc=example,dc=com', cn=['foo'])
-		self.assertIsInstance(obj, Object)
+		obj = subschema.ObjectEntry('cn=foo,dc=example,dc=com', cn=['foo'])
+		self.assertIsInstance(obj, ObjectEntry)
 		self.assertIs(obj.schema, subschema.schema)
 		self.assertEqual(obj.dn, DN(schema, 'cn=foo,dc=example,dc=com'))
 		self.assertEqual(obj['cn'], ['foo'])
@@ -766,7 +764,7 @@ class TestSubschemaSubentry(unittest.TestCase):
 		self.assertIsInstance(rootdse, RootDSE)
 		self.assertIs(rootdse.schema, subschema.schema)
 		self.assertEqual(rootdse['cn'], ['foo'])
-		template = subschema.ObjectTemplate('dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
-		self.assertIsInstance(template, ObjectTemplate)
+		template = subschema.EntryTemplate('dc=example,dc=com', 'cn', objectclass=['top'], cn=[WILDCARD_VALUE])
+		self.assertIsInstance(template, EntryTemplate)
 		self.assertIs(template.schema, subschema.schema)
 		self.assertEqual(template['subschemaSubentry'], [DN(schema, 'cn=Subschema')])
-- 
GitLab