diff --git a/arkindex/documents/date_parser.py b/arkindex/documents/date_parser.py index 20860e2ba63fa23dc9e50867d829962e8bf9409d..a12121c56bd60a1ca4c7be66e9ad51987a22cfdc 100644 --- a/arkindex/documents/date_parser.py +++ b/arkindex/documents/date_parser.py @@ -143,7 +143,7 @@ def instanciate_date(date_elt): try: date.validate() except ValueError as e: - logger.warning("Date fields are incorrect: {}".format(e)) + logger.warning(f"Date fields are incorrect: {e}") raise return date @@ -161,6 +161,6 @@ def parse_date(raw_date, functions_table=DATE_FUNCTIONS_TABLE): if date_elts: return tuple(map(instanciate_date, date_elts)) except Exception: - logger.warning("Failed parsing {} with function {}".format(raw_date, f.__name__)) - logger.warning("Date not supported: {}".format(raw_date)) + logger.warning(f"Failed parsing {raw_date} with function {f.__name__}") + logger.warning(f"Date not supported: {raw_date}") return () diff --git a/arkindex/documents/dates.py b/arkindex/documents/dates.py index dc3012bef8a3276bb890e8cb6bb5607c5876e52d..0c759b7a360cacaa1956fdee67582f84f5408f08 100644 --- a/arkindex/documents/dates.py +++ b/arkindex/documents/dates.py @@ -16,7 +16,7 @@ class DatePrecision(Enum): Day = "d" -class InterpretedDate(object): +class InterpretedDate: def __init__(self, year, month=None, day=None, type=DateType.Exact): self.year = int(year) @@ -26,17 +26,17 @@ class InterpretedDate(object): def validate(self): if self.year < 0: - raise ValueError("Year {} is negative".format(self.year)) + raise ValueError(f"Year {self.year} is negative") if self.month and (self.month < 1 or self.month > 12): - raise ValueError("Month {} is not between 1 and 12".format(self.month)) + raise ValueError(f"Month {self.month} is not between 1 and 12") if self.day and (self.day < 1 or self.day > 31): - raise ValueError("Day {} is not between 1 and 31".format(self.day)) + raise ValueError(f"Day {self.day} is not between 1 and 31") # Check if day is correct depending on year and month if self.precision == DatePrecision.Day: try: datetime(*tuple(self)) except ValueError: - raise ValueError("Date format is incorrect {}".format(self)) + raise ValueError(f"Date format is incorrect {self}") @property def precision(self): @@ -70,10 +70,10 @@ class InterpretedDate(object): return s > o def __str__(self): - return "-".join("{:02d}".format(e) for e in tuple(self) if e) + return "-".join(f"{e:02d}" for e in tuple(self) if e) -class InterpretedDateMixin(object): +class InterpretedDateMixin: """ Adds on-demand date parsing from a text field to InterpretedDates. Requires a `raw_dates` property that returns the date string. diff --git a/arkindex/documents/indexer.py b/arkindex/documents/indexer.py index 203722ec319c6fadd9c9ba537d582693a05d26b7..37f7e200e4133c4d982bcb2ba5349a6c5a2977d7 100644 --- a/arkindex/documents/indexer.py +++ b/arkindex/documents/indexer.py @@ -59,7 +59,7 @@ INNER JOIN documents_elementtype elementtype ON (element.type_id = elementtype.i """ -class Indexer(object): +class Indexer: # The query yielding all the elements to run on will look for all the child elements of all indexable elements # The joins can take a very long time, so the query gets split into one to fetch all the indexable elements, diff --git a/arkindex/documents/management/commands/move_lines_to_parent.py b/arkindex/documents/management/commands/move_lines_to_parent.py index 2927bc6b39163e30fd9e5210c822b976ab7c4a40..dd393f6113eddb474b595e1f9ff8861177413f9b 100644 --- a/arkindex/documents/management/commands/move_lines_to_parent.py +++ b/arkindex/documents/management/commands/move_lines_to_parent.py @@ -6,10 +6,13 @@ from django.core.management.base import BaseCommand, CommandError from arkindex.documents.models import ElementType from arkindex.project.argparse import CorpusArgument + # x and y of top left and bottom right points -BBox = NamedTuple( - "BBox", [("left", int), ("top", int), ("right", int), ("bottom", int)] -) +class BBox(NamedTuple): + left: int + top: int + right: int + bottom: int def compute_polygon_area(polygon: BBox): diff --git a/arkindex/documents/managers.py b/arkindex/documents/managers.py index d09737577c645dfa2e5be78165f3b1cd76e7df0e..9ccb6494b871c79f1e221b5f1569de4b0ce62015 100644 --- a/arkindex/documents/managers.py +++ b/arkindex/documents/managers.py @@ -47,13 +47,13 @@ class ElementQuerySet(models.QuerySet): # by including the target SQL query, and joining it directly with paths # It's not possible to do that join with Django ORM with connections["default"].cursor() as cursor: - cursor.execute("""select min(length), max(length) FROM ( + cursor.execute(f"""select min(length), max(length) FROM ( select array_length(p.path, 1) as length from documents_elementpath as p inner join - ({}) as input on (array[input.id] && p.path) + ({sql}) as input on (array[input.id] && p.path) ) as lengths - """.format(sql), params) + """, params) min_paths, max_paths = cursor.fetchone() # Postgres will give us None when no children is found @@ -86,16 +86,16 @@ class ElementQuerySet(models.QuerySet): # directly into an SQL DELETE statement for paths # Once paths are deleted, we can finally delete the targeted elements with connections["default"].cursor() as cursor: - cursor.execute(""" + cursor.execute(f""" WITH element_ids (id) AS ( DELETE FROM documents_elementpath - WHERE element_id IN ({}) + WHERE element_id IN ({sql}) RETURNING element_id ) DELETE FROM documents_element element USING element_ids WHERE element.id = element_ids.id - """.format(sql), params) + """, params) # Finally, delete top elements. # Ensure the QuerySet does not use a DISTINCT; it is useless for a deletion, and since Django 3.2, diff --git a/arkindex/documents/models.py b/arkindex/documents/models.py index 5d0109d11b64ea98a14d67e3cf857785c2806376..8f981de9721d938dfaca6e97878b05c795eeaa63 100644 --- a/arkindex/documents/models.py +++ b/arkindex/documents/models.py @@ -688,7 +688,7 @@ class Element(IndexableModel): ) def __str__(self): - return "{}: {}".format(self.type.display_name, self.name) + return f"{self.type.display_name}: {self.name}" class EntityType(models.Model): @@ -835,7 +835,7 @@ class Transcription(models.Model): ] def __str__(self): - return "Transcription: {}".format(self.text[:20]) + return f"Transcription: {self.text[:20]}" class TranscriptionEntity(models.Model): @@ -1120,9 +1120,7 @@ class MetaData(InterpretedDateMixin, models.Model): if self.entity is None or self.element is None: return if self.entity.corpus != self.element.corpus: - raise ValidationError("Entity's corpus {} is different from the expected corpus {}".format( - self.entity.corpus, - self.element.corpus)) + raise ValidationError(f"Entity's corpus {self.entity.corpus} is different from the expected corpus {self.element.corpus}") def save(self, *args, **kwargs): self.clean() diff --git a/arkindex/documents/serializers/elements.py b/arkindex/documents/serializers/elements.py index d8ba836bed4af52fe35ef1d7c072fe6d0f890914..b7d70eb7852d42b9023af21f01e714b19b54d1be 100644 --- a/arkindex/documents/serializers/elements.py +++ b/arkindex/documents/serializers/elements.py @@ -500,7 +500,7 @@ class ElementParentSerializer(serializers.Serializer): parent = data.get("parent") if parent.corpus_id != child.corpus_id: - errors["parent"].append("Parent is not from corpus '{}'".format(child.corpus.name)) + errors["parent"].append(f"Parent is not from corpus '{child.corpus.name}'") if parent.id == child.id: errors["parent"].append("A child cannot be its own parent") if errors: @@ -511,12 +511,12 @@ class ElementParentSerializer(serializers.Serializer): # Assert parent is not an child's ancestor already if ElementPath.objects.filter(element_id=child.id, path__contains=[parent.id]).exists(): raise ValidationError({"parent": [ - "'{}' is already a parent of '{}'".format(parent.id, child.id) + f"'{parent.id}' is already a parent of '{child.id}'" ]}) # Assert parent is not an alement's descendant if ElementPath.objects.filter(element_id=parent.id, path__contains=[child.id]).exists(): raise ValidationError({"parent": [ - "'{}' is a child of element '{}'".format(parent.id, child.id) + f"'{parent.id}' is a child of element '{child.id}'" ]}) def create(self, validated_data): @@ -1006,12 +1006,12 @@ class ElementDestinationSerializer(serializers.Serializer): # Assert destination is not a source's direct ancestor already if ElementPath.objects.filter(element_id=source.id, path__last=destination.id).exists(): raise ValidationError({"destination": [ - "'{}' is already a direct parent of '{}'".format(destination.id, source.id) + f"'{destination.id}' is already a direct parent of '{source.id}'" ]}) # Assert destination is not a source's descendant if ElementPath.objects.filter(element_id=destination.id, path__contains=[source.id]).exists(): raise ValidationError({"destination": [ - "'{}' is a child of element '{}'".format(destination.id, source.id) + f"'{destination.id}' is a child of element '{source.id}'" ]}) @@ -1048,12 +1048,12 @@ class SelectionMoveSerializer(serializers.Serializer, SelectionMixin): # Assert destination is not a source's direct ancestor already if ElementPath.objects.filter(element__corpus_id=corpus.id, element__selections__user_id=self.context["request"].user.id, path__last=destination.id).exists(): raise ValidationError({"destination": [ - "'{}' is already a direct parent of one or more selected elements.".format(destination.id) + f"'{destination.id}' is already a direct parent of one or more selected elements." ]}) # Assert destination is not a source's descendant if destination.paths.filter(path__overlap=Array(selected_elements.values_list("id", flat=True))).exists(): raise ValidationError({"destination": [ - "'{}' is a child of one or more selected elements.".format(destination.id) + f"'{destination.id}' is a child of one or more selected elements." ]}) return data diff --git a/arkindex/documents/serializers/ml.py b/arkindex/documents/serializers/ml.py index ee19f0b8c7d865f45a9d85b06a2e1f1412c2b517..daa3aed4dfe14d93e447c2d2b3ea5f6f0833957f 100644 --- a/arkindex/documents/serializers/ml.py +++ b/arkindex/documents/serializers/ml.py @@ -254,10 +254,7 @@ class ClassificationsSelectionSerializer(serializers.ModelSerializer): if ml_class is None: raise ValidationError({"ml_class": "Ml class {} not found".format(data["ml_class"])}) if ml_class.corpus.id != corpus.id: - raise ValidationError("Ml class {} does not belong to the corpus {}".format( - ml_class, - corpus - )) + raise ValidationError(f"Ml class {ml_class} does not belong to the corpus {corpus}") return data diff --git a/arkindex/documents/tests/commands/test_load_export.py b/arkindex/documents/tests/commands/test_load_export.py index fe7176f77bab295f9c39744540f9f7e892ad90b8..3bf8e452907f993998fd74713ada409fc82d2a50 100644 --- a/arkindex/documents/tests/commands/test_load_export.py +++ b/arkindex/documents/tests/commands/test_load_export.py @@ -54,7 +54,7 @@ class TestLoadExport(FixtureTestCase): "documents.classification": ["ml_class"], } - with open(path, "r") as file: + with open(path) as file: data = json.loads(file.read()) results = [] diff --git a/arkindex/documents/tests/test_bulk_classification.py b/arkindex/documents/tests/test_bulk_classification.py index ce5ab5e7d739c82a33ee9bbbcff77d2f338af1c7..60f82ea19b14bc9891496c53e80a88cb54ba1595 100644 --- a/arkindex/documents/tests/test_bulk_classification.py +++ b/arkindex/documents/tests/test_bulk_classification.py @@ -56,7 +56,7 @@ class TestBulkClassification(FixtureAPITestCase): self.assertDictEqual( response.json(), { - "parent": ['Invalid pk "{}" - object does not exist.'.format(private_page.id)] + "parent": [f'Invalid pk "{private_page.id}" - object does not exist.'] } ) diff --git a/arkindex/documents/tests/test_classes.py b/arkindex/documents/tests/test_classes.py index 0f4f7291a6541755497efbeb562c160a5d55762f..20409bbcc53b93f679a3e3562b6ad42b178815f3 100644 --- a/arkindex/documents/tests/test_classes.py +++ b/arkindex/documents/tests/test_classes.py @@ -29,7 +29,7 @@ class TestClasses(FixtureAPITestCase): for elt_num in range(1, 13): elt = cls.corpus.elements.create( - name="elt_{}".format(elt_num), + name=f"elt_{elt_num}", type=cls.classified, ) elt.add_parent(cls.parent) diff --git a/arkindex/documents/tests/test_edit_elementpath.py b/arkindex/documents/tests/test_edit_elementpath.py index ac0f945e98d688a1481e61d08693a88beb374a00..5633763ceace96fd5fa7a00c21313f4224cd3002 100644 --- a/arkindex/documents/tests/test_edit_elementpath.py +++ b/arkindex/documents/tests/test_edit_elementpath.py @@ -358,7 +358,7 @@ class TestEditElementPath(FixtureTestCase): # B will only have one remaining path: the first path that got picked by remove_child for the update. # The other path will have been deleted. We can therefore get this remaining path and compare it by its ID # to the two paths that we had before, and pick the parent that was in the old version of this path. - class FirstParent(object): + class FirstParent: def __str__(self): path_id = elements["B"].paths.get().id if path1.id == path_id: diff --git a/arkindex/documents/tests/test_element_paths_api.py b/arkindex/documents/tests/test_element_paths_api.py index f71d1298e392a981604640bd7992651106c434a3..290d2ce63ee22a2c9874b1f6cffd460156fe9f05 100644 --- a/arkindex/documents/tests/test_element_paths_api.py +++ b/arkindex/documents/tests/test_element_paths_api.py @@ -78,7 +78,7 @@ class TestElementsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( response.json(), - {"child": ['Invalid pk "{}" - object does not exist.'.format(self.desk.id)]} + {"child": [f'Invalid pk "{self.desk.id}" - object does not exist.']} ) def test_different_corpus(self): @@ -94,7 +94,7 @@ class TestElementsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( response.json(), - {"parent": ["Parent is not from corpus '{}'".format(self.room.corpus.name)]} + {"parent": [f"Parent is not from corpus '{self.room.corpus.name}'"]} ) def test_own_parent(self): @@ -116,7 +116,7 @@ class TestElementsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( response.json(), - {"parent": ["'{}' is already a parent of '{}'".format(self.room.id, self.desk.id)]} + {"parent": [f"'{self.room.id}' is already a parent of '{self.desk.id}'"]} ) def test_cycles(self): @@ -129,7 +129,7 @@ class TestElementsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( response.json(), - {"parent": ["'{}' is a child of element '{}'".format(self.room.id, self.house.id)]} + {"parent": [f"'{self.room.id}' is a child of element '{self.house.id}'"]} ) def test_delete_forbidden(self): @@ -147,7 +147,7 @@ class TestElementsAPI(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual( response.json(), - {"child": ['Invalid pk "{}" - object does not exist.'.format(self.desk.id)]} + {"child": [f'Invalid pk "{self.desk.id}" - object does not exist.']} ) def test_delete_relation(self): diff --git a/arkindex/images/management/commands/check_images.py b/arkindex/images/management/commands/check_images.py index a924b16a81e424a54625e00ecdab36277df99a38..9a630e9c4ec9e160caaae793b1564728de910718 100644 --- a/arkindex/images/management/commands/check_images.py +++ b/arkindex/images/management/commands/check_images.py @@ -59,7 +59,7 @@ class Command(BaseCommand): server_sample = server.images \ .filter(status=S3FileStatus.Checked) \ .order_by("?")[:sample] - self.stdout.write("Re-checking {} images in server {}".format(len(server_sample), server.display_name)) + self.stdout.write(f"Re-checking {len(server_sample)} images in server {server.display_name}") self.check(server_sample) self.check(images) @@ -67,7 +67,7 @@ class Command(BaseCommand): successful, failed = 0, 0 for image in images: - self.stdout.write("Checking image {} at {}".format(str(image.id), image.url)) + self.stdout.write(f"Checking image {str(image.id)} at {image.url}") image.perform_check(save=True) if image.status == S3FileStatus.Checked: successful += 1 diff --git a/arkindex/images/models.py b/arkindex/images/models.py index 8b904acaf919c199d8ce862d12280b5da03dd7f4..f2ab13933deb0a645586c3ad543c9b9ff9bf574f 100644 --- a/arkindex/images/models.py +++ b/arkindex/images/models.py @@ -156,11 +156,11 @@ class ImageServer(models.Model): folder_slash = folder.lstrip("/") + "/" images = self.images.filter(path__startswith=folder_slash) if not images.exists(): - raise ValueError('No images are in the "{}" folder'.format(folder)) + raise ValueError(f'No images are in the "{folder}" folder') # Create the new server on the subfolder new_server = ImageServer.objects.create( - display_name="{}_{}".format(self.display_name, slugify(folder)), + display_name=f"{self.display_name}_{slugify(folder)}", url=urllib.parse.urljoin(self.url.lstrip("/") + "/", folder), ) @@ -218,8 +218,8 @@ class Image(S3FileMixin, IndexableModel): if max_width is None and max_height is None: resize = "full" else: - resize = "{0},{1}".format(max_width or "", max_height or "") - return "{0}/full/{1}/0/default.jpg".format(self.url.rstrip("/"), resize) + resize = "{},{}".format(max_width or "", max_height or "") + return "{}/full/{}/0/default.jpg".format(self.url.rstrip("/"), resize) @property def s3_put_url(self): @@ -359,7 +359,7 @@ class Thumbnail(S3FileMixin): @property def name(self): - return "{}.jpg".format(str(self.element.id.hex)) + return f"{str(self.element.id.hex)}.jpg" @cached_property def s3_key(self): diff --git a/arkindex/images/tests/test_image_api.py b/arkindex/images/tests/test_image_api.py index c66e1052c3ef9cf03e417d91edf82c188ac46237..edbeb945c12a910c501a4e0dd17d71b97602827d 100644 --- a/arkindex/images/tests/test_image_api.py +++ b/arkindex/images/tests/test_image_api.py @@ -35,7 +35,7 @@ class TestImageApi(FixtureAPITestCase): def setUp(self): super().setUp() # A random 32-character hex string - self.image_hash = "{:032x}".format(random.randrange(16**32)) + self.image_hash = f"{random.randrange(16**32):032x}" def test_create_image_requires_login(self): with self.assertNumQueries(0): @@ -164,7 +164,7 @@ class TestImageApi(FixtureAPITestCase): def test_create_image_unique_path(self): self.client.force_login(self.superuser) existing_image = self.imgsrv.images.create(path="something", hash=self.image_hash) - new_hash = "{:032x}".format(random.randrange(16**32)) + new_hash = f"{random.randrange(16**32):032x}" with self.assertNumQueries(4): response = self.client.post(reverse("api:image-create"), {"hash": new_hash, "path": "something"}) diff --git a/arkindex/images/views.py b/arkindex/images/views.py index b685c604d6e76ef9f9d85b7e50a11a6c5629c125..e2f05e3b7fb3525d76b96ab8b1299346ef765e9a 100644 --- a/arkindex/images/views.py +++ b/arkindex/images/views.py @@ -43,7 +43,7 @@ class ImageServerMergeView(ImageServerAdminView): except ValueError as e: self.model_admin.message_user( self.request, - "Merging failed: {}".format(str(e)), + f"Merging failed: {str(e)}", level=messages.ERROR, ) return super().form_valid(form) @@ -52,7 +52,7 @@ class ImageServerMergeView(ImageServerAdminView): self.model_admin.message_user( self.request, - "Successfully merged {} into {}".format(source, destination), + f"Successfully merged {source} into {destination}", level=messages.SUCCESS, ) return super().form_valid(form) @@ -70,14 +70,14 @@ class ImageServerSplitView(ImageServerAdminView): except ValueError as e: self.model_admin.message_user( self.request, - "Splitting failed: {}".format(str(e)), + f"Splitting failed: {str(e)}", level=messages.ERROR, ) return super().form_valid(form) self.model_admin.message_user( self.request, - 'Successfully split "{}" from {} into {}'.format(folder, self.server, new_server), + f'Successfully split "{folder}" from {self.server} into {new_server}', level=messages.SUCCESS, ) return super().form_valid(form) diff --git a/arkindex/ponos/models.py b/arkindex/ponos/models.py index 76a91f8a152761d566d025ff9f2558f3db98fd6f..1618e3e632a74dd7e8d0a0b973a260d13a78750f 100644 --- a/arkindex/ponos/models.py +++ b/arkindex/ponos/models.py @@ -22,7 +22,7 @@ from arkindex.project.validators import MaxValueValidator def generate_seed() -> str: - return "{:064x}".format(random.getrandbits(256)) + return f"{random.getrandbits(256):064x}" def gen_nonce(size=16): @@ -60,7 +60,7 @@ class Farm(models.Model): ] def __str__(self) -> str: - return "Farm {}".format(self.name) + return f"Farm {self.name}" def is_available(self, user) -> bool: return True @@ -250,7 +250,7 @@ class TaskLogs(S3FileMixin): try: log_bytes = self.s3_object.get( - Range="bytes=-{}".format(max_length), + Range=f"bytes=-{max_length}", )["Body"].read() except ClientError as e: @@ -376,7 +376,7 @@ class Task(models.Model): ] def __str__(self) -> str: - return "Task {}, run {}, depth {}".format(self.slug, self.run, self.depth) + return f"Task {self.slug}, run {self.run}, depth {self.depth}" def get_absolute_url(self) -> str: """ diff --git a/arkindex/ponos/serializer_fields.py b/arkindex/ponos/serializer_fields.py index 28231333920adead31076f4bfa8ca02aed19bcfa..1bd83378c89a4b1c2b71abdfe3d8a48ebb40988a 100644 --- a/arkindex/ponos/serializer_fields.py +++ b/arkindex/ponos/serializer_fields.py @@ -56,7 +56,7 @@ class Base64Field(serializers.CharField): return base64.b64encode(obj) -class CurrentProcessDefault(object): +class CurrentProcessDefault: """ Use the process of the currently authenticated task as a default value. If Ponos task authentication is not in use, returns None. diff --git a/arkindex/ponos/tasks.py b/arkindex/ponos/tasks.py index b4744c1a0134e57e14ea402fce88598b88ec57ed..f568d59eaaa807681a9c52ca656175e9e450aad9 100644 --- a/arkindex/ponos/tasks.py +++ b/arkindex/ponos/tasks.py @@ -181,14 +181,12 @@ def run_docker_task(client, task, temp_dir): # 4. Download extra_files if task.extra_files: - logger.info("Downloading extra_files for task {!s}".format(task)) + logger.info(f"Downloading extra_files for task {task!s}") try: download_extra_files(task, temp_dir) except Exception as e: logger.warning( - "Failed downloading extra_files for task {!s}: {!s}".format( - task, e - ) + f"Failed downloading extra_files for task {task!s}: {e!s}" ) task.state = State.Error task.save() diff --git a/arkindex/ponos/tests/test_models.py b/arkindex/ponos/tests/test_models.py index 8492b0776579a0a771efcfc9d045bf8cdc1182a5..8b8ff878e1b94e8065db4134bba56ac3f6f62def 100644 --- a/arkindex/ponos/tests/test_models.py +++ b/arkindex/ponos/tests/test_models.py @@ -30,17 +30,17 @@ class TestModels(FixtureAPITestCase): self.task1.save() if state in FINAL_STATES: self.assertTrue( - self.task1.is_final(), msg="{} should be final".format(state) + self.task1.is_final(), msg=f"{state} should be final" ) self.assertTrue( - self.process.is_final, msg="{} should be final".format(state) + self.process.is_final, msg=f"{state} should be final" ) else: self.assertFalse( - self.task1.is_final(), msg="{} should not be final".format(state) + self.task1.is_final(), msg=f"{state} should not be final" ) self.assertFalse( - self.process.is_final, msg="{} should not be final".format(state) + self.process.is_final, msg=f"{state} should not be final" ) def test_requires_gpu(self): diff --git a/arkindex/process/api.py b/arkindex/process/api.py index f527305661111a9e60b334b52f2e63ccaed632c4..09703baa6e5ba1d0e679837deef2ee6da6fbc82d 100644 --- a/arkindex/process/api.py +++ b/arkindex/process/api.py @@ -189,7 +189,7 @@ class ProcessList(ProcessACLMixin, ListAPIView): try: corpus_id = UUID(corpus_id) except (AttributeError, ValueError): - raise ValidationError({"corpus": ["'{}' is not a valid UUID".format(corpus_id)]}) + raise ValidationError({"corpus": [f"'{corpus_id}' is not a valid UUID"]}) # No supplementary validation is required on the corpus ID filter filters &= Q(corpus=corpus_id) @@ -270,7 +270,7 @@ class ProcessList(ProcessACLMixin, ListAPIView): return qs.order_by("-date_order") -class ProcessQuerysetMixin(object): +class ProcessQuerysetMixin: """ Optimized queryset for Retrieve/Update/PartialUpdate/Destroy/RetryProcess """ diff --git a/arkindex/process/builder.py b/arkindex/process/builder.py index 74619b170ed571970446e106b51d35595f3052be..af735708e23d1ed17043e4a226da74cd7b93d8d5 100644 --- a/arkindex/process/builder.py +++ b/arkindex/process/builder.py @@ -16,7 +16,7 @@ from arkindex.images.models import ImageServer from arkindex.ponos.models import GPU, Task, task_token_default -class ProcessBuilder(object): +class ProcessBuilder: def __init__(self, process) -> None: self.process = process diff --git a/arkindex/process/models.py b/arkindex/process/models.py index 8e8dff5625624eb63d18d28b6c3644ac7f529396..1e21d56ef2d584a93ebbaaf71e7ed3ccf8fe71ab 100644 --- a/arkindex/process/models.py +++ b/arkindex/process/models.py @@ -586,8 +586,8 @@ class Revision(IndexableModel): return "{}/commit/{}".format(self.repo.url.rstrip("/"), self.hash) def __str__(self): - message = ' "{}"'.format(self.message.splitlines()[0]) if self.message else "" - return "{}{} by {}".format(self.hash[:8], message, self.author) + message = f' "{self.message.splitlines()[0]}"' if self.message else "" + return f"{self.hash[:8]}{message} by {self.author}" class Worker(models.Model): diff --git a/arkindex/process/serializers/imports.py b/arkindex/process/serializers/imports.py index 23e601754682282d108caba5b82ca3d5cea3180f..2b7ae9c8a8fb7f5fa8ce382449649ebf2fc7e5e2 100644 --- a/arkindex/process/serializers/imports.py +++ b/arkindex/process/serializers/imports.py @@ -670,7 +670,7 @@ class CorpusProcessSerializer(serializers.Serializer): # If element is defined ensure it is part of the right corpus if element and element.corpus_id != corpus.id: raise ValidationError({ - "element": ["Element is not part of corpus {}".format(corpus.name)] + "element": [f"Element is not part of corpus {corpus.name}"] }) # Check type filter is valid diff --git a/arkindex/process/tests/test_workeractivity_stats.py b/arkindex/process/tests/test_workeractivity_stats.py index 3e8e292a6e6d08e986a2107df5be98447a77f3d2..66e584db32df9ee72caf1c184658f1694183c358 100644 --- a/arkindex/process/tests/test_workeractivity_stats.py +++ b/arkindex/process/tests/test_workeractivity_stats.py @@ -86,7 +86,7 @@ class TestWorkerActivityStats(FixtureAPITestCase): with pgtrigger.ignore("process.WorkerActivity:read_only_workeractivity_updated", "process.WorkerActivity:update_workeractivity_updated"): WorkerActivity.objects.filter(element__corpus_id=cls.corpus.id).update(created=Now(), updated=Now(), started=Now()) - cls.error, cls.processed, cls.queued, cls.started = [ + cls.error, cls.processed, cls.queued, cls.started = ( WorkerActivity.objects.filter( element__corpus_id=cls.corpus.id, worker_version_id=cls.version_1.id, @@ -98,9 +98,9 @@ class TestWorkerActivityStats(FixtureAPITestCase): WorkerActivityState.Queued, WorkerActivityState.Started ] - ] + ) - cls.error_2, cls.processed_2, cls.queued_2, cls.started_2 = [ + cls.error_2, cls.processed_2, cls.queued_2, cls.started_2 = ( WorkerActivity.objects.filter( element__corpus_id=cls.corpus.id, worker_version_id=cls.version_3.id, @@ -112,7 +112,7 @@ class TestWorkerActivityStats(FixtureAPITestCase): WorkerActivityState.Queued, WorkerActivityState.Started ] - ] + ) def test_corpus_requires_login(self): with self.assertNumQueries(0): diff --git a/arkindex/project/argparse.py b/arkindex/project/argparse.py index bbb3dcf17ccbee051c11c0c33d2236d32bde414a..d308011199035225000a71e18d1ec98bfdafe4ec 100644 --- a/arkindex/project/argparse.py +++ b/arkindex/project/argparse.py @@ -7,7 +7,7 @@ from arkindex.process.models import Process, Repository, WorkerVersion from arkindex.users.models import User -class ModelArgument(object): +class ModelArgument: model = None text_search_field = "name" text_search_lookup = "icontains" @@ -27,9 +27,9 @@ class ModelArgument(object): try: return self.text_search(qs, arg) except self.model.DoesNotExist: - raise CommandError('{} "{}" does not exist'.format(self.model.__name__, arg)) + raise CommandError(f'{self.model.__name__} "{arg}" does not exist') except self.model.MultipleObjectsReturned: - raise CommandError('"{}" matches multiple {} instances'.format(arg, self.model.__name__)) + raise CommandError(f'"{arg}" matches multiple {self.model.__name__} instances') def text_search(self, qs, arg): if not self.text_search_field: diff --git a/arkindex/project/aws.py b/arkindex/project/aws.py index 659f37326a0e132505e1fb4b63a7729e668ba328..ac98c858d6c621278ace564a9d2e674143b78de1 100644 --- a/arkindex/project/aws.py +++ b/arkindex/project/aws.py @@ -88,7 +88,7 @@ def _retry_delete_predicate(exception): ) -class S3FileMixin(object): +class S3FileMixin: def get_s3_object(self): if not self.s3_bucket or not self.s3_key: @@ -142,12 +142,12 @@ class S3FileMixin(object): def download(self): b = BytesIO() - logger.debug("Downloading file {} from S3".format(self.s3_key)) + logger.debug(f"Downloading file {self.s3_key} from S3") self.s3_object.download_fileobj(b) return b def download_to(self, path): - logger.debug("Downloading file {} from S3".format(self.s3_key)) + logger.debug(f"Downloading file {self.s3_key} from S3") self.s3_object.download_file(path) def check_hash(self, save=True, raise_exc=False): @@ -162,10 +162,7 @@ class S3FileMixin(object): elif "-" in self.s3_object.e_tag: # Multipart hash: a hash of each part's hash, # combined with the number of parts, separated by a dash - logger.warning("Could not check remote multipart hash {!r} against local hash {!r}".format( - self.s3_object.e_tag, - self.hash, - )) + logger.warning(f"Could not check remote multipart hash {self.s3_object.e_tag!r} against local hash {self.hash!r}") self.status = S3FileStatus.Unchecked else: self.status = S3FileStatus.Error diff --git a/arkindex/project/fields.py b/arkindex/project/fields.py index e66f28bb1347a0cdc538ecf48493ef621c70712d..4af2a16a3da763ab77f35029e8f722a930a23d4b 100644 --- a/arkindex/project/fields.py +++ b/arkindex/project/fields.py @@ -120,14 +120,14 @@ class LastItemTransform(Transform): def as_sql(self, compiler, connection): lhs, params = compiler.compile(self.lhs) - return "%s[array_length(%s, 1)]" % (lhs, lhs), params + return f"{lhs}[array_length({lhs}, 1)]", params @property def output_field(self): return self.base_field -class LastItemTransformFactory(object): +class LastItemTransformFactory: """ Create a LastItemTransform with a given base field """ diff --git a/arkindex/project/mixins.py b/arkindex/project/mixins.py index 6f04942aa21809a57f434e94eab942e7bf9801a8..744da257c3a26d54d78e2aa73704681039041d48 100644 --- a/arkindex/project/mixins.py +++ b/arkindex/project/mixins.py @@ -13,7 +13,7 @@ from arkindex.users.models import Role from arkindex.users.utils import filter_rights, get_max_level, has_access -class ACLMixin(object): +class ACLMixin: """ Access control mixin using the generic Right table. """ @@ -140,7 +140,7 @@ class ProcessACLMixin(ACLMixin): return get_max_level(self.user, process.corpus) -class SelectionMixin(object): +class SelectionMixin: def get_selection(self, corpus_id=None): assert settings.ARKINDEX_FEATURES["selection"], "Selection feature is unavailable" @@ -165,7 +165,7 @@ class DeprecatedAPIException(APIException): default_code = "deprecated" -class DeprecatedMixin(object): +class DeprecatedMixin: # Add this mixin to an APIView to make it deprecated. serializer_class = DeprecatedExceptionSerializer @@ -196,7 +196,7 @@ class DeprecatedMixin(object): raise DeprecatedAPIException(detail=getattr(self, "deprecation_message", None)) -class CachedViewMixin(object): +class CachedViewMixin: """ Add this mixin to any class-based view to cache it. """ diff --git a/arkindex/project/permissions.py b/arkindex/project/permissions.py index de38572783b7ae84e1632c49bcaa875b9153fa75..ee36346dcc7e4665aa298be8b715c699d2de75f8 100644 --- a/arkindex/project/permissions.py +++ b/arkindex/project/permissions.py @@ -17,7 +17,7 @@ def require_verified_email(request, view): def _get_scopes(request, view): - specific_scopes_attr = "{}_scopes".format(request.method.lower()) + specific_scopes_attr = f"{request.method.lower()}_scopes" scopes = list(getattr(view, "scopes", [])) scopes.extend(getattr(view, specific_scopes_attr, [])) return set(scopes) diff --git a/arkindex/project/serializer_fields.py b/arkindex/project/serializer_fields.py index 06245b86fc0ef12f757846b56b71927b684d87bd..9a16ad44029637872353df68fbffa3d2cd303430 100644 --- a/arkindex/project/serializer_fields.py +++ b/arkindex/project/serializer_fields.py @@ -35,7 +35,7 @@ class EnumField(serializers.ChoiceField): try: return self.enum(data) except ValueError: - raise serializers.ValidationError("Value is not of type {}".format(self.enum.__name__)) + raise serializers.ValidationError(f"Value is not of type {self.enum.__name__}") class PointField(serializers.ListField): diff --git a/arkindex/project/settings.py b/arkindex/project/settings.py index dbd5a3b43d7c81c3fd3a7a521f1a312f7372502b..1a3cee8d36a314d6df70e5e81c5950d09f50630d 100644 --- a/arkindex/project/settings.py +++ b/arkindex/project/settings.py @@ -414,7 +414,7 @@ LOGGING = { } # Email -EMAIL_SUBJECT_PREFIX = "[Arkindex {}] ".format(ARKINDEX_ENV) +EMAIL_SUBJECT_PREFIX = f"[Arkindex {ARKINDEX_ENV}] " if conf["email"]: ADMINS = [("", address) for address in conf["email"]["error_report_recipients"]] EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" @@ -460,7 +460,7 @@ CORS_URLS_REGEX = r"^/(api|ponos)/.*$" # Support CORS suffixes if conf["cors"]["suffixes"]: CORS_ALLOWED_ORIGIN_REGEXES = [ - r"^https://.+{}".format(suffix) + rf"^https://.+{suffix}" for suffix in conf["cors"]["suffixes"] ] diff --git a/arkindex/project/tests/__init__.py b/arkindex/project/tests/__init__.py index cca5e05eb7ad2ffcf75194ba50cda1c6c01183cb..ecceb69a5ef089ebd49976c3e8a964f63ba7f851 100644 --- a/arkindex/project/tests/__init__.py +++ b/arkindex/project/tests/__init__.py @@ -67,7 +67,7 @@ class _AssertExactQueriesContext(CaptureQueriesContext): if not self.path.exists(): try: self.path.write_text(actual_sql) - except IOError as e: + except OSError as e: raise AssertionError( f"Could not assert on SQL queries; the file at {self.path} did not exist. " "A new file could not be created with the current SQL queries." @@ -96,7 +96,7 @@ class _AssertExactQueriesContext(CaptureQueriesContext): self.test_case.assertEqual(expected_sql, actual_sql) -class FixtureMixin(object): +class FixtureMixin: """ Add the database fixture to a test case """ diff --git a/arkindex/project/validators.py b/arkindex/project/validators.py index 15fb732317819916a9bcae0c15d42596c0fab3f3..980e2636869c9c65cb63dfc789e122a53150c7d2 100644 --- a/arkindex/project/validators.py +++ b/arkindex/project/validators.py @@ -2,7 +2,7 @@ from django.core import validators from rest_framework import serializers -class XorValidator(object): +class XorValidator: """ A generic validator for when two fields can't be used simultaneously """ @@ -45,7 +45,7 @@ class ConditionalUniqueValidator(serializers.UniqueTogetherValidator): return super().__call__(attrs, serializer) -class ForbiddenValidator(object): +class ForbiddenValidator: """ A validator that will show an error message any time a value is set. @@ -65,7 +65,7 @@ class ForbiddenValidator(object): raise serializers.ValidationError(self.message) -class HiddenCallableValidatorMixin(object): +class HiddenCallableValidatorMixin: """ Implements a workaround for some issues with error messages in DRF and with drf-spectacular OpenAPI schema generation when the `limit_value` diff --git a/arkindex/training/api.py b/arkindex/training/api.py index c7d7220ae96ece53f8fd0426cbd0f8d93f4396d6..77289fab01822ca7819042ef6895be4696d7c4aa 100644 --- a/arkindex/training/api.py +++ b/arkindex/training/api.py @@ -737,7 +737,7 @@ class DatasetElementCursorPagination(CountCursorPagination): ordering = ("element_id", "id") -class DatasetSetBase(): +class DatasetSetBase: permission_classes = (IsVerified, ) serializer_class = DatasetSetSerializer diff --git a/arkindex/training/management/commands/migrate_workers.py b/arkindex/training/management/commands/migrate_workers.py index 6e011fe5a60d43123819b71075eb2872f2fca0db..e499d74e2f0cfa10576d082289d02afb1ab147ea 100644 --- a/arkindex/training/management/commands/migrate_workers.py +++ b/arkindex/training/management/commands/migrate_workers.py @@ -46,7 +46,7 @@ def choose(instances, name_field="name", title="Pick one item", allow_skip=False choices["0"] = choices["skip"] = None for i, (id, name) in enumerate(items, 1): print(f"{i}: {id} {name}") - choices[str((i))] = choices[str(id)] = choices[name] = id + choices[str(i)] = choices[str(id)] = choices[name] = id # Get the first valid choice while True: diff --git a/arkindex/training/models.py b/arkindex/training/models.py index ea84bd5c8af8588881188e9580dba7bd64b519ac..028ed4071c90bf32a9ecf10911868187d9527f2c 100644 --- a/arkindex/training/models.py +++ b/arkindex/training/models.py @@ -169,10 +169,7 @@ class ModelVersion(S3FileMixin, IndexableModel): elif "-" in self.s3_object.e_tag: # Multipart hash: a hash of each part's hash, # combined with the number of parts, separated by a dash - logger.warning("Could not check remote multipart hash {!r} against local hash {!r}".format( - self.s3_object.e_tag, - self.archive_hash, - )) + logger.warning(f"Could not check remote multipart hash {self.s3_object.e_tag!r} against local hash {self.archive_hash!r}") self.state = ModelVersionState.Available else: self.state = ModelVersionState.Error diff --git a/arkindex/training/tests/test_datasets_api.py b/arkindex/training/tests/test_datasets_api.py index ce1d83ce0a9e6ca83840bd7e2c6d91815cd3a19d..b824eae2eefbfe5d86efdfe0ca3d2ced855b387d 100644 --- a/arkindex/training/tests/test_datasets_api.py +++ b/arkindex/training/tests/test_datasets_api.py @@ -657,7 +657,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_update_ponos_task_state_forbidden(self): """Dataset's state update is limited to specific transitions""" - op, build, complete, error = [DatasetState[state] for state in ("Open", "Building", "Complete", "Error")] + op, build, complete, error = (DatasetState[state] for state in ("Open", "Building", "Complete", "Error")) states = { (op, op): True, (op, build) : True, @@ -930,7 +930,7 @@ class TestDatasetsAPI(FixtureAPITestCase): def test_partial_update_ponos_task_state_forbidden(self): """Dataset's state update is limited to specific transitions""" - op, build, complete, error = [DatasetState[state] for state in ("Open", "Building", "Complete", "Error")] + op, build, complete, error = (DatasetState[state] for state in ("Open", "Building", "Complete", "Error")) states = { (op, op): True, (op, build) : True, diff --git a/arkindex/users/api.py b/arkindex/users/api.py index 467fd2e7283111cbab05450426c502e5c3ad0ee7..5bc55e012c13abb97df750e9469ab14de34420a2 100644 --- a/arkindex/users/api.py +++ b/arkindex/users/api.py @@ -121,7 +121,7 @@ class UserCreate(CreateAPIView): fail_silently=True, ) if sent == 0: - logger.error("Failed to send registration email to {}".format(user.email)) + logger.error(f"Failed to send registration email to {user.email}") return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED) diff --git a/arkindex/users/tests/test_jobs.py b/arkindex/users/tests/test_jobs.py index 9e4da6d5ca53ddf4fcb45f678e4b747237a56820..8dd4fb11a7a37c79d5a0e380d930c0486368d40f 100644 --- a/arkindex/users/tests/test_jobs.py +++ b/arkindex/users/tests/test_jobs.py @@ -10,7 +10,7 @@ from rq.job import JobStatus from arkindex.project.tests import FixtureAPITestCase -class MockedJob(object): +class MockedJob: def __init__(self, id=None, user_id=None, status=JobStatus.QUEUED, **kwargs): self.id = id or str(uuid4()) @@ -32,7 +32,7 @@ class MockedJob(object): @classmethod def key_for(cls, id): - return f"rq:job:{id}".encode("utf-8") + return f"rq:job:{id}".encode() class TestJobs(FixtureAPITestCase): diff --git a/ruff.toml b/ruff.toml index 943a2b2ee4070b7c70eeb9a89b38c455c8f4bea3..8a02c65695d77b3c18cae483a6ff6db75abbd0ba 100644 --- a/ruff.toml +++ b/ruff.toml @@ -25,6 +25,8 @@ select = [ "RET", # eradicate "ERA", + # pyupgrade + "UP", ] ignore = ["E501", "RET502", "RET503"] diff --git a/setup.py b/setup.py index 39f437cdac5d6275d6dfacd06032a7f5f7e0819c..fdd40bb80d4261c25a3fc17db605abceb2aa2cba 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def _parse_requirement(line): def requirements(path): - assert os.path.exists(path), "Missing requirements {}".format(path) + assert os.path.exists(path), f"Missing requirements {path}" with open(path) as f: return list(map(_parse_requirement, f.read().splitlines()))