Repository: dagster-io/dagster (Python)

Issue:
ConfigPartitionsAssetsQuery error with Launchpad on 1.9.13
### What's the issue?

I just upgraded from 1.9.11 to 1.9.13. On the assets/flows with which I use Launchpad for manually launching with config, I now see a GraphQL error:

```
Operation name: {ConfigPartitionsAssetsQuery}

Message: Cannot return null for non-nullable field PartitionKeys.partitionKeys.

Path: ["pipelineOrError","partitionKeysOrError","partitionKeys"]

Locations: [{"line":16,"column":11}]

Stack Trace:
  File "/usr/local/lib/python3.12/site-packages/graphql/execution/execute.py", line 542, in execute_field
    completed = self.complete_value(
                ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/graphql/execution/execute.py", line 622, in complete_value
    raise TypeError(

```

I did not change anything in the impacted assets. Is it possible this was introduced in 1.9.12 with the fix of the following (from the release notes):

> Fixed an issue where partition keys could be dropped when converting a list of partition keys for a `MultiPartitionsDefinition` to a `PartitionSubset`.


### What did you expect to happen?

_No response_

### How to reproduce?

Select Open Launchpad with any asset/job selected. The GraphQL error appears and the error message shown above appears when clicking View Error Info.

![Image](https://github.com/user-attachments/assets/76e5abfe-ffcb-4242-926f-47b22b37f04b)

### Dagster version

dagster, version 1.9.13

### Deployment type

Docker Compose

### Deployment details

This is a docker compose deployment with multiple code locations. The assets in question are not configured to do anything with partitions. It impacts all uses of the Launchpad.

### Additional information

_No response_

### Message from the maintainers

Impacted by this issue? Give it a 👍! We factor engagement into prioritization.


Discussion:
Facing the same issue when i try to run a Job in my dagster self hosted AWS ECS setup. 

How to produce?
When i go into any job and click on materalize a job this is the error in get.

> Operation name: {ConfigPartitionsAssetsQuery}
> 
> Message: Cannot return null for non-nullable field PartitionKeys.partitionKeys.
> 
> Path: ["pipelineOrError","partitionKeysOrError","partitionKeys"]
> 
> Locations: [{"line":16,"column":11}]
> 
> Stack Trace:
>   File "/usr/local/lib/python3.9/site-packages/graphql/execution/execute.py", line 542, in execute_field
>     completed = self.complete_value(
>   File "/usr/local/lib/python3.9/site-packages/graphql/execution/execute.py", line 622, in complete_value
>     raise TypeError(
> 

The current dagster version - 1.9.11
 
@clayheaton can you share how the job where you're seeing this is defined? does it have any partitions?
If the job in question only had unpartitioned assets, I believe https://github.com/dagster-io/dagster/pull/27841 will fix that.
> [@clayheaton](https://github.com/clayheaton) can you share how the job where you're seeing this is defined? does it have any partitions?

No, it did not have partitions defined. It happens every time that I open launchpad, regardless of whether there's a job that uses it or not. 
This should be fixed in the latest 0.10.1 release - thanks for the report, let us know if you're still seeing any issues with this query.



Patch:
diff --git a/python_modules/dagster-graphql/dagster_graphql/schema/pipelines/pipeline.py b/python_modules/dagster-graphql/dagster_graphql/schema/pipelines/pipeline.py
index b26f6c784a5b1..2ed7e37323fca 100644
--- a/python_modules/dagster-graphql/dagster_graphql/schema/pipelines/pipeline.py
+++ b/python_modules/dagster-graphql/dagster_graphql/schema/pipelines/pipeline.py
@@ -980,7 +980,6 @@ def resolve_repository(self, graphene_info: ResolveInfo):
 
         return GrapheneRepository(self._remote_job.repository_handle)
 
-    @capture_error
     def resolve_partitionKeysOrError(
         self,
         graphene_info: ResolveInfo,
diff --git a/python_modules/dagster/dagster/_core/remote_representation/external.py b/python_modules/dagster/dagster/_core/remote_representation/external.py
index bbf656b7a0f0b..502dc9038712a 100644
--- a/python_modules/dagster/dagster/_core/remote_representation/external.py
+++ b/python_modules/dagster/dagster/_core/remote_representation/external.py
@@ -350,9 +350,12 @@ def get_partition_names_for_asset_job(
         selected_asset_keys: Optional[AbstractSet[AssetKey]],
         instance: DagsterInstance,
     ) -> Sequence[str]:
-        return self._get_partitions_def_for_job(
+        partitions_def = self._get_partitions_def_for_job(
             job_name=job_name, selected_asset_keys=selected_asset_keys
-        ).get_partition_keys(dynamic_partitions_store=instance)
+        )
+        if not partitions_def:
+            return []
+        return partitions_def.get_partition_keys(dynamic_partitions_store=instance)
 
     def get_partition_tags_for_implicit_asset_job(
         self,
@@ -361,15 +364,17 @@ def get_partition_tags_for_implicit_asset_job(
         instance: DagsterInstance,
         partition_name: str,
     ) -> Mapping[str, str]:
-        return self._get_partitions_def_for_job(
-            job_name=job_name, selected_asset_keys=selected_asset_keys
+        return check.not_none(
+            self._get_partitions_def_for_job(
+                job_name=job_name, selected_asset_keys=selected_asset_keys
+            )
         ).get_tags_for_partition_key(partition_name)
 
     def _get_partitions_def_for_job(
         self,
         job_name: str,
         selected_asset_keys: Optional[AbstractSet[AssetKey]],
-    ) -> PartitionsDefinition:
+    ) -> Optional[PartitionsDefinition]:
         asset_nodes = self.get_asset_node_snaps(job_name)
         unique_partitions_defs: set[PartitionsDefinition] = set()
         for asset_node in asset_nodes:
@@ -379,6 +384,9 @@ def _get_partitions_def_for_job(
             if asset_node.partitions is not None:
                 unique_partitions_defs.add(asset_node.partitions.get_partitions_definition())
 
+        if len(unique_partitions_defs) == 0:
+            # Assets are all unpartitioned
+            return None
         if len(unique_partitions_defs) == 1:
             return next(iter(unique_partitions_defs))
         else:


Test patch:
diff --git a/python_modules/dagster-graphql/dagster_graphql_tests/graphql/test_job_partitions.py b/python_modules/dagster-graphql/dagster_graphql_tests/graphql/test_job_partitions.py
index 80c723f2baf46..40f5df2911333 100644
--- a/python_modules/dagster-graphql/dagster_graphql_tests/graphql/test_job_partitions.py
+++ b/python_modules/dagster-graphql/dagster_graphql_tests/graphql/test_job_partitions.py
@@ -103,6 +103,18 @@ def asset3(resource1: MyResource): ...
     ).get_repository_def()
 
 
+def get_repo_with_unpartitioned_assets():
+    @asset
+    def asset1(): ...
+
+    @asset
+    def asset2(): ...
+
+    return Definitions(
+        assets=[asset1, asset2],
+    ).get_repository_def()
+
+
 def test_get_partition_names():
     with instance_for_test() as instance:
         with define_out_of_process_context(
@@ -153,6 +165,30 @@ def test_get_partition_names_asset_selection():
             ]
 
 
+def test_get_partition_names_asset_selection_no_partitions():
+    with instance_for_test() as instance:
+        with define_out_of_process_context(
+            __file__, "get_repo_with_unpartitioned_assets", instance
+        ) as context:
+            result = execute_dagster_graphql(
+                context,
+                GET_PARTITIONS_QUERY,
+                variables={
+                    "selector": {
+                        "repositoryLocationName": context.code_location_names[0],
+                        "repositoryName": SINGLETON_REPOSITORY_NAME,
+                        "pipelineName": "__ASSET_JOB",
+                    },
+                    "selectedAssetKeys": [
+                        AssetKey("asset1").to_graphql_input(),
+                        AssetKey("asset2").to_graphql_input(),
+                    ],
+                },
+            )
+            assert result.data["pipelineOrError"]["name"] == "__ASSET_JOB"
+            assert result.data["pipelineOrError"]["partitionKeysOrError"]["partitionKeys"] == []
+
+
 def test_get_partition_tags():
     with instance_for_test() as instance:
         with define_out_of_process_context(