Skip to content

Commit a894888

Browse files
authored
Use proper serializer_class for the ProjectViewSet (#1952)
Signed-off-by: tdruez <[email protected]>
1 parent a55913d commit a894888

File tree

5 files changed

+119
-34
lines changed

5 files changed

+119
-34
lines changed

.github/workflows/run-unit-tests-macos.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ env:
1717

1818
jobs:
1919
run-unit-tests:
20-
runs-on: macos-13
20+
runs-on: macos-15
2121

2222
strategy:
2323
matrix:
@@ -32,16 +32,18 @@ jobs:
3232
with:
3333
python-version: ${{ matrix.python-version }}
3434

35-
- name: Set up Python ${{ matrix.python-version }}
36-
uses: ikalnytskyi/action-setup-postgres@v7
37-
id: postgres
35+
- name: Set up PostgreSQL
36+
uses: ikalnytskyi/action-setup-postgres@v8
3837
with:
39-
postgres-version: "14" # 13 is not supported.
38+
postgres-version: "17"
4039
database: ${{ env.POSTGRES_DB }}
4140
username: ${{ env.POSTGRES_USER }}
4241
password: ${{ env.POSTGRES_PASSWORD }}
4342
port: 5432
4443

44+
- name: Install libmagic
45+
run: brew install libmagic
46+
4547
- name: Install Python dependencies
4648
run: make dev envfile
4749

.github/workflows/run-unit-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222

2323
services:
2424
postgres:
25-
image: postgres:13
25+
image: postgres:17
2626
env:
2727
POSTGRES_DB: ${{ env.POSTGRES_DB }}
2828
POSTGRES_USER: ${{ env.POSTGRES_USER }}

scanpipe/api/serializers.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,20 @@ def get_traceback(self, project_error):
518518
return project_error.traceback.splitlines()
519519

520520

521+
class InputSerializer(serializers.Serializer):
522+
"""Serializer used in the `ProjectViewSet.add_input` action."""
523+
524+
upload_file = serializers.FileField(required=False, help_text="A file to upload.")
525+
upload_file_tag = serializers.CharField(
526+
required=False,
527+
allow_blank=True,
528+
help_text="An optional tag to add on the uploaded file.",
529+
)
530+
input_urls = serializers.ListField(
531+
required=False, default=list, help_text="A list of URLs to download."
532+
)
533+
534+
521535
class PipelineSerializer(PipelineChoicesMixin, serializers.ModelSerializer):
522536
"""Serializer used in the `ProjectViewSet.add_pipeline` action."""
523537

@@ -536,6 +550,50 @@ class Meta:
536550
]
537551

538552

553+
class ProjectArchiveSerializer(serializers.Serializer):
554+
"""Serializer for the `ProjectViewSet.archive` action."""
555+
556+
remove_input = serializers.BooleanField(
557+
required=False,
558+
default=False,
559+
help_text=(
560+
"Delete the input directory during archival. "
561+
"InputSource entries are kept for reference."
562+
),
563+
)
564+
remove_codebase = serializers.BooleanField(
565+
required=False,
566+
default=False,
567+
help_text="Delete the codebase directory during archival.",
568+
)
569+
remove_output = serializers.BooleanField(
570+
required=False,
571+
default=False,
572+
help_text="Delete the output directory during archival.",
573+
)
574+
575+
576+
class ProjectResetSerializer(serializers.Serializer):
577+
"""Serializer for the `ProjectViewSet.reset` action."""
578+
579+
keep_input = serializers.BooleanField(
580+
required=False,
581+
default=True,
582+
initial=True,
583+
help_text="Keep the input directory and input sources when resetting.",
584+
)
585+
restore_pipelines = serializers.BooleanField(
586+
required=False,
587+
default=False,
588+
help_text="Restore all pipelines that were previously existing on the project.",
589+
)
590+
execute_now = serializers.BooleanField(
591+
required=False,
592+
default=False,
593+
help_text="Execute the restored pipelines immediately after restoration.",
594+
)
595+
596+
539597
def get_model_serializer(model_class):
540598
"""Return a Serializer class that ia related to a given `model_class`."""
541599
serializer = {

scanpipe/api/views.py

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@
4141
from scanpipe.api.serializers import CodebaseResourceSerializer
4242
from scanpipe.api.serializers import DiscoveredDependencySerializer
4343
from scanpipe.api.serializers import DiscoveredPackageSerializer
44+
from scanpipe.api.serializers import InputSerializer
4445
from scanpipe.api.serializers import PipelineSerializer
46+
from scanpipe.api.serializers import ProjectArchiveSerializer
4547
from scanpipe.api.serializers import ProjectMessageSerializer
48+
from scanpipe.api.serializers import ProjectResetSerializer
4649
from scanpipe.api.serializers import ProjectSerializer
4750
from scanpipe.api.serializers import RunSerializer
4851
from scanpipe.api.serializers import WebhookSubscriptionSerializer
@@ -356,7 +359,7 @@ def add_pipeline(self, request, *args, **kwargs):
356359
}
357360
return ErrorResponse(message)
358361

359-
@action(detail=True, methods=["get", "post"])
362+
@action(detail=True, methods=["get", "post"], serializer_class=InputSerializer)
360363
def add_input(self, request, *args, **kwargs):
361364
project = self.get_object()
362365

