We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Enable file-less "uploads"
**Is your feature request related to a problem? Please describe.**

Right now pulpcore knows artifactless types, that can be created via file upload using the `NoArtifactContentUploadViewSet` and the `NoArtifactContentUploadSerializer`, which can be combined with "retrieve behaviour" (do not throw errors if the requested content already esists/is already in the repo it should be added to) by defining a plugin specific `retrieve` function on the serializer.

However, pulp_deb has several artifact less types, that do not need an actual uploaded file as part of this process at all. All they need (for pulp_deb to be able to create them) is the set of required API parameters. Examples include the `ReleaseComponent` and `ReleaseArchitecture`. These content types should still use the `repository` parameter to create and add them to a repository in one action, along with "retrieve behaviour". Since this means creating new repository versions, this action must be performed as a task to ensure resource locks.

As far as I can tell this is currently not possible, because pulpcore does not have the right kind of `ViewSet`. I was able to get things to work with the following adjustments to the `NoArtifactContentUploadViewSet`: https://github.com/pulp/pulpcore/pull/5084

An alternative might be to split up `NoArtifactContentUploadViewSet` into `NoArtifactContentUploadViewSet` and `NoArtifactContentViewSet`, which would mirror the class structure on the serializer side, and possibly make the semantic intention more clear.

**Additional context**

