diff --git a/lib/vsc/ldap/__init__.py b/lib/vsc/ldap/__init__.py index e711d20..7454760 100644 --- a/lib/vsc/ldap/__init__.py +++ b/lib/vsc/ldap/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright 2009-2023 Ghent University # @@ -54,7 +53,7 @@ class NoSuchUserError(Exception): """If a user cannot be found in the LDAP.""" def __init__(self, name): """Initialisation.""" - super(NoSuchUserError, self).__init__() + super().__init__() self.name = name @@ -62,7 +61,7 @@ class UserAlreadyExistsError(Exception): """If a user already is present in the LDAP, i.e., the dn already exists.""" def __init__(self, name): """Initialisation.""" - super(UserAlreadyExistsError, self).__init__() + super().__init__() self.name = name @@ -70,7 +69,7 @@ class NoSuchVoError(Exception): """If a VO cannot be found in the LDAP.""" def __init__(self, name): """Initialisation.""" - super(NoSuchVoError, self).__init__() + super().__init__() self.name = name @@ -78,7 +77,7 @@ class NoSuchGroupError(Exception): """If a group cannot be found in the LDAP.""" def __init__(self, name): """Initialisation.""" - super(NoSuchGroupError, self).__init__() + super().__init__() self.name = name @@ -86,7 +85,7 @@ class NoSuchProjectError(Exception): """If a project cannot be found in the LDAP.""" def __init__(self, name): """Initialisation.""" - super(NoSuchProjectError, self).__init__() + super().__init__() self.name = name @@ -94,5 +93,5 @@ class GroupAlreadyExistsError(Exception): """If a group is already present, i.e., the dn already exists.""" def __init__(self, name): """Initialisation.""" - super(GroupAlreadyExistsError, self).__init__() + super().__init__() self.name = name diff --git a/lib/vsc/ldap/filters.py b/lib/vsc/ldap/filters.py index 5993db6..3803245 100644 --- a/lib/vsc/ldap/filters.py +++ b/lib/vsc/ldap/filters.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright 2009-2023 Ghent University # @@ -83,7 +82,7 @@ class LdapFilterError(Exception): pass -class LdapFilter(object): +class LdapFilter: """Representing an LDAP filter with operators between the filter values. This is implemented as a tree, where the nodes are the operations, e.g., @@ -172,7 +171,7 @@ def _to_string(self, previous_operator=None): if self.left is None: # single value, self.root should be a string not representing an operator - return "(%s)" % (self.root) + return f"({self.root})" left_string = self.left._to_string(self.root) if not self.right is None: @@ -181,9 +180,9 @@ def _to_string(self, previous_operator=None): right_string = "" if self.root == previous_operator: - return "%s%s" % (left_string, right_string) + return f"{left_string}{right_string}" else: - return "(%s%s%s)" % (self.root, left_string, right_string) + return f"({self.root}{left_string}{right_string})" def _combine(self, operator, value=None): """Updates the tree with a new root, i.e., the given operator and @@ -215,7 +214,7 @@ def __init__(self, value, timestamp, comparator): will be converted to a format LDAP groks. @type comparator: string representing a comparison operation, e.g., <=, >= """ - super(TimestampFilter, self).__init__(value) + super().__init__(value) self.timestamp = convert_timestamp(timestamp)[1] if comparator != '>=' and comparator != '<=': raise LdapFilterError() @@ -223,9 +222,7 @@ def __init__(self, value, timestamp, comparator): def __str__(self): """Converts the filter to an LDAP understood string.""" - return "(& (modifyTimestamp%s%s) %s)" % (self.comparator, - self.timestamp, - super(TimestampFilter, self).__str__()) + return f"(& (modifyTimestamp{self.comparator}{self.timestamp}) {super().__str__()})" class NewerThanFilter(TimestampFilter): @@ -237,7 +234,7 @@ def __init__(self, value, timestamp): @type timestamp: string or datetime instance representing a timestamp. This value will be converted to a format LDAP groks. """ - super(NewerThanFilter, self).__init__(value, timestamp, '>=') + super().__init__(value, timestamp, '>=') class OlderThanFilter(TimestampFilter): @@ -249,32 +246,32 @@ def __init__(self, value, timestamp): @type timestamp: string or datetime instance representing a timestamp. This value will be converted to a format LDAP groks. """ - super(OlderThanFilter, self).__init__(value, timestamp, '<=') + super().__init__(value, timestamp, '<=') class CnFilter(LdapFilter): """Representa a filter that matches a given common name.""" def __init__(self, cn): - super(CnFilter, self).__init__("cn=%s" % (cn)) + super().__init__(f"cn={cn}") class MemberFilter(LdapFilter): """Represents a filter that looks if a member is listed in the memberUid.""" def __init__(self, user_id): - super(MemberFilter, self).__init__("memberUid=%s" % (user_id)) + super().__init__(f"memberUid={user_id}") class LoginFilter(LdapFilter): """Represents a filter that looks up a user based on his institute login name.""" def __init__(self, login): - super(LoginFilter, self).__init__("login=%s" % (login)) + super().__init__(f"login={login}") class InstituteFilter(LdapFilter): """Represents a filter that looks up a user based on his institute login name.""" def __init__(self, institute): - super(InstituteFilter, self).__init__("institute=%s" % (institute)) + super().__init__(f"institute={institute}") diff --git a/lib/vsc/ldap/group.py b/lib/vsc/ldap/group.py index 598bcc0..df28056 100644 --- a/lib/vsc/ldap/group.py +++ b/lib/vsc/ldap/group.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright 2009-2023 Ghent University # @@ -69,12 +68,12 @@ def __init__(self, group_id): @raise NoSuchGroupError if the group cannot be found. """ - super(LdapGroup, self).__init__(self.LDAP_OBJECT_CLASS_ATTRIBUTES) + super().__init__(self.LDAP_OBJECT_CLASS_ATTRIBUTES) self.group_id = group_id def get_ldap_info(self): """Retrieve the data from the LDAP to initially fill up the ldap_info field.""" - group_ldap_info = self.ldap_query.group_filter_search("cn=%s" % (self.group_id)) + group_ldap_info = self.ldap_query.group_filter_search(f"cn={self.group_id}") if len(group_ldap_info) == 0: logging.error("Could not find a group in the LDAP with the ID %s, raising NoSuchGroupError", self.group_id) diff --git a/lib/vsc/ldap/project.py b/lib/vsc/ldap/project.py index 3dbdcde..4e2b9b8 100644 --- a/lib/vsc/ldap/project.py +++ b/lib/vsc/ldap/project.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright 2009-2023 Ghent University # @@ -74,12 +73,12 @@ def __init__(self, project_id): @raise NoSuchProjectError if the project cannot be found. """ - super(LdapProject, self).__init__(self.LDAP_OBJECT_CLASS_ATTRIBUTES) + super().__init__(self.LDAP_OBJECT_CLASS_ATTRIBUTES) self.project_id = project_id def get_ldap_info(self): """Retrieve the data from the LDAP to initially fill up the ldap_info field.""" - project_ldap_info = self.ldap_query.project_filter_search("cn=%s" % (self.project_id)) + project_ldap_info = self.ldap_query.project_filter_search(f"cn={self.project_id}") if len(project_ldap_info) == 0: logging.error("Could not find a project in the LDAP with the ID %s, raising NoSuchGroupError", self.project_id) @@ -113,7 +112,7 @@ def get_for_member(user): """ # get all the projects in the LDAP - projects_info = user.ldap_query.projects_filter_search("memberUid=%s" % user.user_id) + projects_info = user.ldap_query.projects_filter_search(f"memberUid={user.user_id}") projects = [] for p_info in projects_info: diff --git a/lib/vsc/ldap/user.py b/lib/vsc/ldap/user.py index f11c811..171a5b1 100644 --- a/lib/vsc/ldap/user.py +++ b/lib/vsc/ldap/user.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright 2009-2023 Ghent University # @@ -78,7 +77,7 @@ def __init__(self, user_id): @type user_id: string representing the ID of the user, i.e., his cn in LDAP. """ - super(LdapUser, self).__init__(self.LDAP_OBJECT_CLASS_ATTRIBUTES) + super().__init__(self.LDAP_OBJECT_CLASS_ATTRIBUTES) self.user_id = user_id self.vo = None diff --git a/lib/vsc/ldap/utils.py b/lib/vsc/ldap/utils.py index f691a8a..a837ec7 100644 --- a/lib/vsc/ldap/utils.py +++ b/lib/vsc/ldap/utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright 2009-2023 Ghent University # @@ -45,7 +44,7 @@ EMPTY_GECOS_DURING_MODIFY = "EMPTYGECOSDURINGMODIFY" -class LdapConfiguration(object): +class LdapConfiguration: """Represents some LDAP configuration. @param url: url to ldap server (default None) @@ -78,7 +77,7 @@ class SchemaConfiguration(LdapConfiguration): """Represents an LDAP configuration with some extra schema-related information.""" def __init__(self): - super(SchemaConfiguration, self).__init__() + super().__init__() self.user_dn_base = None self.group_dn_base = None @@ -91,7 +90,7 @@ def __init__(self): self.vo_multi_value_attributes = None -class LdapConnection(object): +class LdapConnection: """Represents a connection to an LDAP server. - Offers a set of convenience functions for querying and updating the server. @@ -111,6 +110,7 @@ def __init__(self, configuration): """ self.configuration = configuration self.ldap_connection = None + self.ldap_url = None @TryOrFail(3, (ldap.LDAPError,), 10) def connect(self): @@ -193,7 +193,7 @@ def search_sync(self, ldap_filter, base, attributes=None): self.bind() # ldap_filter can also be an LdapFilter instance - ldap_filter = "%s" % ldap_filter + ldap_filter = f"{ldap_filter}" try: res = self.ldap_connection.search_s(base, ldap.SCOPE_SUBTREE, ldap_filter, attributes) @@ -218,7 +218,7 @@ def search_async_timeout(self, ldap_filter, base, attributes=None, timeout=10): attrs_only = False # filter can also be LdapFilter instance - ldap_filter = "%s" % ldap_filter + ldap_filter = f"{ldap_filter}" try: res = self.ldap_connection.search_st(base, ldap.SCOPE_SUBTREE, ldap_filter, attributes, attrs_only, timeout) @@ -274,8 +274,8 @@ def add(self, dn, attributes): if self.ldap_connection is None: self.bind() - changes = [(k, [v]) for (k, v) in attributes if not type(v) == list] - changes.extend([(k, v) for (k, v) in attributes if type(v) == list]) + changes = [(k, [v]) for (k, v) in attributes if not isinstance(v, list)] + changes.extend([(k, v) for (k, v) in attributes if isinstance(v, list)]) logging.info("Adding for dn=%s with changes = %s", dn, changes) try: @@ -462,8 +462,8 @@ def user_search(self, user_id, institute, attributes=None): @returns: a dictionary, with the values for the requested attributes for the given user """ - login_filter = LdapFilter("instituteLogin=%s" % (user_id)) - institute_filter = LdapFilter("institute=%s" % (institute)) + login_filter = LdapFilter(f"instituteLogin={user_id}") + institute_filter = LdapFilter(f"institute={institute}") result = self.user_filter_search(login_filter & institute_filter, attributes) logging.debug("user_search for %s, %s yields %s", user_id, institute, result) @@ -504,7 +504,7 @@ def __modify(self, current, dn, attributes): current_ = {} for key in attributes.keys(): current_[key] = current.get(key, []) - if current_[key] is '': + if current_[key] == '': logging.warning("Replacing empty string for key %s with %s before making modlist for dn %s", key, EMPTY_GECOS_DURING_MODIFY, dn) current_[key] = EMPTY_GECOS_DURING_MODIFY # hack to allow replacing empty strings @@ -525,7 +525,7 @@ def group_modify(self, cn, attributes): @raise: NoSuchGroupError """ - dn = "cn=%s,%s" % (cn, self.configuration.group_dn_base) + dn = f"cn={cn},{self.configuration.group_dn_base}" current = self.group_filter_search(CnFilter(cn)) if current is None: logging.error("group_modify did not find group with cn = %s (dn = %s)", cn, dn) @@ -542,7 +542,7 @@ def user_modify(self, cn, attributes): @raise: NoSuchUserError """ - dn = "cn=%s,%s" % (cn, self.configuration.user_dn_base) + dn = f"cn={cn},{self.configuration.user_dn_base}" current = self.user_filter_search(CnFilter(cn)) if current is None: logging.error("user_modify did not find user with cn = %s (dn = %s)", cn, dn) @@ -559,7 +559,7 @@ def project_modify(self, cn, attributes): @raise: NoSuchProjectError """ - dn = "cn=%s,%s" % (cn, self.configuration.project_dn_base) + dn = f"cn={cn},{self.configuration.project_dn_base}" current = self.project_filter_search(CnFilter(cn)) if current is None: logging.error("project_modify did not find project with cn = %s (dn = %s)", cn, dn) @@ -574,8 +574,8 @@ def user_add(self, cn, attributes): @type cn: string representing the common name for the user. Together with the subtree, this forms the dn. @type attributes: dictionary with attributes for which a value should be added """ - dn = "cn=%s,%s" % (cn, self.configuration.user_dn_base) - attributes = {key:[v.encode("utf-8") if type(v) == str else v for v in values] + dn = f"cn={cn},{self.configuration.user_dn_base}" + attributes = {key:[v.encode("utf-8") if isinstance(v, str) else v for v in values] for key, values in attributes.items()} self.ldap.add(dn, attributes.items()) @@ -585,8 +585,8 @@ def group_add(self, cn, attributes): @type cn: string representing the common name for the group. Together with the subtree, this forms the dn. @type attributes: dictionary with attributes for which a value should be added """ - dn = "cn=%s,%s" % (cn, self.configuration.group_dn_base) - attributes = {key:[v.encode("utf-8") if type(v) == str else v for v in values] + dn = f"cn={cn},{self.configuration.group_dn_base}" + attributes = {key:[v.encode("utf-8") if isinstance(v, str) else v for v in values] for key, values in attributes.items()} self.ldap.add(dn, attributes.items()) @@ -596,8 +596,8 @@ def project_add(self, cn, attributes): @type cn: string representing the common name for the project. Together with the subtree, this forms the dn. @type attributes: dictionary with attributes for which a value should be added """ - dn = "cn=%s,%s" % (cn, self.configuration.project_dn_base) - attributes = {key:[v.encode("utf-8") if type(v) == str else v for v in values] + dn = f"cn={cn},{self.configuration.project_dn_base}" + attributes = {key:[v.encode("utf-8") if isinstance(v, str) else v for v in values] for key, values in attributes.items()} self.ldap.add(dn, attributes.items()) @@ -678,7 +678,7 @@ def get_schema(self, ldap_obj_class_name_or_oid, reload=False, do_reload=False): return self.schema[ldap_obj_class_name_or_oid] -class LdapEntity(object): +class LdapEntity: """Base class for all things LDAP that work on a higher level.""" def __init__(self, object_classes=None): @@ -703,7 +703,6 @@ def modify_ldap(self, attributes): Should be iplemented by deriving classes. """ - pass def __getattr__(self, name): """Getter for the LdapUser fields. Only accessed for fields that are in @@ -758,7 +757,6 @@ def __setattr__(self, name, value): except ldap.LDAPError: logging.error("Could not save the new value %s for %s with cn=%s to the LDAP", value, name, self.vsc_user_id) - pass else: object.__setattr__(self, name, value) except AttributeError: diff --git a/lib/vsc/ldap/vo.py b/lib/vsc/ldap/vo.py index fb50bba..2b9fc95 100644 --- a/lib/vsc/ldap/vo.py +++ b/lib/vsc/ldap/vo.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright 2009-2023 Ghent University # @@ -69,7 +68,7 @@ def __init__(self, vo_id): @raise NoSuchVoError if the VO cannot be found. """ - super(LdapVo, self).__init__(vo_id) + super().__init__(vo_id) def get_ldap_info(self): """Retrieve the data from the LDAP to initially fill up the ldap_info field.""" @@ -98,9 +97,9 @@ def get_for_member(user): # there should be at most a single VO. if len(vos) > 1: - msg = "Found multiple VOs for the given user (%s), vos = %s" % (user, vos) + msg = f"Found multiple VOs for the given user ({user}), vos = {vos}" logging.error(msg) - raise Exception(msg) + raise ValueError(msg) vo_info = vos[0] diff --git a/setup.py b/setup.py index afa0754..eca1722 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- ## # Copyright 2009-2012 Ghent University # @@ -43,7 +42,7 @@ 'future >= 0.16.0', 'python-ldap', ], - 'version': '2.2.2', + 'version': '2.2.3', 'author': [ag, kh, sdw, wdp, jt], 'maintainer': [ag], } diff --git a/test/filters.py b/test/filters.py index 3fba227..3118fd9 100644 --- a/test/filters.py +++ b/test/filters.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright 2009-2023 Ghent University # @@ -38,7 +37,7 @@ from vsc.ldap.filters import LdapFilter -class SingleChoiceGenerator(object): +class SingleChoiceGenerator: """Provides a list of exhausting choices, reducing the choices until in each iteration.""" def __init__(self, values): self.values = values @@ -52,7 +51,7 @@ def next(self): return v -class LdapFilterGenerator(object): +class LdapFilterGenerator: """Generates random LdapFilter instances""" def __init__(self): @@ -79,12 +78,12 @@ def next(self): attribute_choice = SingleChoiceGenerator(attributes) size = random.randint(1, random.randint(1, len(self.attributes))) - for d in range(0, size): + for _ in range(0, size): op = random.choice(self.operators) at = attribute_choice.next() - new = LdapFilter("%s=%s" % (at, ''.join([random.choice(string.printable) for x in range(16)]))) + new = LdapFilter(f"{at}={''.join([random.choice(string.printable) for x in range(16)])}") if not ldap_filter: ldap_filter = LdapFilter(new) @@ -107,11 +106,11 @@ def test_and(self): """Test the and operator for combining two filters.""" left = LFG.next() right = LFG.next() - combination = (left & right) + combination = left & right - left_string = "%s" % (left) - right_string = "%s" % (right) - combination_string = "%s" % (combination) + left_string = f"{left}" + right_string = f"{right}" + combination_string = f"{combination}" self.assertTrue(len(combination_string) <= 3 + len(left_string) + len(right_string)) self.assertTrue(combination_string[0] == '(') @@ -127,9 +126,9 @@ def test_or(self): right = LFG.next() combination = left | right - left_string = "%s" % (left) - right_string = "%s" % (right) - combination_string = "%s" % (combination) + left_string = f"{left}" + right_string = f"{right}" + combination_string = f"{combination}" self.assertTrue(len(combination_string) <= 3 + len(left_string) + len(right_string)) self.assertTrue(combination_string[0] == '(') @@ -144,7 +143,7 @@ def test_negate(self): left = LFG.next() negation = left.negate() - negation_string = "%s" % (negation) + negation_string = f"{negation}" self.assertTrue(negation_string[0] == '(') self.assertTrue(negation_string[1] == '!') @@ -155,9 +154,9 @@ def test_from_list_and(self): fs = [LFG.next() for x in range(random.randint(2,30))] combination = LdapFilter.from_list(lambda x, y: x & y, fs) - combination_string = "%s" % (combination) + combination_string = f"{combination}" - self.assertTrue(len(combination_string) <= 3 + sum(map(lambda f: len("%s" % (f)), fs))) + self.assertTrue(len(combination_string) <= 3 + sum(map(lambda f: len(f"{f}"), fs))) self.assertTrue(combination_string[0] == '(') self.assertTrue(combination_string[1] == '&') diff --git a/tox.ini b/tox.ini index 159ce91..1fcd1e0 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ # DO NOT EDIT MANUALLY [tox] -envlist = py36 +envlist = py36,py39 skipsdist = true [testenv] diff --git a/vsc-ci.ini b/vsc-ci.ini new file mode 100644 index 0000000..ebb0e46 --- /dev/null +++ b/vsc-ci.ini @@ -0,0 +1,2 @@ +[vsc-ci] +py39_tests_must_pass=1