@@ -365,9 +368,15 @@ def add_input(self, request, *args, **kwargs):
365368
"Cannot add inputs once a pipeline has started to execute."
366369
)
367370

368-
upload_file = request.data.get("upload_file")
369-
upload_file_tag = request.data.get("upload_file_tag", "")
370-
input_urls = request.data.get("input_urls", [])
371+
# Validate input using the action serializer
372+
serializer = self.get_serializer(data=request.data)
373+
if not serializer.is_valid():
374+
return ErrorResponse(serializer.errors)
375+
376+
# Extract validated data
377+
upload_file = serializer.validated_data.get("upload_file")
378+
upload_file_tag = serializer.validated_data.get("upload_file_tag", "")
379+
input_urls = serializer.validated_data.get("input_urls", [])
371380

372381
if not (upload_file or input_urls):
373382
return ErrorResponse("upload_file or input_urls required.")
@@ -393,24 +402,25 @@ def add_input(self, request, *args, **kwargs):
393402
def add_webhook(self, request, *args, **kwargs):
394403
project = self.get_object()
395404

396-
# Validate input using the serializer
397-
serializer = WebhookSubscriptionSerializer(data=request.data)
398-
if serializer.is_valid():
399-
project.add_webhook_subscription(**serializer.validated_data)
400-
return Response(
401-
{"status": "Webhook added."}, status=status.HTTP_201_CREATED
402-
)
405+
# Validate input using the action serializer
406+
serializer = self.get_serializer(data=request.data)
407+
if not serializer.is_valid():
408+
return ErrorResponse(serializer.errors)
403409

404-
# Return validation errors
405-
return ErrorResponse(serializer.errors)
410+
project.add_webhook_subscription(**serializer.validated_data)
411+
return Response({"status": "Webhook added."}, status=status.HTTP_201_CREATED)
406412

407413
def destroy(self, request, *args, **kwargs):
408414
try:
409415
return super().destroy(request, *args, **kwargs)
410416
except RunInProgressError:
411417
return ErrorResponse("Cannot delete project while a run is in progress.")
412418

413-
@action(detail=True, methods=["get", "post"])
419+
@action(
420+
detail=True,
421+
methods=["get", "post"],
422+
serializer_class=ProjectArchiveSerializer,
423+
)
414424
def archive(self, request, *args, **kwargs):
415425
project = self.get_object()
416426

@@ -423,36 +433,41 @@ def archive(self, request, *args, **kwargs):
423433
)
424434
return Response({"status": message})
425435

436+
# Validate input using the action serializer
437+
serializer = self.get_serializer(data=request.data)
438+
if not serializer.is_valid():
439+
return ErrorResponse(serializer.errors)
440+
426441
try:
427-
project.archive(
428-
remove_input=request.data.get("remove_input"),
429-
remove_codebase=request.data.get("remove_codebase"),
430-
remove_output=request.data.get("remove_output"),
431-
)
442+
project.archive(**serializer.validated_data)
432443
except RunInProgressError:
433444
return ErrorResponse("Cannot archive project while a run is in progress.")
434445

435446
return Response({"status": f"The project {project} has been archived."})
436447

437-
@action(detail=True, methods=["get", "post"])
448+
@action(
449+
detail=True,
450+
methods=["get", "post"],
451+
serializer_class=ProjectResetSerializer,
452+
)
438453
def reset(self, request, *args, **kwargs):
439454
project = self.get_object()
440455

441456
if self.request.method == "GET":
442457
message = "POST on this URL to reset the project."
443458
return Response({"status": message})
444459

460+
# Validate input using the action serializer
461+
serializer = self.get_serializer(data=request.data)
462+
if not serializer.is_valid():
463+
return ErrorResponse(serializer.errors)
464+
445465
try:
446-
project.reset(
447-
keep_input=request.data.get("keep_input", True),
448-
restore_pipelines=request.data.get("restore_pipelines", False),
449-
execute_now=request.data.get("execute_now", False),
450-
)
466+
project.reset(**serializer.validated_data)
451467
except RunInProgressError:
452468
return ErrorResponse("Cannot reset project while a run is in progress.")
453-
else:
454-
message = f"The {project} project has been reset."
455-
return Response({"status": message})
469+
470+
return Response({"status": f"The {project} project has been reset."})
456471

457472
@action(detail=True, methods=["get"])
458473
def outputs(self, request, *args, **kwargs):

scanpipe/tests/test_api.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,16 @@ def test_scanpipe_api_project_action_add_input(self):
10351035
self.assertEqual(data["input_urls"], input_source.download_url)
10361036
self.assertEqual("tag", input_source.tag)
10371037

1038+
data = {
1039+
"input_urls": ["docker://alpine", "docker://postgresql"],
1040+
}
1041+
response = self.csrf_client.post(url, data=data)
1042+
self.assertEqual({"status": "Input(s) added."}, response.data)
1043+
input_sources = self.project1.inputsources.filter(
1044+
download_url__startswith="docker://"
1045+
)
1046+
self.assertEqual(2, len(input_sources))
1047+
10381048
data = {
10391049
"upload_file": io.BytesIO(b"Content"),
10401050
"upload_file_tag": "tag value",

0 commit comments

Comments
 (0)