From 602a2fc7e56d530746ea3ed759cbc933d47a46b2 Mon Sep 17 00:00:00 2001
From: James Robinson <james.em.robinson@gmail.com>
Date: Thu, 2 May 2024 16:23:10 +0100
Subject: [PATCH 1/2] :bug: Add ContextBase class to break circular dependency

---
 data_safe_haven/context/context.py            |  3 ++-
 data_safe_haven/serialisers/__init__.py       |  2 ++
 .../serialisers/azure_serialisable_model.py   |  8 ++++----
 data_safe_haven/serialisers/context_base.py   | 19 +++++++++++++++++++
 4 files changed, 27 insertions(+), 5 deletions(-)
 create mode 100644 data_safe_haven/serialisers/context_base.py

diff --git a/data_safe_haven/context/context.py b/data_safe_haven/context/context.py
index 4c772d4e7a..09154a897c 100644
--- a/data_safe_haven/context/context.py
+++ b/data_safe_haven/context/context.py
@@ -10,6 +10,7 @@
 from data_safe_haven import __version__
 from data_safe_haven.external import AzureApi
 from data_safe_haven.functions import alphanumeric
+from data_safe_haven.serialisers import ContextBase
 from data_safe_haven.types import (
     AzureLocation,
     AzureLongName,
@@ -18,7 +19,7 @@
 from data_safe_haven.utility import config_dir
 
 
-class Context(BaseModel, validate_assignment=True):
+class Context(ContextBase, BaseModel, validate_assignment=True):
     admin_group_id: Guid
     location: AzureLocation
     name: str
diff --git a/data_safe_haven/serialisers/__init__.py b/data_safe_haven/serialisers/__init__.py
index 31a2f7e373..bc50594ad1 100644
--- a/data_safe_haven/serialisers/__init__.py
+++ b/data_safe_haven/serialisers/__init__.py
@@ -1,7 +1,9 @@
 from .azure_serialisable_model import AzureSerialisableModel
+from .context_base import ContextBase
 from .yaml_serialisable_model import YAMLSerialisableModel
 
 __all__ = [
     "AzureSerialisableModel",
+    "ContextBase",
     "YAMLSerialisableModel",
 ]
diff --git a/data_safe_haven/serialisers/azure_serialisable_model.py b/data_safe_haven/serialisers/azure_serialisable_model.py
index 2bb1be4a6b..6b1b638c20 100644
--- a/data_safe_haven/serialisers/azure_serialisable_model.py
+++ b/data_safe_haven/serialisers/azure_serialisable_model.py
@@ -2,9 +2,9 @@
 
 from typing import Any, ClassVar, TypeVar
 
-from data_safe_haven.context.context import Context
 from data_safe_haven.external import AzureApi
 
+from .context_base import ContextBase
 from .yaml_serialisable_model import YAMLSerialisableModel
 
 T = TypeVar("T", bound="AzureSerialisableModel")
@@ -17,7 +17,7 @@ class AzureSerialisableModel(YAMLSerialisableModel):
     filename: ClassVar[str] = "config.yaml"
 
     @classmethod
-    def from_remote(cls: type[T], context: Context) -> T:
+    def from_remote(cls: type[T], context: ContextBase) -> T:
         """Construct an AzureSerialisableModel from a YAML file in Azure storage."""
         azure_api = AzureApi(subscription_name=context.subscription_name)
         config_yaml = azure_api.download_blob(
@@ -30,7 +30,7 @@ def from_remote(cls: type[T], context: Context) -> T:
 
     @classmethod
     def from_remote_or_create(
-        cls: type[T], context: Context, **default_args: dict[Any, Any]
+        cls: type[T], context: ContextBase, **default_args: dict[Any, Any]
     ) -> T:
         """
         Construct an AzureSerialisableModel from a YAML file in Azure storage, or from
@@ -47,7 +47,7 @@ def from_remote_or_create(
         else:
             return cls(**default_args)
 
-    def upload(self, context: Context) -> None:
+    def upload(self, context: ContextBase) -> None:
         """Serialise an AzureSerialisableModel to a YAML file in Azure storage."""
         azure_api = AzureApi(subscription_name=context.subscription_name)
         azure_api.upload_blob(
diff --git a/data_safe_haven/serialisers/context_base.py b/data_safe_haven/serialisers/context_base.py
new file mode 100644
index 0000000000..3c4cecd387
--- /dev/null
+++ b/data_safe_haven/serialisers/context_base.py
@@ -0,0 +1,19 @@
+from abc import ABC, abstractmethod
+from typing import ClassVar
+
+from data_safe_haven.types import AzureLongName
+
+
+class ContextBase(ABC):
+    subscription_name: AzureLongName
+    storage_container_name: ClassVar[str]
+
+    @property
+    @abstractmethod
+    def resource_group_name(self) -> str:
+        pass
+
+    @property
+    @abstractmethod
+    def storage_account_name(self) -> str:
+        pass

From 26dcb495b2e030485b8cfe90b8a2bb1f6757a17b Mon Sep 17 00:00:00 2001
From: James Robinson <james.em.robinson@gmail.com>
Date: Thu, 2 May 2024 16:29:37 +0100
Subject: [PATCH 2/2] :bug: Remove deep imports

---
 tests/commands/test_config.py  | 2 +-
 tests/commands/test_context.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/commands/test_config.py b/tests/commands/test_config.py
index e14f1bbb92..a08cd34dab 100644
--- a/tests/commands/test_config.py
+++ b/tests/commands/test_config.py
@@ -1,4 +1,4 @@
-from data_safe_haven.commands.config import config_command_group
+from data_safe_haven.commands import config_command_group
 from data_safe_haven.config import Config
 from data_safe_haven.external import AzureApi
 
diff --git a/tests/commands/test_context.py b/tests/commands/test_context.py
index d1359e1c3d..fac41f15d5 100644
--- a/tests/commands/test_context.py
+++ b/tests/commands/test_context.py
@@ -1,4 +1,4 @@
-from data_safe_haven.commands.context import context_command_group
+from data_safe_haven.commands import context_command_group
 from data_safe_haven.context_infrastructure import ContextInfrastructure