- See here for the pulp_deb change that prompted this need: https://github.com/pulp/pulp_deb/pull/1018
- I am happy to implement this as soon as there is a consensus on whether to add a whole new ViewSet in the class hierarchy, or whether it is enough to adjust `NoArtifactContentUploadViewSet` to support both use cases.
- I could use help in designing a good test to cover this special use case.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pulpcore/plugin/viewsets/content.py`
Content:
```
1 from drf_spectacular.utils import extend_schema
2 
3 from django.db import DatabaseError
4 from django.db.utils import IntegrityError
5 
6 from pulpcore.app import tasks
7 from pulpcore.plugin.serializers import (
8     ArtifactSerializer,
9     AsyncOperationResponseSerializer,
10 )
11 from pulpcore.plugin.models import Artifact, PulpTemporaryFile
12 from pulpcore.plugin.tasking import dispatch
13 from pulpcore.plugin.viewsets import (
14     ContentViewSet,
15     OperationPostponedResponse,
16 )
17 
18 
19 class DefaultDeferredContextMixin:
20     """A mixin that provides a method for retrieving the default deferred context."""
21 
22     def get_deferred_context(self, request):
23         """
24         Supply context for deferred validation.
25 
26         When overwriting this method, it must return a dict, that is JSON serializable by
27         and does _not_ contain 'request' as a key.
28         """
29         return {}
30 
31 
32 class NoArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):
33     """A ViewSet for uploads that do not require to store an uploaded content as an Artifact."""
34 
35     @extend_schema(
36         description="Trigger an asynchronous task to create content,"
37         "optionally create new repository version.",
38         responses={202: AsyncOperationResponseSerializer},
39     )
40     def create(self, request):
41         """Create a content unit."""
42         serializer = self.get_serializer(data=request.data)
43         serializer.is_valid(raise_exception=True)
44 
45         task_payload = {k: v for k, v in request.data.items()}
46 
47         file_content = task_payload.pop("file", None)
48         temp_file = PulpTemporaryFile.init_and_validate(file_content)
49         temp_file.save()
50 
51         exclusive_resources = [
52             item for item in (serializer.validated_data.get(key) for key in ("repository",)) if item
53         ]
54 
55         app_label = self.queryset.model._meta.app_label
56         context = self.get_deferred_context(request)
57         context["pulp_temp_file_pk"] = str(temp_file.pk)
58         task = dispatch(
59             tasks.base.general_create,
60             exclusive_resources=exclusive_resources,
61             args=(app_label, serializer.__class__.__name__),
62             kwargs={"data": task_payload, "context": context},
63         )
64         return OperationPostponedResponse(task, request)
65 
66 
67 class SingleArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):
68     """A ViewSet which can be used to store an uploaded content as an Artifact."""
69 
70     @extend_schema(
71         description="Trigger an asynchronous task to create content,"
72         "optionally create new repository version.",
73         responses={202: AsyncOperationResponseSerializer},
74     )
75     def create(self, request):
76         """Create a content unit."""
77         serializer = self.get_serializer(data=request.data)
78         serializer.is_valid(raise_exception=True)
79 
80         task_payload = self.init_content_data(serializer, request)
81 
82         exclusive_resources = [
83             item
84             for item in (serializer.validated_data.get(key) for key in ("upload", "repository"))
85             if item
86         ]
87 
88         app_label = self.queryset.model._meta.app_label
89         task = dispatch(
90             tasks.base.general_create,
91             exclusive_resources=exclusive_resources,
92             args=(app_label, serializer.__class__.__name__),
93             kwargs={
94                 "data": task_payload,
95                 "context": self.get_deferred_context(request),
96             },
97         )
98         return OperationPostponedResponse(task, request)
99 
100     def init_content_data(self, serializer, request):
101         """Initialize the reference to an Artifact along with relevant task's payload data."""
102         task_payload = {k: v for k, v in request.data.items()}
103         if "file" in task_payload:
104             # in the upload code path make sure, the artifact exists, and the 'file'
105             # parameter is replaced by 'artifact'
106             artifact = Artifact.init_and_validate(task_payload.pop("file"))
107             try:
108                 artifact.save()
109             except IntegrityError:
110                 # if artifact already exists, let's use it
111                 try:
112                     artifact = Artifact.objects.get(
113                         sha256=artifact.sha256, pulp_domain=request.pulp_domain
114                     )
115                     artifact.touch()
116                 except (Artifact.DoesNotExist, DatabaseError):
117                     # the artifact has since been removed from when we first attempted to save it
118                     artifact.save()
119 
120             task_payload["artifact"] = ArtifactSerializer(
121                 artifact, context={"request": request}
122             ).data["pulp_href"]
123         elif "artifact" in serializer.validated_data:
124             serializer.validated_data["artifact"].touch()
125         # In case of a provided upload object, there is no artifact to touch yet.
126 
127         return task_payload
128
```

Path: `pulpcore/plugin/viewsets/__init__.py`
Content:
```
1 from pulpcore.filters import BaseFilterSet
2 
3 # Allow plugin viewsets to return 202s
4 from pulpcore.app.response import OperationPostponedResponse, TaskGroupOperationResponse
5 
6 # Import Viewsets in platform that are potentially useful to plugin writers
7 from pulpcore.app.viewsets import (
8     AlternateContentSourceViewSet,
9     AsyncUpdateMixin,
10     ContentFilter,
11     ContentGuardFilter,
12     ContentGuardViewSet,
13     ContentViewSet,
14     DistributionFilter,
15     DistributionViewSet,
16     ExporterViewSet,
17     ExportViewSet,
18     ImmutableRepositoryViewSet,
19     ImporterViewSet,
20     ImportViewSet,
21     LabelsMixin,
22     NamedModelViewSet,
23     NAME_FILTER_OPTIONS,
24     NULLABLE_NUMERIC_FILTER_OPTIONS,
25     PublicationFilter,
26     PublicationViewSet,
27     ReadOnlyContentViewSet,
28     ReadOnlyRepositoryViewSet,
29     RemoteFilter,
30     RemoteViewSet,
31     RepositoryVersionViewSet,
32     RepositoryViewSet,
33     RolesMixin,
34     TaskGroupViewSet,
35     TaskViewSet,
36 )
37 
38 from pulpcore.app.viewsets.custom_filters import (
39     CharInFilter,
40     LabelFilter,
41     RepositoryVersionFilter,
42 )
43 
44 from pulpcore.filters import HyperlinkRelatedFilter
45 
46 from .content import (
47     NoArtifactContentUploadViewSet,
48     SingleArtifactContentUploadViewSet,
49 )
50
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pulpcore/plugin/viewsets/__init__.py b/pulpcore/plugin/viewsets/__init__.py
--- a/pulpcore/plugin/viewsets/__init__.py
+++ b/pulpcore/plugin/viewsets/__init__.py
@@ -44,6 +44,7 @@
 from pulpcore.filters import HyperlinkRelatedFilter
 
 from .content import (
+    NoArtifactContentViewSet,
     NoArtifactContentUploadViewSet,
     SingleArtifactContentUploadViewSet,
 )
