Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
FROM python:3.13 AS gv-base

# System Dependencies:
RUN apt-get update && apt-get install -y libpq-dev
RUN apt-get update && apt-get install -y libpq-dev libxml2-dev libxslt-dev

COPY pyproject.toml /code/
COPY poetry.lock /code/
Expand All @@ -15,7 +15,7 @@ ENV PATH="$PATH:/root/.local/bin"
FROM python:3.13-slim AS gv-runtime-base

# System Dependencies:
RUN apt-get update && apt-get install -y libpq-dev
RUN apt-get update && apt-get install -y libpq-dev libxml2-dev libxslt-dev

COPY pyproject.toml /code/
COPY poetry.lock /code/
Expand Down
20 changes: 15 additions & 5 deletions addon_service/addon_operation_invocation/views.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from drf_spectacular.utils import (
extend_schema,
extend_schema_view,
)
from rest_framework.response import Response

from addon_service.common.permissions import (
IsAuthenticated,
SessionUserIsOwner,
SessionUserMayAccessInvocation,
SessionUserMayPerformInvocation,
)
from addon_service.common.viewsets import RetrieveWriteViewSet
from addon_service.common.viewsets import RetrieveCreateViewSet
from addon_service.tasks.invocation import (
perform_invocation__blocking,
perform_invocation__celery,
Expand All @@ -31,16 +34,23 @@
from .serializers import AddonOperationInvocationSerializer


class AddonOperationInvocationViewSet(RetrieveWriteViewSet):
@extend_schema_view(
create=extend_schema(
description="Perform some action using external service, for instance list files on storage provider. "
"In order to perform such action you need to include configured_addon relationship"
),
retrieve=extend_schema(
description="Get singular instance of addon operation invocation by it's pk. May be useful to view action log",
),
)
class AddonOperationInvocationViewSet(RetrieveCreateViewSet):
Comment thread
cslzchen marked this conversation as resolved.
queryset = AddonOperationInvocation.objects.all()
serializer_class = AddonOperationInvocationSerializer

def get_permissions(self):
match self.action:
case "retrieve" | "retrieve_related":
return [IsAuthenticated(), SessionUserMayAccessInvocation()]
case "partial_update" | "update" | "destroy":
return [IsAuthenticated(), SessionUserIsOwner()]
case "create":
return [SessionUserMayPerformInvocation()]
case None:
Expand Down
4 changes: 4 additions & 0 deletions addon_service/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@

class AddonServiceConfig(AppConfig):
name = "addon_service"

def ready(self):
# need to import openapi extensions here for them to be registered
import addon_service.common.openapi_extensions # noqa: F401
12 changes: 12 additions & 0 deletions addon_service/authorized_account/citation/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
from drf_spectacular.utils import (
extend_schema,
extend_schema_view,
)

from addon_service.authorized_account.views import AuthorizedAccountViewSet

from .models import AuthorizedCitationAccount
from .serializers import AuthorizedCitationAccountSerializer


@extend_schema_view(
create=extend_schema(
description="Create new authorized citation account for given external citation service. "
'For OAuth services it\'s required to create account with `"initiate_oauth"=true` '
"in order to proceed with OAuth flow"
),
)
class AuthorizedCitationAccountViewSet(AuthorizedAccountViewSet):
queryset = AuthorizedCitationAccount.objects.all()
serializer_class = AuthorizedCitationAccountSerializer
12 changes: 12 additions & 0 deletions addon_service/authorized_account/computing/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
from drf_spectacular.utils import (
extend_schema,
extend_schema_view,
)

from addon_service.authorized_account.views import AuthorizedAccountViewSet

from .models import AuthorizedComputingAccount
from .serializers import AuthorizedComputingAccountSerializer


@extend_schema_view(
create=extend_schema(
description="Create new authorized computing account for given external computing service.\n "
'For OAuth services it\'s required to create account with `"initiate_oauth"=true` '
"in order to proceed with OAuth flow"
),
)
class AuthorizedComputingAccountViewSet(AuthorizedAccountViewSet):
queryset = AuthorizedComputingAccount.objects.all()
serializer_class = AuthorizedComputingAccountSerializer
12 changes: 12 additions & 0 deletions addon_service/authorized_account/link/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
from drf_spectacular.utils import (
extend_schema,
extend_schema_view,
)

from addon_service.authorized_account.views import AuthorizedAccountViewSet

from .models import AuthorizedLinkAccount
from .serializers import AuthorizedLinkAccountSerializer


@extend_schema_view(
create=extend_schema(
description="Create new authorized link account for given external link service.\n "
'For OAuth services it\'s required to create account with `"initiate_oauth"=true` '
"in order to proceed with OAuth flow"
),
)
class AuthorizedLinkAccountViewSet(AuthorizedAccountViewSet):
queryset = AuthorizedLinkAccount.objects.all()
serializer_class = AuthorizedLinkAccountSerializer
12 changes: 12 additions & 0 deletions addon_service/authorized_account/storage/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
from drf_spectacular.utils import (
extend_schema,
extend_schema_view,
)

from addon_imps.storage.google_drive import GoogleDriveStorageImp
from addon_service.authorized_account.views import AuthorizedAccountViewSet

Expand All @@ -8,6 +13,13 @@
)


