Skip to content

Commit ea25f6a

Browse files
committed
fix lint and add dependencies check
1 parent 7e23d89 commit ea25f6a

File tree

4 files changed

+61
-18
lines changed

4 files changed

+61
-18
lines changed

gateway/api/use_cases/functions/upload.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from django.contrib.auth.models import AbstractUser
77
from rest_framework import serializers
8-
from rest_framework.utils.serializer_helpers import ReturnDict
98

109
from api.access_policies.providers import ProviderAccessPolicy
1110
from api.domain.exceptions.bad_request import BadRequest
@@ -35,6 +34,9 @@ class UploadFunctionData:
3534
description: Optional[str] = None
3635

3736
def dict(self):
37+
"""
38+
Transforms dataclass to dict
39+
"""
3840
the_dict = {k: str(v) for k, v in asdict(self).items() if v}
3941
title = the_dict.pop("function_title")
4042
the_dict["title"] = title

gateway/api/v1/endpoint_handle_exceptions.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,11 @@ def wrapped_view(*args, **kwargs):
4949
status=status.HTTP_403_FORBIDDEN,
5050
)
5151
except BadRequest as error:
52-
print("BadRequest")
53-
print(error)
5452
return Response(
5553
{"message": error.message},
5654
status=status.HTTP_400_BAD_REQUEST,
5755
)
5856
except ValidationError as error:
59-
print("ValidationError")
60-
print(error)
61-
print(error.detail)
6257
return Response(
6358
{"message": _first_error_message(error.detail)},
6459
status=status.HTTP_400_BAD_REQUEST,

gateway/api/v1/views/programs_new/upload.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
from django.contrib.auth.models import AbstractUser
1212
from django.core.exceptions import ValidationError
1313
from django.core.validators import FileExtensionValidator
14+
from packaging.requirements import Requirement, InvalidRequirement
1415
from rest_framework import permissions, serializers
1516
from rest_framework.decorators import api_view, permission_classes
1617
from rest_framework.request import Request
1718
from rest_framework.response import Response
1819

1920
from api.models import Program
2021
from api.use_cases.functions.upload import UploadFunctionData, FunctionUploadUseCase
21-
from api.utils import encrypt_env_vars
22+
from api.utils import encrypt_env_vars, check_whitelisted
2223
from api.v1.endpoint_decorator import endpoint
2324
from api.v1.endpoint_handle_exceptions import endpoint_handle_exceptions
2425
from api.v1.views.serializer_utils import SanitizedCharField
@@ -31,7 +32,6 @@ def get_upload_path(instance, filename):
3132
return f"{instance.author.username}/{instance.id}/{filename}"
3233

3334

34-
# TODO: review validation inheritances such as dependencies
3535
class InputSerializer(serializers.Serializer):
3636
"""
3737
Validates and sanitizes query parameters for the jobs list endpoint.
@@ -71,13 +71,67 @@ def _normalize_dependency(self, raw_dependency):
7171

7272
return dependency_name + dependency_version
7373

74+
def _parse_dependency(self, dep: Any):
75+
if not isinstance(dep, dict) and not isinstance(dep, str):
76+
raise ValidationError(
77+
"'dependencies' should be an array with strings or dict."
78+
)
79+
80+
if isinstance(dep, str):
81+
dep_string = dep
82+
else:
83+
dep_name = list(dep.keys())
84+
if len(dep_name) > 1 or len(dep_name) == 0:
85+
raise ValidationError(
86+
"'dependencies' should be an array with dict containing one dependency only."
87+
)
88+
dep_name = str(dep_name[0])
89+
dep_version = str(list(dep.values())[0])
90+
91+
# if starts with a number then prefix ==
92+
try:
93+
if int(dep_version[0]) >= 0:
94+
dep_version = f"=={dep_version}"
95+
except ValueError:
96+
pass
97+
98+
dep_string = dep_name + dep_version
99+
100+
requirement = Requirement(dep_string)
101+
req_specifier_list = list(requirement.specifier)
102+
req_specifier_first = next(iter(req_specifier_list), None)
103+
104+
if len(req_specifier_list) > 1 or (
105+
req_specifier_first and req_specifier_first.operator != "=="
106+
):
107+
raise ValidationError(
108+
"'dependencies' needs one fixed version using the '==' operator."
109+
)
110+
111+
return requirement
112+
74113
def validate_dependencies(self, value: list):
75114
"""
76115
Validates the function title
77116
"""
78117
if not isinstance(value, list):
79118
raise serializers.ValidationError("'dependencies' should be a list.")
80119

120+
if len(value) == 0:
121+
return []
122+
123+
try:
124+
required_deps = [self._parse_dependency(dep) for dep in value]
125+
except InvalidRequirement as invalid_requirement:
126+
raise ValidationError(
127+
"Error while parsing dependencies."
128+
) from invalid_requirement
129+
130+
try:
131+
check_whitelisted(required_deps)
132+
except ValueError as value_error:
133+
raise ValidationError(value_error.args[0]) from value_error
134+
81135
return [self._normalize_dependency(dep) for dep in value]
82136

83137
def validate_artifact(self, value):
@@ -121,7 +175,8 @@ def validate(self, attrs):
121175
else:
122176
if len(title_split) > 1:
123177
raise ValidationError(
124-
"Qiskit Function title is malformed. It cannot contain title with slash and provider."
178+
"Qiskit Function title is malformed. "
179+
"It cannot contain title with slash and provider."
125180
)
126181

127182
if env_vars:

gateway/tests/api/test_v1_program.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ def test_provider_programs_list(self):
4747

4848
programs_response = self.client.get(reverse("v1:programs-list"), format="json")
4949

50-
print(programs_response)
51-
print(programs_response.reason_phrase)
5250
self.assertEqual(programs_response.status_code, status.HTTP_200_OK)
5351
self.assertEqual(len(programs_response.data), 1)
5452
self.assertEqual(
@@ -70,8 +68,6 @@ def test_provider_programs_catalog_list(self):
7068
reverse("v1:programs-list"), {"filter": "catalog"}, format="json"
7169
)
7270

73-
print(programs_response)
74-
print(programs_response.reason_phrase)
7571
self.assertEqual(programs_response.status_code, status.HTTP_200_OK)
7672
self.assertEqual(len(programs_response.data), 2)
7773
self.assertEqual(
@@ -101,9 +97,6 @@ def test_provider_programs_serverless_list(self):
10197
reverse("v1:programs-list"), {"filter": "serverless"}, format="json"
10298
)
10399

104-
print(programs_response)
105-
print(programs_response.reason_phrase)
106-
107100
self.assertEqual(programs_response.status_code, status.HTTP_200_OK)
108101
self.assertEqual(len(programs_response.data), 1)
109102
self.assertEqual(
@@ -443,8 +436,6 @@ def test_upload_provider_function_with_description(self):
443436
self.assertEqual(len(programs_response.data), 2)
444437
found = False
445438

446-
print(programs_response.data)
447-
448439
for resp_data in programs_response.data:
449440
if resp_data.get("title") == "Provider Function":
450441
self.assertEqual(

0 commit comments

Comments
 (0)