diff --git a/pulpcore/plugin/viewsets/content.py b/pulpcore/plugin/viewsets/content.py
--- a/pulpcore/plugin/viewsets/content.py
+++ b/pulpcore/plugin/viewsets/content.py
@@ -29,6 +29,35 @@
         return {}
 
 
+class NoArtifactContentViewSet(DefaultDeferredContextMixin, ContentViewSet):
+    """A ViewSet for content creation that does not require a file to be uploaded."""
+
+    @extend_schema(
+        description="Trigger an asynchronous task to create content,"
+        "optionally create new repository version.",
+        responses={202: AsyncOperationResponseSerializer},
+    )
+    def create(self, request):
+        """Create a content unit."""
+        serializer = self.get_serializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
+
+        exclusive_resources = [
+            item for item in (serializer.validated_data.get(key) for key in ("repository",)) if item
+        ]
+
+        task = dispatch(
+            tasks.base.general_create,
+            exclusive_resources=exclusive_resources,
+            args=(self.queryset.model._meta.app_label, serializer.__class__.__name__),
+            kwargs={
+                "data": {k: v for k, v in request.data.items()},
+                "context": self.get_deferred_context(request),
+            },
+        )
+        return OperationPostponedResponse(task, request)
+
+
 class NoArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):
     """A ViewSet for uploads that do not require to store an uploaded content as an Artifact."""

