diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b8a56570c6c22c5b5058cb24d4aa8e83b92f9646..571548691a6780990f728bcee684e93516f5d578 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,7 +11,7 @@ include: # For jobs that run backend scripts directly .backend-setup: - image: registry.gitlab.teklia.com/arkindex/backend/base:django-5.0.8 + image: registry.gitlab.teklia.com/arkindex/backend/base:python3.12 cache: paths: @@ -61,7 +61,7 @@ backend-tests: - arkindex test backend-lint: - image: python:3.10 + image: python:3.12 stage: test except: @@ -159,7 +159,7 @@ backend-build: backend-build-binary: stage: build - image: python:3.10 + image: python:3.12 before_script: - pip install nuitka diff --git a/.isort.cfg b/.isort.cfg index 0b8bd7b946695db57c49d6f0d920c390b8b27af7..83a3f011bdd9b6b79cf86a0ce4ac6759a50800b4 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -7,4 +7,4 @@ use_parentheses = True line_length = 120 default_section=FIRSTPARTY -known_third_party = SolrClient,bleach,boto3,botocore,cryptography,corsheaders,django,django_admin_hstore_widget,django_rq,drf_spectacular,enumfields,gitlab,psycopg2,requests,responses,rest_framework,rq,setuptools,sqlparse,teklia_toolbox,tenacity,tripoli,yaml +known_third_party = SolrClient,bleach,boto3,botocore,cryptography,corsheaders,django,django_admin_hstore_widget,django_rq,drf_spectacular,enumfields,gitlab,psycopg,requests,responses,rest_framework,rq,setuptools,sqlparse,teklia_toolbox,tenacity,tripoli,yaml diff --git a/Dockerfile b/Dockerfile index 01ec3163d4249441f31c1cabada9f7bc9f1da6ab..9ebc012df162b7d108da020dadb05d84d7d0eace 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ # syntax=docker/dockerfile:1 -FROM registry.gitlab.teklia.com/arkindex/backend/base:django-5.0.8 as build +FROM registry.gitlab.teklia.com/arkindex/backend/base:python3.12 AS build RUN mkdir build ADD . build RUN cd build && python3 setup.py sdist -FROM registry.gitlab.teklia.com/arkindex/backend/base:django-5.0.8 +FROM registry.gitlab.teklia.com/arkindex/backend/base:python3.12 # Install arkindex and its deps # Uses a source archive instead of full local copy to speedup docker build diff --git a/VERSION b/VERSION index f8a696c8dc56436062b4d179d96c78cbbbb718d7..661e7aeadf36f8e35f09094781c1abf2afab5c6a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.7.2 +1.7.3 diff --git a/arkindex/budget/__init__.py b/arkindex/budget/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/arkindex/budget/apps.py b/arkindex/budget/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..d24215cc34d6bd983d8525543a655aecbb90edff --- /dev/null +++ b/arkindex/budget/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BudgetConfig(AppConfig): + name = "arkindex.budget" diff --git a/arkindex/budget/migrations/0001_initial.py b/arkindex/budget/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..67ebfb8b710210ec03d3f261aa2e0957c11d3459 --- /dev/null +++ b/arkindex/budget/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 5.0.8 on 2025-02-10 09:37 + +import uuid + +import django.core.validators +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("process", "0048_worker_cost_fields"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Budget", + fields=[ + ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ("name", models.CharField(max_length=100, validators=[django.core.validators.MinLengthValidator(1)])), + ("total", models.DecimalField(decimal_places=3, default=0, max_digits=12)), + ], + ), + migrations.CreateModel( + name="BudgetEntry", + fields=[ + ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ("created", models.DateTimeField(auto_now_add=True)), + ("description", models.CharField(max_length=255, validators=[django.core.validators.MinLengthValidator(1)])), + ("value", models.DecimalField(decimal_places=3, max_digits=12)), + ("budget", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="entries", to="budget.budget")), + ("process", models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="budget_entries", to="process.process")), + ("user", models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="budget_entries", to=settings.AUTH_USER_MODEL)), + ], + options={ + "verbose_name_plural": "budget entries", + }, + ), + ] diff --git a/arkindex/budget/migrations/__init__.py b/arkindex/budget/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/arkindex/budget/models.py b/arkindex/budget/models.py new file mode 100644 index 0000000000000000000000000000000000000000..8d1bd9757e3062ea63655f6a4d82bb7f5b694e05 --- /dev/null +++ b/arkindex/budget/models.py @@ -0,0 +1,46 @@ +import uuid + +from django.conf import settings +from django.contrib.contenttypes.fields import GenericRelation +from django.core.validators import MinLengthValidator +from django.db import models + + +class Budget(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + name = models.CharField(max_length=100, validators=[MinLengthValidator(1)]) + total = models.DecimalField(max_digits=12, decimal_places=3, default=0) + memberships = GenericRelation("users.Right", "content_id") + + def __str__(self) -> str: + return self.name + + +class BudgetEntry(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + created = models.DateTimeField(auto_now_add=True) + + budget = models.ForeignKey(Budget, on_delete=models.CASCADE, related_name="entries") + description = models.CharField(max_length=255, validators=[MinLengthValidator(1)]) + value = models.DecimalField(max_digits=12, decimal_places=3) + + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name="budget_entries", + ) + process = models.ForeignKey( + "process.Process", + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name="budget_entries", + ) + + class Meta: + verbose_name_plural = "budget entries" + + def __str__(self) -> str: + return self.description diff --git a/arkindex/documents/admin.py b/arkindex/documents/admin.py index 1375921632ed67df5c8e8e20cb7dcbc3d79557f4..f4d4f4a63ca864555077abc73afee704ae282130 100644 --- a/arkindex/documents/admin.py +++ b/arkindex/documents/admin.py @@ -31,6 +31,8 @@ class ElementTypeInline(admin.TabularInline): class CorpusAdmin(admin.ModelAdmin): + fields = ("id", "name", "description", "top_level_type", "public", "indexable", "maximum_task_ttl", "created") + readonly_fields = ("id", "created") list_display = ("id", "name", "public", "top_level_type", "created") search_fields = ("name", ) inlines = (ElementTypeInline, ) diff --git a/arkindex/documents/api/elements.py b/arkindex/documents/api/elements.py index 5a027a5179824fb0243f06f1669544ad731c847a..d9644c226b4876dfee9cdef4cbc19b065e603e00 100644 --- a/arkindex/documents/api/elements.py +++ b/arkindex/documents/api/elements.py @@ -32,7 +32,6 @@ from drf_spectacular.utils import ( extend_schema_view, inline_serializer, ) -from psycopg2.extras import execute_values from rest_framework import permissions, serializers, status from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError from rest_framework.generics import ( @@ -133,13 +132,17 @@ def _fetch_has_children(elements): if not elements: return elements + # psycopg 2 used to have `execute_values` as a clean way to manage a VALUES statement, but psycopg 3 doesn't, + # so we will add a variable amount of placeholders to put each element ID separately. + # This results in `VALUES (%s),(%s),(%s)`. + placeholders = ",".join("(%s)" for _ in range(len(elements))) + with connection.cursor() as cursor: - execute_values( - cursor, + cursor.execute( "SELECT DISTINCT ON (e.id) e.id, p.id is not null as has_children " - "FROM (VALUES %s) e (id) " + f"FROM (VALUES {placeholders}) e (id) " "LEFT JOIN documents_elementpath p ON ARRAY[e.id] && p.path AND p.path[array_length(p.path, 1)] = e.id", - tuple((element.id, ) for element in elements), + tuple(element.id for element in elements), ) has_children = dict(cursor.fetchall()) diff --git a/arkindex/documents/api/entities.py b/arkindex/documents/api/entities.py index 7c60ae8f439c61c71247fd45fe1dd0fa6a0276c8..32fd14373930d4db767ba3b2fd0761ebce5c91de 100644 --- a/arkindex/documents/api/entities.py +++ b/arkindex/documents/api/entities.py @@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError as DjangoValidationError from django.db.utils import OperationalError from django.shortcuts import get_object_or_404 from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema, extend_schema_view -from psycopg2.errors import ProgramLimitExceeded +from psycopg.errors import ProgramLimitExceeded from rest_framework import permissions, serializers, status from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveUpdateDestroyAPIView diff --git a/arkindex/documents/export/__init__.py b/arkindex/documents/export/__init__.py index ac1ccd78142bd42c8fece5e1299e0f9ab9d8f830..cdf88ee5ec1d0d4e9518e4271138f93341e93f41 100644 --- a/arkindex/documents/export/__init__.py +++ b/arkindex/documents/export/__init__.py @@ -48,7 +48,7 @@ EXPORT_QUERIES = [ def run_pg_query(query, source_db): """ Run a single Postgresql query and split the results into chunks. - When a name is given to a cursor, psycopg2 uses a server-side cursor; we just use a random string as a name. + When a name is given to a cursor, psycopg uses a server-side cursor; we just use a random string as a name. """ db = connections[source_db] @@ -86,7 +86,7 @@ def save_sqlite(rows, table, cursor): return float(value) # Show very explicit error messages if we stumble upon an unexpected type - # https://docs.python.org/3.10/library/sqlite3.html#sqlite-and-python-types + # https://docs.python.org/3.12/library/sqlite3.html#sqlite-and-python-types assert value is None or isinstance(value, (int, float, str, bytes)), f"Type {type(value)} is not supported by sqlite3" return value diff --git a/arkindex/documents/fixtures/data.json b/arkindex/documents/fixtures/data.json index 3b1b3abcdba0e240785cf9a5a8329182f87c3c2f..48662e5a99292b19d3284508726ecfe8de180efb 100644 --- a/arkindex/documents/fixtures/data.json +++ b/arkindex/documents/fixtures/data.json @@ -355,8 +355,7 @@ "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "has_results": false, - "use_gpu": false, - "ttl": 3600 + "use_gpu": false } }, { @@ -393,8 +392,7 @@ "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "has_results": false, - "use_gpu": false, - "ttl": 3600 + "use_gpu": false } }, { @@ -410,8 +408,7 @@ "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "has_results": false, - "use_gpu": false, - "ttl": 3600 + "use_gpu": false } }, { @@ -427,8 +424,7 @@ "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "has_results": false, - "use_gpu": false, - "ttl": 0 + "use_gpu": false } }, { @@ -444,8 +440,7 @@ "created": "2020-02-02T01:23:45.678Z", "updated": "2020-02-02T01:23:45.678Z", "has_results": false, - "use_gpu": false, - "ttl": 0 + "use_gpu": false } }, { diff --git a/arkindex/documents/indexer.py b/arkindex/documents/indexer.py index bee4bd7fc983ac95e51ccec5c374cb2a965a3440..fa273647b4e6d37eb1b560da1a41e1a5e18720b6 100644 --- a/arkindex/documents/indexer.py +++ b/arkindex/documents/indexer.py @@ -30,7 +30,7 @@ SELECT element.name AS name, elementtype.display_name AS type_name, element.image_id AS image_id, - element.polygon::bytea AS polygon, + element.polygon AS polygon, element.worker_run_id AS worker_run_id FROM documents_element element INNER JOIN documents_elementtype elementtype ON (elementtype.id = element.type_id) @@ -50,7 +50,7 @@ SELECT element.name as name, elementtype.display_name as type_name, element.image_id AS image_id, - element.polygon::bytea AS polygon, + element.polygon AS polygon, element.worker_run_id AS worker_run_id FROM (SELECT * FROM parent LIMIT %(limit)s OFFSET %(offset)s) AS parent_chunk INNER JOIN documents_elementpath as elementpath ON (elementpath.path @> ARRAY[parent_chunk.id]) diff --git a/arkindex/documents/management/commands/build_fixtures.py b/arkindex/documents/management/commands/build_fixtures.py index 87a12ff637d1ba11203d01ebe7a9a1e379e2afde..c691f669f8db03c6f73e653d650c8d1a6f881b00 100644 --- a/arkindex/documents/management/commands/build_fixtures.py +++ b/arkindex/documents/management/commands/build_fixtures.py @@ -206,14 +206,8 @@ class Command(BaseCommand): mode=ProcessMode.Local, creator=superuser, ) - user_local_process.worker_runs.create( - version=custom_version, - ttl=0, - ) - superuser_local_process.worker_runs.create( - version=custom_version, - ttl=0, - ) + user_local_process.worker_runs.create(version=custom_version) + superuser_local_process.worker_runs.create(version=custom_version) # Create a corpus corpus = Corpus.objects.create( @@ -234,16 +228,13 @@ class Command(BaseCommand): ) init_worker_run = process.worker_runs.create( version=init_worker, - ttl=3600, ) dla_worker_run = process.worker_runs.create( version=dla_worker, - ttl=3600, parents=[init_worker_run.id], ) reco_run = process.worker_runs.create( version=recognizer_worker, - ttl=3600, parents=[dla_worker_run.id], ) diff --git a/arkindex/documents/management/commands/load_export.py b/arkindex/documents/management/commands/load_export.py index 9b2fad0ee55dea000fbd0a93ec471f18b63e0648..9096a4303a16684bfdf7a97f75d97e2dacf7ae25 100644 --- a/arkindex/documents/management/commands/load_export.py +++ b/arkindex/documents/management/commands/load_export.py @@ -444,7 +444,6 @@ class Command(BaseCommand): version_id=worker_version_id, model_version=model_version, configuration=configuration, - defaults={"ttl": 0}, ) def create_image_server(self, row): diff --git a/arkindex/documents/migrations/0014_corpus_budget.py b/arkindex/documents/migrations/0014_corpus_budget.py new file mode 100644 index 0000000000000000000000000000000000000000..026c25cfb45183301b48072927efb9514456d19d --- /dev/null +++ b/arkindex/documents/migrations/0014_corpus_budget.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.8 on 2025-02-10 09:41 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("budget", "0001_initial"), + ("documents", "0013_corpus_maximum_task_ttl"), + ] + + operations = [ + migrations.AddField( + model_name="corpus", + name="budget", + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="corpora", to="budget.budget"), + ), + ] diff --git a/arkindex/documents/models.py b/arkindex/documents/models.py index 6ea6ee7745c9cba8008808ddf6b669ae2ea007b9..e21ef63b10a23bd1f029ac5c12f1d17919a37567 100644 --- a/arkindex/documents/models.py +++ b/arkindex/documents/models.py @@ -59,6 +59,14 @@ class Corpus(IndexableModel): "When not set, the instance-wide maximum time-to-live will be used instead.", ) + budget = models.ForeignKey( + "budget.Budget", + on_delete=models.SET_NULL, + related_name="corpora", + blank=True, + null=True, + ) + # Specific manager for ACL objects = CorpusManager() diff --git a/arkindex/documents/serializers/elements.py b/arkindex/documents/serializers/elements.py index ee9ec29171e743ad7b17eaa618f0ea80d0c54ba1..528cd7b0d238b20c7a059be15a191073cf9001d9 100644 --- a/arkindex/documents/serializers/elements.py +++ b/arkindex/documents/serializers/elements.py @@ -285,6 +285,7 @@ class CorpusSerializer(serializers.ModelSerializer): "authorized_users", "indexable", "maximum_task_ttl", + "budget_id", ) extra_kwargs = { "public": { @@ -293,6 +294,9 @@ class CorpusSerializer(serializers.ModelSerializer): }, "indexable": { "help_text": "Whether this corpus should be indexed in Solr and searchable using the `SearchCorpus` endpoint." + }, + "budget_id": { + "read_only": True, } } diff --git a/arkindex/documents/serializers/entities.py b/arkindex/documents/serializers/entities.py index bcc27783682a8a19bbf85198d0e1168983d13ac5..bd9a291d8ef730f6b2412896b8359061dcca4754 100644 --- a/arkindex/documents/serializers/entities.py +++ b/arkindex/documents/serializers/entities.py @@ -3,7 +3,7 @@ from textwrap import dedent from django.db import transaction from django.db.utils import OperationalError -from psycopg2.errors import ProgramLimitExceeded +from psycopg.errors import ProgramLimitExceeded from rest_framework import serializers from rest_framework.exceptions import ValidationError diff --git a/arkindex/documents/tests/commands/test_cleanup.py b/arkindex/documents/tests/commands/test_cleanup.py index 16fe3290c65e199dafe8a8ca5adf51c3ce68d78c..51489faf4803fbad0294a5a6344df6b20f32cbfb 100644 --- a/arkindex/documents/tests/commands/test_cleanup.py +++ b/arkindex/documents/tests/commands/test_cleanup.py @@ -1163,14 +1163,13 @@ class TestCleanupCommand(FixtureTestCase): # from the WorkerRuns, so there would be 2 runs with the same version and no configuration when they should be unique process = self.corpus.processes.create(mode=ProcessMode.Workers, creator=self.superuser) version = removable_worker.versions.first() - process.worker_runs.create(version=version, ttl=0) + process.worker_runs.create(version=version) process.worker_runs.create( version=version, configuration=removable_worker.configurations.create( name="Some configuration", configuration={}, ), - ttl=0, ) # This worker cannot be cleaned up because it is used in ML results @@ -1249,7 +1248,6 @@ class TestCleanupCommand(FixtureTestCase): worker_run = process.worker_runs.create( version=worker_version, model_version=used_model.versions.create(), - ttl=0, ) self.corpus.elements.create( type=self.corpus.types.first(), diff --git a/arkindex/documents/tests/commands/test_load_export.py b/arkindex/documents/tests/commands/test_load_export.py index 0787a57f666980bdb4eab0240c459c20b6bfbe2e..383c1bc7ef1eeb733a17a7f60febfefcde60d417 100644 --- a/arkindex/documents/tests/commands/test_load_export.py +++ b/arkindex/documents/tests/commands/test_load_export.py @@ -39,7 +39,7 @@ class TestLoadExport(FixtureTestCase): "process.workerversion": ["created", "updated", "configuration", "state", "docker_image_iid"], # The WorkerRuns lose their parents, use different worker versions that just got recreated, # are assigned to the user's local process and not the original one - "process.workerrun": ["parents", "version", "process", "summary", "created", "updated", "ttl"], + "process.workerrun": ["parents", "version", "process", "summary", "created", "updated"], "process.workertype": [], "images.imageserver": ["s3_bucket", "s3_region", "created", "updated", "read_only"], "images.image": ["created", "updated", "hash", "status"], diff --git a/arkindex/documents/tests/tasks/test_corpus_delete.py b/arkindex/documents/tests/tasks/test_corpus_delete.py index 7f0ff00642610028dedbe728915196333818e7af..cd5ad64153f21586311a3964b62a396365d19088 100644 --- a/arkindex/documents/tests/tasks/test_corpus_delete.py +++ b/arkindex/documents/tests/tasks/test_corpus_delete.py @@ -45,7 +45,7 @@ class TestDeleteCorpus(FixtureTestCase): ml_class=cls.corpus.ml_classes.create(name="a class"), ) element_process.elements.add(element) - worker_run = element_process.worker_runs.create(version=cls.worker_version, ttl=0) + worker_run = element_process.worker_runs.create(version=cls.worker_version) task_1, task_2, task_3, task_4 = Task.objects.bulk_create( [ Task( @@ -174,7 +174,7 @@ class TestDeleteCorpus(FixtureTestCase): def test_run(self): receivers = pre_delete.receivers - with force_constraints_immediate(), self.assertExactQueries("corpus_delete.sql", params={"corpus_id": self.corpus.id}): + with force_constraints_immediate(), self.assertExactQueries("corpus_delete.sql", params={"corpus_id": self.corpus.id.hex}): corpus_delete(self.corpus.id) # Ensure the task restores the signal receivers @@ -221,7 +221,7 @@ class TestDeleteCorpus(FixtureTestCase): self.corpus.top_level_type = self.corpus.types.first() self.corpus.save() - with force_constraints_immediate(), self.assertExactQueries("corpus_delete_top_level_type.sql", params={"corpus_id": self.corpus.id}): + with force_constraints_immediate(), self.assertExactQueries("corpus_delete_top_level_type.sql", params={"corpus_id": self.corpus.id.hex}): corpus_delete(self.corpus.id) # Ensure the task restores the signal receivers diff --git a/arkindex/documents/tests/tasks/test_export.py b/arkindex/documents/tests/tasks/test_export.py index 8b21ea830c002881fa53b6e8b9935f57c407e231..9831b8eed4f1f7e73a91d17e3bbb45d855255a97 100644 --- a/arkindex/documents/tests/tasks/test_export.py +++ b/arkindex/documents/tests/tasks/test_export.py @@ -82,7 +82,6 @@ class TestExport(FixtureTestCase): model=Model.objects.create(name="Some model"), ), configuration=metadata_version.worker.configurations.create(name="Some configuration"), - ttl=0, ) element.metadatas.create( diff --git a/arkindex/documents/tests/tasks/test_move_element.py b/arkindex/documents/tests/tasks/test_move_element.py index 32d5a9cc788758283bb68d3013ff697e985928f0..84272f9adeb0aa700beaeeb5b7b65e6f28293903 100644 --- a/arkindex/documents/tests/tasks/test_move_element.py +++ b/arkindex/documents/tests/tasks/test_move_element.py @@ -26,9 +26,9 @@ class TestMoveElement(FixtureTestCase): self.assertEqual(list(source_paths.values("path")), [{"path": [self.parent.id]}]) with self.assertExactQueries("element_move_without_child.sql", params={ - "source_id": str(self.source_without_child.id), - "parent_id": str(self.parent.id), - "destination_id": str(self.destination.id), + "source_id": self.source_without_child.id.hex, + "parent_id": self.parent.id.hex, + "destination_id": self.destination.id.hex, "savepoints": [f"s{_thread.get_ident()}_x{connections['default'].savepoint_state + 1}", f"s{_thread.get_ident()}_x{connections['default'].savepoint_state + 2}"] }): move_element(self.source_without_child, self.destination) @@ -52,9 +52,9 @@ class TestMoveElement(FixtureTestCase): self.assertEqual(list(source_paths.values("path")), [{"path": [self.parent.id]}]) with self.assertExactQueries("element_move_with_children.sql", params={ - "source_id": str(self.source_with_children.id), - "parent_id": str(self.parent.id), - "destination_id": str(self.destination.id), + "source_id": self.source_with_children.id.hex, + "parent_id": self.parent.id.hex, + "destination_id": self.destination.id.hex, "savepoints": [f"s{_thread.get_ident()}_x{connections['default'].savepoint_state + 1}", f"s{_thread.get_ident()}_x{connections['default'].savepoint_state + 2}"] }): move_element(self.source_with_children, self.destination) diff --git a/arkindex/documents/tests/tasks/test_worker_results_delete.py b/arkindex/documents/tests/tasks/test_worker_results_delete.py index 264d9fa821ab457e0e8996d01f0bf71d9302d563..cca0e0dcb0959a574ce1fa2aa5075c581ab736bf 100644 --- a/arkindex/documents/tests/tasks/test_worker_results_delete.py +++ b/arkindex/documents/tests/tasks/test_worker_results_delete.py @@ -40,7 +40,6 @@ class TestDeleteWorkerResults(FixtureTestCase): version=cls.version_1, model_version=cls.model_version, configuration=cls.configuration, - ttl=0, ) cls.vol = cls.corpus.elements.get(name="Volume 1") @@ -133,8 +132,8 @@ class TestDeleteWorkerResults(FixtureTestCase): def test_run_on_corpus(self): with self.assertExactQueries("worker_results_delete_in_corpus.sql", params={ - "corpus_id": str(self.corpus.id), - "version_id": str(self.version_1.id), + "corpus_id": self.corpus.id.hex, + "version_id": self.version_1.id.hex, }): worker_results_delete( corpus_id=self.corpus.id, @@ -153,9 +152,9 @@ class TestDeleteWorkerResults(FixtureTestCase): def test_run_on_parent(self): with self.assertExactQueries("worker_results_delete_under_parent.sql", params={ - "corpus_id": str(self.corpus.id), - "version_id": str(self.version_1.id), - "element_id": str(self.page1.id), + "corpus_id": self.corpus.id.hex, + "version_id": self.version_1.id.hex, + "element_id": self.page1.id.hex, }): worker_results_delete(corpus_id=self.corpus.id, version_id=self.version_1.id, element_id=self.page1.id) self.check_deleted( @@ -169,9 +168,9 @@ class TestDeleteWorkerResults(FixtureTestCase): The element itself is deleted after its related results from the same version """ with self.assertExactQueries("worker_results_delete_under_parent_included.sql", params={ - "corpus_id": str(self.corpus.id), - "version_id": str(self.version_1.id), - "element_id": str(self.page2.id), + "corpus_id": self.corpus.id.hex, + "version_id": self.version_1.id.hex, + "element_id": self.page2.id.hex, }): worker_results_delete(corpus_id=self.corpus.id, version_id=self.version_1.id, element_id=self.page2.id) self.check_deleted( @@ -183,9 +182,9 @@ class TestDeleteWorkerResults(FixtureTestCase): def test_run_model_version_filter_on_parent(self): with self.assertExactQueries("worker_results_delete_model_version_under_parent.sql", params={ - "corpus_id": str(self.corpus.id), - "element_id": str(self.page2.id), - "model_version_id": str(self.model_version.id), + "corpus_id": self.corpus.id.hex, + "element_id": self.page2.id.hex, + "model_version_id": self.model_version.id.hex, }): worker_results_delete( corpus_id=self.corpus.id, @@ -199,8 +198,8 @@ class TestDeleteWorkerResults(FixtureTestCase): def test_run_configuration_filter(self): with self.assertExactQueries("worker_results_delete_configuration_filter.sql", params={ - "corpus_id": str(self.corpus.id), - "configuration_id": str(self.configuration.id), + "corpus_id": self.corpus.id.hex, + "configuration_id": self.configuration.id.hex, }): worker_results_delete( corpus_id=self.corpus.id, @@ -219,8 +218,8 @@ class TestDeleteWorkerResults(FixtureTestCase): self.page2.worker_run = self.worker_run_2 self.page2.save() with self.assertExactQueries("worker_results_delete_unset_configuration.sql", params={ - "corpus_id": str(self.corpus.id), - "element_id": str(self.page2.id), + "corpus_id": self.corpus.id.hex, + "element_id": self.page2.id.hex, }): worker_results_delete( corpus_id=self.corpus.id, @@ -250,7 +249,7 @@ class TestDeleteWorkerResults(FixtureTestCase): def test_run_all_versions(self): with self.assertExactQueries("worker_results_delete_all_versions.sql", params={ - "corpus_id": str(self.corpus.id), + "corpus_id": self.corpus.id.hex, }): worker_results_delete(corpus_id=self.corpus.id) self.check_deleted( @@ -291,8 +290,8 @@ class TestDeleteWorkerResults(FixtureTestCase): def test_run_worker_run_on_corpus(self): with self.assertExactQueries("worker_results_delete_in_corpus_worker_run.sql", params={ - "corpus_id": str(self.corpus.id), - "worker_run_id": str(self.worker_run_1.id), + "corpus_id": self.corpus.id.hex, + "worker_run_id": self.worker_run_1.id.hex, }): worker_results_delete( corpus_id=self.corpus.id, @@ -306,9 +305,9 @@ class TestDeleteWorkerResults(FixtureTestCase): def test_run_worker_run_on_parent(self): with self.assertExactQueries("worker_results_delete_under_parent_worker_run.sql", params={ - "corpus_id": str(self.corpus.id), - "worker_run_id": str(self.worker_run_2.id), - "element_id": str(self.page1.id), + "corpus_id": self.corpus.id.hex, + "worker_run_id": self.worker_run_2.id.hex, + "element_id": self.page1.id.hex, }): worker_results_delete(corpus_id=self.corpus.id, worker_run_id=self.worker_run_2.id, element_id=self.page1.id) self.check_deleted( @@ -324,9 +323,9 @@ class TestDeleteWorkerResults(FixtureTestCase): self.page1.worker_version = self.version_2 self.page1.save() with self.assertExactQueries("worker_results_delete_under_parent_included_worker_run.sql", params={ - "corpus_id": str(self.corpus.id), - "worker_run_id": str(self.worker_run_2.id), - "element_id": str(self.page1.id), + "corpus_id": self.corpus.id.hex, + "worker_run_id": self.worker_run_2.id.hex, + "element_id": self.page1.id.hex, }): worker_results_delete(corpus_id=self.corpus.id, worker_run_id=self.worker_run_2.id, element_id=self.page1.id) self.check_deleted( @@ -339,8 +338,8 @@ class TestDeleteWorkerResults(FixtureTestCase): def test_run_worker_run_ignore_filters(self): with self.assertExactQueries("worker_results_delete_in_corpus_worker_run.sql", params={ - "corpus_id": str(self.corpus.id), - "worker_run_id": str(self.worker_run_1.id) + "corpus_id": self.corpus.id.hex, + "worker_run_id": self.worker_run_1.id.hex }): worker_results_delete( corpus_id=self.corpus.id, diff --git a/arkindex/documents/tests/test_bulk_classification.py b/arkindex/documents/tests/test_bulk_classification.py index ddcc0d5c71d11584af9fb9c6d98a09f13c9d3399..492209723d686ed5d9ed6ac9c30d6f023538688e 100644 --- a/arkindex/documents/tests/test_bulk_classification.py +++ b/arkindex/documents/tests/test_bulk_classification.py @@ -287,7 +287,7 @@ class TestBulkClassification(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_run.version, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_run.version) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run.process.run() task = self.worker_run.process.tasks.first() diff --git a/arkindex/documents/tests/test_bulk_element_transcriptions.py b/arkindex/documents/tests/test_bulk_element_transcriptions.py index f4578bdce00ec3c58f24c48039f54ba05d86bc95..042eb16df25b730cfbc93156977919dba2e66f48 100644 --- a/arkindex/documents/tests/test_bulk_element_transcriptions.py +++ b/arkindex/documents/tests/test_bulk_element_transcriptions.py @@ -748,7 +748,7 @@ class TestBulkElementTranscriptions(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_run.version, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_run.version) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run.process.run() task = self.worker_run.process.tasks.first() diff --git a/arkindex/documents/tests/test_bulk_elements.py b/arkindex/documents/tests/test_bulk_elements.py index 05d3516d2df3acc5b3950d81de00214711764881..23964877c0093329ab0066e80c772e12971aac62 100644 --- a/arkindex/documents/tests/test_bulk_elements.py +++ b/arkindex/documents/tests/test_bulk_elements.py @@ -427,7 +427,7 @@ class TestBulkElements(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_run.version, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_run.version) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run.process.run() task = self.worker_run.process.tasks.first() diff --git a/arkindex/documents/tests/test_bulk_transcription_entities.py b/arkindex/documents/tests/test_bulk_transcription_entities.py index 8c60037671d12e50cfb6b3d08610ff1243f3d991..e0f159919c4e4043a6a686e7204e207efebd4fb3 100644 --- a/arkindex/documents/tests/test_bulk_transcription_entities.py +++ b/arkindex/documents/tests/test_bulk_transcription_entities.py @@ -233,7 +233,7 @@ class TestBulkTranscriptionEntities(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_run.version, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_run.version) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run.process.run() diff --git a/arkindex/documents/tests/test_bulk_transcriptions.py b/arkindex/documents/tests/test_bulk_transcriptions.py index 8fbbcdac41d84b2cb57f2221c5b4ea25dea9d38a..57ffaffdf329aaa5262f956d673f70d462bb17e2 100644 --- a/arkindex/documents/tests/test_bulk_transcriptions.py +++ b/arkindex/documents/tests/test_bulk_transcriptions.py @@ -263,7 +263,7 @@ class TestBulkTranscriptions(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_run.version, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_run.version) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run.process.run() task = self.worker_run.process.tasks.first() diff --git a/arkindex/documents/tests/test_classification.py b/arkindex/documents/tests/test_classification.py index 84ccefcfec56ecd8d506049f77db6e3fc7e4b314..935d36958c3e349d5fd49416608dc9523ded39f8 100644 --- a/arkindex/documents/tests/test_classification.py +++ b/arkindex/documents/tests/test_classification.py @@ -368,7 +368,7 @@ class TestClassifications(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_run.version, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_run.version) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run.process.run() task = self.worker_run.process.tasks.first() diff --git a/arkindex/documents/tests/test_corpus.py b/arkindex/documents/tests/test_corpus.py index 805447a33f06bf23f7ac7a47f022b69bac8db688..3055b5e8e67b7fb6787c53db95bb3568a9af3a51 100644 --- a/arkindex/documents/tests/test_corpus.py +++ b/arkindex/documents/tests/test_corpus.py @@ -104,6 +104,7 @@ class TestCorpus(FixtureAPITestCase): "authorized_users": 1, "top_level_type": None, "maximum_task_ttl": 3600, + "budget_id": None, } ] ) @@ -138,6 +139,7 @@ class TestCorpus(FixtureAPITestCase): "authorized_users": 2, "top_level_type": None, "maximum_task_ttl": 3600, + "budget_id": None, }, { "id": str(self.corpus_public.id), @@ -150,6 +152,7 @@ class TestCorpus(FixtureAPITestCase): "authorized_users": 1, "top_level_type": None, "maximum_task_ttl": 3600, + "budget_id": None, } ] ) @@ -184,6 +187,7 @@ class TestCorpus(FixtureAPITestCase): "authorized_users": 2, "top_level_type": None, "maximum_task_ttl": 3600, + "budget_id": None, }, { "id": str(self.corpus_hidden.id), @@ -196,6 +200,7 @@ class TestCorpus(FixtureAPITestCase): "authorized_users": 0, "top_level_type": None, "maximum_task_ttl": 3600, + "budget_id": None, }, { "id": str(self.corpus_public.id), @@ -208,6 +213,7 @@ class TestCorpus(FixtureAPITestCase): "authorized_users": 1, "top_level_type": None, "maximum_task_ttl": 3600, + "budget_id": None, } ] ) @@ -356,6 +362,7 @@ class TestCorpus(FixtureAPITestCase): "authorized_users": 1, "top_level_type": None, "maximum_task_ttl": 3600, + "budget_id": None, }) def test_retrieve(self): @@ -375,6 +382,7 @@ class TestCorpus(FixtureAPITestCase): "authorized_users": 2, "top_level_type": None, "maximum_task_ttl": 3600, + "budget_id": None, }) def test_retrieve_not_found(self): @@ -405,6 +413,7 @@ class TestCorpus(FixtureAPITestCase): "authorized_users": 1, "top_level_type": None, "maximum_task_ttl": 3600, + "budget_id": None, }) @expectedFailure @@ -434,6 +443,7 @@ class TestCorpus(FixtureAPITestCase): "authorized_users": 2, "top_level_type": None, "maximum_task_ttl": 3600, + "budget_id": None, }) def test_retrieve_maximum_task_ttl(self): diff --git a/arkindex/documents/tests/test_corpus_elements.py b/arkindex/documents/tests/test_corpus_elements.py index 78c3929e91a392c430441dc05166347f676d26af..0a252144e826decc2660034db6e410f6506f75c5 100644 --- a/arkindex/documents/tests/test_corpus_elements.py +++ b/arkindex/documents/tests/test_corpus_elements.py @@ -88,7 +88,7 @@ class TestListElements(FixtureAPITestCase): expected_image_ids = set(filter(None, expected_elements.values_list("image_id", flat=True))) expected_type_ids = set(expected_elements.values_list("type_id", flat=True)) - with self.assertExactQueries("list_elements.sql", params={"corpus_id": self.corpus.id}) as ctx: + with self.assertExactQueries("list_elements.sql", params={"corpus_id": self.corpus.id.hex}) as ctx: response = self.client.get( reverse("api:corpus-elements", kwargs={"corpus": self.corpus.id}), ) diff --git a/arkindex/documents/tests/test_create_elements.py b/arkindex/documents/tests/test_create_elements.py index 0cd08a757f2d0f78935cc70555d891cf35e1c54c..5c3a14342bb2fe58bd9ba0d8291ae7d756070bb5 100644 --- a/arkindex/documents/tests/test_create_elements.py +++ b/arkindex/documents/tests/test_create_elements.py @@ -720,7 +720,7 @@ class TestCreateElements(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_run.version, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_run.version) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run.process.run() task = self.worker_run.process.tasks.first() diff --git a/arkindex/documents/tests/test_create_transcriptions.py b/arkindex/documents/tests/test_create_transcriptions.py index 18c03d0f16a08fd4c353a90d4b514d74ef2ad34e..819e1d18b85bb55d340a3b1ee38906117cb9f0be 100644 --- a/arkindex/documents/tests/test_create_transcriptions.py +++ b/arkindex/documents/tests/test_create_transcriptions.py @@ -337,7 +337,7 @@ class TestTranscriptionCreate(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_run.version, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_run.version) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run.process.run() task = self.worker_run.process.tasks.first() diff --git a/arkindex/documents/tests/test_destroy_elements.py b/arkindex/documents/tests/test_destroy_elements.py index d6f39024b8bfa567d0810fe7bef6701b330842eb..c875c06a2130d7952525033de8dbc3e104d33626 100644 --- a/arkindex/documents/tests/test_destroy_elements.py +++ b/arkindex/documents/tests/test_destroy_elements.py @@ -219,7 +219,7 @@ class TestDestroyElements(FixtureAPITestCase): self.assertEqual(len(ids), 24) self.assertEqual(len(Element.objects.exclude(id__in=ids)), 5) - with self.assertExactQueries("element_trash_children.sql", params={"id": str(self.vol.id)}): + with self.assertExactQueries("element_trash_children.sql", params={"id": self.vol.id.hex}): Element.objects.filter(id=self.vol.id).trash() with self.assertRaises(Element.DoesNotExist): @@ -234,7 +234,7 @@ class TestDestroyElements(FixtureAPITestCase): ids = list(children.values_list("id", flat=True)) self.assertEqual(len(ids), 24) - with self.assertExactQueries("element_trash_no_children.sql", params={"id": str(self.vol.id)}): + with self.assertExactQueries("element_trash_no_children.sql", params={"id": self.vol.id.hex}): Element.objects.filter(id=self.vol.id).trash(delete_children=False) with self.assertRaises(Element.DoesNotExist): @@ -248,7 +248,7 @@ class TestDestroyElements(FixtureAPITestCase): ids = list(children.values_list("id", flat=True)) self.assertEqual(len(ids), 24) - with self.assertExactQueries("element_trash_children.sql", params={"id": str(self.vol.id)}): + with self.assertExactQueries("element_trash_children.sql", params={"id": self.vol.id.hex}): Element.objects.filter(id=self.vol.id).order_by("name").trash() with self.assertRaises(Element.DoesNotExist): @@ -324,7 +324,7 @@ class TestDestroyElements(FixtureAPITestCase): corpus=self.corpus, ) - with self.assertExactQueries("element_trash_deep.sql", params={"id": str(elements["A"].id)}): + with self.assertExactQueries("element_trash_deep.sql", params={"id": elements["A"].id.hex}): Element.objects.filter(id=elements["A"].id).trash() self.assertFalse(Element.objects.filter(id__in=[e.id for e in elements.values()]).exists()) @@ -563,7 +563,7 @@ class TestDestroyElements(FixtureAPITestCase): test Element.delete method """ self.client.force_login(self.user) - with self.assertExactQueries("element_dot_delete.sql", params={"id": str(self.vol.id)}): + with self.assertExactQueries("element_dot_delete.sql", params={"id": self.vol.id.hex}): self.vol.delete() with self.assertRaises(Element.DoesNotExist): self.vol.refresh_from_db() diff --git a/arkindex/documents/tests/test_edit_elementpath.py b/arkindex/documents/tests/test_edit_elementpath.py index 250072d979f3dc9905e5b675043f4da05bbfe8c5..44e20b7c8791c47be59927a944a4140e0005fb5a 100644 --- a/arkindex/documents/tests/test_edit_elementpath.py +++ b/arkindex/documents/tests/test_edit_elementpath.py @@ -96,14 +96,14 @@ class TestEditElementPath(FixtureTestCase): # add_parent uses transaction.atomic(), and we are running in a unit test, which is already in a transaction. # This will cause a savepoint to be created, with a name that is hard to mock. "savepoint": f"s{_thread.get_ident()}_x{connections['default'].savepoint_state + 1}", - "A": elements["A"].id, - "B": elements["B"].id, + "A": elements["A"].id.hex, + "B": elements["B"].id.hex, # Element A has two parents so it has two paths. # add_parent will pick the first one to perform updates on the new child's paths, # so it will be seen in the SQL queries. To avoid intermittent failures, # add_parent sorts parent paths by `path`, so we apply the same sort here. # The paths only contain one ID, X's or Y's. - "first_parent": elements["A"].paths.order_by("path").first().path[0], + "first_parent": elements["A"].paths.order_by("path").first().path[0].hex, } ): elements["B"].add_parent(elements["A"]) @@ -171,9 +171,9 @@ class TestEditElementPath(FixtureTestCase): # add_parent uses transaction.atomic(), and we are running in a unit test, which is already in a transaction. # This will cause a savepoint to be created, with a name that is hard to mock. "savepoint": f"s{_thread.get_ident()}_x{connections['default'].savepoint_state + 1}", - "A": elements["A"].id, - "B": elements["B"].id, - "K": elements["K"].id, + "A": elements["A"].id.hex, + "B": elements["B"].id.hex, + "K": elements["K"].id.hex, } ): elements["B"].add_parent(elements["A"]) @@ -285,8 +285,8 @@ class TestEditElementPath(FixtureTestCase): # remove_child uses transaction.atomic(), and we are running in a unit test, which is already in a transaction. # This will cause a savepoint to be created, with a name that is hard to mock. "savepoint": f"s{_thread.get_ident()}_x{connections['default'].savepoint_state + 1}", - "A": elements["A"].id, - "B": elements["B"].id, + "A": elements["A"].id.hex, + "B": elements["B"].id.hex, } ): elements["A"].remove_child(elements["B"]) @@ -362,9 +362,9 @@ class TestEditElementPath(FixtureTestCase): def __str__(self): path_id = elements["B"].paths.get().id if path1.id == path_id: - return str(path1.path[0]) + return path1.path[0].hex if path2.id == path_id: - return str(path2.path[0]) + return path2.path[0].hex raise AssertionError("Unexpected top-level path ID") with self.assertExactQueries( @@ -372,8 +372,8 @@ class TestEditElementPath(FixtureTestCase): # remove_child uses transaction.atomic(), and we are running in a unit test, which is already in a transaction. # This will cause a savepoint to be created, with a name that is hard to mock. "savepoint": f"s{_thread.get_ident()}_x{connections['default'].savepoint_state + 1}", - "A": elements["A"].id, - "B": elements["B"].id, + "A": elements["A"].id.hex, + "B": elements["B"].id.hex, "first_parent": FirstParent(), } ): @@ -514,7 +514,7 @@ class TestEditElementPath(FixtureTestCase): # remove_children uses transaction.atomic(), and we are running in a unit test, which is already in a transaction. # This will cause a savepoint to be created, with a name that is hard to mock. "savepoint": f"s{_thread.get_ident()}_x{connections['default'].savepoint_state + 1}", - "A": elements["A"].id, + "A": elements["A"].id.hex, } ): elements["A"].remove_children() @@ -575,8 +575,8 @@ class TestEditElementPath(FixtureTestCase): # remove_children uses transaction.atomic(), and we are running in a unit test, which is already in a transaction. # This will cause a savepoint to be created, with a name that is hard to mock. "savepoint": f"s{_thread.get_ident()}_x{connections['default'].savepoint_state + 1}", - "A": elements["A"].id, - "X": elements["X"].id, + "A": elements["A"].id.hex, + "X": elements["X"].id.hex, } ): elements["A"].remove_children() @@ -645,8 +645,8 @@ class TestEditElementPath(FixtureTestCase): # remove_children uses transaction.atomic(), and we are running in a unit test, which is already in a transaction. # This will cause a savepoint to be created, with a name that is hard to mock. "savepoint": f"s{_thread.get_ident()}_x{connections['default'].savepoint_state + 1}", - "A": elements["A"].id, - "first_parent": elements["A"].paths.order_by("id").first().path[0], + "A": elements["A"].id.hex, + "first_parent": elements["A"].paths.order_by("id").first().path[0].hex, } ): elements["A"].remove_children() diff --git a/arkindex/documents/tests/test_entities_api.py b/arkindex/documents/tests/test_entities_api.py index 32b4dffa231488169b0f3f34ac80ad5c08e3524a..90675af67f1e8e4bc7b76924eb4883294f9576df 100644 --- a/arkindex/documents/tests/test_entities_api.py +++ b/arkindex/documents/tests/test_entities_api.py @@ -388,7 +388,7 @@ class TestEntitiesAPI(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_version_1, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_version_1) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run_1.process.run() task = self.worker_run_1.process.tasks.first() @@ -863,7 +863,7 @@ class TestEntitiesAPI(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_version_1, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_version_1) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run_1.process.run() task = self.worker_run_1.process.tasks.first() diff --git a/arkindex/documents/tests/test_indexer.py b/arkindex/documents/tests/test_indexer.py index 8fd06cb9e49d9d95511ea12ed86d563e807b899c..2cd29e02e2a5ff3d4ce20f48f1c4584fbe478827 100644 --- a/arkindex/documents/tests/test_indexer.py +++ b/arkindex/documents/tests/test_indexer.py @@ -295,14 +295,13 @@ class TestIndexerCommand(FixtureTestCase): indexer = Indexer(self.private_corpus.id) with self.assertExactQueries("indexer_prefetch.sql", params={ - "corpus_id": self.private_corpus.id, - "page_id": self.page.id, - "image_id": self.page.image_id, - "worker_run_id": self.worker_run.id, - "worker_version_id": self.worker_version.id, - "worker_id": self.worker.id, - "transcription_id": tr.id, - "type_id": location_type.id + "corpus_id": self.private_corpus.id.hex, + "page_id": self.page.id.hex, + "worker_run_id": self.worker_run.id.hex, + "worker_version_id": self.worker_version.id.hex, + "worker_id": self.worker.id.hex, + "transcription_id": tr.id.hex, + "type_id": location_type.id.hex, }): indexer.index() self.assertEqual(mock_solr.index.call_count, 1) diff --git a/arkindex/documents/tests/test_metadata.py b/arkindex/documents/tests/test_metadata.py index 55b026370d0252154f8f913acaf995cc3dcf3f50..ae0fc7a3c80023828b3d1a3afc242961fe54f61a 100644 --- a/arkindex/documents/tests/test_metadata.py +++ b/arkindex/documents/tests/test_metadata.py @@ -44,7 +44,7 @@ class TestMetaData(FixtureAPITestCase): creator=cls.user, farm=Farm.objects.first(), ) - cls.process.worker_runs.create(version=cls.worker_version, ttl=0) + cls.process.worker_runs.create(version=cls.worker_version) with patch("arkindex.process.tasks.initialize_activity.delay"): cls.process.run() cls.task = cls.process.tasks.first() @@ -463,7 +463,7 @@ class TestMetaData(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_run.version, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_run.version) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run.process.run() task = self.worker_run.process.tasks.first() @@ -1458,7 +1458,7 @@ class TestMetaData(FixtureAPITestCase): mode=ProcessMode.Workers, corpus=self.corpus, ) - other_worker_run = process2.worker_runs.create(version=self.worker_run.version, ttl=0) + other_worker_run = process2.worker_runs.create(version=self.worker_run.version) with patch("arkindex.process.tasks.initialize_activity.delay"): self.worker_run.process.run() task = self.worker_run.process.tasks.first() diff --git a/arkindex/documents/tests/test_path_constraints.py b/arkindex/documents/tests/test_path_constraints.py index 751270a2219e7182c7df25541593d257c7a2d566..0e627cdb87d42b03c370485fea75035f6a47a751 100644 --- a/arkindex/documents/tests/test_path_constraints.py +++ b/arkindex/documents/tests/test_path_constraints.py @@ -1,5 +1,4 @@ -from django.db import IntegrityError, connections, transaction -from django.db.utils import InternalError +from django.db import IntegrityError, ProgrammingError, connections, transaction from arkindex.documents.models import ElementPath from arkindex.project.tests import FixtureTestCase @@ -55,7 +54,7 @@ class TestPathConstraints(FixtureTestCase): ordering=11111, ) - with self.assertRaisesMessage(InternalError, "Each element may only have one ordering within the same parent"): + with self.assertRaisesMessage(ProgrammingError, "Each element may only have one ordering within the same parent"): # Committing the savepoint would not execute the deferred trigger, and committing the transaction # would mess with the test class and any subsequent unit tests, so the next best thing is to act # like we are about to commit by forcing all constraint checks to run. @@ -91,7 +90,7 @@ class TestPathConstraints(FixtureTestCase): # We save using update_fields to really ensure only the element gets updated. path.save(update_fields=["element"]) - with self.assertRaisesMessage(InternalError, "Each element may only have one ordering within the same parent"): + with self.assertRaisesMessage(ProgrammingError, "Each element may only have one ordering within the same parent"): # Committing the savepoint would not execute the deferred trigger, and committing the transaction # would mess with the test class and any subsequent unit tests, so the next best thing is to act # like we are about to commit by forcing all constraint checks to run. @@ -127,7 +126,7 @@ class TestPathConstraints(FixtureTestCase): # We save using update_fields to really ensure only the path gets updated. path.save(update_fields=["path"]) - with self.assertRaisesMessage(InternalError, "Each element may only have one ordering within the same parent"): + with self.assertRaisesMessage(ProgrammingError, "Each element may only have one ordering within the same parent"): # Committing the savepoint would not execute the deferred trigger, and committing the transaction # would mess with the test class and any subsequent unit tests, so the next best thing is to act # like we are about to commit by forcing all constraint checks to run. @@ -153,7 +152,7 @@ class TestPathConstraints(FixtureTestCase): # We save using update_fields to really ensure only the ordering gets updated. path.save(update_fields=["ordering"]) - with self.assertRaisesMessage(InternalError, "Each element may only have one ordering within the same parent"): + with self.assertRaisesMessage(ProgrammingError, "Each element may only have one ordering within the same parent"): # Committing the savepoint would not execute the deferred trigger, and committing the transaction # would mess with the test class and any subsequent unit tests, so the next best thing is to act # like we are about to commit by forcing all constraint checks to run. @@ -176,7 +175,7 @@ class TestPathConstraints(FixtureTestCase): ordering=3, ) - with self.assertRaisesMessage(InternalError, "Each element within a parent must have a distinct ordering"): + with self.assertRaisesMessage(ProgrammingError, "Each element within a parent must have a distinct ordering"): # Committing the savepoint would not execute the deferred trigger, and committing the transaction # would mess with the test class and any subsequent unit tests, so the next best thing is to act # like we are about to commit by forcing all constraint checks to run. @@ -201,7 +200,7 @@ class TestPathConstraints(FixtureTestCase): # We save using update_fields to really ensure only the element gets updated. path.save(update_fields=["element"]) - with self.assertRaisesMessage(InternalError, "Each element within a parent must have a distinct ordering"): + with self.assertRaisesMessage(ProgrammingError, "Each element within a parent must have a distinct ordering"): # Committing the savepoint would not execute the deferred trigger, and committing the transaction # would mess with the test class and any subsequent unit tests, so the next best thing is to act # like we are about to commit by forcing all constraint checks to run. @@ -234,7 +233,7 @@ class TestPathConstraints(FixtureTestCase): # We save using update_fields to really ensure only the path gets updated. path.save(update_fields=["path"]) - with self.assertRaisesMessage(InternalError, "Each element within a parent must have a distinct ordering"): + with self.assertRaisesMessage(ProgrammingError, "Each element within a parent must have a distinct ordering"): # Committing the savepoint would not execute the deferred trigger, and committing the transaction # would mess with the test class and any subsequent unit tests, so the next best thing is to act # like we are about to commit by forcing all constraint checks to run. @@ -261,7 +260,7 @@ class TestPathConstraints(FixtureTestCase): # We save using update_fields to really ensure only the ordering gets updated. path.save() - with self.assertRaisesMessage(InternalError, "Each element within a parent must have a distinct ordering"): + with self.assertRaisesMessage(ProgrammingError, "Each element within a parent must have a distinct ordering"): # Committing the savepoint would not execute the deferred trigger, and committing the transaction # would mess with the test class and any subsequent unit tests, so the next best thing is to act # like we are about to commit by forcing all constraint checks to run. diff --git a/arkindex/metrics/tests/test_metrics_api.py b/arkindex/metrics/tests/test_metrics_api.py index 917979e5978d8907d7568c7c66dbf53bdcf34be7..4a0719a5f53ba2c95eb39a99527059f61ef42d1b 100644 --- a/arkindex/metrics/tests/test_metrics_api.py +++ b/arkindex/metrics/tests/test_metrics_api.py @@ -35,6 +35,7 @@ class TestMetricsAPI(FixtureAPITestCase): mode=AgentMode.Docker, hostname="Demo Agent", farm=farm, + fingerprint="demo" * 16, last_ping=datetime.now(), cpu_cores=42, cpu_frequency=42e8, diff --git a/arkindex/ponos/api.py b/arkindex/ponos/api.py index 91f6d607dac16a1e63429ccd1baee27a6c4a2317..7616e944decdb0f56e25ef1a5e5f537a005d23dd 100644 --- a/arkindex/ponos/api.py +++ b/arkindex/ponos/api.py @@ -12,7 +12,7 @@ from rest_framework.generics import CreateAPIView, ListCreateAPIView, RetrieveUp from rest_framework.response import Response from rest_framework.views import APIView -from arkindex.ponos.models import FINAL_STATES, Artifact, State, Task, task_token_default +from arkindex.ponos.models import FINAL_STATES, Artifact, State, Task, token_default from arkindex.ponos.permissions import ( IsAgentOrArtifactGuest, IsAgentOrTaskGuest, @@ -234,7 +234,7 @@ class TaskRestart(ProcessACLMixin, CreateAPIView): copy.id = uuid.uuid4() copy.slug = basename copy.state = State.Pending - copy.token = task_token_default() + copy.token = token_default() copy.agent_id = None copy.gpu_id = None copy.started = None diff --git a/arkindex/ponos/migrations/0001_initial.py b/arkindex/ponos/migrations/0001_initial.py index 7a72645956ffc490e65c3d2f7d6cfc2a68733cd6..0c9d9a237838fc0c46cff5eca9b76cf9f9c70231 100644 --- a/arkindex/ponos/migrations/0001_initial.py +++ b/arkindex/ponos/migrations/0001_initial.py @@ -114,7 +114,7 @@ class Migration(migrations.Migration): ("updated", models.DateTimeField(auto_now=True)), ("expiry", models.DateTimeField(default=arkindex.ponos.models.expiry_default)), ("extra_files", django.contrib.postgres.fields.hstore.HStoreField(default=dict, blank=True)), - ("token", models.CharField(default=arkindex.ponos.models.task_token_default, max_length=52, unique=True)), + ("token", models.CharField(default=arkindex.ponos.models.token_default, max_length=52, unique=True)), ("agent", models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="tasks", to="ponos.agent")), ("gpu", models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="tasks", to="ponos.gpu")), ("image_artifact", models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="tasks_using_image", to="ponos.artifact")), diff --git a/arkindex/ponos/migrations/0004_index_cleanup.py b/arkindex/ponos/migrations/0004_index_cleanup.py index 6e25ec7c743706c0bf9aa8b530e3dfc9834fbf6c..939e2531189a281d2db82afd1cb4468d3c226bef 100644 --- a/arkindex/ponos/migrations/0004_index_cleanup.py +++ b/arkindex/ponos/migrations/0004_index_cleanup.py @@ -3,7 +3,7 @@ from django.core.validators import RegexValidator from django.db import migrations, models -from arkindex.ponos.models import generate_seed, task_token_default +from arkindex.ponos.models import generate_seed, token_default class Migration(migrations.Migration): @@ -89,7 +89,7 @@ class Migration(migrations.Migration): model_name="task", name="token", field=models.CharField( - default=task_token_default, + default=token_default, max_length=52, ), ), diff --git a/arkindex/ponos/migrations/0015_agent_token.py b/arkindex/ponos/migrations/0015_agent_token.py new file mode 100644 index 0000000000000000000000000000000000000000..b3f118b8f0fdb2ab6ab3ed8bfa197768dab6541a --- /dev/null +++ b/arkindex/ponos/migrations/0015_agent_token.py @@ -0,0 +1,38 @@ +# Generated by Django 5.0.8 on 2025-02-17 13:50 + +from django.db import migrations, models + +from arkindex.ponos.models import token_default + + +def add_agent_tokens(apps, schema_editor): + Agent = apps.get_model("ponos", "Agent") + to_update = [] + for agent in Agent.objects.filter(token=None).only("id").iterator(): + agent.token = token_default() + to_update.append(agent) + Agent.objects.bulk_update(to_update, ["token"], batch_size=100) + + +class Migration(migrations.Migration): + + dependencies = [ + ("ponos", "0014_task_task_finished_requires_final_state"), + ] + + operations = [ + migrations.AddField( + model_name="agent", + name="token", + field=models.CharField( + max_length=52, + # Make the field temporarily nullable and not unique, so that we can + # fill the tokens on existing agents before adding the constraints. + null=True, + ), + ), + migrations.RunPython( + add_agent_tokens, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/arkindex/ponos/migrations/0016_agent_token_constraints.py b/arkindex/ponos/migrations/0016_agent_token_constraints.py new file mode 100644 index 0000000000000000000000000000000000000000..c0ae4fd40253090576dfbb165f886627a411531b --- /dev/null +++ b/arkindex/ponos/migrations/0016_agent_token_constraints.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.8 on 2025-02-17 14:26 + +from django.db import migrations, models + +import arkindex.ponos.models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ponos", "0015_agent_token"), + ] + + operations = [ + migrations.AlterField( + model_name="agent", + name="token", + field=models.CharField(default=arkindex.ponos.models.token_default, max_length=52), + ), + migrations.AddConstraint( + model_name="agent", + constraint=models.UniqueConstraint(models.F("token"), name="unique_agent_token"), + ), + ] diff --git a/arkindex/ponos/migrations/0017_remove_agents.py b/arkindex/ponos/migrations/0017_remove_agents.py new file mode 100644 index 0000000000000000000000000000000000000000..87be71102c71d202b8518e70c84b4bcf7a8e91c1 --- /dev/null +++ b/arkindex/ponos/migrations/0017_remove_agents.py @@ -0,0 +1,38 @@ +# Generated by Django 5.0.8 on 2025-02-18 11:48 + +from django.core.management.base import CommandError +from django.db import migrations + +from arkindex.ponos.models import State + + +def remove_agents(apps, schema_editor): + Agent = apps.get_model("ponos", "Agent") + GPU = apps.get_model("ponos", "GPU") + Task = apps.get_model("ponos", "Task") + + if Task.objects.exclude(agent=None, gpu=None).filter(state=State.Running).exists(): + raise CommandError( + "All existing Ponos agents and GPUs are about to be deleted, but some are currently assigned to running tasks.\n" + "Wait for the tasks to finish or stop them before running this migration." + ) + + Task.objects.exclude(gpu=None).update(gpu=None) + Task.objects.exclude(agent=None).update(agent=None) + GPU.objects.all().delete() + Agent.objects.all().delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ("ponos", "0016_agent_token_constraints"), + ] + + operations = [ + migrations.RunPython( + remove_agents, + reverse_code=migrations.RunPython.noop, + elidable=True, + ), + ] diff --git a/arkindex/ponos/migrations/0018_agent_fingerprint.py b/arkindex/ponos/migrations/0018_agent_fingerprint.py new file mode 100644 index 0000000000000000000000000000000000000000..75c7a285dcac1bda2553032fd80affd2bdba4374 --- /dev/null +++ b/arkindex/ponos/migrations/0018_agent_fingerprint.py @@ -0,0 +1,32 @@ +# Generated by Django 5.0.8 on 2025-02-18 11:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ponos", "0017_remove_agents"), + ] + + operations = [ + # Set a default value for the public key so that the migration is reversible + migrations.AlterField( + model_name="agent", + name="public_key", + field=models.TextField(default=""), + ), + migrations.RemoveField( + model_name="agent", + name="public_key", + ), + migrations.AddField( + model_name="agent", + name="fingerprint", + field=models.CharField(max_length=64), + ), + migrations.AddConstraint( + model_name="agent", + constraint=models.UniqueConstraint(models.F("fingerprint"), name="unique_agent_fingerprint"), + ), + ] diff --git a/arkindex/ponos/models.py b/arkindex/ponos/models.py index e77e10df10c44acead36174613d930f84b16ae54..2211c3e89d32619418d62835a62a333ccabc65e8 100644 --- a/arkindex/ponos/models.py +++ b/arkindex/ponos/models.py @@ -34,6 +34,15 @@ def gen_nonce(size=16): return urandom(size) +def token_default(): + """ + Default value for task and agent tokens. + + :rtype: str + """ + return base64.encodebytes(uuid.uuid4().bytes + uuid.uuid4().bytes).strip().decode("utf-8") + + class Farm(models.Model): """ A group of agents, whose ID and seed can be used to register new agents @@ -80,7 +89,7 @@ class Agent(models.Model): created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) farm = models.ForeignKey(Farm, on_delete=models.PROTECT) - public_key = models.TextField() + fingerprint = models.CharField(max_length=64) mode = EnumField(AgentMode, default=AgentMode.Docker, max_length=20) accept_tasks = models.BooleanField(default=True) @@ -95,12 +104,26 @@ class Agent(models.Model): ram_load = models.FloatField(null=True, blank=True) last_ping = models.DateTimeField(editable=False) + token = models.CharField( + default=token_default, + # The token generation always returns 52 characters + max_length=52, + ) + class Meta: constraints = [ models.CheckConstraint( check=Q(mode=AgentMode.Slurm) | Q(cpu_cores__isnull=False, cpu_frequency__isnull=False, ram_total__isnull=False), name="slurm_or_hardware_requirements", ), + models.UniqueConstraint( + "token", + name="unique_agent_token", + ), + models.UniqueConstraint( + "fingerprint", + name="unique_agent_fingerprint", + ) ] def __str__(self) -> str: @@ -224,15 +247,6 @@ def expiry_default(): return timezone.now() + timedelta(days=settings.PONOS_TASK_EXPIRY) -def task_token_default(): - """ - Default value for Task.token. - - :rtype: str - """ - return base64.encodebytes(uuid.uuid4().bytes + uuid.uuid4().bytes).strip().decode("utf-8") - - class TaskLogs(S3FileMixin): s3_bucket = settings.PONOS_S3_LOGS_BUCKET @@ -357,7 +371,7 @@ class Task(models.Model): extra_files = HStoreField(default=dict, blank=True) token = models.CharField( - default=task_token_default, + default=token_default, # The token generation always returns 52 characters max_length=52, ) diff --git a/arkindex/ponos/serializer_fields.py b/arkindex/ponos/serializer_fields.py index 1bd83378c89a4b1c2b71abdfe3d8a48ebb40988a..80eef856a802795d60cf3aa85a77d2e3276579fa 100644 --- a/arkindex/ponos/serializer_fields.py +++ b/arkindex/ponos/serializer_fields.py @@ -1,61 +1,8 @@ -import base64 -from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, load_pem_public_key -from rest_framework import serializers from arkindex.ponos.utils import get_process_from_task_auth -class PublicKeyField(serializers.CharField): - """ - An EC public key, serialized in PEM format - """ - - default_error_messages = { - "invalid_pem": "Incorrect PEM data", - "unsupported_algorithm": "Key algorithm is not supported", - "not_ec": "Key is not an EC public key", - } - - def to_internal_value(self, data) -> ec.EllipticCurvePublicKey: - data = super().to_internal_value(data) - try: - key = load_pem_public_key( - data.encode("utf-8"), - backend=default_backend(), - ) - except ValueError: - self.fail("invalid_pem") - except UnsupportedAlgorithm: - self.fail("unsupported_algorithm") - - if not isinstance(key, ec.EllipticCurvePublicKey): - self.fail("not_ec") - - return key - - def to_representation(self, key: ec.EllipticCurvePublicKey) -> str: - return key.public_bytes( - Encoding.PEM, - PublicFormat.SubjectPublicKeyInfo, - ).decode("utf-8") - - -class Base64Field(serializers.CharField): - """ - A base64-encoded bytestring. - """ - - def to_internal_value(self, data) -> bytes: - return base64.b64decode(super().to_internal_value(data)) - - def to_representation(self, obj: bytes) -> str: - return base64.b64encode(obj) - - class CurrentProcessDefault: """ Use the process of the currently authenticated task as a default value. diff --git a/arkindex/ponos/tests/rq/test_trigger.py b/arkindex/ponos/tests/rq/test_trigger.py index 406dd77a8987f02ae48552a5d71bd11afbf4360e..e65f9093e8b4d60e7e1042300825f932941b44f2 100644 --- a/arkindex/ponos/tests/rq/test_trigger.py +++ b/arkindex/ponos/tests/rq/test_trigger.py @@ -21,8 +21,8 @@ class TestTrigger(FixtureTestCase): ) cls.worker_version1 = WorkerVersion.objects.get(worker__slug="reco") cls.worker_version2 = WorkerVersion.objects.get(worker__slug="dla") - cls.run1 = cls.process.worker_runs.create(version=cls.worker_version1, ttl=0) - cls.run2 = cls.process.worker_runs.create(version=cls.worker_version2, parents=[cls.run1.id], ttl=0) + cls.run1 = cls.process.worker_runs.create(version=cls.worker_version1) + cls.run2 = cls.process.worker_runs.create(version=cls.worker_version2, parents=[cls.run1.id]) @override_settings(PONOS_RQ_EXECUTION=True) @patch("arkindex.ponos.tasks.run_task_rq.delay") diff --git a/arkindex/ponos/tests/tasks/test_partial_update.py b/arkindex/ponos/tests/tasks/test_partial_update.py index f4c8adbe30a6260adb86d1c18ff89f9943af60e6..45646f7edda745d8bff9306cabef7a97d8bc626a 100644 --- a/arkindex/ponos/tests/tasks/test_partial_update.py +++ b/arkindex/ponos/tests/tasks/test_partial_update.py @@ -29,6 +29,7 @@ class TestTaskPartialUpdate(FixtureAPITestCase): cls.docker_agent = Agent.objects.create( mode=AgentMode.Docker, farm=cls.farm, + fingerprint="a" * 64, last_ping=datetime.now(timezone.utc), cpu_cores=42, cpu_frequency=42e8, @@ -37,6 +38,7 @@ class TestTaskPartialUpdate(FixtureAPITestCase): cls.slurm_agent = Agent.objects.create( mode=AgentMode.Slurm, farm=cls.farm, + fingerprint="b" * 64, last_ping=datetime.now(timezone.utc), ) diff --git a/arkindex/ponos/tests/tasks/test_retrieve.py b/arkindex/ponos/tests/tasks/test_retrieve.py index 5fe9581a411349d3f0a58d075a9416235a9058a1..7643f6d8531aee6db295bb0eb019ca994420135b 100644 --- a/arkindex/ponos/tests/tasks/test_retrieve.py +++ b/arkindex/ponos/tests/tasks/test_retrieve.py @@ -51,6 +51,7 @@ class TestTaskRetrieve(FixtureAPITestCase): cls.docker_agent = Agent.objects.create( mode=AgentMode.Docker, farm=cls.farm, + fingerprint="a" * 64, last_ping=datetime.now(timezone.utc), cpu_cores=42, cpu_frequency=42e8, @@ -59,6 +60,7 @@ class TestTaskRetrieve(FixtureAPITestCase): cls.slurm_agent = Agent.objects.create( mode=AgentMode.Slurm, farm=cls.farm, + fingerprint="b" * 64, last_ping=datetime.now(timezone.utc), ) diff --git a/arkindex/ponos/tests/tasks/test_update.py b/arkindex/ponos/tests/tasks/test_update.py index 762a7fd31cac76e51dfad964cc3aee684902708c..bd93e8766eb12cc249492014a5b600d61f0c0082 100644 --- a/arkindex/ponos/tests/tasks/test_update.py +++ b/arkindex/ponos/tests/tasks/test_update.py @@ -39,14 +39,16 @@ class TestTaskUpdate(FixtureAPITestCase): cls.docker_agent = Agent.objects.create( mode=AgentMode.Docker, farm=cls.farm, + fingerprint="a" * 64, last_ping=datetime.now(timezone.utc), cpu_cores=42, cpu_frequency=42e8, - ram_total=42e3 + ram_total=42e3, ) cls.slurm_agent = Agent.objects.create( mode=AgentMode.Slurm, farm=cls.farm, + fingerprint="b" * 64, last_ping=datetime.now(timezone.utc), ) @@ -131,8 +133,8 @@ class TestTaskUpdate(FixtureAPITestCase): corpus=self.corpus, activity_state=ActivityState.Ready ) - init_run = test_process.worker_runs.create(version=WorkerVersion.objects.get(worker__slug="initialisation"), ttl=0) - test_run = test_process.worker_runs.create(version=self.recognizer, parents=[init_run.id], ttl=0) + init_run = test_process.worker_runs.create(version=WorkerVersion.objects.get(worker__slug="initialisation")) + test_run = test_process.worker_runs.create(version=self.recognizer, parents=[init_run.id]) test_process.run() @@ -227,8 +229,8 @@ class TestTaskUpdate(FixtureAPITestCase): activity_state=ActivityState.Ready ) init_version = WorkerVersion.objects.get(worker__slug="initialisation") - init_run = test_process.worker_runs.create(version=init_version, ttl=0) - test_run = test_process.worker_runs.create(version=self.recognizer, parents=[init_run.id], ttl=0) + init_run = test_process.worker_runs.create(version=init_version) + test_run = test_process.worker_runs.create(version=self.recognizer, parents=[init_run.id]) test_process.run() @@ -300,7 +302,7 @@ class TestTaskUpdate(FixtureAPITestCase): chunks=2, activity_state=ActivityState.Ready ) - test_run = test_process.worker_runs.create(version=self.recognizer, ttl=0) + test_run = test_process.worker_runs.create(version=self.recognizer) test_process.run() @@ -402,18 +404,15 @@ class TestTaskUpdate(FixtureAPITestCase): ) test_run_1 = test_process.worker_runs.create( version=self.recognizer, - ttl=0, ) test_run = test_process.worker_runs.create( version=self.recognizer, model_version_id=test_model_version.id, - ttl=0, ) test_run_2 = test_process.worker_runs.create( version=self.recognizer, model_version_id=test_model_version.id, configuration_id=test_configuration.id, - ttl=0, ) test_process.run() diff --git a/arkindex/ponos/tests/test_models.py b/arkindex/ponos/tests/test_models.py index a94dc7373408de5e8e8fe786756c352c3bc9dc3c..ce83600bac8aa71190e5eddd065a08c9636b7b4c 100644 --- a/arkindex/ponos/tests/test_models.py +++ b/arkindex/ponos/tests/test_models.py @@ -145,7 +145,7 @@ class TestModels(FixtureAPITestCase): hostname="agent_smith", cpu_cores=2, cpu_frequency=4.2e9, - public_key="", + fingerprint="a" * 64, farm=self.farm, ram_total=2e9, last_ping=timezone.now(), @@ -159,7 +159,7 @@ class TestModels(FixtureAPITestCase): def test_agent_slurm_mode(self): Agent.objects.create( hostname="agent_smith", - public_key="", + fingerprint="a" * 64, farm=self.farm, last_ping=timezone.now(), mode=AgentMode.Slurm.value @@ -174,7 +174,7 @@ class TestModels(FixtureAPITestCase): hostname="agent_smith", cpu_cores=2, cpu_frequency=4.2e9, - public_key="", + fingerprint="a" * 64, farm=self.farm, ram_total=2e9, last_ping=timezone.now(), @@ -195,7 +195,7 @@ class TestModels(FixtureAPITestCase): "params": { "hostname": "agent_smith", "cpu_frequency": 4.2e9, - "public_key": "", + "fingerprint": "a" * 64, "farm": self.farm, "ram_total": 2e9, "last_ping": timezone.now(), @@ -210,7 +210,7 @@ class TestModels(FixtureAPITestCase): "params": { "hostname": "agent_smith", "cpu_cores": 2, - "public_key": "", + "fingerprint": "a" * 64, "farm": self.farm, "ram_total": 2e9, "last_ping": timezone.now(), @@ -226,7 +226,7 @@ class TestModels(FixtureAPITestCase): "hostname": "agent_smith", "cpu_cores": 2, "cpu_frequency": 4.2e9, - "public_key": "", + "fingerprint": "a" * 64, "farm": self.farm, "last_ping": timezone.now(), "ram_load": 0.49, @@ -241,7 +241,7 @@ class TestModels(FixtureAPITestCase): "hostname": "agent_smith", "cpu_cores": None, "cpu_frequency": None, - "public_key": "", + "fingerprint": "a" * 64, "farm": self.farm, "ram_total": None, "last_ping": timezone.now(), @@ -257,7 +257,7 @@ class TestModels(FixtureAPITestCase): "hostname": "agent_smith", "cpu_cores": 2, "cpu_frequency": 4.2e9, - "public_key": "", + "fingerprint": "a" * 64, "farm": self.farm, "ram_total": 2e9, "last_ping": timezone.now(), @@ -271,7 +271,7 @@ class TestModels(FixtureAPITestCase): "mode": AgentMode.Slurm, "params": { "hostname": "agent_smith", - "public_key": "", + "fingerprint": "b" * 64, "farm": self.farm, "last_ping": timezone.now(), "ram_load": 0.49, diff --git a/arkindex/process/builder.py b/arkindex/process/builder.py index 5a1c347a32e95b8bc3fc0234e71338388c860c27..9756ba6944a7b36fe0598d6d96626dd810890499 100644 --- a/arkindex/process/builder.py +++ b/arkindex/process/builder.py @@ -13,7 +13,7 @@ from django.utils.functional import cached_property from rest_framework.exceptions import ValidationError from arkindex.images.models import ImageServer -from arkindex.ponos.models import GPU, Task, task_token_default +from arkindex.ponos.models import GPU, Task, token_default class ProcessBuilder: @@ -79,7 +79,7 @@ class ProcessBuilder: Build a Task with default attributes and add it to the current stack. Depth is not set while building individual Task instances. """ - token = task_token_default() + token = token_default() env = { **self.base_env, @@ -102,7 +102,7 @@ class ProcessBuilder: shm_size=shm_size, extra_files=extra_files, worker_run=worker_run, - ttl=worker_run.ttl, + ttl=self.process.corpus.applied_maximum_task_ttl, ) ) @@ -218,10 +218,7 @@ class ProcessBuilder: from arkindex.process.models import ArkindexFeature, WorkerVersion import_version = WorkerVersion.objects.get_by_feature(ArkindexFeature.FileImport) - worker_run, _ = self.process.worker_runs.get_or_create( - version=import_version, - defaults={"ttl": self.process.corpus.applied_maximum_task_ttl}, - ) + worker_run, _ = self.process.worker_runs.get_or_create(version=import_version) self._build_task( slug="import_files", @@ -250,7 +247,6 @@ class ProcessBuilder: version=ingest_version, model_version=None, configuration=worker_configuration, - defaults={"ttl": self.process.corpus.applied_maximum_task_ttl}, ) env = { @@ -294,10 +290,7 @@ class ProcessBuilder: worker_runs.remove(initialisation_worker_run) # If there is no elements initialisation worker run in the process, create one else: - initialisation_worker_run = self.process.worker_runs.create( - version=init_elements_version, - ttl=self.process.corpus.applied_maximum_task_ttl, - ) + initialisation_worker_run = self.process.worker_runs.create(version=init_elements_version) # Link all parentless worker runs to the initialisation worker run no_parents = [run for run in worker_runs if not len(run.parents)] for run in no_parents: diff --git a/arkindex/process/management/commands/fake_worker_run.py b/arkindex/process/management/commands/fake_worker_run.py index e1e9e1d72fc2ef648f2e4d4c3967507e6401b8d6..7533141bf5c4f67b4875de194986f1cbda085dcb 100644 --- a/arkindex/process/management/commands/fake_worker_run.py +++ b/arkindex/process/management/commands/fake_worker_run.py @@ -33,10 +33,7 @@ class Command(BaseCommand): else: self.stdout.write(f"Using existing local process {process.id}") - worker_run, created = process.worker_runs.get_or_create( - version=worker_version, - defaults={"ttl": 0}, - ) + worker_run, created = process.worker_runs.get_or_create(version=worker_version) if created: self.stdout.write(self.style.SUCCESS(f"Created WorkerRun {worker_run.id}")) diff --git a/arkindex/process/migrations/0049_remove_workerrun_ttl.py b/arkindex/process/migrations/0049_remove_workerrun_ttl.py new file mode 100644 index 0000000000000000000000000000000000000000..fad0275417963d229bec4151d6d5c7ac95ebf00c --- /dev/null +++ b/arkindex/process/migrations/0049_remove_workerrun_ttl.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.8 on 2025-02-13 14:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("process", "0048_worker_cost_fields"), + ] + + operations = [ + migrations.RemoveField( + model_name="workerrun", + name="ttl", + ), + ] diff --git a/arkindex/process/models.py b/arkindex/process/models.py index 3d5f00821cddb6d5eb9a65ab0173c4b0870499a6..16e89ce50961e7f1c0e1b2c7b42d5ba1ff0e23b3 100644 --- a/arkindex/process/models.py +++ b/arkindex/process/models.py @@ -15,7 +15,7 @@ from enumfields import Enum, EnumField import pgtrigger from arkindex.documents.models import Classification, Element -from arkindex.ponos.models import FINAL_STATES, STATES_ORDERING, State, Task, task_token_default +from arkindex.ponos.models import FINAL_STATES, STATES_ORDERING, State, Task, token_default from arkindex.process.builder import ProcessBuilder from arkindex.process.managers import ( ActivityManager, @@ -342,18 +342,6 @@ class Process(IndexableModel): if run.version.is_init_elements(): continue - if new_process.mode == ProcessMode.Template or new_process.corpus.applied_maximum_task_ttl == 0: - # When the destination process is a template, we do not apply any limits and copy the original TTL. - # The limits will be applied only when applying a template. - # With other modes, when the corpus has no limits, we also just use the original TTL. - ttl = run.ttl - elif run.ttl == 0: - # The original TTL was infinite and there is a limit, so we use the corpus' limit - ttl = new_process.corpus.applied_maximum_task_ttl - else: - # Apply the limit normally when no infinity is involved - ttl = min(run.ttl, new_process.corpus.applied_maximum_task_ttl) - # Create a new WorkerRun with same version, configuration and parents. new_run = WorkerRun( process=new_process, @@ -369,7 +357,6 @@ class Process(IndexableModel): else run.version.gpu_usage == FeatureUsage.Required ), summary=run.summary, - ttl=ttl, ) # Save the correspondence between this process' worker_run and the new one new_runs[run.id] = new_run @@ -736,6 +723,7 @@ class ArkindexFeature(Enum): InitElements = "init_elements" FileImport = "file_import" S3Ingest = "s3_ingest" + DatasetExtractor = "dataset_extractor" # When adding a new export worker, don't forget to also update the ExportFormat enum and the # FEATURE_FORMAT_MAP dictionary which maps export formats to arkindex features ExportPDF = "pdf_export" @@ -938,10 +926,6 @@ class WorkerRun(models.Model): updated = models.DateTimeField(auto_now=True) has_results = models.BooleanField(default=False) use_gpu = models.BooleanField(default=False) - ttl = models.PositiveIntegerField( - verbose_name="TTL", - help_text="Maximum time-to-live for tasks created from this WorkerRun, in seconds. 0 means infinite.", - ) objects = WorkerRunManager() @@ -1028,7 +1012,7 @@ class WorkerRun(models.Model): ) task_env = env.copy() - token = task_token_default() + token = token_default() task_env["ARKINDEX_TASK_TOKEN"] = token task_env["TASK_ELEMENTS"] = elements_path task_env["ARKINDEX_WORKER_RUN_ID"] = str(self.id) @@ -1057,7 +1041,7 @@ class WorkerRun(models.Model): worker_run=self, extra_files=extra_files, requires_gpu=requires_gpu, - ttl=self.ttl, + ttl=process.corpus.applied_maximum_task_ttl, ) return task, parents diff --git a/arkindex/process/serializers/imports.py b/arkindex/process/serializers/imports.py index 71bdbf729fdeb74b28596134658d7a946fcdce3c..9a345d520c403ca3be27bf5a43a04c8f591242cb 100644 --- a/arkindex/process/serializers/imports.py +++ b/arkindex/process/serializers/imports.py @@ -612,7 +612,6 @@ class ExportProcessSerializer(ProcessDetailsSerializer): export_process.worker_runs.create( version=worker_version, configuration=worker_configuration, - ttl=corpus.applied_maximum_task_ttl, ) # Start the export process export_process.run() diff --git a/arkindex/process/serializers/worker_runs.py b/arkindex/process/serializers/worker_runs.py index eb6ab16138af0062854135348292a6422ea330b9..9f6146db0405ff4537e4e34da6f0ee2bb9a8fac0 100644 --- a/arkindex/process/serializers/worker_runs.py +++ b/arkindex/process/serializers/worker_runs.py @@ -1,7 +1,6 @@ from collections import defaultdict from textwrap import dedent -from django.core.validators import MaxValueValidator, MinValueValidator from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -20,25 +19,6 @@ from arkindex.process.serializers.workers import WorkerConfigurationSerializer, from arkindex.training.models import ModelVersion, ModelVersionState from arkindex.training.serializers import ModelVersionLightSerializer -# To prevent each element worker to retrieve contextual information -# (process, worker version, model version…) with extra GET requests, we -# do serialize all the related information on WorkerRun serializers. - -def _ttl_from_corpus(serializer_field) -> int: - if isinstance(serializer_field.parent.instance, WorkerRun): - process = serializer_field.parent.instance.process - else: - process = serializer_field.context["process"] - - # This function may be called on a local process, which does not have a corpus, even if the API blocks them later on - if process.mode == ProcessMode.Local: - return 0 - - return process.corpus.applied_maximum_task_ttl - - -_ttl_from_corpus.requires_context = True - class WorkerRunSerializer(serializers.ModelSerializer): @@ -96,15 +76,6 @@ class WorkerRunSerializer(serializers.ModelSerializer): "Only a configuration of the WorkerVersion's worker may be set.", ) - ttl = serializers.IntegerField( - default=_ttl_from_corpus, - help_text=dedent(""" - Maximum time-to-live for tasks created from this WorkerRun, in seconds. `0` means infinite. - - Defaults to, and cannot exceed, the `maximum_task_ttl` on the corpus of the process. - """), - ) - process = ProcessLightSerializer(read_only=True) class Meta: @@ -121,7 +92,6 @@ class WorkerRunSerializer(serializers.ModelSerializer): "model_version", "summary", "use_gpu", - "ttl", ) read_only_fields = ( "id", @@ -145,24 +115,6 @@ class WorkerRunSerializer(serializers.ModelSerializer): return self.instance.process return self.context["process"] - def validate_ttl(self, value) -> int: - if self._process.mode == ProcessMode.Local: - # Don't validate anything, the endpoint will not work on local processes anyway - return value - - corpus_ttl = self._process.corpus.applied_maximum_task_ttl - if corpus_ttl == 0: - # Allow infinity, and limit to the maximum value of an integer field - min_ttl, max_ttl = 0, 2147483647 - else: - # Restrict the maximum TTL further using the limit - min_ttl, max_ttl = 1, corpus_ttl - - MinValueValidator(min_ttl)(value) - MaxValueValidator(max_ttl)(value) - - return value - def validate(self, data): data = super().validate(data) errors = defaultdict(list) @@ -296,8 +248,6 @@ class UserWorkerRunSerializer(serializers.ModelSerializer): queryset=WorkerConfiguration.objects.all(), style={"base_template": "input.html"}, ) - # Default value for the TTL, as the local process does not have a corpus and the run will never actually run - ttl = serializers.HiddenField(default=0) def validate_worker_version_id(self, worker_version_id): # Check that the worker version exists @@ -369,7 +319,6 @@ class UserWorkerRunSerializer(serializers.ModelSerializer): "worker_version_id", "model_version_id", "configuration_id", - "ttl", ) diff --git a/arkindex/process/tests/commands/test_fake_worker_run.py b/arkindex/process/tests/commands/test_fake_worker_run.py index 97055247eafd7d759f77c860d379d89ed153e55e..68020a16c97ce9b24f4972cfc5a5d97520a9a0a8 100644 --- a/arkindex/process/tests/commands/test_fake_worker_run.py +++ b/arkindex/process/tests/commands/test_fake_worker_run.py @@ -64,7 +64,7 @@ class TestFakeWorkerRun(FixtureTestCase): def test_existing_worker_run(self): process = Process.objects.get(mode=ProcessMode.Local, creator=self.user) - worker_run = process.worker_runs.create(version=self.worker_version, ttl=0) + worker_run = process.worker_runs.create(version=self.worker_version) self.assertEqual(process.worker_runs.count(), 2) output = self.fake_worker_run(["--user", str(self.user.id), "--worker-version", str(self.worker_version.id)]) diff --git a/arkindex/process/tests/process/test_clear.py b/arkindex/process/tests/process/test_clear.py index 0593a54ce403a38d4fda7f0e1e42e70d112e9907..83ad163bc0ab0752b9c2591a4364038c29189442 100644 --- a/arkindex/process/tests/process/test_clear.py +++ b/arkindex/process/tests/process/test_clear.py @@ -22,11 +22,9 @@ class TestProcessClear(FixtureAPITestCase): ) cls.process.worker_runs.create( version=WorkerVersion.objects.get(worker__slug="reco"), - ttl=0, ) cls.process.worker_runs.create( version=WorkerVersion.objects.get(worker__slug="dla"), - ttl=0, ) def test_clear(self): diff --git a/arkindex/process/tests/process/test_create.py b/arkindex/process/tests/process/test_create.py index 3f1d6aaec3e07f6f23e8d5c263d69f6340a96984..17278e26e37618d8e2523b8fd4b242ec853fbbd8 100644 --- a/arkindex/process/tests/process/test_create.py +++ b/arkindex/process/tests/process/test_create.py @@ -33,6 +33,7 @@ class TestCreateProcess(FixtureAPITestCase): super().setUpTestData() cls.agent = Agent.objects.create( farm=Farm.objects.first(), + fingerprint="a" * 64, hostname="claude", cpu_cores=42, cpu_frequency=1e15, @@ -488,17 +489,14 @@ class TestCreateProcess(FixtureAPITestCase): ) init_run = process_2.worker_runs.create( version=self.init_elements_version, - ttl=0, ) run_1 = process_2.worker_runs.create( version=self.version_1, parents=[init_run.id], - ttl=0, ) run_2 = process_2.worker_runs.create( version=self.version_2, parents=[run_1.id], - ttl=0, ) self.assertFalse(process_2.tasks.exists()) @@ -589,7 +587,7 @@ class TestCreateProcess(FixtureAPITestCase): dataset = self.corpus.datasets.first() test_set = dataset.sets.get(name="test") ProcessDatasetSet.objects.create(process=process, set=test_set) - process.worker_runs.create(version=self.version_1, ttl=0) + process.worker_runs.create(version=self.version_1) with self.assertNumQueries(9): response = self.client.post(reverse("api:process-start", kwargs={"pk": str(process.id)})) @@ -605,7 +603,7 @@ class TestCreateProcess(FixtureAPITestCase): self.worker_1.save() process = self.corpus.processes.create(creator=self.user, mode=ProcessMode.Workers) - process.worker_runs.create(version=self.version_1, ttl=0) + process.worker_runs.create(version=self.version_1) with self.assertNumQueries(9): response = self.client.post(reverse("api:process-start", kwargs={"pk": str(process.id)})) diff --git a/arkindex/process/tests/process/test_default_process_name.py b/arkindex/process/tests/process/test_default_process_name.py index e496e7ff00af52728120e7d4e9876b8081ce70ca..00223e3e596084c99fc80c5aef295c316aa60b00 100644 --- a/arkindex/process/tests/process/test_default_process_name.py +++ b/arkindex/process/tests/process/test_default_process_name.py @@ -50,22 +50,18 @@ class TestProcessName(FixtureAPITestCase): init_elements_version = WorkerVersion.objects.get_by_feature(ArkindexFeature.InitElements) init_run = self.workers_process.worker_runs.create( version=init_elements_version, - ttl=0, ) self.workers_process.worker_runs.create( version=self.recognizer, parents=[init_run.id], - ttl=0, ) dla_run = self.workers_process.worker_runs.create( version=self.dla, parents=[init_run.id], - ttl=0, ) self.workers_process.worker_runs.create( version=self.version_gpu, parents=[dla_run.id], - ttl=0, ) self.workers_process.save() @@ -95,41 +91,34 @@ class TestProcessName(FixtureAPITestCase): init_elements_version = WorkerVersion.objects.get_by_feature(ArkindexFeature.InitElements) init_run = self.workers_process.worker_runs.create( version=init_elements_version, - ttl=0, ) reco_run_1 = self.workers_process.worker_runs.create( version=self.recognizer, parents=[init_run.id], - ttl=0, ) reco_run_2 = self.workers_process.worker_runs.create( version=self.recognizer, configuration=self.reco_config_1, parents=[reco_run_1.id], - ttl=0, ) self.workers_process.worker_runs.create( version=self.recognizer, configuration=self.reco_config_2, parents=[reco_run_2.id], - ttl=0, ) dla_run_1 = self.workers_process.worker_runs.create( version=self.dla, parents=[init_run.id], - ttl=0, ) reco_run_4 = self.workers_process.worker_runs.create( version=self.recognizer, configuration=self.reco_config_3, parents=[dla_run_1.id], - ttl=0, ) self.workers_process.worker_runs.create( version=self.dla, configuration=self.dla_config, parents=[reco_run_4.id], - ttl=0, ) self.workers_process.save() @@ -150,26 +139,23 @@ class TestProcessName(FixtureAPITestCase): If the default process worker name is too long (len() > 250) it gets truncated """ init_elements_version = WorkerVersion.objects.get_by_feature(ArkindexFeature.InitElements) - init_run = self.workers_process.worker_runs.create(version=init_elements_version, ttl=0) + init_run = self.workers_process.worker_runs.create(version=init_elements_version) # Update the recognizer worker's name so that it is long self.recognizer.worker.name = "animula vagula blandula hospes comesque corporis quae nunc abibis in loca pallidula rigida nudula ne" self.recognizer.worker.save() reco_run_1 = self.workers_process.worker_runs.create( version=self.recognizer, parents=[init_run.id], - ttl=0, ) reco_run_2 = self.workers_process.worker_runs.create( version=self.recognizer, configuration=self.reco_config_1, parents=[reco_run_1.id], - ttl=0, ) self.workers_process.worker_runs.create( version=self.recognizer, configuration=self.reco_config_2, parents=[reco_run_2.id], - ttl=0, ) self.workers_process.save() @@ -191,8 +177,8 @@ class TestProcessName(FixtureAPITestCase): self.workers_process.name = "My process" self.workers_process.save() init_elements_version = WorkerVersion.objects.get_by_feature(ArkindexFeature.InitElements) - init_run = self.workers_process.worker_runs.create(version=init_elements_version, ttl=0) - self.workers_process.worker_runs.create(version=self.recognizer, parents=[init_run.id], ttl=0) + init_run = self.workers_process.worker_runs.create(version=init_elements_version) + self.workers_process.worker_runs.create(version=self.recognizer, parents=[init_run.id]) self.assertEqual(self.workers_process.name, "My process") builder = ProcessBuilder(process=self.workers_process) diff --git a/arkindex/process/tests/process/test_destroy.py b/arkindex/process/tests/process/test_destroy.py index b87856e50cc290f43f37fc638862e8f1f1c2c4d8..4bfe2959296185ddf5e47a2a39138523551ea2fb 100644 --- a/arkindex/process/tests/process/test_destroy.py +++ b/arkindex/process/tests/process/test_destroy.py @@ -111,7 +111,7 @@ class TestProcessDestroy(FixtureAPITestCase): A process with worker runs linked to data cannot be deleted """ self.client.force_login(self.user) - run = self.process.worker_runs.create(version=WorkerVersion.objects.first(), ttl=0) + run = self.process.worker_runs.create(version=WorkerVersion.objects.first()) page = self.corpus.elements.get(name="Volume 1, page 1r") metadata = page.metadatas.get() @@ -148,7 +148,7 @@ class TestProcessDestroy(FixtureAPITestCase): """ self.client.force_login(self.user) - with self.assertNumQueries(19): + with self.assertNumQueries(20): response = self.client.delete(reverse("api:process-details", kwargs={"pk": self.process.id})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -161,7 +161,7 @@ class TestProcessDestroy(FixtureAPITestCase): """ self.client.force_login(self.superuser) - with self.assertNumQueries(19): + with self.assertNumQueries(20): response = self.client.delete(reverse("api:process-details", kwargs={"pk": self.process.id})) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -211,7 +211,7 @@ class TestProcessDestroy(FixtureAPITestCase): state=WorkerActivityState.Processed, ) - with self.assertNumQueries(20): + with self.assertNumQueries(21): response = self.client.delete(reverse("api:process-details", kwargs={"pk": self.process.id})) self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) diff --git a/arkindex/process/tests/process/test_process_artifact_download.py b/arkindex/process/tests/process/test_process_artifact_download.py index 004e375f67c2128ae705e54e769c31ac1c7bedf6..394000cdb1449ef247a1270149824d02524cfeee 100644 --- a/arkindex/process/tests/process/test_process_artifact_download.py +++ b/arkindex/process/tests/process/test_process_artifact_download.py @@ -21,7 +21,7 @@ class TestProcessArtifactDownload(FixtureAPITestCase): super().setUpTestData() cls.pdf_export_version = WorkerVersion.objects.get_by_feature(ArkindexFeature.ExportPDF) cls.process = Process.objects.create(mode=ProcessMode.Export, creator=cls.user, corpus=cls.corpus) - cls.worker_run = cls.process.worker_runs.create(version=cls.pdf_export_version, ttl=0) + cls.worker_run = cls.process.worker_runs.create(version=cls.pdf_export_version) with patch("arkindex.process.tasks.initialize_activity.delay"): cls.process.run() cls.task = cls.process.tasks.get() diff --git a/arkindex/process/tests/process/test_retry.py b/arkindex/process/tests/process/test_retry.py index 9407e2e4c5b3dc82c5a697c9799d7742a0eb01c8..547868fcba410dd103368ab5df70f58f9ad9cd68 100644 --- a/arkindex/process/tests/process/test_retry.py +++ b/arkindex/process/tests/process/test_retry.py @@ -156,7 +156,6 @@ class TestProcessRetry(FixtureAPITestCase): process.worker_runs.create( version=self.recognizer, model_version=self.model_version, - ttl=0, ) with patch("arkindex.process.tasks.initialize_activity.delay"): process.run() @@ -209,7 +208,7 @@ class TestProcessRetry(FixtureAPITestCase): worker_type, _ = WorkerType.objects.get_or_create(slug=f"type_{slug}", display_name=slug.capitalize()) worker, _ = Worker.objects.get_or_create(slug=slug, defaults={"type": worker_type, "repository_url": "fake"}) version, _ = worker.versions.get_or_create(version=1, defaults={"state": WorkerVersionState.Available, "docker_image_iid": "test"}) - return version.worker_runs.create(process=process, ttl=0) + return version.worker_runs.create(process=process) init_version = WorkerVersion.objects.get_by_feature(feature=ArkindexFeature.InitElements) @@ -263,7 +262,7 @@ class TestProcessRetry(FixtureAPITestCase): mode=ProcessMode.Files, creator=self.user, ) - process.worker_runs.create(version=self.recognizer, ttl=0) + process.worker_runs.create(version=self.recognizer) process.tasks.create(state=State.Error, run=0, depth=0, ttl=0) self.assertEqual(process.state, State.Error) process.finished = timezone.now() @@ -305,7 +304,6 @@ class TestProcessRetry(FixtureAPITestCase): "iiif_base_url": self.imgsrv.url, }, ), - ttl=0, ) process.tasks.create(state=State.Error, run=0, depth=0, ttl=0) self.assertEqual(process.state, State.Error) @@ -350,7 +348,7 @@ class TestProcessRetry(FixtureAPITestCase): pdf_export_version = WorkerVersion.objects.get_by_feature(ArkindexFeature.ExportPDF) self.client.force_login(self.user) process = self.corpus.processes.create(mode=ProcessMode.Export, creator=self.user) - process.worker_runs.create(version=pdf_export_version, ttl=0) + process.worker_runs.create(version=pdf_export_version) process.tasks.create(state=State.Error, run=0, depth=0, ttl=0) self.assertEqual(process.state, State.Error) process.finished = timezone.now() diff --git a/arkindex/process/tests/process/test_run.py b/arkindex/process/tests/process/test_run.py index ef0ba31c9f3e15ecff7b2b368348f0056edbcc3b..99d606d8642dce04d3a71372ced67ce9c8341828 100644 --- a/arkindex/process/tests/process/test_run.py +++ b/arkindex/process/tests/process/test_run.py @@ -25,7 +25,7 @@ class TestProcessRun(FixtureTestCase): ) @override_settings(PONOS_DEFAULT_ENV={"ARKINDEX_API_TOKEN": "testToken"}) - @patch("arkindex.process.builder.task_token_default") + @patch("arkindex.process.builder.token_default") def test_pdf_import_run(self, token_mock): process = self.corpus.processes.create( creator=self.user, @@ -65,8 +65,8 @@ class TestProcessRun(FixtureTestCase): mode=ProcessMode.Workers, ) token_mock.side_effect = [b"12345", b"78945"] - init_run = process.worker_runs.create(version=self.init_worker_version, ttl=0) - run = process.worker_runs.create(version=self.version_with_model, parents=[init_run.id], ttl=0) + init_run = process.worker_runs.create(version=self.init_worker_version) + run = process.worker_runs.create(version=self.version_with_model, parents=[init_run.id]) run.model_version = self.model_version run.save() with patch("arkindex.process.tasks.initialize_activity.delay"): diff --git a/arkindex/process/tests/process/test_start.py b/arkindex/process/tests/process/test_start.py index 4d96cadcbbe3cd43171bbd93a8d6809c70409f50..32d9e5b2dd33ef773caffffd24d6342102122027 100644 --- a/arkindex/process/tests/process/test_start.py +++ b/arkindex/process/tests/process/test_start.py @@ -117,7 +117,7 @@ class TestProcessStart(FixtureAPITestCase): @override_settings(PUBLIC_HOSTNAME="https://darkindex.lol") def test_without_required_model(self): - self.workers_process.worker_runs.create(version=self.version_with_model, ttl=0) + self.workers_process.worker_runs.create(version=self.version_with_model) self.client.force_login(self.user) @@ -135,7 +135,7 @@ class TestProcessStart(FixtureAPITestCase): @override_settings(PUBLIC_HOSTNAME="https://arkindex.localhost") @patch("arkindex.project.triggers.process_tasks.initialize_activity.delay") def test_with_required_model(self, activities_delay_mock): - self.workers_process.worker_runs.create(version=self.version_with_model, model_version=self.model_version, ttl=0) + self.workers_process.worker_runs.create(version=self.version_with_model, model_version=self.model_version) self.assertFalse(self.workers_process.tasks.exists()) self.client.force_login(self.user) @@ -168,7 +168,7 @@ class TestProcessStart(FixtureAPITestCase): ) def test_unavailable_worker_version(self): - self.workers_process.worker_runs.create(version=self.recognizer, ttl=0) + self.workers_process.worker_runs.create(version=self.recognizer) self.recognizer.state = WorkerVersionState.Error self.recognizer.save() self.assertFalse(self.workers_process.tasks.exists()) @@ -185,7 +185,7 @@ class TestProcessStart(FixtureAPITestCase): ) def test_unavailable_model_version(self): - self.workers_process.worker_runs.create(version=self.recognizer, model_version=self.model_version, ttl=0) + self.workers_process.worker_runs.create(version=self.recognizer, model_version=self.model_version) self.model_version.state = ModelVersionState.Error self.model_version.save() self.assertFalse(self.workers_process.tasks.exists()) @@ -202,7 +202,7 @@ class TestProcessStart(FixtureAPITestCase): ) def test_archived_models(self): - self.workers_process.worker_runs.create(version=self.recognizer, model_version=self.model_version, ttl=0) + self.workers_process.worker_runs.create(version=self.recognizer, model_version=self.model_version) self.model.archived = timezone.now() self.model.save() self.assertFalse(self.workers_process.tasks.exists()) @@ -242,7 +242,6 @@ class TestProcessStart(FixtureAPITestCase): version=self.recognizer, configuration=None, model_version=None, - ttl=0, ) # The other version is used with a configuration missing the required field self.workers_process.worker_runs.create( @@ -254,7 +253,6 @@ class TestProcessStart(FixtureAPITestCase): }, ), model_version=None, - ttl=0, ) self.client.force_login(self.user) @@ -278,8 +276,8 @@ class TestProcessStart(FixtureAPITestCase): Default chunks, thumbnails and farm are used. Cache is disabled, and worker activities are enabled. """ init_elements_version = WorkerVersion.objects.get_by_feature(ArkindexFeature.InitElements) - init_run = self.workers_process.worker_runs.create(version=init_elements_version, ttl=0) - run = self.workers_process.worker_runs.create(version=self.recognizer, parents=[init_run.id], ttl=0) + init_run = self.workers_process.worker_runs.create(version=init_elements_version) + run = self.workers_process.worker_runs.create(version=self.recognizer, parents=[init_run.id]) self.assertFalse(self.workers_process.tasks.exists()) self.client.force_login(self.user) @@ -311,13 +309,13 @@ class TestProcessStart(FixtureAPITestCase): def test_inconsistent_gpu_usages(self): # The version's gpu_usage is Disabled, so the run's use_gpu is set to False - self.workers_process.worker_runs.create(version=self.recognizer, ttl=0) + self.workers_process.worker_runs.create(version=self.recognizer) self.recognizer.gpu_usage = FeatureUsage.Required self.recognizer.save() self.dla.gpu_usage = FeatureUsage.Required self.dla.save() # The version's gpu_usage is Required, so the run's use_gpu is set to True - self.workers_process.worker_runs.create(version=self.dla, ttl=0) + self.workers_process.worker_runs.create(version=self.dla) self.dla.gpu_usage = FeatureUsage.Disabled self.dla.save() self.assertFalse(self.workers_process.tasks.exists()) @@ -338,7 +336,7 @@ class TestProcessStart(FixtureAPITestCase): ) def test_dataset_requires_datasets(self): - self.dataset_process.worker_runs.create(version=self.recognizer, ttl=0) + self.dataset_process.worker_runs.create(version=self.recognizer) self.assertFalse(self.dataset_process.tasks.exists()) self.client.force_login(self.user) @@ -355,7 +353,7 @@ class TestProcessStart(FixtureAPITestCase): def test_dataset_requires_dataset_in_same_corpus(self): test_set = self.other_dataset.sets.get(name="test") ProcessDatasetSet.objects.create(process=self.dataset_process, set=test_set) - self.dataset_process.worker_runs.create(version=self.recognizer, ttl=0) + self.dataset_process.worker_runs.create(version=self.recognizer) self.assertFalse(self.dataset_process.tasks.exists()) self.client.force_login(self.user) @@ -375,7 +373,7 @@ class TestProcessStart(FixtureAPITestCase): test_set_2 = self.dataset2.sets.get(name="test") ProcessDatasetSet.objects.create(process=self.dataset_process, set=test_set_1) ProcessDatasetSet.objects.create(process=self.dataset_process, set=test_set_2) - self.dataset_process.worker_runs.create(version=self.recognizer, ttl=0) + self.dataset_process.worker_runs.create(version=self.recognizer) self.client.force_login(self.user) @@ -400,7 +398,7 @@ class TestProcessStart(FixtureAPITestCase): test_set_2 = self.other_dataset.sets.get(name="test") ProcessDatasetSet.objects.create(process=self.dataset_process, set=test_set_1) ProcessDatasetSet.objects.create(process=self.dataset_process, set=test_set_2) - run = self.dataset_process.worker_runs.create(version=self.recognizer, ttl=0) + run = self.dataset_process.worker_runs.create(version=self.recognizer) self.assertFalse(self.dataset_process.tasks.exists()) self.client.force_login(self.user) @@ -434,7 +432,7 @@ class TestProcessStart(FixtureAPITestCase): self.recognizer.save() self.assertEqual(self.recognizer.state, WorkerVersionState.Available) - run = self.workers_process.worker_runs.create(version=self.recognizer, ttl=0) + run = self.workers_process.worker_runs.create(version=self.recognizer) self.assertFalse(self.workers_process.tasks.exists()) self.client.force_login(self.user) @@ -463,7 +461,7 @@ class TestProcessStart(FixtureAPITestCase): """ A user can specify a ponos farm to use for a process """ - self.workers_process.worker_runs.create(version=self.recognizer, ttl=0) + self.workers_process.worker_runs.create(version=self.recognizer) farm = Farm.objects.get(name="Wheat farm") self.client.force_login(self.user) @@ -491,7 +489,7 @@ class TestProcessStart(FixtureAPITestCase): farm = Farm.objects.get(name="Wheat farm") get_default_farm_mock.return_value = farm - self.workers_process.worker_runs.create(version=self.recognizer, ttl=0) + self.workers_process.worker_runs.create(version=self.recognizer) self.assertFalse(self.workers_process.tasks.exists()) self.client.force_login(self.user) @@ -518,7 +516,7 @@ class TestProcessStart(FixtureAPITestCase): @patch("arkindex.process.serializers.imports.get_default_farm") def test_default_farm_guest(self, get_default_farm_mock, is_available_mock): get_default_farm_mock.return_value = Farm.objects.first() - self.workers_process.worker_runs.create(version=self.recognizer, ttl=0) + self.workers_process.worker_runs.create(version=self.recognizer) self.client.force_login(self.user) with self.assertNumQueries(5): @@ -539,7 +537,7 @@ class TestProcessStart(FixtureAPITestCase): @patch("arkindex.ponos.models.Farm.is_available", return_value=False) def test_farm_guest(self, is_available_mock): - self.workers_process.worker_runs.create(version=self.recognizer, ttl=0) + self.workers_process.worker_runs.create(version=self.recognizer) self.client.force_login(self.user) with self.assertNumQueries(7): @@ -592,7 +590,7 @@ class TestProcessStart(FixtureAPITestCase): """ StartProcess should restrict the chunks to `settings.MAX_CHUNKS` """ - self.workers_process.worker_runs.create(version=self.recognizer, ttl=0) + self.workers_process.worker_runs.create(version=self.recognizer) self.client.force_login(self.user) with self.assertNumQueries(5): @@ -611,7 +609,7 @@ class TestProcessStart(FixtureAPITestCase): """ It should be possible to pass chunks parameters when starting a workers process """ - run = self.workers_process.worker_runs.create(version=self.recognizer, ttl=0) + run = self.workers_process.worker_runs.create(version=self.recognizer) self.client.force_login(self.user) with self.assertNumQueries(18): @@ -644,7 +642,7 @@ class TestProcessStart(FixtureAPITestCase): test_set_2 = self.dataset2.sets.get(name="test") ProcessDatasetSet.objects.create(process=self.dataset_process, set=test_set_1) ProcessDatasetSet.objects.create(process=self.dataset_process, set=test_set_2) - run = self.dataset_process.worker_runs.create(version=self.recognizer, ttl=0) + run = self.dataset_process.worker_runs.create(version=self.recognizer) self.client.force_login(self.user) with self.assertNumQueries(12): @@ -690,7 +688,7 @@ class TestProcessStart(FixtureAPITestCase): """ self.assertFalse(self.workers_process.use_cache) self.assertEqual(self.workers_process.activity_state, ActivityState.Disabled) - self.workers_process.worker_runs.create(version=self.version_gpu, ttl=0) + self.workers_process.worker_runs.create(version=self.version_gpu) self.client.force_login(self.user) @@ -723,12 +721,10 @@ class TestProcessStart(FixtureAPITestCase): name="some_config", configuration={"a": "b"}, ), - ttl=0, ) run_2 = self.workers_process.worker_runs.create( version=self.recognizer, parents=[run_1.id], - ttl=0, ) self.assertNotEqual(run_1.task_slug, run_2.task_slug) diff --git a/arkindex/process/tests/templates/test_apply.py b/arkindex/process/tests/templates/test_apply.py index 0f59330309ab6bb8b803884d30bfbd1e85225b3a..43a4a9893777d8b2e1b19f769f4403a8b88f0969 100644 --- a/arkindex/process/tests/templates/test_apply.py +++ b/arkindex/process/tests/templates/test_apply.py @@ -1,7 +1,6 @@ from datetime import datetime, timezone from unittest.mock import call, patch -from django.test import override_settings from rest_framework import status from rest_framework.reverse import reverse @@ -19,7 +18,6 @@ from arkindex.training.models import Model, ModelVersionState from arkindex.users.models import Role -@override_settings(PONOS_MAXIMUM_TASK_TTL=3600) class TestApplyTemplate(FixtureAPITestCase): @classmethod @@ -59,19 +57,16 @@ class TestApplyTemplate(FixtureAPITestCase): cls.template_run_1 = cls.template.worker_runs.create( version=cls.version_1, configuration=cls.worker_configuration, - ttl=7200, ) cls.template_run_2 = cls.template.worker_runs.create( version=cls.version_2, parents=[cls.template_run_1.id], model_version=cls.model_version, - ttl=0, ) cls.private_template.worker_runs.create( version=cls.version_1, configuration=cls.worker_configuration, - ttl=0, ) @patch("arkindex.project.mixins.get_max_level", return_value=Role.Guest.value) @@ -170,7 +165,6 @@ class TestApplyTemplate(FixtureAPITestCase): def test_apply(self): self.assertIsNotNone(self.version_2.docker_image_iid) - self.assertIsNone(self.corpus.maximum_task_ttl) self.client.force_login(self.user) with self.assertNumQueries(10): response = self.client.post( @@ -190,52 +184,16 @@ class TestApplyTemplate(FixtureAPITestCase): self.assertIsNone(parent_run.model_version_id) self.assertEqual(parent_run.configuration_id, self.worker_configuration.id) self.assertListEqual(parent_run.parents, []) - # This had a 7200 seconds TTL, but is limited by the instance limit - self.assertEqual(parent_run.ttl, 3600) self.assertEqual(child_run.process_id, self.process.id) self.assertEqual(child_run.version_id, self.version_2.id) self.assertEqual(child_run.model_version_id, self.model_version.id) self.assertIsNone(child_run.configuration_id) self.assertListEqual(child_run.parents, [parent_run.id]) - # This had an infinite TTL, but is limited by the instance limit - self.assertEqual(child_run.ttl, 3600) - - def test_unlimited_ttl(self): - self.corpus.maximum_task_ttl = 0 - self.corpus.save() - self.client.force_login(self.user) - - with self.assertNumQueries(10): - response = self.client.post( - reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), - data={"process_id": str(self.process.id)}, - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - child_run, parent_run = self.process.worker_runs.order_by("version__worker__slug") - self.assertEqual(parent_run.ttl, 7200) - self.assertEqual(child_run.ttl, 0) - - def test_corpus_limited_ttl(self): - self.corpus.maximum_task_ttl = 9000 - self.corpus.save() - self.client.force_login(self.user) - - with self.assertNumQueries(10): - response = self.client.post( - reverse("api:apply-process-template", kwargs={"pk": str(self.template.id)}), - data={"process_id": str(self.process.id)}, - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - child_run, parent_run = self.process.worker_runs.order_by("version__worker__slug") - self.assertEqual(parent_run.ttl, 7200) - self.assertEqual(child_run.ttl, 9000) def test_excludes_init_elements(self): init_version = WorkerVersion.objects.get_by_feature(ArkindexFeature.InitElements) - init_run = self.template.worker_runs.create(version=init_version, ttl=0) + init_run = self.template.worker_runs.create(version=init_version) self.template_run_1.parents = [init_run.id] self.template_run_1.save() @@ -281,10 +239,7 @@ class TestApplyTemplate(FixtureAPITestCase): process = self.corpus.processes.create( creator=self.user, mode=ProcessMode.Workers ) - process.worker_runs.create( - version=self.version_2, - ttl=0, - ) + process.worker_runs.create(version=self.version_2) # Apply a template that has two other worker runs with self.assertNumQueries(12): response = self.client.post( @@ -318,13 +273,13 @@ class TestApplyTemplate(FixtureAPITestCase): # Set invalid values: the version with disabled GPU usage gets a GPU self.template.worker_runs.filter(version=self.version_1).update(use_gpu=True) # A signal is trying to set use_gpu to the correct values, so we create then update to give no GPU to a version that requires a GPU - self.template.worker_runs.create(version=self.version_3, ttl=0) + self.template.worker_runs.create(version=self.version_3) self.template.worker_runs.filter(version=self.version_3).update(use_gpu=False) # Have two runs with a version that supports GPU usage, to test that both True and False are copied self.version_2.gpu_usage = FeatureUsage.Supported self.version_2.save() - self.template.worker_runs.create(version=self.version_2, configuration=self.worker_configuration, use_gpu=True, ttl=0) + self.template.worker_runs.create(version=self.version_2, configuration=self.worker_configuration, use_gpu=True) self.assertQuerySetEqual(( self.template.worker_runs diff --git a/arkindex/process/tests/templates/test_create.py b/arkindex/process/tests/templates/test_create.py index 8f819de010092c3714c3e15cb3715a7b22fd9ca8..f0bc7787500b00e258f1b87c671220d859f886a8 100644 --- a/arkindex/process/tests/templates/test_create.py +++ b/arkindex/process/tests/templates/test_create.py @@ -1,6 +1,5 @@ from unittest.mock import call, patch -from django.test import override_settings from rest_framework import status from rest_framework.reverse import reverse @@ -19,7 +18,6 @@ from arkindex.training.models import Model, ModelVersionState from arkindex.users.models import Role, User -@override_settings(PONOS_MAXIMUM_TASK_TTL=3600) class TestCreateTemplate(FixtureAPITestCase): @classmethod @@ -61,12 +59,10 @@ class TestCreateTemplate(FixtureAPITestCase): cls.run_1 = cls.process_template.worker_runs.create( version=cls.version_1, configuration=cls.worker_configuration, - ttl=7200, ) cls.run_2 = cls.process_template.worker_runs.create( version=cls.version_2, parents=[cls.run_1.id], - ttl=0, ) cls.model = Model.objects.create(name="moo") @@ -75,24 +71,20 @@ class TestCreateTemplate(FixtureAPITestCase): cls.template_run_1 = cls.template.worker_runs.create( version=cls.version_1, configuration=cls.worker_configuration, - ttl=0, ) cls.template_run_2 = cls.template.worker_runs.create( version=cls.version_2, parents=[cls.template_run_1.id], model_version=cls.model_version, - ttl=0, ) cls.private_process_template.worker_runs.create( version=cls.version_1, configuration=cls.worker_configuration, - ttl=0, ) cls.private_template.worker_runs.create( version=cls.version_1, configuration=cls.worker_configuration, - ttl=0, ) def test_create(self): @@ -116,10 +108,10 @@ class TestCreateTemplate(FixtureAPITestCase): self.assertQuerySetEqual(( template_process.worker_runs .order_by("version__worker__slug") - .values_list("version_id", "model_version_id", "configuration_id", "parents", "ttl") + .values_list("version_id", "model_version_id", "configuration_id", "parents") ), [ - (self.version_2.id, None, None, [parent_run.id], 0), - (self.version_1.id, None, self.worker_configuration.id, [], 7200), + (self.version_2.id, None, None, [parent_run.id]), + (self.version_1.id, None, self.worker_configuration.id, []), ]) def test_use_gpu(self): @@ -128,13 +120,13 @@ class TestCreateTemplate(FixtureAPITestCase): # Set invalid values: the version with disabled GPU usage gets a GPU self.process_template.worker_runs.filter(version=self.version_1).update(use_gpu=True) # A signal is trying to set use_gpu to the correct values, so we create then update to give no GPU to a version that requires a GPU - self.process_template.worker_runs.create(version=self.version_3, ttl=0) + self.process_template.worker_runs.create(version=self.version_3) self.process_template.worker_runs.filter(version=self.version_3).update(use_gpu=False) # Have two runs with a version that supports GPU usage, to test that both True and False are copied self.version_2.gpu_usage = FeatureUsage.Supported self.version_2.save() - self.process_template.worker_runs.create(version=self.version_2, configuration=self.worker_configuration, use_gpu=True, ttl=0) + self.process_template.worker_runs.create(version=self.version_2, configuration=self.worker_configuration, use_gpu=True) self.assertQuerySetEqual(( self.process_template.worker_runs @@ -170,7 +162,7 @@ class TestCreateTemplate(FixtureAPITestCase): def test_excludes_init_elements(self): init_version = WorkerVersion.objects.get_by_feature(ArkindexFeature.InitElements) - init_run = self.process_template.worker_runs.create(version=init_version, ttl=0) + init_run = self.process_template.worker_runs.create(version=init_version) self.run_1.parents = [init_run.id] self.run_1.save() diff --git a/arkindex/process/tests/test_corpus_worker_runs.py b/arkindex/process/tests/test_corpus_worker_runs.py index 590156dcc7708d2e8580e5777c0590bd2733800b..8b9754004f5c970925041eb17baaf7b6521485b9 100644 --- a/arkindex/process/tests/test_corpus_worker_runs.py +++ b/arkindex/process/tests/test_corpus_worker_runs.py @@ -29,7 +29,6 @@ class TestCorpusWorkerRuns(FixtureAPITestCase): cls.run_1 = WorkerRun.objects.create( process=cls.process, version=cls.dla_worker_version, - ttl=0, has_results=True ) @@ -57,12 +56,10 @@ class TestCorpusWorkerRuns(FixtureAPITestCase): cls.run_2 = WorkerRun.objects.create( process=cls.private_process, version=cls.reco_worker_version, - ttl=0, ) cls.run_3 = WorkerRun.objects.create( process=cls.private_process, version=cls.dla_worker_version, - ttl=0, has_results=True ) @@ -144,7 +141,6 @@ class TestCorpusWorkerRuns(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 0, }, { "id": str(self.run_3.id), @@ -190,6 +186,5 @@ class TestCorpusWorkerRuns(FixtureAPITestCase): }, "use_gpu": False, "summary": "Worker Document layout analyser @ version 1", - "ttl": 0, } ]) diff --git a/arkindex/process/tests/test_elements_initialisation.py b/arkindex/process/tests/test_elements_initialisation.py index 0ec36078b5955b4561276a2b9a24efe37c4693bb..5deb50f40c13f3edd2bd2ae794d473d32869757c 100644 --- a/arkindex/process/tests/test_elements_initialisation.py +++ b/arkindex/process/tests/test_elements_initialisation.py @@ -44,12 +44,10 @@ class TestElementsInit(FixtureAPITestCase): ) init_run = process.worker_runs.create( version=self.init_elements_version, - ttl=0, ) worker_run = process.worker_runs.create( version=self.reco_version, parents=[init_run.id], - ttl=0, ) with self.assertNumQueries(16): @@ -86,8 +84,8 @@ class TestElementsInit(FixtureAPITestCase): then one is created when the process is started. """ self.client.force_login(self.user) - reco_run = self.process.worker_runs.create(version=self.reco_version, ttl=0) - dla_run = self.process.worker_runs.create(version=self.dla_version, parents=[reco_run.id], ttl=0) + reco_run = self.process.worker_runs.create(version=self.reco_version) + dla_run = self.process.worker_runs.create(version=self.dla_version, parents=[reco_run.id]) with self.assertNumQueries(18): response = self.client.post( diff --git a/arkindex/process/tests/test_managers.py b/arkindex/process/tests/test_managers.py index aa5c3c12f99c5bd64cfacfb6116494758403e7be..dc6b4a4c4fda61260f39d1dffd9fbfbe0fce6ec8 100644 --- a/arkindex/process/tests/test_managers.py +++ b/arkindex/process/tests/test_managers.py @@ -28,23 +28,19 @@ class TestManagers(FixtureTestCase): cls.worker_run_1 = cls.worker_version.worker_runs.create( process=cls.corpus.processes.create(mode=ProcessMode.Workers, creator=cls.user), - ttl=0, ) cls.worker_run_2 = cls.worker_version.worker_runs.create( process=cls.corpus.processes.create(mode=ProcessMode.Workers, creator=cls.user), configuration=cls.worker_configuration, - ttl=0, ) cls.worker_run_3 = cls.worker_version.worker_runs.create( process=cls.corpus.processes.create(mode=ProcessMode.Workers, creator=cls.user), model_version=cls.model_version, - ttl=0, ) cls.worker_run_4 = cls.worker_version.worker_runs.create( process=cls.corpus.processes.create(mode=ProcessMode.Workers, creator=cls.user), model_version=cls.model_version, configuration=cls.worker_configuration, - ttl=0, ) def test_corpus_worker_version_rebuild(self): @@ -105,5 +101,5 @@ class TestManagers(FixtureTestCase): Ensure the in_use method iterates over all related items """ worker_run_id = uuid4() - with self.assertExactQueries("worker_run_in_use.sql", params={"id": worker_run_id}): + with self.assertExactQueries("worker_run_in_use.sql", params={"id": worker_run_id.hex}): self.assertFalse(WorkerRun.objects.filter(id=worker_run_id).in_use()) diff --git a/arkindex/process/tests/test_process_elements.py b/arkindex/process/tests/test_process_elements.py index 0881b4fe1cf77cc96e507d7831c58e916329164d..e2c60dd54113cb978d8046fb450ca0587f855bb8 100644 --- a/arkindex/process/tests/test_process_elements.py +++ b/arkindex/process/tests/test_process_elements.py @@ -304,9 +304,9 @@ class TestProcessElements(FixtureAPITestCase): self.client.force_login(self.superuser) with self.assertExactQueries("process_elements_filter_type.sql", skip=1, params={ "user_id": self.superuser.id, - "process_id": str(self.process.id), - "corpus_id": str(self.private_corpus.id), - "type_id": str(self.folder_type.id), + "process_id": self.process.id.hex, + "corpus_id": self.private_corpus.id.hex, + "type_id": self.folder_type.id.hex, }): response = self.client.get(reverse("api:process-elements-list", kwargs={"pk": self.process.id})) @@ -333,9 +333,9 @@ class TestProcessElements(FixtureAPITestCase): self.client.force_login(self.superuser) with self.assertExactQueries("process_elements_filter_ml_class.sql", skip=1, params={ "user_id": self.superuser.id, - "process_id": str(self.process.id), - "corpus_id": str(self.private_corpus.id), - "ml_class_id": str(self.ml_class.id), + "process_id": self.process.id.hex, + "corpus_id": self.private_corpus.id.hex, + "ml_class_id": self.ml_class.id.hex, }): response = self.client.get(reverse("api:process-elements-list", kwargs={"pk": self.process.id})) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -719,8 +719,8 @@ class TestProcessElements(FixtureAPITestCase): self.process.save() with self.assertExactQueries("process_elements_with_image.sql", skip=1, params={ "user_id": self.superuser.id, - "process_id": str(self.process.id), - "corpus_id": str(self.private_corpus.id), + "process_id": self.process.id.hex, + "corpus_id": self.private_corpus.id.hex, }): response = self.client.get( reverse("api:process-elements-list", kwargs={"pk": self.process.id}), @@ -749,8 +749,8 @@ class TestProcessElements(FixtureAPITestCase): self.client.force_login(self.superuser) with self.assertExactQueries("process_elements_top_level.sql", skip=1, params={ "user_id": self.superuser.id, - "process_id": str(self.process.id), - "corpus_id": str(self.private_corpus.id), + "process_id": self.process.id.hex, + "corpus_id": self.private_corpus.id.hex, }): response = self.client.get( reverse("api:process-elements-list", kwargs={"pk": self.process.id}), diff --git a/arkindex/process/tests/test_signals.py b/arkindex/process/tests/test_signals.py index 1c6323c2a153924f0e80eb473fe9543e5b36b8b2..f1dc954aa854bb8c45e90ec5d8643bd2993d0b96 100644 --- a/arkindex/process/tests/test_signals.py +++ b/arkindex/process/tests/test_signals.py @@ -38,10 +38,7 @@ class TestSignals(FixtureAPITestCase): mode=ProcessMode.Workers, farm=cls.farm, ) - cls.run_1 = cls.process_1.worker_runs.create( - version=cls.version_1, - ttl=0, - ) + cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1) cls.process_2 = cls.corpus.processes.create( creator=cls.user, mode=ProcessMode.Workers, @@ -49,10 +46,7 @@ class TestSignals(FixtureAPITestCase): ) def test_worker_run_check_parents_recursive(self): - run_2 = self.process_1.worker_runs.create( - version=self.version_2, - ttl=0, - ) + run_2 = self.process_1.worker_runs.create(version=self.version_2) self.assertListEqual(run_2.parents, []) run_2.parents = [str(run_2.id)] @@ -68,7 +62,6 @@ class TestSignals(FixtureAPITestCase): self.process_2.worker_runs.create( version=self.version_2, parents=[str(self.run_1.id)], - ttl=0, ) self.process_1.refresh_from_db() @@ -80,7 +73,6 @@ class TestSignals(FixtureAPITestCase): self.process_2.worker_runs.create( version=self.version_2, parents=["12341234-1234-1234-1234-123412341234"], - ttl=0, ) self.process_1.refresh_from_db() @@ -133,7 +125,6 @@ class TestSignals(FixtureAPITestCase): run_2 = self.process_1.worker_runs.create( version=self.version_2, parents=[self.run_1.id], - ttl=0, ) self.run_1.parents = [run_2.id] @@ -175,22 +166,18 @@ class TestSignals(FixtureAPITestCase): run_2 = self.process_1.worker_runs.create( version=self.version_2, parents=[self.run_1.id], - ttl=0, ) run_3 = self.process_1.worker_runs.create( version=version_3, parents=[run_2.id], - ttl=0, ) run_4 = self.process_1.worker_runs.create( version=version_4, parents=[run_3.id], - ttl=0, ) run_5 = self.process_1.worker_runs.create( version=version_5, parents=[run_4.id], - ttl=0, ) self.run_1.parents = [run_5.id] @@ -211,12 +198,10 @@ class TestSignals(FixtureAPITestCase): run_2 = self.process_1.worker_runs.create( version=self.version_2, parents=[self.run_1.id], - ttl=0, ) run_3 = self.process_1.worker_runs.create( version=version_3, parents=[run_2.id], - ttl=0, ) run_3.parents.append(self.run_1.id) @@ -231,7 +216,6 @@ class TestSignals(FixtureAPITestCase): run_2 = self.process_1.worker_runs.create( version=self.version_2, parents=[self.run_1.id], - ttl=0, ) self.assertEqual(len(self.process_1.worker_runs.all()), 2) @@ -245,7 +229,6 @@ class TestSignals(FixtureAPITestCase): run = self.process_1.worker_runs.create( version=self.version_2, parents=[self.run_1.id], - ttl=0, ) self.assertIsNotNone(run.summary) diff --git a/arkindex/process/tests/test_user_workerruns.py b/arkindex/process/tests/test_user_workerruns.py index d98a8d5a33d32c9f80f557ec477730165006fbfc..4a515ef8c31e2ec8deb96d595c4df9bd7de69a27 100644 --- a/arkindex/process/tests/test_user_workerruns.py +++ b/arkindex/process/tests/test_user_workerruns.py @@ -1,7 +1,6 @@ from datetime import datetime, timezone from unittest.mock import call, patch -from django.test import override_settings from django.urls import reverse from rest_framework import status @@ -19,7 +18,6 @@ from arkindex.training.models import Model, ModelVersion, ModelVersionState from arkindex.users.models import Right, Role, User -@override_settings(PONOS_MAXIMUM_TASK_TTL=3600) class TestUserWorkerRuns(FixtureAPITestCase): @classmethod def setUpTestData(cls): @@ -67,7 +65,6 @@ class TestUserWorkerRuns(FixtureAPITestCase): test_local_run = WorkerRun.objects.create( process=self.local_process, version=self.version_1, - ttl=0, ) self.client.force_login(self.user) with self.assertNumQueries(5): @@ -118,7 +115,6 @@ class TestUserWorkerRuns(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 0, }, { "configuration": None, "id": str(self.local_run.id), @@ -163,7 +159,6 @@ class TestUserWorkerRuns(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 0, }]) def test_list_user_runs_only_own_runs(self): @@ -175,7 +170,6 @@ class TestUserWorkerRuns(FixtureAPITestCase): test_local_run = WorkerRun.objects.create( process=test_local_process, version=self.version_1, - ttl=0, ) assert WorkerRun.objects.filter(process__mode=ProcessMode.Local, process__creator=write_user).count() == 1 self.client.force_login(self.user) @@ -264,8 +258,6 @@ class TestUserWorkerRuns(FixtureAPITestCase): "use_cache": False, }, "use_gpu": False, - # The TTL is always 0 for user worker runs - "ttl": 0, }) def test_create_user_run_no_local_process(self): @@ -483,7 +475,6 @@ class TestUserWorkerRuns(FixtureAPITestCase): "use_cache": False, }, "use_gpu": False, - "ttl": 0, }) def test_create_user_run_duplicate(self): diff --git a/arkindex/process/tests/worker_activity/test_bulk_insert.py b/arkindex/process/tests/worker_activity/test_bulk_insert.py index 026292cd5476b6ed60b138c13dbf4dd8de848013..e51dd68498424bad9c79a695673659475a205840 100644 --- a/arkindex/process/tests/worker_activity/test_bulk_insert.py +++ b/arkindex/process/tests/worker_activity/test_bulk_insert.py @@ -40,7 +40,6 @@ class TestWorkerActivityBulkInsert(FixtureAPITestCase): version=cls.worker_version, configuration=cls.configuration, model_version=cls.model_version, - ttl=0, ) def test_worker_version(self): @@ -57,9 +56,9 @@ class TestWorkerActivityBulkInsert(FixtureAPITestCase): elements_qs = Element.objects.filter(type__slug="act", type__corpus_id=self.corpus.id) params = { - "worker_version_id": self.worker_version.id, - "corpus_id": self.corpus.id, - "process_id": self.process.id, + "worker_version_id": self.worker_version.id.hex, + "corpus_id": self.corpus.id.hex, + "process_id": self.process.id.hex, } with self.assertExactQueries("workeractivity_bulk_insert_worker_version_only.sql", params=params): WorkerActivity.objects.bulk_insert( @@ -87,10 +86,10 @@ class TestWorkerActivityBulkInsert(FixtureAPITestCase): elements_qs = Element.objects.filter(type__slug="act", type__corpus_id=self.corpus.id) params = { - "worker_version_id": self.worker_version.id, - "corpus_id": self.corpus.id, - "process_id": self.process.id, - "configuration_id": self.configuration.id, + "worker_version_id": self.worker_version.id.hex, + "corpus_id": self.corpus.id.hex, + "process_id": self.process.id.hex, + "configuration_id": self.configuration.id.hex, } with self.assertExactQueries("workeractivity_bulk_insert_no_model.sql", params=params): WorkerActivity.objects.bulk_insert( @@ -118,10 +117,10 @@ class TestWorkerActivityBulkInsert(FixtureAPITestCase): elements_qs = Element.objects.filter(type__slug="act", type__corpus_id=self.corpus.id) params = { - "worker_version_id": self.worker_version.id, - "model_version_id": self.model_version.id, - "corpus_id": self.corpus.id, - "process_id": self.process.id, + "worker_version_id": self.worker_version.id.hex, + "model_version_id": self.model_version.id.hex, + "corpus_id": self.corpus.id.hex, + "process_id": self.process.id.hex, } with self.assertExactQueries("workeractivity_bulk_insert_no_configuration.sql", params=params): WorkerActivity.objects.bulk_insert( @@ -184,11 +183,11 @@ class TestWorkerActivityBulkInsert(FixtureAPITestCase): elements_qs = Element.objects.filter(type__slug="act", type__corpus_id=self.corpus.id) params = { - "worker_version_id": self.worker_version.id, - "configuration_id": self.configuration.id, - "model_version_id": self.model_version.id, - "corpus_id": self.corpus.id, - "process_id": self.process.id, + "worker_version_id": self.worker_version.id.hex, + "configuration_id": self.configuration.id.hex, + "model_version_id": self.model_version.id.hex, + "corpus_id": self.corpus.id.hex, + "process_id": self.process.id.hex, } with self.assertExactQueries("workeractivity_bulk_insert.sql", params=params): WorkerActivity.objects.bulk_insert( diff --git a/arkindex/process/tests/worker_activity/test_initialize.py b/arkindex/process/tests/worker_activity/test_initialize.py index 321f1855f7c8d2e9062538552abb57aceddea987..caaed902d0c140d5051d4d7685dcbb4cd00f75c9 100644 --- a/arkindex/process/tests/worker_activity/test_initialize.py +++ b/arkindex/process/tests/worker_activity/test_initialize.py @@ -19,8 +19,8 @@ class TestInitializeActivity(FixtureTestCase): element_type=cls.corpus.types.get(slug="volume"), activity_state=ActivityState.Pending, ) - cls.process.worker_runs.create(version=cls.worker_version_1, ttl=0) - cls.process.worker_runs.create(version=cls.worker_version_2, ttl=0) + cls.process.worker_runs.create(version=cls.worker_version_1) + cls.process.worker_runs.create(version=cls.worker_version_2) @patch("arkindex.process.tasks.get_current_job") def test_rq_progress(self, job_mock): diff --git a/arkindex/process/tests/worker_activity/test_list.py b/arkindex/process/tests/worker_activity/test_list.py index bc980a1189a5738caac9943f165c41a3b160948c..75af915f2948d86625833312b0bc4f34a17221b4 100644 --- a/arkindex/process/tests/worker_activity/test_list.py +++ b/arkindex/process/tests/worker_activity/test_list.py @@ -48,7 +48,6 @@ class TestListWorkerActivities(FixtureAPITestCase): version=cls.worker_version, configuration=cls.configuration, model_version=cls.model_version, - ttl=0, ) # Run the process, but skip the real activity initialization so that we can control it ourselves diff --git a/arkindex/process/tests/worker_activity/test_update.py b/arkindex/process/tests/worker_activity/test_update.py index e275425a7e5f933cb2378e9775192038ac2078cd..4d1a5aa34e6f8cf775a31f9b74990d1ec290111e 100644 --- a/arkindex/process/tests/worker_activity/test_update.py +++ b/arkindex/process/tests/worker_activity/test_update.py @@ -49,7 +49,6 @@ class TestUpdateWorkerActivity(FixtureAPITestCase): version=cls.worker_version, configuration=cls.configuration, model_version=cls.model_version, - ttl=0, ) # Run the process, but skip the real activity initialization so that we can control it ourselves @@ -201,7 +200,6 @@ class TestUpdateWorkerActivity(FixtureAPITestCase): # Different configuration configuration=None, model_version=self.model_version, - ttl=0, ) with self.assertNumQueries(4): @@ -457,17 +455,14 @@ class TestUpdateWorkerActivity(FixtureAPITestCase): run_2 = self.process.worker_runs.create( version=worker_version_2, parents=[run_1.id], - ttl=0, ) self.process.worker_runs.create( version=worker_version_3, parents=[run_2.id], - ttl=0, ) self.process.worker_runs.create( version=worker_version_4, - ttl=0, ) # Create activities for run_2, run_3 and run_4 diff --git a/arkindex/process/tests/worker_runs/test_build_task.py b/arkindex/process/tests/worker_runs/test_build_task.py index 65ce96c232f643b396f70afcfbc4d21ad0f36400..022348a66055f610eb5344ee248efefede735c24 100644 --- a/arkindex/process/tests/worker_runs/test_build_task.py +++ b/arkindex/process/tests/worker_runs/test_build_task.py @@ -25,7 +25,7 @@ class TestWorkerRunsBuildTask(FixtureAPITestCase): ) cls.version = WorkerVersion.objects.get(worker__slug="reco") cls.worker = cls.version.worker - cls.worker_run = cls.process.worker_runs.create(version=cls.version, ttl=0) + cls.worker_run = cls.process.worker_runs.create(version=cls.version) # Model and Model version setup cls.model_1 = Model.objects.create(name="My model") @@ -52,7 +52,7 @@ class TestWorkerRunsBuildTask(FixtureAPITestCase): "TASK_ELEMENTS": "/data/import/elements.json", "ARKINDEX_WORKER_RUN_ID": str(self.worker_run.id), }) - self.assertEqual(task.ttl, 0) + self.assertEqual(task.ttl, 3600) def test_build_task_with_chunk(self): task, parent_slugs = self.worker_run.build_task(self.process, ENV.copy(), "import", "/data/import/elements.json", chunk=4) @@ -81,7 +81,6 @@ class TestWorkerRunsBuildTask(FixtureAPITestCase): run_2 = self.process.worker_runs.create( version=version_2, parents=[self.worker_run.id], - ttl=42, ) task, parent_slugs = run_2.build_task(self.process, ENV.copy(), "import", "/data/import/elements.json") @@ -97,7 +96,7 @@ class TestWorkerRunsBuildTask(FixtureAPITestCase): "TASK_ELEMENTS": "/data/import/elements.json", "ARKINDEX_WORKER_RUN_ID": str(run_2.id), }) - self.assertEqual(task.ttl, 42) + self.assertEqual(task.ttl, 3600) def test_build_task_with_parent_and_chunk(self): version_2 = WorkerVersion.objects.create( @@ -110,7 +109,6 @@ class TestWorkerRunsBuildTask(FixtureAPITestCase): run_2 = self.process.worker_runs.create( version=version_2, parents=[self.worker_run.id], - ttl=1000, ) task, parent_slugs = run_2.build_task(self.process, ENV.copy(), "import", "/data/import/elements.json", chunk=4) @@ -127,7 +125,7 @@ class TestWorkerRunsBuildTask(FixtureAPITestCase): "TASK_ELEMENTS": "/data/import/elements.json", "ARKINDEX_WORKER_RUN_ID": str(run_2.id), }) - self.assertEqual(task.ttl, 1000) + self.assertEqual(task.ttl, 3600) def test_build_task_shm_size(self): self.version.configuration = { @@ -160,7 +158,6 @@ class TestWorkerRunsBuildTask(FixtureAPITestCase): run_2 = self.process.worker_runs.create( version=version_2, parents=[self.worker_run.id], - ttl=0, ) with self.assertRaisesRegex( diff --git a/arkindex/process/tests/worker_runs/test_create.py b/arkindex/process/tests/worker_runs/test_create.py index 41ac10e52738e197473f8d2e434c033ada84a3df..1d066bfb420854071d510929527f3ee18213362b 100644 --- a/arkindex/process/tests/worker_runs/test_create.py +++ b/arkindex/process/tests/worker_runs/test_create.py @@ -3,7 +3,6 @@ from datetime import datetime, timezone from unittest.mock import call, patch from django.db import transaction -from django.test import override_settings from django.urls import reverse from rest_framework import status @@ -20,7 +19,6 @@ from arkindex.training.models import Model, ModelVersion, ModelVersionState from arkindex.users.models import Role -@override_settings(PONOS_MAXIMUM_TASK_TTL=3600) class TestWorkerRunsCreate(FixtureAPITestCase): """ Test worker runs create endpoint @@ -38,7 +36,7 @@ class TestWorkerRunsCreate(FixtureAPITestCase): ) cls.version_1 = WorkerVersion.objects.get(worker__slug="reco") cls.worker_1 = cls.version_1.worker - cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1, ttl=0) + cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1) cls.configuration_1 = cls.worker_1.configurations.create(name="My config", configuration={"key": "value"}) worker_version = WorkerVersion.objects.exclude(worker=cls.version_1.worker).first() cls.configuration_2 = worker_version.worker.configurations.create(name="Config") @@ -330,7 +328,6 @@ class TestWorkerRunsCreate(FixtureAPITestCase): }, "use_gpu": False, "summary": "Worker Recognizer @ version 1", - "ttl": 3600, }) run = WorkerRun.objects.get(pk=pk) # Check generated summary @@ -415,7 +412,6 @@ class TestWorkerRunsCreate(FixtureAPITestCase): }, "use_gpu": False, "summary": "Worker Recognizer @ version 1 using configuration 'My config'", - "ttl": 3600, }) run = WorkerRun.objects.get(pk=pk) # Check generated summary @@ -563,7 +559,6 @@ class TestWorkerRunsCreate(FixtureAPITestCase): }, "summary": f"Worker Recognizer @ version {worker_version.version}", "use_gpu": use_gpu, - "ttl": 3600, }) run = WorkerRun.objects.get(pk=pk) self.assertEqual(run.use_gpu, use_gpu) @@ -623,7 +618,6 @@ class TestWorkerRunsCreate(FixtureAPITestCase): }, "summary": "Worker Recognizer @ version 2", "use_gpu": True, - "ttl": 3600, }) run = WorkerRun.objects.get(pk=pk) self.assertEqual(run.use_gpu, True) diff --git a/arkindex/process/tests/worker_runs/test_delete.py b/arkindex/process/tests/worker_runs/test_delete.py index 410e5a3e4422a1d81dbce6fb42ea2d679d94ab34..364cda2564db59a7b0932daacc88036b8b4cbc4c 100644 --- a/arkindex/process/tests/worker_runs/test_delete.py +++ b/arkindex/process/tests/worker_runs/test_delete.py @@ -28,10 +28,11 @@ class TestWorkerRunsDelete(FixtureAPITestCase): cls.version_1 = WorkerVersion.objects.get(worker__slug="reco") cls.worker_1 = cls.version_1.worker cls.version_2 = WorkerVersion.objects.get(worker__slug="dla") - cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1, ttl=0) + cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1) cls.agent = Agent.objects.create( farm=cls.farm, + fingerprint="a" * 64, hostname="claude", cpu_cores=42, cpu_frequency=1e15, @@ -77,7 +78,7 @@ class TestWorkerRunsDelete(FixtureAPITestCase): """ A user cannot delete a worker run on a local process """ - run = self.local_process.worker_runs.create(version=self.version_1, ttl=0) + run = self.local_process.worker_runs.create(version=self.version_1) self.client.force_login(self.user) with self.assertNumQueries(4): @@ -114,12 +115,10 @@ class TestWorkerRunsDelete(FixtureAPITestCase): run_2 = self.process_1.worker_runs.create( version=version_2, parents=[self.run_1.id], - ttl=0, ) run_3 = self.process_1.worker_runs.create( version=version_3, parents=[self.run_1.id, run_2.id], - ttl=0, ) self.assertTrue(self.run_1.id in run_2.parents) diff --git a/arkindex/process/tests/worker_runs/test_list.py b/arkindex/process/tests/worker_runs/test_list.py index 8b0698d4a482ba9d56369be6d0f8b0e9458a2cf0..10bf646f478e2d251ed5b8983cef61fab9e602a6 100644 --- a/arkindex/process/tests/worker_runs/test_list.py +++ b/arkindex/process/tests/worker_runs/test_list.py @@ -22,7 +22,7 @@ class TestWorkerRunsList(FixtureAPITestCase): ) cls.version_1 = WorkerVersion.objects.get(worker__slug="reco") cls.worker_1 = cls.version_1.worker - cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1, ttl=0) + cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1) cls.process_2 = cls.corpus.processes.create(creator=cls.user, mode=ProcessMode.Workers) def test_list_requires_login(self): @@ -94,14 +94,10 @@ class TestWorkerRunsList(FixtureAPITestCase): }, "use_gpu": False, "summary": "Worker Recognizer @ version 1", - "ttl": 0, }]) def test_list_filter_process(self): - run_2 = self.process_2.worker_runs.create( - version=self.version_1, - ttl=0, - ) + run_2 = self.process_2.worker_runs.create(version=self.version_1) self.client.force_login(self.user) with self.assertNumQueries(6): @@ -165,5 +161,4 @@ class TestWorkerRunsList(FixtureAPITestCase): }, "use_gpu": False, "summary": "Worker Recognizer @ version 1", - "ttl": 0, }]) diff --git a/arkindex/process/tests/worker_runs/test_partial_update.py b/arkindex/process/tests/worker_runs/test_partial_update.py index cb7ebb4664e613a2b70e5e93637d876c793e527c..91c635b57c0d9709763bd2d623848b4191e53685 100644 --- a/arkindex/process/tests/worker_runs/test_partial_update.py +++ b/arkindex/process/tests/worker_runs/test_partial_update.py @@ -30,7 +30,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): cls.version_1 = WorkerVersion.objects.get(worker__slug="reco") cls.worker_1 = cls.version_1.worker cls.version_2 = WorkerVersion.objects.get(worker__slug="dla") - cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1, ttl=1000) + cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1) cls.configuration_1 = cls.worker_1.configurations.create(name="My config", configuration={"key": "value"}) worker_version = WorkerVersion.objects.exclude(worker=cls.version_1.worker).first() cls.configuration_2 = worker_version.worker.configurations.create(name="Config") @@ -95,6 +95,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): cls.agent = Agent.objects.create( farm=cls.farm, + fingerprint="a" * 64, hostname="claude", cpu_cores=42, cpu_frequency=1e15, @@ -115,10 +116,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): version=2, configuration={"test": "test2"} ) - run_2 = self.process_1.worker_runs.create( - version=version_2, - ttl=0, - ) + run_2 = self.process_1.worker_runs.create(version=version_2) with self.assertNumQueries(0): response = self.client.patch( @@ -154,10 +152,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): version=2, configuration={"test": "test2"} ) - run_2 = self.process_1.worker_runs.create( - version=version_2, - ttl=0, - ) + run_2 = self.process_1.worker_runs.create(version=version_2) self.client.force_login(self.user) with self.assertNumQueries(3): @@ -173,7 +168,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): """ A user cannot update a worker run on a local process """ - run = self.local_process.worker_runs.create(version=self.version_1, ttl=1000) + run = self.local_process.worker_runs.create(version=self.version_1) self.client.force_login(self.user) with self.assertNumQueries(5): @@ -266,7 +261,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 1000, "summary": "Worker Recognizer @ version 1", }) self.run_1.refresh_from_db() @@ -332,7 +326,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 1000, "summary": "Worker Recognizer @ version 1", }) self.run_1.refresh_from_db() @@ -403,7 +396,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 1000, "summary": "Worker Recognizer @ version 1 using configuration 'My config'", }) self.assertEqual(self.run_1.configuration.id, self.configuration_1.id) @@ -455,10 +447,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): configuration={"test": "test2"}, model_usage=FeatureUsage.Disabled ) - run_2 = self.process_1.worker_runs.create( - version=version_no_model, - ttl=1000, - ) + run_2 = self.process_1.worker_runs.create(version=version_no_model) with self.assertNumQueries(5): response = self.client.patch( @@ -484,10 +473,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): configuration={"test": "test2"}, model_usage=FeatureUsage.Required ) - run_2 = self.process_1.worker_runs.create( - version=version_no_model, - ttl=1000, - ) + run_2 = self.process_1.worker_runs.create(version=version_no_model) random_model_version_uuid = str(uuid.uuid4()) with self.assertNumQueries(4): @@ -515,10 +501,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): configuration={"test": "test2"}, model_usage=FeatureUsage.Required ) - run_2 = self.process_1.worker_runs.create( - version=version_no_model, - ttl=1000, - ) + run_2 = self.process_1.worker_runs.create(version=version_no_model) # Create a model version, the user has no access to model_no_access = Model.objects.create(name="Secret model") @@ -555,10 +538,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): configuration={"test": "test2"}, model_usage=FeatureUsage.Required ) - run_2 = self.process_1.worker_runs.create( - version=version_no_model, - ttl=1000, - ) + run_2 = self.process_1.worker_runs.create(version=version_no_model) def filter_rights(user, model, level): """ @@ -619,10 +599,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): configuration={"test": "test2"}, model_usage=FeatureUsage.Required ) - run = self.process_1.worker_runs.create( - version=version, - ttl=1000, - ) + run = self.process_1.worker_runs.create(version=version) self.model_version_1.state = ModelVersionState.Error self.model_version_1.save() @@ -648,7 +625,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): configuration={"test": "test2"}, model_usage=FeatureUsage.Required ) - run = self.process_1.worker_runs.create(version=version, ttl=1000) + run = self.process_1.worker_runs.create(version=version) self.model_1.archived = datetime.now(timezone.utc) self.model_1.save() @@ -676,10 +653,7 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): configuration={"test": "test2"}, model_usage=FeatureUsage.Required ) - run = self.process_1.worker_runs.create( - version=version_with_model, - ttl=0, - ) + run = self.process_1.worker_runs.create(version=version_with_model) self.assertIsNone(run.model_version_id) self.assertEqual(run.summary, "Worker Recognizer @ version 2") @@ -758,7 +732,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 0, "summary": f"Worker Recognizer @ version 2 with model {model_version.model.name} @ {str(model_version.id)[:6]}", }) self.assertEqual(run.model_version_id, model_version.id) @@ -778,7 +751,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): ) run = self.process_1.worker_runs.create( version=version_with_model, - ttl=0, configuration=self.configuration_1 ) self.assertEqual(run.model_version_id, None) @@ -853,7 +825,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 0, "summary": f"Worker Recognizer @ version 2 with model My model @ {str(self.model_version_1.id)[:6]} using configuration 'My config'", }) self.assertEqual(run.model_version_id, self.model_version_1.id) @@ -867,7 +838,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): ) run_2 = self.process_1.worker_runs.create( version=version_2, - ttl=0, ) self.client.force_login(self.user) @@ -926,7 +896,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 1000, "summary": "Worker Recognizer @ version 1", }) @@ -954,7 +923,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): version=self.version_1, model_version=None if model_version else self.model_version_1, configuration=None if configuration else self.configuration_1, - ttl=0, ) # Having a model version or a configuration adds one query for each @@ -1005,7 +973,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): run = WorkerRun.objects.create( process=self.process_1, version=worker_version, - ttl=0 ) self.assertEqual(run.use_gpu, True if worker_version.gpu_usage == FeatureUsage.Required else False) with self.assertNumQueries(3): @@ -1035,7 +1002,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): run = WorkerRun.objects.create( process=self.process_1, version=worker_version, - ttl=0 ) self.assertEqual(run.use_gpu, True if worker_version.gpu_usage == FeatureUsage.Required else False) @@ -1093,7 +1059,6 @@ class TestWorkerRunsPartialUpdate(FixtureAPITestCase): }, "summary": f"Worker Recognizer @ version {worker_version.version}", "use_gpu": use_gpu, - "ttl": 0, }) run.refresh_from_db() self.assertEqual(run.use_gpu, use_gpu) diff --git a/arkindex/process/tests/worker_runs/test_retrieve.py b/arkindex/process/tests/worker_runs/test_retrieve.py index e253e85f25da5e605625ac07faa49e1906654593..0ffe02b790455b509122f2ae0416dd4f3d463845 100644 --- a/arkindex/process/tests/worker_runs/test_retrieve.py +++ b/arkindex/process/tests/worker_runs/test_retrieve.py @@ -35,12 +35,13 @@ class TestWorkerRunsRetrieve(FixtureAPITestCase): cls.worker_custom = Worker.objects.get(slug="custom") cls.version_custom = cls.worker_custom.versions.get() - cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1, ttl=1000) + cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1) cls.run_custom = cls.local_process.worker_runs.get(version=cls.version_custom) cls.process_2 = cls.corpus.processes.create(creator=cls.user, mode=ProcessMode.Workers) cls.agent = Agent.objects.create( farm=cls.farm, + fingerprint="a" * 64, hostname="claude", cpu_cores=42, cpu_frequency=1e15, @@ -114,7 +115,6 @@ class TestWorkerRunsRetrieve(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 1000, "summary": "Worker Recognizer @ version 1", }) @@ -179,7 +179,6 @@ class TestWorkerRunsRetrieve(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 1000, "summary": "Worker Recognizer @ version 1", }) @@ -274,14 +273,13 @@ class TestWorkerRunsRetrieve(FixtureAPITestCase): }, "summary": "Worker Custom worker @ version 1", "use_gpu": False, - "ttl": 0, }) def test_retrieve_local(self): """ A user can retrieve a run on their own local process """ - run = self.local_process.worker_runs.create(version=self.version_1, ttl=0) + run = self.local_process.worker_runs.create(version=self.version_1) self.client.force_login(self.user) with self.assertNumQueries(5): @@ -335,7 +333,6 @@ class TestWorkerRunsRetrieve(FixtureAPITestCase): "prefix": None, }, "use_gpu": False, - "ttl": 0, "summary": "Worker Recognizer @ version 1", }) @@ -411,7 +408,6 @@ class TestWorkerRunsRetrieve(FixtureAPITestCase): "prefix": None, }, "use_gpu": False, - "ttl": 1000, "summary": "Worker Recognizer @ version 1", }) diff --git a/arkindex/process/tests/worker_runs/test_ttl.py b/arkindex/process/tests/worker_runs/test_ttl.py deleted file mode 100644 index edd98b6a9b2b821370a857a6a9022561487e2fa5..0000000000000000000000000000000000000000 --- a/arkindex/process/tests/worker_runs/test_ttl.py +++ /dev/null @@ -1,272 +0,0 @@ -from django.test import override_settings -from django.urls import reverse -from rest_framework import status - -from arkindex.ponos.models import Farm -from arkindex.process.models import ( - ProcessMode, - WorkerVersion, -) -from arkindex.project.tests import FixtureAPITestCase - - -@override_settings(PONOS_MAXIMUM_TASK_TTL=3600) -class TestWorkerRunTTL(FixtureAPITestCase): - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.process = cls.corpus.processes.create( - creator=cls.user, - mode=ProcessMode.Workers, - farm=Farm.objects.first(), - ) - cls.recognizer = WorkerVersion.objects.get(worker__slug="reco") - cls.dla = WorkerVersion.objects.get(worker__slug="dla") - cls.worker_run = cls.process.worker_runs.create(version=cls.dla, ttl=0) - - def test_create_default_ttl(self): - self.client.force_login(self.superuser) - # Corpus TTL / WorkerRun TTL - cases = [ - (0, 0), - (10000, 10000), - # No corpus TTL means the instance wide value should be set - (None, 3600), - ] - - for corpus_ttl, expected_ttl in cases: - with self.subTest(corpus_ttl=corpus_ttl): - self.process.worker_runs.filter(version=self.recognizer).delete() - self.corpus.maximum_task_ttl = corpus_ttl - self.corpus.save() - - with self.assertNumQueries(6): - response = self.client.post( - reverse("api:worker-run-list", kwargs={"pk": str(self.process.id)}), - {"worker_version_id": str(self.recognizer.id)}, - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - data = response.json() - self.assertEqual(data["ttl"], expected_ttl) - run = self.process.worker_runs.get(id=data["id"]) - self.assertEqual(run.ttl, expected_ttl) - - def test_create_set_ttl(self): - self.client.force_login(self.superuser) - # Corpus TTL / WorkerRun TTL / Expected WorkerRun TTL - cases = [ - (0, 0, 0), - (0, 1000, 1000), - (1800, 1000, 1000), - (1800, 1800, 1800), - # No corpus TTL means the instance wide value is the limit - (None, 600, 600), - (None, 3600, 3600), - ] - - for corpus_ttl, worker_run_ttl, expected_ttl in cases: - with self.subTest(corpus_ttl=corpus_ttl, worker_run_ttl=worker_run_ttl): - self.process.worker_runs.filter(version=self.recognizer).delete() - self.corpus.maximum_task_ttl = corpus_ttl - self.corpus.save() - - with self.assertNumQueries(6): - response = self.client.post( - reverse("api:worker-run-list", kwargs={"pk": str(self.process.id)}), - { - "worker_version_id": str(self.recognizer.id), - "ttl": worker_run_ttl, - }, - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - data = response.json() - self.assertEqual(data["ttl"], expected_ttl) - run = self.process.worker_runs.get(id=data["id"]) - self.assertEqual(run.ttl, expected_ttl) - - def test_create_invalid_ttl(self): - self.client.force_login(self.superuser) - self.process.worker_runs.filter(version=self.recognizer).delete() - - # Corpus TTL, WorkerRun TTL, error message - cases = [ - (None, "one hour", ["A valid integer is required."]), - (None, -1, ["Ensure this value is greater than or equal to 1."]), - (None, 0, ["Ensure this value is greater than or equal to 1."]), - (None, 1e12, ["Ensure this value is less than or equal to 3600."]), - (0, -1, ["Ensure this value is greater than or equal to 0."]), - (0, 1e12, ["Ensure this value is less than or equal to 2147483647."]), - (1800, -1, ["Ensure this value is greater than or equal to 1."]), - (1800, 0, ["Ensure this value is greater than or equal to 1."]), - (1800, 1e12, ["Ensure this value is less than or equal to 1800."]), - ] - for corpus_ttl, worker_run_ttl, expected_error in cases: - with self.subTest(corpus_ttl=corpus_ttl, worker_run_ttl=worker_run_ttl): - self.corpus.maximum_task_ttl = corpus_ttl - self.corpus.save() - - with self.assertNumQueries(4): - response = self.client.post( - reverse("api:worker-run-list", kwargs={"pk": str(self.process.id)}), - { - "worker_version_id": str(self.recognizer.id), - "ttl": worker_run_ttl, - }, - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - self.assertEqual(response.json(), { - "ttl": expected_error, - }) - - def test_partial_update_set_ttl(self): - self.client.force_login(self.superuser) - # Corpus TTL / WorkerRun TTL / Expected WorkerRun TTL - cases = [ - (0, 0, 0), - (0, 1000, 1000), - (1800, 1000, 1000), - (1800, 1800, 1800), - # No corpus TTL means the instance wide value is the limit - (None, 600, 600), - (None, 3600, 3600), - ] - - for corpus_ttl, worker_run_ttl, expected_ttl in cases: - with self.subTest(corpus_ttl=corpus_ttl, worker_run_ttl=worker_run_ttl): - self.corpus.maximum_task_ttl = corpus_ttl - self.corpus.save() - - with self.assertNumQueries(5): - response = self.client.patch( - reverse("api:worker-run-details", kwargs={"pk": str(self.worker_run.id)}), - {"ttl": worker_run_ttl}, - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - self.assertEqual(data["ttl"], expected_ttl) - run = self.process.worker_runs.get(id=data["id"]) - self.assertEqual(run.ttl, expected_ttl) - - def test_partial_update_invalid_ttl(self): - self.client.force_login(self.superuser) - - # Corpus TTL, WorkerRun TTL, error message - cases = [ - (None, "one hour", ["A valid integer is required."]), - (None, -1, ["Ensure this value is greater than or equal to 1."]), - (None, 0, ["Ensure this value is greater than or equal to 1."]), - (None, 1e12, ["Ensure this value is less than or equal to 3600."]), - (0, -1, ["Ensure this value is greater than or equal to 0."]), - (0, 1e12, ["Ensure this value is less than or equal to 2147483647."]), - (1800, -1, ["Ensure this value is greater than or equal to 1."]), - (1800, 0, ["Ensure this value is greater than or equal to 1."]), - (1800, 1e12, ["Ensure this value is less than or equal to 1800."]), - ] - for corpus_ttl, worker_run_ttl, expected_error in cases: - with self.subTest(corpus_ttl=corpus_ttl, worker_run_ttl=worker_run_ttl): - self.corpus.maximum_task_ttl = corpus_ttl - self.corpus.save() - - with self.assertNumQueries(3): - response = self.client.patch( - reverse("api:worker-run-details", kwargs={"pk": str(self.worker_run.id)}), - {"ttl": worker_run_ttl}, - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - self.assertEqual(response.json(), { - "ttl": expected_error, - }) - - - def test_update_default_ttl(self): - self.client.force_login(self.superuser) - # Corpus TTL / WorkerRun TTL - cases = [ - (0, 0), - (10000, 10000), - # No corpus TTL means the instance wide value should be set - (None, 3600), - ] - - for corpus_ttl, expected_ttl in cases: - with self.subTest(corpus_ttl=corpus_ttl): - self.corpus.maximum_task_ttl = corpus_ttl - self.corpus.save() - - with self.assertNumQueries(5): - response = self.client.put( - reverse("api:worker-run-details", kwargs={"pk": str(self.worker_run.id)}), - {}, - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - self.assertEqual(response.json()["ttl"], expected_ttl) - self.worker_run.refresh_from_db() - self.assertEqual(self.worker_run.ttl, expected_ttl) - - def test_update_set_ttl(self): - self.client.force_login(self.superuser) - # Corpus TTL / WorkerRun TTL / Expected WorkerRun TTL - cases = [ - (0, 0, 0), - (0, 1000, 1000), - (1800, 1000, 1000), - (1800, 1800, 1800), - # No corpus TTL means the instance wide value is the limit - (None, 600, 600), - (None, 3600, 3600), - ] - - for corpus_ttl, worker_run_ttl, expected_ttl in cases: - with self.subTest(corpus_ttl=corpus_ttl, worker_run_ttl=worker_run_ttl): - self.corpus.maximum_task_ttl = corpus_ttl - self.corpus.save() - - with self.assertNumQueries(5): - response = self.client.put( - reverse("api:worker-run-details", kwargs={"pk": str(self.worker_run.id)}), - {"ttl": worker_run_ttl}, - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - self.assertEqual(data["ttl"], expected_ttl) - run = self.process.worker_runs.get(id=data["id"]) - self.assertEqual(run.ttl, expected_ttl) - - def test_update_invalid_ttl(self): - self.client.force_login(self.superuser) - - # Corpus TTL, WorkerRun TTL, error message - cases = [ - (None, "one hour", ["A valid integer is required."]), - (None, -1, ["Ensure this value is greater than or equal to 1."]), - (None, 0, ["Ensure this value is greater than or equal to 1."]), - (None, 1e12, ["Ensure this value is less than or equal to 3600."]), - (0, -1, ["Ensure this value is greater than or equal to 0."]), - (0, 1e12, ["Ensure this value is less than or equal to 2147483647."]), - (1800, -1, ["Ensure this value is greater than or equal to 1."]), - (1800, 0, ["Ensure this value is greater than or equal to 1."]), - (1800, 1e12, ["Ensure this value is less than or equal to 1800."]), - ] - for corpus_ttl, worker_run_ttl, expected_error in cases: - with self.subTest(corpus_ttl=corpus_ttl, worker_run_ttl=worker_run_ttl): - self.corpus.maximum_task_ttl = corpus_ttl - self.corpus.save() - - with self.assertNumQueries(3): - response = self.client.put( - reverse("api:worker-run-details", kwargs={"pk": str(self.worker_run.id)}), - {"ttl": worker_run_ttl}, - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - self.assertEqual(response.json(), { - "ttl": expected_error, - }) diff --git a/arkindex/process/tests/worker_runs/test_update.py b/arkindex/process/tests/worker_runs/test_update.py index b412d3b9da98f1ef6af1ef23d32e7bbbd519c701..a79cc7059ac668b4f611d3e284f1ce0c1b69f448 100644 --- a/arkindex/process/tests/worker_runs/test_update.py +++ b/arkindex/process/tests/worker_runs/test_update.py @@ -2,7 +2,6 @@ import uuid from datetime import datetime, timezone from unittest.mock import call, patch -from django.test import override_settings from django.urls import reverse from rest_framework import status from rest_framework.exceptions import ValidationError @@ -14,7 +13,6 @@ from arkindex.training.models import Model, ModelVersion, ModelVersionState from arkindex.users.models import Role -@override_settings(PONOS_MAXIMUM_TASK_TTL=3600) class TestWorkerRunsUpdate(FixtureAPITestCase): """ Test worker runs update endpoint @@ -33,7 +31,7 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): cls.version_1 = WorkerVersion.objects.get(worker__slug="reco") cls.worker_1 = cls.version_1.worker cls.version_2 = WorkerVersion.objects.get(worker__slug="dla") - cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1, ttl=1000) + cls.run_1 = cls.process_1.worker_runs.create(version=cls.version_1) cls.configuration_1 = cls.worker_1.configurations.create(name="My config", configuration={"key": "value"}) worker_version = WorkerVersion.objects.exclude(worker=cls.version_1.worker).first() cls.configuration_2 = worker_version.worker.configurations.create(name="Config") @@ -71,6 +69,7 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): cls.agent = Agent.objects.create( farm=cls.farm, + fingerprint="a" * 64, hostname="claude", cpu_cores=42, cpu_frequency=1e15, @@ -99,7 +98,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): ) run_2 = self.process_1.worker_runs.create( version=version_2, - ttl=0, ) with self.assertNumQueries(0): @@ -137,7 +135,7 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): """ A user cannot update a worker run on a local process """ - run = self.local_process.worker_runs.create(version=self.version_1, ttl=1000) + run = self.local_process.worker_runs.create(version=self.version_1) self.client.force_login(self.user) with self.assertNumQueries(5): @@ -161,7 +159,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): ) run_2 = self.process_1.worker_runs.create( version=version_2, - ttl=0, ) self.client.force_login(self.user) @@ -194,7 +191,7 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): def test_update_duplicate_parents(self): self.client.force_login(self.user) - run_2 = self.process_1.worker_runs.create(version=self.version_2, ttl=0) + run_2 = self.process_1.worker_runs.create(version=self.version_2) with self.assertNumQueries(4): response = self.client.put( @@ -218,7 +215,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): """ run_2 = self.process_1.worker_runs.create( version=self.version_2, - ttl=0, ) run_2.parents = [self.run_1.id, self.run_1.id] @@ -286,7 +282,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 3600, "summary": "Worker Recognizer @ version 1", }) self.run_1.refresh_from_db() @@ -352,7 +347,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): }, }, "use_gpu": False, - "ttl": 3600, "summary": "Worker Recognizer @ version 1", }) self.run_1.refresh_from_db() @@ -426,7 +420,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 3600, "summary": "Worker Recognizer @ version 1 using configuration 'My config'", }) self.assertEqual(self.run_1.configuration.id, self.configuration_1.id) @@ -481,7 +474,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): ) run_2 = self.process_1.worker_runs.create( version=version_no_model, - ttl=0, ) with self.assertNumQueries(5): @@ -511,7 +503,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): ) run_2 = self.process_1.worker_runs.create( version=version_no_model, - ttl=0, ) random_model_version_uuid = str(uuid.uuid4()) @@ -543,7 +534,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): ) run_2 = self.process_1.worker_runs.create( version=version_no_model, - ttl=0, ) # Create a model version, the user has no access to @@ -590,7 +580,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): ) run_2 = self.process_1.worker_runs.create( version=version_no_model, - ttl=0, ) def filter_rights(user, model, level): @@ -655,7 +644,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): ) run = self.process_1.worker_runs.create( version=version, - ttl=0, ) self.model_version_1.state = ModelVersionState.Error self.model_version_1.save() @@ -682,7 +670,7 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): configuration={"test": "test2"}, model_usage=FeatureUsage.Required ) - run = self.process_1.worker_runs.create(version=version, ttl=0) + run = self.process_1.worker_runs.create(version=version) self.model_1.archived = datetime.now(timezone.utc) self.model_1.save() @@ -712,7 +700,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): ) run = self.process_1.worker_runs.create( version=version_with_model, - ttl=0, ) self.assertEqual(run.model_version, None) # Check generated summary, before updating, there should be only information about the worker version @@ -794,7 +781,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 3600, "summary": f"Worker Recognizer @ version 2 with model {model_version.model.name} @ {str(model_version.id)[:6]}", }) self.assertEqual(run.model_version_id, model_version.id) @@ -813,7 +799,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): ) run = self.process_1.worker_runs.create( version=version_with_model, - ttl=0, ) self.assertIsNone(run.model_version) self.assertIsNone(run.configuration) @@ -892,7 +877,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 3600, "summary": f"Worker Recognizer @ version 2 with model My model @ {str(self.model_version_1.id)[:6]} using configuration 'My config'", }) self.assertEqual(run.model_version_id, self.model_version_1.id) @@ -907,7 +891,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): ) run_2 = self.process_1.worker_runs.create( version=version_2, - ttl=0, ) self.client.force_login(self.user) @@ -916,7 +899,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): reverse("api:worker-run-details", kwargs={"pk": str(self.run_1.id)}), data={ "parents": [str(run_2.id)], - "ttl": 500, }, ) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -967,7 +949,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): } }, "use_gpu": False, - "ttl": 500, "summary": "Worker Recognizer @ version 1", }) @@ -995,7 +976,6 @@ class TestWorkerRunsUpdate(FixtureAPITestCase): version=self.version_1, model_version=None if model_version else self.model_version_1, configuration=None if configuration else self.configuration_1, - ttl=0, ) # Having a model version or a configuration adds one query for each diff --git a/arkindex/project/config.py b/arkindex/project/config.py index b97f561fc4b63e234fb82432f7d4847c544aa4e8..71a74d8282f86f52f85d19884a9feffd139e3b46 100644 --- a/arkindex/project/config.py +++ b/arkindex/project/config.py @@ -91,7 +91,6 @@ def get_settings_parser(base_dir): # SECURITY WARNING: keep the secret key used in production secret! parser.add_option("secret_key", type=str, default="jf0w^y&ml(caax8f&a1mub)(js9(l5mhbbhosz3gi+m01ex+lo") - parser.add_option("jwt_signing_key", type=str, default=None) database_parser = parser.add_subparser("database", default={}) database_parser.add_option("name", type=str, default="arkindex_dev") diff --git a/arkindex/project/settings.py b/arkindex/project/settings.py index b9fbcad8c7af6fb75c9cf77979ea5ca34f330e6c..eaed15d931496e9e27069cdc2689654255972dff 100644 --- a/arkindex/project/settings.py +++ b/arkindex/project/settings.py @@ -14,7 +14,6 @@ import decimal import os import sys import warnings -from datetime import timedelta from pathlib import Path from textwrap import dedent @@ -127,6 +126,7 @@ INSTALLED_APPS = [ "arkindex.users", "arkindex.process", "arkindex.training", + "arkindex.budget", ] MIDDLEWARE = [ @@ -218,13 +218,6 @@ REST_FRAMEWORK = { "TEST_REQUEST_DEFAULT_FORMAT": "json", } -SIMPLE_JWT = { - "USER_ID_CLAIM": "agent_id", - "ROTATE_REFRESH_TOKENS": True, - "ACCESS_TOKEN_LIFETIME": timedelta(hours=6), - "SIGNING_KEY": conf["jwt_signing_key"] or SECRET_KEY, -} - SPECTACULAR_SETTINGS = { "CAMELIZE_NAMES": True, # Remove the automatically generated `description` that lists the members of all enums, diff --git a/arkindex/project/tests/__init__.py b/arkindex/project/tests/__init__.py index f1bc9e90bae7adc959beb8f116aec90cfdfaad3d..fc326f20e0a4770d3b40de1649baeb3ab428ef58 100644 --- a/arkindex/project/tests/__init__.py +++ b/arkindex/project/tests/__init__.py @@ -77,7 +77,7 @@ class _AssertExactQueriesContext(CaptureQueriesContext): items = enumerate(self.params) for name, value in items: - actual_sql = actual_sql.replace(value, f"{{{name}}}") + actual_sql = actual_sql.replace(str(value), f"{{{name}}}") self.path.write_text(actual_sql) except OSError as e: diff --git a/arkindex/project/tests/config_samples/defaults.yaml b/arkindex/project/tests/config_samples/defaults.yaml index 24aa0d51d4b66cab220df1ad131648ab26039348..d45fe54f5d5bd1450e367f7615e82c46675df887 100644 --- a/arkindex/project/tests/config_samples/defaults.yaml +++ b/arkindex/project/tests/config_samples/defaults.yaml @@ -60,7 +60,6 @@ job_timeouts: send_verification_email: 120 task: 36000 worker_results_delete: 3600 -jwt_signing_key: null local_imageserver_id: 1 metrics_port: 3000 ponos: diff --git a/arkindex/project/tests/config_samples/errors.yaml b/arkindex/project/tests/config_samples/errors.yaml index fd795eded5eb97f408d21b5263afabbb62e8f64e..455ae2d4ca25f153dff5577f2db6c455564b9f9e 100644 --- a/arkindex/project/tests/config_samples/errors.yaml +++ b/arkindex/project/tests/config_samples/errors.yaml @@ -43,7 +43,6 @@ job_timeouts: task: '' worker_results_delete: null send_verification_email: lol -jwt_signing_key: null local_imageserver_id: 1 metrics_port: 12 ponos: diff --git a/arkindex/project/tests/config_samples/override.yaml b/arkindex/project/tests/config_samples/override.yaml index fed278c1c26d9b17837037b68b9b6922cffe7745..52b0d9869fc5863f513130342cc049b868d7910c 100644 --- a/arkindex/project/tests/config_samples/override.yaml +++ b/arkindex/project/tests/config_samples/override.yaml @@ -75,7 +75,6 @@ job_timeouts: send_verification_email: 10 task: 11 worker_results_delete: 12 -jwt_signing_key: deadbeef local_imageserver_id: 45 metrics_port: 4242 ponos: diff --git a/arkindex/project/tests/test_gis.py b/arkindex/project/tests/test_gis.py index 1e0dfde3157242c3f5c6345c0116a5622b35a29d..9d3c7647aa6098b127517059988bacbb40d25aee 100644 --- a/arkindex/project/tests/test_gis.py +++ b/arkindex/project/tests/test_gis.py @@ -1,5 +1,5 @@ from django.contrib.gis.geos import LinearRing, LineString, Point -from psycopg2 import Binary +from psycopg.sql import quote from arkindex.documents.models import Element from arkindex.project.gis import ensure_linear_ring @@ -69,8 +69,8 @@ class TestGis(FixtureTestCase): """ polygon = LinearRing((0, 0), (0, 10), (10, 10), (10, 0), (0, 0)) - # psycopg2.Binary provides the encoding from a geometry's Extended Well Known Binary to a PostgreSQL bytea - encoded_polygon = str(Binary(bytes(polygon.ewkb))) + # psycopg.Binary provides the encoding from a geometry's Extended Well Known Binary to a PostgreSQL bytea + encoded_polygon = quote(bytes(polygon.ewkb)) self.assertEqual( str(Element.objects.filter(polygon=polygon).only("id").query), diff --git a/arkindex/sql_validation/add_first_parent.sql b/arkindex/sql_validation/add_first_parent.sql index 296ee48210be34bf8a23848b1b3586df69cbf9cf..31f0ed72bcf776a20f4d58f49ff83d91553a819a 100644 --- a/arkindex/sql_validation/add_first_parent.sql +++ b/arkindex/sql_validation/add_first_parent.sql @@ -30,7 +30,7 @@ SELECT EXISTS (SELECT COALESCE(MAX(ordering) + 1, 0) FROM documents_elementpath WHERE path @> ARRAY['{A}'::uuid] - AND path[array_length(path, 1)] = '{A}'::uuid ) ; + AND path[array_length(path, 1)] = '{A}'::uuid ) ; INSERT INTO documents_elementpath (id, element_id, path, ordering) SELECT gen_random_uuid(), @@ -39,16 +39,15 @@ SELECT gen_random_uuid(), 1 FROM documents_elementpath WHERE element_id = '{A}'::uuid - AND path <> ARRAY['{first_parent}'::uuid]; + AND path <> '{{{first_parent}}}'::uuid[]; UPDATE "documents_elementpath" -SET "path" = ARRAY['{first_parent}'::uuid, - '{A}'::uuid]::uuid[], "ordering" = 1 +SET "path" = '{{{first_parent},{A}}}'::uuid[]::uuid[], "ordering" = 1 WHERE ("documents_elementpath"."element_id" = '{B}'::uuid AND "documents_elementpath"."path" = '{{}}'::uuid[]); UPDATE "documents_elementpath" -SET "path" = array_cat(ARRAY['{first_parent}'::uuid, '{A}'::uuid], "documents_elementpath"."path")::uuid[] +SET "path" = array_cat('{{{first_parent},{A}}}'::uuid[], "documents_elementpath"."path")::uuid[] WHERE "documents_elementpath"."path" && (ARRAY['{B}'::uuid])::uuid[]; INSERT INTO documents_elementpath (id, element_id, path, ordering) @@ -58,10 +57,8 @@ SELECT gen_random_uuid(), child_paths.ordering FROM documents_elementpath child_paths, documents_elementpath new_parent_paths -WHERE child_paths.path @> ARRAY['{first_parent}'::uuid, - '{A}'::uuid, - '{B}'::uuid] +WHERE child_paths.path @> '{{{first_parent},{A},{B}}}'::uuid[] AND new_parent_paths.element_id = '{A}'::uuid - AND new_parent_paths.path <> ARRAY['{first_parent}'::uuid]; + AND new_parent_paths.path <> '{{{first_parent}}}'::uuid[]; RELEASE SAVEPOINT "{savepoint}" diff --git a/arkindex/sql_validation/add_second_parent.sql b/arkindex/sql_validation/add_second_parent.sql index 27375cfedebc965bf8443bed799a01847d6a589c..ad0fb7c23ec502d4152d0519416c5bd24bc1ff5e 100644 --- a/arkindex/sql_validation/add_second_parent.sql +++ b/arkindex/sql_validation/add_second_parent.sql @@ -30,7 +30,7 @@ SELECT EXISTS (SELECT COALESCE(MAX(ordering) + 1, 0) FROM documents_elementpath WHERE path @> ARRAY['{A}'::uuid] - AND path[array_length(path, 1)] = '{A}'::uuid ) ; + AND path[array_length(path, 1)] = '{A}'::uuid ) ; INSERT INTO documents_elementpath (id, element_id, path, ordering) SELECT gen_random_uuid(), @@ -47,8 +47,7 @@ SELECT gen_random_uuid(), child_paths.ordering FROM documents_elementpath child_paths, documents_elementpath new_parent_paths -WHERE child_paths.path @> ARRAY['{K}'::uuid, - '{B}'::uuid] +WHERE child_paths.path @> '{{{K},{B}}}'::uuid[] AND new_parent_paths.element_id = '{A}'::uuid ; RELEASE SAVEPOINT "{savepoint}" diff --git a/arkindex/sql_validation/corpus_delete.sql b/arkindex/sql_validation/corpus_delete.sql index bf5f22713085f427c02cea00062664ba4268beab..f789e6f8c7cd5ca1f78a0ef925f011695f106e65 100644 --- a/arkindex/sql_validation/corpus_delete.sql +++ b/arkindex/sql_validation/corpus_delete.sql @@ -6,7 +6,8 @@ SELECT "documents_corpus"."created", "documents_corpus"."top_level_type_id", "documents_corpus"."public", "documents_corpus"."indexable", - "documents_corpus"."maximum_task_ttl" + "documents_corpus"."maximum_task_ttl", + "documents_corpus"."budget_id" FROM "documents_corpus" WHERE "documents_corpus"."id" = '{corpus_id}'::uuid LIMIT 21; diff --git a/arkindex/sql_validation/corpus_delete_top_level_type.sql b/arkindex/sql_validation/corpus_delete_top_level_type.sql index dbbe418e7389f4b1d9b79c37984632511f432571..d3ccdb0d2d4ceec1105a22f3a6e0dfbd97be161f 100644 --- a/arkindex/sql_validation/corpus_delete_top_level_type.sql +++ b/arkindex/sql_validation/corpus_delete_top_level_type.sql @@ -6,7 +6,8 @@ SELECT "documents_corpus"."created", "documents_corpus"."top_level_type_id", "documents_corpus"."public", "documents_corpus"."indexable", - "documents_corpus"."maximum_task_ttl" + "documents_corpus"."maximum_task_ttl", + "documents_corpus"."budget_id" FROM "documents_corpus" WHERE "documents_corpus"."id" = '{corpus_id}'::uuid LIMIT 21; diff --git a/arkindex/sql_validation/corpus_rights_filter.sql b/arkindex/sql_validation/corpus_rights_filter.sql index 6456c7da350966b8be2f82ef0215810931153463..f914e4f909827dbddd3783b6a5e44ac19f9bb342 100644 --- a/arkindex/sql_validation/corpus_rights_filter.sql +++ b/arkindex/sql_validation/corpus_rights_filter.sql @@ -23,6 +23,7 @@ SELECT "documents_corpus"."created", "documents_corpus"."public", "documents_corpus"."indexable", "documents_corpus"."maximum_task_ttl", + "documents_corpus"."budget_id", LEAST("users_right"."level", T5."level") AS "max_level" FROM "documents_corpus" INNER JOIN "users_right" ON ("documents_corpus"."id" = "users_right"."content_id" diff --git a/arkindex/sql_validation/corpus_rights_filter_public.sql b/arkindex/sql_validation/corpus_rights_filter_public.sql index 60427780297b6d7fb4ab6a5d61865cdbc63bc03e..d46840b4d4d4ed8862c234a1e7a708f7d139b113 100644 --- a/arkindex/sql_validation/corpus_rights_filter_public.sql +++ b/arkindex/sql_validation/corpus_rights_filter_public.sql @@ -24,6 +24,7 @@ LIMIT 21; "documents_corpus"."public", "documents_corpus"."indexable", "documents_corpus"."maximum_task_ttl", + "documents_corpus"."budget_id", LEAST("users_right"."level", T5."level") AS "max_level" FROM "documents_corpus" INNER JOIN "users_right" ON ("documents_corpus"."id" = "users_right"."content_id" @@ -44,6 +45,7 @@ UNION "documents_corpus"."public", "documents_corpus"."indexable", "documents_corpus"."maximum_task_ttl", + "documents_corpus"."budget_id", 10 AS "max_level" FROM "documents_corpus" WHERE "documents_corpus"."public") diff --git a/arkindex/sql_validation/element_dot_delete.sql b/arkindex/sql_validation/element_dot_delete.sql index 31722f6a59efd51b8306722e36224174ad02f723..c28d23ce6ee67814379395dd95fb1a9e9831aa5b 100644 --- a/arkindex/sql_validation/element_dot_delete.sql +++ b/arkindex/sql_validation/element_dot_delete.sql @@ -1,59 +1,75 @@ -DELETE FROM documents_transcriptionentity WHERE transcription_id IN ( - SELECT t.id FROM documents_transcription t - LEFT JOIN documents_elementpath elementpath USING (element_id) - WHERE t.element_id = '{id}'::uuid OR elementpath.path && ARRAY['{id}'::uuid] -) ; +DELETE +FROM documents_transcriptionentity +WHERE transcription_id IN + (SELECT t.id + FROM documents_transcription t + LEFT JOIN documents_elementpath elementpath USING (element_id) + WHERE t.element_id = '{id}'::uuid + OR elementpath.path && ARRAY['{id}'::uuid] ) ; -DELETE FROM documents_transcription +DELETE +FROM documents_transcription WHERE element_id = '{id}'::uuid -OR element_id IN ( - SELECT element_id FROM documents_elementpath WHERE path && ARRAY['{id}'::uuid] -) ; + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; -DELETE FROM documents_classification +DELETE +FROM documents_classification WHERE element_id = '{id}'::uuid -OR element_id IN ( - SELECT element_id FROM documents_elementpath WHERE path && ARRAY['{id}'::uuid] -) ; + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; -DELETE FROM documents_metadata +DELETE +FROM documents_metadata WHERE element_id = '{id}'::uuid -OR element_id IN ( - SELECT element_id FROM documents_elementpath WHERE path && ARRAY['{id}'::uuid] - ) ; + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; -DELETE FROM process_processelement +DELETE +FROM process_processelement WHERE element_id = '{id}'::uuid -OR element_id IN ( - SELECT element_id FROM documents_elementpath WHERE path && ARRAY['{id}'::uuid] -) ; + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; UPDATE process_process SET element_id = NULL WHERE element_id = '{id}'::uuid -OR element_id IN ( - SELECT element_id FROM documents_elementpath WHERE path && ARRAY['{id}'::uuid] -) ; + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; -DELETE FROM documents_selection selection +DELETE +FROM documents_selection selection WHERE element_id = '{id}'::uuid -OR element_id IN ( - SELECT element_id FROM documents_elementpath WHERE path && ARRAY['{id}'::uuid] -) ; + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; -DELETE FROM process_workeractivity +DELETE +FROM process_workeractivity WHERE element_id = '{id}'::uuid -OR element_id IN ( - SELECT element_id FROM documents_elementpath WHERE path && ARRAY['{id}'::uuid] -) ; - -WITH children_ids (id) AS ( - DELETE FROM documents_elementpath - WHERE element_id = '{id}'::uuid OR path && ARRAY['{id}'::uuid] - RETURNING element_id -) -DELETE FROM documents_element element -USING children_ids + OR element_id IN + (SELECT element_id + FROM documents_elementpath + WHERE path && ARRAY['{id}'::uuid] ) ; + +WITH children_ids (id) AS + (DELETE + FROM documents_elementpath + WHERE element_id = '{id}'::uuid + OR path && ARRAY['{id}'::uuid] RETURNING element_id) +DELETE +FROM documents_element element USING children_ids WHERE element.id = children_ids.id ; DELETE diff --git a/arkindex/sql_validation/element_move_with_children.sql b/arkindex/sql_validation/element_move_with_children.sql index 354e208d6070f14d16b1290e5228ee07924927c4..9466ccaeb2ec438e36fb2b593ec58a3ab1d83d89 100644 --- a/arkindex/sql_validation/element_move_with_children.sql +++ b/arkindex/sql_validation/element_move_with_children.sql @@ -21,10 +21,8 @@ WHERE element_id = '{source_id}'::uuid ; UPDATE documents_elementpath SET path = path[2:] -WHERE path @> ARRAY['{parent_id}'::uuid, - '{source_id}'::uuid] - AND path[0:2] = ARRAY['{parent_id}'::uuid, - '{source_id}'::uuid] ; +WHERE path @> '{{{parent_id},{source_id}}}'::uuid[] + AND path[0:2] = '{{{parent_id},{source_id}}}'::uuid[] ; UPDATE "documents_elementpath" SET "path" = '{{}}'::uuid[] @@ -65,15 +63,15 @@ SELECT EXISTS (SELECT COALESCE(MAX(ordering) + 1, 0) FROM documents_elementpath WHERE path @> ARRAY['{destination_id}'::uuid] - AND path[array_length(path, 1)] = '{destination_id}'::uuid ) ; + AND path[array_length(path, 1)] = '{destination_id}'::uuid ) ; UPDATE "documents_elementpath" -SET "path" = ARRAY['{destination_id}'::uuid]::uuid[], "ordering" = 3 +SET "path" = '{{{destination_id}}}'::uuid[]::uuid[], "ordering" = 3 WHERE ("documents_elementpath"."element_id" = '{source_id}'::uuid AND "documents_elementpath"."path" = '{{}}'::uuid[]); UPDATE "documents_elementpath" -SET "path" = array_cat(ARRAY['{destination_id}'::uuid], "documents_elementpath"."path")::uuid[] +SET "path" = array_cat('{{{destination_id}}}'::uuid[], "documents_elementpath"."path")::uuid[] WHERE "documents_elementpath"."path" && (ARRAY['{source_id}'::uuid])::uuid[]; RELEASE SAVEPOINT "{savepoints[1]}" diff --git a/arkindex/sql_validation/element_move_without_child.sql b/arkindex/sql_validation/element_move_without_child.sql index 354e208d6070f14d16b1290e5228ee07924927c4..9466ccaeb2ec438e36fb2b593ec58a3ab1d83d89 100644 --- a/arkindex/sql_validation/element_move_without_child.sql +++ b/arkindex/sql_validation/element_move_without_child.sql @@ -21,10 +21,8 @@ WHERE element_id = '{source_id}'::uuid ; UPDATE documents_elementpath SET path = path[2:] -WHERE path @> ARRAY['{parent_id}'::uuid, - '{source_id}'::uuid] - AND path[0:2] = ARRAY['{parent_id}'::uuid, - '{source_id}'::uuid] ; +WHERE path @> '{{{parent_id},{source_id}}}'::uuid[] + AND path[0:2] = '{{{parent_id},{source_id}}}'::uuid[] ; UPDATE "documents_elementpath" SET "path" = '{{}}'::uuid[] @@ -65,15 +63,15 @@ SELECT EXISTS (SELECT COALESCE(MAX(ordering) + 1, 0) FROM documents_elementpath WHERE path @> ARRAY['{destination_id}'::uuid] - AND path[array_length(path, 1)] = '{destination_id}'::uuid ) ; + AND path[array_length(path, 1)] = '{destination_id}'::uuid ) ; UPDATE "documents_elementpath" -SET "path" = ARRAY['{destination_id}'::uuid]::uuid[], "ordering" = 3 +SET "path" = '{{{destination_id}}}'::uuid[]::uuid[], "ordering" = 3 WHERE ("documents_elementpath"."element_id" = '{source_id}'::uuid AND "documents_elementpath"."path" = '{{}}'::uuid[]); UPDATE "documents_elementpath" -SET "path" = array_cat(ARRAY['{destination_id}'::uuid], "documents_elementpath"."path")::uuid[] +SET "path" = array_cat('{{{destination_id}}}'::uuid[], "documents_elementpath"."path")::uuid[] WHERE "documents_elementpath"."path" && (ARRAY['{source_id}'::uuid])::uuid[]; RELEASE SAVEPOINT "{savepoints[1]}" diff --git a/arkindex/sql_validation/indexer_prefetch.sql b/arkindex/sql_validation/indexer_prefetch.sql index 74c6e2bc86bd08c3d908b82bbe0186e9c9f945b8..31118a36fb017e3469755d35d223db5b4a8e2bef 100644 --- a/arkindex/sql_validation/indexer_prefetch.sql +++ b/arkindex/sql_validation/indexer_prefetch.sql @@ -5,7 +5,7 @@ SELECT element.id AS parent_id, element.name AS name, elementtype.display_name AS type_name, element.image_id AS image_id, - element.polygon::bytea AS polygon, + element.polygon AS polygon, element.worker_run_id AS worker_run_id FROM documents_element element INNER JOIN documents_elementtype elementtype ON (elementtype.id = element.type_id) @@ -21,7 +21,7 @@ WITH parent AS element.name AS name, elementtype.display_name AS type_name, element.image_id AS image_id, - element.polygon::bytea AS polygon, + element.polygon AS polygon, element.worker_run_id AS worker_run_id FROM documents_element element INNER JOIN documents_elementtype elementtype ON (elementtype.id = element.type_id) @@ -35,7 +35,7 @@ SELECT parent_id, element.name as name, elementtype.display_name as type_name, element.image_id AS image_id, - element.polygon::bytea AS polygon, + element.polygon AS polygon, element.worker_run_id AS worker_run_id FROM (SELECT * @@ -56,8 +56,7 @@ SELECT "process_workerrun"."id", "process_workerrun"."created", "process_workerrun"."updated", "process_workerrun"."has_results", - "process_workerrun"."use_gpu", - "process_workerrun"."ttl" + "process_workerrun"."use_gpu" FROM "process_workerrun" WHERE "process_workerrun"."id" IN ('{worker_run_id}'::uuid); @@ -152,8 +151,7 @@ SELECT "process_workerrun"."id", "process_workerrun"."created", "process_workerrun"."updated", "process_workerrun"."has_results", - "process_workerrun"."use_gpu", - "process_workerrun"."ttl" + "process_workerrun"."use_gpu" FROM "process_workerrun" WHERE "process_workerrun"."id" IN ('{worker_run_id}'::uuid); diff --git a/arkindex/sql_validation/list_elements.sql b/arkindex/sql_validation/list_elements.sql index 98e3284cfa465154415b194f505440a0ae88d3b5..1a542e371a3e03cecff3059fe69f92ab010b7ee5 100644 --- a/arkindex/sql_validation/list_elements.sql +++ b/arkindex/sql_validation/list_elements.sql @@ -6,7 +6,8 @@ SELECT "documents_corpus"."created", "documents_corpus"."top_level_type_id", "documents_corpus"."public", "documents_corpus"."indexable", - "documents_corpus"."maximum_task_ttl" + "documents_corpus"."maximum_task_ttl", + "documents_corpus"."budget_id" FROM "documents_corpus" WHERE "documents_corpus"."id" = '{corpus_id}'::uuid LIMIT 21; @@ -26,7 +27,7 @@ SELECT "documents_element"."id", "documents_element"."worker_version_id", "documents_element"."worker_run_id", "documents_element"."image_id", - "documents_element"."polygon"::bytea, + "documents_element"."polygon", "documents_element"."rotation_angle", "documents_element"."mirrored", "documents_element"."confidence", @@ -40,8 +41,7 @@ SELECT "documents_element"."id", "process_workerrun"."created", "process_workerrun"."updated", "process_workerrun"."has_results", - "process_workerrun"."use_gpu", - "process_workerrun"."ttl" + "process_workerrun"."use_gpu" FROM "documents_element" LEFT OUTER JOIN "process_workerrun" ON ("documents_element"."worker_run_id" = "process_workerrun"."id") WHERE ("documents_element"."corpus_id" = '{corpus_id}'::uuid diff --git a/arkindex/sql_validation/process_elements_filter_ml_class.sql b/arkindex/sql_validation/process_elements_filter_ml_class.sql index fb1e9d4389cea1361a85639375aad08f83c50b18..bc15a46955079de959819a7ad85860be0e2e951f 100644 --- a/arkindex/sql_validation/process_elements_filter_ml_class.sql +++ b/arkindex/sql_validation/process_elements_filter_ml_class.sql @@ -52,7 +52,8 @@ SELECT "documents_corpus"."created", "documents_corpus"."top_level_type_id", "documents_corpus"."public", "documents_corpus"."indexable", - "documents_corpus"."maximum_task_ttl" + "documents_corpus"."maximum_task_ttl", + "documents_corpus"."budget_id" FROM "documents_corpus" WHERE "documents_corpus"."id" = '{corpus_id}'::uuid LIMIT 21; diff --git a/arkindex/sql_validation/process_elements_filter_type.sql b/arkindex/sql_validation/process_elements_filter_type.sql index a566bc4a782d58d2fe29839372458bf19dfb37d8..931808c1ed99f5d98d030add763a5103a9bbee20 100644 --- a/arkindex/sql_validation/process_elements_filter_type.sql +++ b/arkindex/sql_validation/process_elements_filter_type.sql @@ -52,7 +52,8 @@ SELECT "documents_corpus"."created", "documents_corpus"."top_level_type_id", "documents_corpus"."public", "documents_corpus"."indexable", - "documents_corpus"."maximum_task_ttl" + "documents_corpus"."maximum_task_ttl", + "documents_corpus"."budget_id" FROM "documents_corpus" WHERE "documents_corpus"."id" = '{corpus_id}'::uuid LIMIT 21; diff --git a/arkindex/sql_validation/process_elements_top_level.sql b/arkindex/sql_validation/process_elements_top_level.sql index 77423e4582a0ca0620335a39ec096af91e4ee5b0..d10220c4fe27cfe33da7fe6b9eaf4aaf67543ff2 100644 --- a/arkindex/sql_validation/process_elements_top_level.sql +++ b/arkindex/sql_validation/process_elements_top_level.sql @@ -52,7 +52,8 @@ SELECT "documents_corpus"."created", "documents_corpus"."top_level_type_id", "documents_corpus"."public", "documents_corpus"."indexable", - "documents_corpus"."maximum_task_ttl" + "documents_corpus"."maximum_task_ttl", + "documents_corpus"."budget_id" FROM "documents_corpus" WHERE "documents_corpus"."id" = '{corpus_id}'::uuid LIMIT 21; diff --git a/arkindex/sql_validation/process_elements_with_image.sql b/arkindex/sql_validation/process_elements_with_image.sql index 2b6d311781ba790d1e63af7d571895f2c1beba55..75a14883916f3a452c2c789c71309237b4fe83e5 100644 --- a/arkindex/sql_validation/process_elements_with_image.sql +++ b/arkindex/sql_validation/process_elements_with_image.sql @@ -52,7 +52,8 @@ SELECT "documents_corpus"."created", "documents_corpus"."top_level_type_id", "documents_corpus"."public", "documents_corpus"."indexable", - "documents_corpus"."maximum_task_ttl" + "documents_corpus"."maximum_task_ttl", + "documents_corpus"."budget_id" FROM "documents_corpus" WHERE "documents_corpus"."id" = '{corpus_id}'::uuid LIMIT 21; @@ -70,7 +71,7 @@ SELECT "documents_element"."id", "documents_element"."image_id", "images_image"."width", "images_image"."height", - "documents_element"."polygon"::bytea, + "documents_element"."polygon", "documents_element"."rotation_angle", "documents_element"."mirrored", NULLIF(CONCAT((RTRIM("images_imageserver"."url", '/'))::text, (CONCAT(('/')::text, ("images_image"."path")::text))::text), '/') AS "image_url" diff --git a/arkindex/sql_validation/remove_child_last_parent.sql b/arkindex/sql_validation/remove_child_last_parent.sql index fa81733d12fb29f028d5348cb35ed6d96794c7df..c44943c5c5d5fecb42cfe93376550a70bf69b7b2 100644 --- a/arkindex/sql_validation/remove_child_last_parent.sql +++ b/arkindex/sql_validation/remove_child_last_parent.sql @@ -14,12 +14,8 @@ WHERE element_id = '{B}'::uuid ; UPDATE documents_elementpath SET path = path[3:] -WHERE path @> ARRAY['{first_parent}'::uuid, - '{A}'::uuid, - '{B}'::uuid] - AND path[0:3] = ARRAY['{first_parent}'::uuid, - '{A}'::uuid, - '{B}'::uuid] ; +WHERE path @> '{{{first_parent},{A},{B}}}'::uuid[] + AND path[0:3] = '{{{first_parent},{A},{B}}}'::uuid[] ; UPDATE "documents_elementpath" SET "path" = '{{}}'::uuid[] diff --git a/arkindex/sql_validation/remove_children_multiple_parents.sql b/arkindex/sql_validation/remove_children_multiple_parents.sql index c1565c51de3235b33068628fec68dd730438e3c6..03cfbf7a930623288d84db198474463dc58fcde5 100644 --- a/arkindex/sql_validation/remove_children_multiple_parents.sql +++ b/arkindex/sql_validation/remove_children_multiple_parents.sql @@ -10,7 +10,7 @@ LIMIT 1; DELETE FROM documents_elementpath child_paths USING documents_elementpath parent_paths WHERE parent_paths.element_id = '{A}'::uuid - AND parent_paths.path <> ARRAY['{first_parent}'::uuid] + AND parent_paths.path <> '{{{first_parent}}}'::uuid[] AND child_paths.path @> (parent_paths.path || '{A}'::uuid) ; DELETE @@ -21,9 +21,7 @@ WHERE parent_paths.path && ARRAY['{A}'::uuid] UPDATE documents_elementpath SET path = path[2 + 1:] -WHERE path @> ARRAY['{first_parent}'::uuid, - '{A}'::uuid] - AND path[:2] = ARRAY['{first_parent}'::uuid, - '{A}'::uuid] ; +WHERE path @> '{{{first_parent},{A}}}'::uuid[] + AND path[:2] = '{{{first_parent},{A}}}'::uuid[] ; RELEASE SAVEPOINT "{savepoint}" diff --git a/arkindex/sql_validation/remove_children_no_parents.sql b/arkindex/sql_validation/remove_children_no_parents.sql index dba5386430e325efe67307007ec0b1af839f9177..1c939384a7fb430f580ee82865888dd7de999ab8 100644 --- a/arkindex/sql_validation/remove_children_no_parents.sql +++ b/arkindex/sql_validation/remove_children_no_parents.sql @@ -15,7 +15,7 @@ WHERE parent_paths.path && ARRAY['{A}'::uuid] UPDATE documents_elementpath SET path = path[1 + 1:] -WHERE path @> ARRAY['{A}'::uuid] - AND path[:1] = ARRAY['{A}'::uuid] ; +WHERE path @> '{{{A}}}'::uuid[] + AND path[:1] = '{{{A}}}'::uuid[] ; RELEASE SAVEPOINT "{savepoint}" diff --git a/arkindex/sql_validation/remove_children_single_parent.sql b/arkindex/sql_validation/remove_children_single_parent.sql index 7d86e0d01945344eb36a0f4c88df5e20f5eaca33..e884f1d34f65b6c66dee27eb800eca041034546c 100644 --- a/arkindex/sql_validation/remove_children_single_parent.sql +++ b/arkindex/sql_validation/remove_children_single_parent.sql @@ -15,9 +15,7 @@ WHERE parent_paths.path && ARRAY['{A}'::uuid] UPDATE documents_elementpath SET path = path[2 + 1:] -WHERE path @> ARRAY['{X}'::uuid, - '{A}'::uuid] - AND path[:2] = ARRAY['{X}'::uuid, - '{A}'::uuid] ; +WHERE path @> '{{{X},{A}}}'::uuid[] + AND path[:2] = '{{{X},{A}}}'::uuid[] ; RELEASE SAVEPOINT "{savepoint}" diff --git a/arkindex/sql_validation/workeractivity_bulk_insert_no_model.sql b/arkindex/sql_validation/workeractivity_bulk_insert_no_model.sql index cd539dd953c2b4c3a74fd7ccc0009cc4da7b1ea5..461456a39e64b4b0a49370fbcd3314ee6df90cfb 100644 --- a/arkindex/sql_validation/workeractivity_bulk_insert_no_model.sql +++ b/arkindex/sql_validation/workeractivity_bulk_insert_no_model.sql @@ -15,7 +15,7 @@ FROM WHERE ("documents_elementtype"."corpus_id" = '{corpus_id}'::uuid AND "documents_elementtype"."slug" = 'act')) AS elt ON CONFLICT (worker_version_id, element_id, - configuration_id) + configuration_id) WHERE configuration_id IS NOT NULL AND model_version_id IS NULL DO UPDATE diff --git a/arkindex/system_workers.yml b/arkindex/system_workers.yml index 03efc879b2a34400fa2164c4c93b761867682b87..7bf51563bf61461ac0e58537e2ed25a87035096c 100644 --- a/arkindex/system_workers.yml +++ b/arkindex/system_workers.yml @@ -1,7 +1,7 @@ # When releasing Arkindex, check that the Docker images set here are up to date, # then update the `version` to the current Arkindex version as set in the `VERSION` file # to confirm that the images have been manually checked. -version: 1.7.2 +version: 1.7.3 features: file_import: @@ -10,7 +10,7 @@ features: image: registry.gitlab.teklia.com/arkindex/workers/init-elements:0.1.1 command: worker-init-elements s3_ingest: - image: registry.gitlab.teklia.com/arkindex/workers/import/s3:0.2.0rc3 + image: registry.gitlab.teklia.com/arkindex/workers/import/s3:0.2.0 pdf_export: teklia_worker: name: arkindex/workers/export @@ -31,3 +31,8 @@ features: name: arkindex/workers/export version: 0.2.1 slug: csv-export + dataset_extractor: + teklia_worker: + name: arkindex/workers/generic-training-dataset + version: 0.3.0 + slug: generic-training-dataset diff --git a/arkindex/training/api.py b/arkindex/training/api.py index b23502c88fa40f70063e5386a1d748f6cedffe78..fd56c44f0ad256b92d1c1bd4a74ac3ecb1f627dc 100644 --- a/arkindex/training/api.py +++ b/arkindex/training/api.py @@ -69,9 +69,16 @@ def _fetch_datasetelement_neighbors(datasetelements): """ if not datasetelements: return datasetelements + + # psycopg 3 does not support `IN %s`, so we use a variable amount of placeholders for each ID instead. + # https://www.psycopg.org/psycopg3/docs/basic/from_pg2.html#you-cannot-use-in-s-with-a-tuple + # We use named placeholders with list indices so that we can refer to the same ID twice, + # because we are filtering on DatasetElement IDs twice + placeholders = ",".join(f"%({i})s" for i in range(len(datasetelements))) + with connection.cursor() as cursor: cursor.execute( - """ + f""" WITH neighbors AS ( SELECT n.id, @@ -89,7 +96,7 @@ def _fetch_datasetelement_neighbors(datasetelements): WHERE set_id IN ( SELECT set_id FROM training_datasetelement - WHERE id IN %(ids)s + WHERE id IN ({placeholders}) ) ORDER BY n.element_id ) @@ -97,9 +104,9 @@ def _fetch_datasetelement_neighbors(datasetelements): neighbors.id, neighbors.previous, neighbors.next FROM neighbors - WHERE neighbors.id in %(ids)s + WHERE neighbors.id in ({placeholders}) """, - {"ids": tuple(datasetelement.id for datasetelement in datasetelements)} + {str(i): datasetelement.id for i, datasetelement in enumerate(datasetelements)}, ) neighbors = { diff --git a/base/Dockerfile b/base/Dockerfile index 5bf809476cd2e090d7ec41a60b3a4184a4bad7db..1efe60a29f7c9116f288922d513c323341980f59 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-slim-bookworm +FROM python:3.12-slim-bookworm # Install some runtime deps and python dependencies that are slow to install or require specific build deps ADD requirements.txt bootstrap.sh / diff --git a/base/requirements.txt b/base/requirements.txt index 3d48b30e397a495b3470c6e592663d6ce13c060f..691940d4e599534cbf466336b306b711a50d9f12 100644 --- a/base/requirements.txt +++ b/base/requirements.txt @@ -1,6 +1,3 @@ -boto3==1.18.13 -cryptography==3.4.7 +boto3==1.36.16 +cryptography==44.0.1 Django==5.0.8 -ed25519==1.5 -lxml==4.9.2 -psycopg2-binary==2.9.1 diff --git a/requirements.txt b/requirements.txt index fdfda8d85c4c1f1858e6c14a93588fde9d5aa9b9..29cf9f325bc4239d08d8d281d51889df9cd77162 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ # -r ./base/requirements.txt - bleach==6.0.0 django-admin-hstore-widget==1.2.1 django-cors-headers==3.14.0 @@ -8,12 +7,11 @@ django-enumfields2==3.0.2 django-pgtrigger==4.7.0 django-rq==2.10.1 djangorestframework==3.15.2 -djangorestframework-simplejwt==5.2.2 docker==7.0.0 drf-spectacular==0.27.2 +psycopg[binary]==3.2.4 python-magic==0.4.27 python-memcached==1.59 -PyYAML==6.0 requests==2.28.2 rq==1.16.0 sentry-sdk==2.7.1 @@ -22,4 +20,4 @@ SolrClient==0.3.1 teklia-toolbox==0.1.3 tenacity==8.2.2 uritemplate==4.1.1 -zstandard==0.20.0 +zstandard==0.23.0 diff --git a/ruff.toml b/ruff.toml index 8a02c65695d77b3c18cae483a6ff6db75abbd0ba..453bb5b5c9bb7afaa8413f7c29e6968ee5404854 100644 --- a/ruff.toml +++ b/ruff.toml @@ -45,7 +45,7 @@ known-third-party = [ "drf_spectacular", "enumfields", "gitlab", - "psycopg2", + "psycopg", "requests", "responses", "rest_framework", diff --git a/setup.py b/setup.py index fdd40bb80d4261c25a3fc17db605abceb2aa2cba..6dce684b6c90c29fe11f43d69c11405276647787 100755 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ setup( license_files=("LICENSE",), description="Manuscripts indexation framework", author="Teklia", - author_email="abadie@teklia.com", + author_email="contact@teklia.com", url="https://arkindex.teklia.com", python_requires=">=3.10", install_requires=install_requires,