@extend_schema_view(
create=extend_schema(
description="Create new authorized storage account for given external storage service."
'\n For OAuth services it\'s required to create account with `"initiate_oauth"=true` '
"in order to proceed with OAuth flow"
),
)
class AuthorizedStorageAccountViewSet(AuthorizedAccountViewSet):
queryset = AuthorizedStorageAccount.objects.all()
serializer_class = AuthorizedStorageAccountSerializer
Expand Down
2 changes: 2 additions & 0 deletions addon_service/common/filtering.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def filter_queryset(self, request, queryset, view):

class RestrictedListEndpointFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
if view.action != "list":
return queryset
Comment thread
cslzchen marked this conversation as resolved.
required_filters = set(view.required_list_filter_fields)
filter_expressions = extract_filter_expressions(
request.query_params, view.get_serializer()
Expand Down
28 changes: 28 additions & 0 deletions addon_service/common/openapi_extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from drf_spectacular.extensions import OpenApiFilterExtension


class RestrictedReadOnlyViewSetExtension(OpenApiFilterExtension):
Comment thread
cslzchen marked this conversation as resolved.
target_class = "addon_service.common.filtering.RestrictedListEndpointFilterBackend" # full dotted path
Comment thread
cslzchen marked this conversation as resolved.

def get_schema_operation_parameters(self, auto_schema, *args, **kwargs):
if auto_schema.method != "GET" or "list" not in auto_schema.view.action:
return []

required_filter_fields = getattr(
auto_schema.view, "required_list_filter_fields", ()
)

parameters = []
for field_name in required_filter_fields:
parameters.append(
{
"name": f"filter[{field_name}]",
"in": "query", # This corresponds to OpenApiParameter.QUERY
"description": f"Filter by {field_name}. This filter must be uniquely identifying.",
"required": True,
"schema": {
"type": "string" # This corresponds to OpenApiTypes.STR, OpenApiTypes.URI, etc.
},
}
)
return parameters
14 changes: 12 additions & 2 deletions addon_service/common/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class _DrfJsonApiHelpers(AutoPrefetchMixin, PreloadIncludesMixin, RelatedMixin):


class RestrictedReadOnlyViewSet(ReadOnlyModelViewSet):
filter_backends = [RestrictedListEndpointFilterBackend]
Comment thread
cslzchen marked this conversation as resolved.
"""ReadOnlyViewSet that requires `list` actions return only one result.

UserReference and ResourceReference endpoints are major entry points into
Expand All @@ -47,8 +48,6 @@ def list(self, request, *args, **kwargs):
RestrictedListEndpointFilterBackend and check_object_permissions
to enforce permissions on returned entities.
"""
self.filter_backends = [RestrictedListEndpointFilterBackend]

qs = self.filter_queryset(self.get_queryset())
try:
self.check_object_permissions(self.request, qs.get())
Expand All @@ -74,6 +73,17 @@ def perform_create(self, serializer):
_instance.save()


class RetrieveCreateViewSet(
Comment thread
cslzchen marked this conversation as resolved.
_DrfJsonApiHelpers,
_CreateWithPermissionsMixin,
drf_mixins.RetrieveModelMixin,
GenericViewSet,
):
"""viewset allowing create, retrieve, update"""

http_method_names = ["get", "post", "head", "options"]


class RetrieveWriteViewSet(
_DrfJsonApiHelpers,
_CreateWithPermissionsMixin,
Expand Down
16 changes: 16 additions & 0 deletions addon_service/configured_addon/citation/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
from drf_spectacular.utils import (
extend_schema,
extend_schema_view,
)

from addon_service.configured_addon.views import ConfiguredAddonViewSet

from .models import ConfiguredCitationAddon
from .serializers import ConfiguredCitationAddonSerializer


@extend_schema_view(
create=extend_schema(
description="Create new configured citation addon for given authorized citation account, linking it to desired project.\n "
"To configure it properly, you must specify `root_folder` on the provider's side.\n "
"Note that everything under this folder is going to be accessible to everyone who has access to this project"
),
get=extend_schema(
description="Get configured citation addon by it's pk. "
"\nIf you want to fetch all configured citation addons, you should do so through resource_reference related view",
),
)
class ConfiguredCitationAddonViewSet(ConfiguredAddonViewSet):
queryset = ConfiguredCitationAddon.objects.active()
serializer_class = ConfiguredCitationAddonSerializer
2 changes: 1 addition & 1 deletion addon_service/configured_addon/computing/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ def config(self) -> ComputingConfig:
return self.base_account.authorizedcomputingaccount.config

@property
def external_service_name(self):
def external_service_name(self) -> str:
number = self.base_account.external_service.int_addon_imp
return AddonImpNumbers(number).name.lower()
16 changes: 16 additions & 0 deletions addon_service/configured_addon/computing/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from http import HTTPMethod

from drf_spectacular.utils import (
extend_schema,
extend_schema_view,
)
from rest_framework.decorators import action
from rest_framework.response import Response

Expand All @@ -10,6 +14,18 @@
from .serializers import ConfiguredComputingAddonSerializer


@extend_schema_view(
create=extend_schema(
description="Create new configured computing addon for given authorized computing account, linking it to desired project.\n "
"To configure it properly, you must specify `root_folder` on the provider's side.\n "
"Note that everything under this folder is going to be accessible to everyone who has access to this project"
),
get=extend_schema(
description="Get configured computing addon by it's pk. "
"\nIf you want to fetch all configured computing addons, you should do so through resource_reference related view",
),
get_wb_credentials=extend_schema(exclude=True),
)
class ConfiguredComputingAddonViewSet(ConfiguredAddonViewSet):
queryset = ConfiguredComputingAddon.objects.active()
serializer_class = ConfiguredComputingAddonSerializer
Expand Down
15 changes: 15 additions & 0 deletions addon_service/configured_addon/link/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from http import HTTPMethod

from drf_spectacular.utils import (
extend_schema,
extend_schema_view,
)
from rest_framework.decorators import action
from rest_framework.response import Response

Expand All @@ -13,6 +17,17 @@
)


@extend_schema_view(
create=extend_schema(
description="Create new configured link addon for given authorized link account, linking it to desired project.\n "
"To configure it properly, you must specify `root_folder` on the provider's side.\n "
"Note that everything under this folder is going to be accessible to everyone who has access to this project"
),
get=extend_schema(
description="Get configured link addon by it's pk. "
"\nIf you want to fetch all configured link addons, you should do so through resource_reference related view",
),
)
class ConfiguredLinkAddonViewSet(ConfiguredAddonViewSet):
queryset = ConfiguredLinkAddon.objects.active().select_related(
"base_account__authorizedlinkaccount",
Expand Down
2 changes: 1 addition & 1 deletion addon_service/configured_addon/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def imp_cls(self) -> type[AddonImp]:
return self.base_account.imp_cls

@property
def external_service_name(self):
def external_service_name(self) -> str:
number = self.base_account.external_service.int_addon_imp
return AddonImpNumbers(number).name.lower()

Expand Down
2 changes: 1 addition & 1 deletion addon_service/configured_addon/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def __init__(self, *args, **kwargs):

current_user_is_owner = serializers.SerializerMethodField()

def get_current_user_is_owner(self, configured_addon: ConfiguredAddon):
def get_current_user_is_owner(self, configured_addon: ConfiguredAddon) -> bool:
return configured_addon.owner_uri == get_user_uri(self.context["request"])

def create(self, validated_data):
Expand Down
13 changes: 13 additions & 0 deletions addon_service/configured_addon/storage/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from http import HTTPMethod

import drf_spectacular.utils
from django.http import Http404
from rest_framework.decorators import action
from rest_framework.response import Response
Expand All @@ -14,6 +15,18 @@
from .serializers import ConfiguredStorageAddonSerializer


@drf_spectacular.utils.extend_schema_view(
create=drf_spectacular.utils.extend_schema(
description="Create new configured storage addon for given authorized storage account, linking it to desired project.\n "
"To configure it properly, you must specify `root_folder` on the provider's side.\n "
"Note that everything under this folder is going to be accessible to everyone who has access to this project"
),
get=drf_spectacular.utils.extend_schema(
description="Get configured storage addon by it's pk. "
"\nIf you want to fetch all configured storage addons, you should do so through resource_reference related view",
),
get_wb_credentials=drf_spectacular.utils.extend_schema(exclude=True),
)
class ConfiguredStorageAddonViewSet(ConfiguredAddonViewSet):
queryset = ConfiguredStorageAddon.objects.active().select_related(
"base_account__authorizedstorageaccount",
Expand Down
12 changes: 12 additions & 0 deletions addon_service/external_service/citation/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
from drf_spectacular.utils import (
extend_schema,
extend_schema_view,
)
from rest_framework_json_api.views import ReadOnlyModelViewSet

from .models import ExternalCitationService
from .serializers import ExternalCitationServiceSerializer


@extend_schema_view(
list=extend_schema(
description="Get the list of all available external citation services"
),
get=extend_schema(
description="Get particular external citation service",
),
)
class ExternalCitationServiceViewSet(ReadOnlyModelViewSet):
queryset = ExternalCitationService.objects.all().select_related(
"oauth2_client_config", "oauth1_client_config"
Expand Down
Loading