{"golden_diff": "diff --git a/pulpcore/plugin/viewsets/__init__.py b/pulpcore/plugin/viewsets/__init__.py\n--- a/pulpcore/plugin/viewsets/__init__.py\n+++ b/pulpcore/plugin/viewsets/__init__.py\n@@ -44,6 +44,7 @@\n from pulpcore.filters import HyperlinkRelatedFilter\n \n from .content import (\n+    NoArtifactContentViewSet,\n     NoArtifactContentUploadViewSet,\n     SingleArtifactContentUploadViewSet,\n )\ndiff --git a/pulpcore/plugin/viewsets/content.py b/pulpcore/plugin/viewsets/content.py\n--- a/pulpcore/plugin/viewsets/content.py\n+++ b/pulpcore/plugin/viewsets/content.py\n@@ -29,6 +29,35 @@\n         return {}\n \n \n+class NoArtifactContentViewSet(DefaultDeferredContextMixin, ContentViewSet):\n+    \"\"\"A ViewSet for content creation that does not require a file to be uploaded.\"\"\"\n+\n+    @extend_schema(\n+        description=\"Trigger an asynchronous task to create content,\"\n+        \"optionally create new repository version.\",\n+        responses={202: AsyncOperationResponseSerializer},\n+    )\n+    def create(self, request):\n+        \"\"\"Create a content unit.\"\"\"\n+        serializer = self.get_serializer(data=request.data)\n+        serializer.is_valid(raise_exception=True)\n+\n+        exclusive_resources = [\n+            item for item in (serializer.validated_data.get(key) for key in (\"repository\",)) if item\n+        ]\n+\n+        task = dispatch(\n+            tasks.base.general_create,\n+            exclusive_resources=exclusive_resources,\n+            args=(self.queryset.model._meta.app_label, serializer.__class__.__name__),\n+            kwargs={\n+                \"data\": {k: v for k, v in request.data.items()},\n+                \"context\": self.get_deferred_context(request),\n+            },\n+        )\n+        return OperationPostponedResponse(task, request)\n+\n+\n class NoArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):\n     \"\"\"A ViewSet for uploads that do not require to store an uploaded content as an Artifact.\"\"\"\n", "issue": "Enable file-less \"uploads\"\n**Is your feature request related to a problem? Please describe.**\r\n\r\nRight now pulpcore knows artifactless types, that can be created via file upload using the `NoArtifactContentUploadViewSet` and the `NoArtifactContentUploadSerializer`, which can be combined with \"retrieve behaviour\" (do not throw errors if the requested content already esists/is already in the repo it should be added to) by defining a plugin specific `retrieve` function on the serializer.\r\n\r\nHowever, pulp_deb has several artifact less types, that do not need an actual uploaded file as part of this process at all. All they need (for pulp_deb to be able to create them) is the set of required API parameters. Examples include the `ReleaseComponent` and `ReleaseArchitecture`. These content types should still use the `repository` parameter to create and add them to a repository in one action, along with \"retrieve behaviour\". Since this means creating new repository versions, this action must be performed as a task to ensure resource locks.\r\n\r\nAs far as I can tell this is currently not possible, because pulpcore does not have the right kind of `ViewSet`. I was able to get things to work with the following adjustments to the `NoArtifactContentUploadViewSet`: https://github.com/pulp/pulpcore/pull/5084\r\n\r\nAn alternative might be to split up `NoArtifactContentUploadViewSet` into `NoArtifactContentUploadViewSet` and `NoArtifactContentViewSet`, which would mirror the class structure on the serializer side, and possibly make the semantic intention more clear.\r\n\r\n**Additional context**\r\n\r\n- See here for the pulp_deb change that prompted this need: https://github.com/pulp/pulp_deb/pull/1018\r\n- I am happy to implement this as soon as there is a consensus on whether to add a whole new ViewSet in the class hierarchy, or whether it is enough to adjust `NoArtifactContentUploadViewSet` to support both use cases.\r\n- I could use help in designing a good test to cover this special use case.\r\n\n", "before_files": [{"content": "from drf_spectacular.utils import extend_schema\n\nfrom django.db import DatabaseError\nfrom django.db.utils import IntegrityError\n\nfrom pulpcore.app import tasks\nfrom pulpcore.plugin.serializers import (\n    ArtifactSerializer,\n    AsyncOperationResponseSerializer,\n)\nfrom pulpcore.plugin.models import Artifact, PulpTemporaryFile\nfrom pulpcore.plugin.tasking import dispatch\nfrom pulpcore.plugin.viewsets import (\n    ContentViewSet,\n    OperationPostponedResponse,\n)\n\n\nclass DefaultDeferredContextMixin:\n    \"\"\"A mixin that provides a method for retrieving the default deferred context.\"\"\"\n\n    def get_deferred_context(self, request):\n        \"\"\"\n        Supply context for deferred validation.\n\n        When overwriting this method, it must return a dict, that is JSON serializable by\n        and does _not_ contain 'request' as a key.\n        \"\"\"\n        return {}\n\n\nclass NoArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):\n    \"\"\"A ViewSet for uploads that do not require to store an uploaded content as an Artifact.\"\"\"\n\n    @extend_schema(\n        description=\"Trigger an asynchronous task to create content,\"\n        \"optionally create new repository version.\",\n        responses={202: AsyncOperationResponseSerializer},\n    )\n    def create(self, request):\n        \"\"\"Create a content unit.\"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n\n        task_payload = {k: v for k, v in request.data.items()}\n\n        file_content = task_payload.pop(\"file\", None)\n        temp_file = PulpTemporaryFile.init_and_validate(file_content)\n        temp_file.save()\n\n        exclusive_resources = [\n            item for item in (serializer.validated_data.get(key) for key in (\"repository\",)) if item\n        ]\n\n        app_label = self.queryset.model._meta.app_label\n        context = self.get_deferred_context(request)\n        context[\"pulp_temp_file_pk\"] = str(temp_file.pk)\n        task = dispatch(\n            tasks.base.general_create,\n            exclusive_resources=exclusive_resources,\n            args=(app_label, serializer.__class__.__name__),\n            kwargs={\"data\": task_payload, \"context\": context},\n        )\n        return OperationPostponedResponse(task, request)\n\n\nclass SingleArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):\n    \"\"\"A ViewSet which can be used to store an uploaded content as an Artifact.\"\"\"\n\n    @extend_schema(\n        description=\"Trigger an asynchronous task to create content,\"\n        \"optionally create new repository version.\",\n        responses={202: AsyncOperationResponseSerializer},\n    )\n    def create(self, request):\n        \"\"\"Create a content unit.\"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n\n        task_payload = self.init_content_data(serializer, request)\n\n        exclusive_resources = [\n            item\n            for item in (serializer.validated_data.get(key) for key in (\"upload\", \"repository\"))\n            if item\n        ]\n\n        app_label = self.queryset.model._meta.app_label\n        task = dispatch(\n            tasks.base.general_create,\n            exclusive_resources=exclusive_resources,\n            args=(app_label, serializer.__class__.__name__),\n            kwargs={\n                \"data\": task_payload,\n                \"context\": self.get_deferred_context(request),\n            },\n        )\n        return OperationPostponedResponse(task, request)\n\n    def init_content_data(self, serializer, request):\n        \"\"\"Initialize the reference to an Artifact along with relevant task's payload data.\"\"\"\n        task_payload = {k: v for k, v in request.data.items()}\n        if \"file\" in task_payload:\n            # in the upload code path make sure, the artifact exists, and the 'file'\n            # parameter is replaced by 'artifact'\n            artifact = Artifact.init_and_validate(task_payload.pop(\"file\"))\n            try:\n                artifact.save()\n            except IntegrityError:\n                # if artifact already exists, let's use it\n                try:\n                    artifact = Artifact.objects.get(\n                        sha256=artifact.sha256, pulp_domain=request.pulp_domain\n                    )\n                    artifact.touch()\n                except (Artifact.DoesNotExist, DatabaseError):\n                    # the artifact has since been removed from when we first attempted to save it\n                    artifact.save()\n\n            task_payload[\"artifact\"] = ArtifactSerializer(\n                artifact, context={\"request\": request}\n            ).data[\"pulp_href\"]\n        elif \"artifact\" in serializer.validated_data:\n            serializer.validated_data[\"artifact\"].touch()\n        # In case of a provided upload object, there is no artifact to touch yet.\n\n        return task_payload\n", "path": "pulpcore/plugin/viewsets/content.py"}, {"content": "from pulpcore.filters import BaseFilterSet\n\n# Allow plugin viewsets to return 202s\nfrom pulpcore.app.response import OperationPostponedResponse, TaskGroupOperationResponse\n\n# Import Viewsets in platform that are potentially useful to plugin writers\nfrom pulpcore.app.viewsets import (\n    AlternateContentSourceViewSet,\n    AsyncUpdateMixin,\n    ContentFilter,\n    ContentGuardFilter,\n    ContentGuardViewSet,\n    ContentViewSet,\n    DistributionFilter,\n    DistributionViewSet,\n    ExporterViewSet,\n    ExportViewSet,\n    ImmutableRepositoryViewSet,\n    ImporterViewSet,\n    ImportViewSet,\n    LabelsMixin,\n    NamedModelViewSet,\n    NAME_FILTER_OPTIONS,\n    NULLABLE_NUMERIC_FILTER_OPTIONS,\n    PublicationFilter,\n    PublicationViewSet,\n    ReadOnlyContentViewSet,\n    ReadOnlyRepositoryViewSet,\n    RemoteFilter,\n    RemoteViewSet,\n    RepositoryVersionViewSet,\n    RepositoryViewSet,\n    RolesMixin,\n    TaskGroupViewSet,\n    TaskViewSet,\n)\n\nfrom pulpcore.app.viewsets.custom_filters import (\n    CharInFilter,\n    LabelFilter,\n    RepositoryVersionFilter,\n)\n\nfrom pulpcore.filters import HyperlinkRelatedFilter\n\nfrom .content import (\n    NoArtifactContentUploadViewSet,\n    SingleArtifactContentUploadViewSet,\n)\n", "path": "pulpcore/plugin/viewsets/__init__.py"}], "after_files": [{"content": "from drf_spectacular.utils import extend_schema\n\nfrom django.db import DatabaseError\nfrom django.db.utils import IntegrityError\n\nfrom pulpcore.app import tasks\nfrom pulpcore.plugin.serializers import (\n    ArtifactSerializer,\n    AsyncOperationResponseSerializer,\n)\nfrom pulpcore.plugin.models import Artifact, PulpTemporaryFile\nfrom pulpcore.plugin.tasking import dispatch\nfrom pulpcore.plugin.viewsets import (\n    ContentViewSet,\n    OperationPostponedResponse,\n)\n\n\nclass DefaultDeferredContextMixin:\n    \"\"\"A mixin that provides a method for retrieving the default deferred context.\"\"\"\n\n    def get_deferred_context(self, request):\n        \"\"\"\n        Supply context for deferred validation.\n\n        When overwriting this method, it must return a dict, that is JSON serializable by\n        and does _not_ contain 'request' as a key.\n        \"\"\"\n        return {}\n\n\nclass NoArtifactContentViewSet(DefaultDeferredContextMixin, ContentViewSet):\n    \"\"\"A ViewSet for content creation that does not require a file to be uploaded.\"\"\"\n\n    @extend_schema(\n        description=\"Trigger an asynchronous task to create content,\"\n        \"optionally create new repository version.\",\n        responses={202: AsyncOperationResponseSerializer},\n    )\n    def create(self, request):\n        \"\"\"Create a content unit.\"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n\n        exclusive_resources = [\n            item for item in (serializer.validated_data.get(key) for key in (\"repository\",)) if item\n        ]\n\n        task = dispatch(\n            tasks.base.general_create,\n            exclusive_resources=exclusive_resources,\n            args=(self.queryset.model._meta.app_label, serializer.__class__.__name__),\n            kwargs={\n                \"data\": {k: v for k, v in request.data.items()},\n                \"context\": self.get_deferred_context(request),\n            },\n        )\n        return OperationPostponedResponse(task, request)\n\n\nclass NoArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):\n    \"\"\"A ViewSet for uploads that do not require to store an uploaded content as an Artifact.\"\"\"\n\n    @extend_schema(\n        description=\"Trigger an asynchronous task to create content,\"\n        \"optionally create new repository version.\",\n        responses={202: AsyncOperationResponseSerializer},\n    )\n    def create(self, request):\n        \"\"\"Create a content unit.\"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n\n        task_payload = {k: v for k, v in request.data.items()}\n\n        file_content = task_payload.pop(\"file\", None)\n        temp_file = PulpTemporaryFile.init_and_validate(file_content)\n        temp_file.save()\n\n        exclusive_resources = [\n            item for item in (serializer.validated_data.get(key) for key in (\"repository\",)) if item\n        ]\n\n        app_label = self.queryset.model._meta.app_label\n        context = self.get_deferred_context(request)\n        context[\"pulp_temp_file_pk\"] = str(temp_file.pk)\n        task = dispatch(\n            tasks.base.general_create,\n            exclusive_resources=exclusive_resources,\n            args=(app_label, serializer.__class__.__name__),\n            kwargs={\"data\": task_payload, \"context\": context},\n        )\n        return OperationPostponedResponse(task, request)\n\n\nclass SingleArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):\n    \"\"\"A ViewSet which can be used to store an uploaded content as an Artifact.\"\"\"\n\n    @extend_schema(\n        description=\"Trigger an asynchronous task to create content,\"\n        \"optionally create new repository version.\",\n        responses={202: AsyncOperationResponseSerializer},\n    )\n    def create(self, request):\n        \"\"\"Create a content unit.\"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n\n        task_payload = self.init_content_data(serializer, request)\n\n        exclusive_resources = [\n            item\n            for item in (serializer.validated_data.get(key) for key in (\"upload\", \"repository\"))\n            if item\n        ]\n\n        app_label = self.queryset.model._meta.app_label\n        task = dispatch(\n            tasks.base.general_create,\n            exclusive_resources=exclusive_resources,\n            args=(app_label, serializer.__class__.__name__),\n            kwargs={\n                \"data\": task_payload,\n                \"context\": self.get_deferred_context(request),\n            },\n        )\n        return OperationPostponedResponse(task, request)\n\n    def init_content_data(self, serializer, request):\n        \"\"\"Initialize the reference to an Artifact along with relevant task's payload data.\"\"\"\n        task_payload = {k: v for k, v in request.data.items()}\n        if \"file\" in task_payload:\n            # in the upload code path make sure, the artifact exists, and the 'file'\n            # parameter is replaced by 'artifact'\n            artifact = Artifact.init_and_validate(task_payload.pop(\"file\"))\n            try:\n                artifact.save()\n            except IntegrityError:\n                # if artifact already exists, let's use it\n                try:\n                    artifact = Artifact.objects.get(\n                        sha256=artifact.sha256, pulp_domain=request.pulp_domain\n                    )\n                    artifact.touch()\n                except (Artifact.DoesNotExist, DatabaseError):\n                    # the artifact has since been removed from when we first attempted to save it\n                    artifact.save()\n\n            task_payload[\"artifact\"] = ArtifactSerializer(\n                artifact, context={\"request\": request}\n            ).data[\"pulp_href\"]\n        elif \"artifact\" in serializer.validated_data:\n            serializer.validated_data[\"artifact\"].touch()\n        # In case of a provided upload object, there is no artifact to touch yet.\n\n        return task_payload\n", "path": "pulpcore/plugin/viewsets/content.py"}, {"content": "from pulpcore.filters import BaseFilterSet\n\n# Allow plugin viewsets to return 202s\nfrom pulpcore.app.response import OperationPostponedResponse, TaskGroupOperationResponse\n\n# Import Viewsets in platform that are potentially useful to plugin writers\nfrom pulpcore.app.viewsets import (\n    AlternateContentSourceViewSet,\n    AsyncUpdateMixin,\n    ContentFilter,\n    ContentGuardFilter,\n    ContentGuardViewSet,\n    ContentViewSet,\n    DistributionFilter,\n    DistributionViewSet,\n    ExporterViewSet,\n    ExportViewSet,\n    ImmutableRepositoryViewSet,\n    ImporterViewSet,\n    ImportViewSet,\n    LabelsMixin,\n    NamedModelViewSet,\n    NAME_FILTER_OPTIONS,\n    NULLABLE_NUMERIC_FILTER_OPTIONS,\n    PublicationFilter,\n    PublicationViewSet,\n    ReadOnlyContentViewSet,\n    ReadOnlyRepositoryViewSet,\n    RemoteFilter,\n    RemoteViewSet,\n    RepositoryVersionViewSet,\n    RepositoryViewSet,\n    RolesMixin,\n    TaskGroupViewSet,\n    TaskViewSet,\n)\n\nfrom pulpcore.app.viewsets.custom_filters import (\n    CharInFilter,\n    LabelFilter,\n    RepositoryVersionFilter,\n)\n\nfrom pulpcore.filters import HyperlinkRelatedFilter\n\nfrom .content import (\n    NoArtifactContentViewSet,\n    NoArtifactContentUploadViewSet,\n    SingleArtifactContentUploadViewSet,\n)\n", "path": "pulpcore/plugin/viewsets/__init__.py"}]}