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
2 changes: 1 addition & 1 deletion app.test.conf
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ service_name =
tracer_name =

[stats]
enabled = True
enabled = False
host =
port = 0
module_name = "test_stats"
Expand Down
9 changes: 6 additions & 3 deletions app/routers/directory_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from app.services.directory_provider.directory_provider import DirectoryProvider
from app.services.entity.directory_info_service import DirectoryInfoService

import logging
import logging
from typing import Any, Dict, List

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -38,6 +38,9 @@ def get_all_directories(
def get_one_directory(
_id: str, provider: DirectoryProvider = Depends(get_directory_provider),
) -> Dict[str, Any]:
return provider.get_one_directory(_id).model_dump()

directory = provider.get_one_directory(_id)
if directory is None:
logger.warning("Directory with ID not found.")
return {}

return directory.model_dump()
3 changes: 3 additions & 0 deletions app/routers/update_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ def update_single_directory(
) -> Any:
since = query_params.since.astimezone(timezone.utc) if query_params.since else None
directory = directory_provider.get_one_directory(directory_id)
if directory is None:
raise HTTPException(status_code=404, detail=f"Directory {directory_id} not found")

if not override_ignore:
is_ignored = directory_service.get_one_by_id(directory_id).is_ignored

Expand Down
110 changes: 0 additions & 110 deletions app/services/directory_provider/caching_provider.py

This file was deleted.

84 changes: 84 additions & 0 deletions app/services/directory_provider/capability_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import logging
from typing import List

from app.models.directory.dto import DirectoryDto
from app.services.api.authenticators.null_authenticator import NullAuthenticator
from app.services.api.fhir_api import FhirApi
from app.services.directory_provider.directory_provider import DirectoryProvider

logger = logging.getLogger(__name__)

class CapabilityProvider(DirectoryProvider):
"""
Filters out directories that do not meet certain capability statement criteria.
"""

def __init__(
self,
inner: DirectoryProvider,
validate_capability_statement: bool = True,
) -> None:
self.__inner = inner
self.__validate_capability_statement = validate_capability_statement


def get_all_directories(self, include_ignored: bool = False) -> List[DirectoryDto]:
dirs = self.__inner.get_all_directories(include_ignored)

dirs = self.filter_on_capability(dirs)

return dirs

def get_all_directories_include_ignored_ids(
self, include_ignored_ids: List[str]
) -> List[DirectoryDto]:
dirs = self.__inner.get_all_directories_include_ignored_ids(include_ignored_ids)

dirs = self.filter_on_capability(dirs)

return dirs

def get_one_directory(self, directory_id: str) -> DirectoryDto|None:
the_dir = self.__inner.get_one_directory(directory_id)
if the_dir is None:
return None

dirs = self.filter_on_capability([the_dir])
if not dirs or len(dirs) == 0:
return None

return dirs[0]

def filter_on_capability(self, dirs: List[DirectoryDto]) -> List[DirectoryDto]:
if self.__validate_capability_statement:
dirs = [d for d in dirs if self.check_capability_statement(d)]

return dirs

@staticmethod
def check_capability_statement(dir_dto: DirectoryDto) -> bool:
"""
Validates the capability statement of a directory to ensure it meets mCSD requirements.
Logs the process and returns True if valid, False otherwise.
"""
logger.info(f"Checking capability statement for {dir_dto.id}")
try:
fhir_api=FhirApi(
timeout=5,
backoff=5,
auth=NullAuthenticator(),
base_url=dir_dto.endpoint_address,
request_count=5,
fill_required_fields=False,
retries=5,
)
if not fhir_api.validate_capability_statement():
logger.warning(
f"Directory {dir_dto.id} at {dir_dto.endpoint_address} does not support mCSD requirements"
)
return False
except Exception as e:
logger.error(f"Error checking capability statement for {dir_dto.id}: {e}")
return False

return True
31 changes: 4 additions & 27 deletions app/services/directory_provider/directory_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from typing import List
import logging
from app.models.directory.dto import DirectoryDto
from app.services.api.authenticators.null_authenticator import NullAuthenticator
from app.services.api.fhir_api import FhirApi

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -40,35 +38,14 @@ def get_all_directories_include_ignored_ids(self, include_ignored_ids: List[str]
pass

@abc.abstractmethod
def get_one_directory(self, directory_id: str) -> DirectoryDto:
def get_one_directory(self, directory_id: str) -> DirectoryDto|None:
"""
Returns a specific directory by their unique identifier or raises Exception if the directory provider could not be reached.
"""
pass

@staticmethod
def check_capability_statement(dir_dto: DirectoryDto) -> bool:
def is_deleted(self, dir_dto: DirectoryDto) -> bool:
"""
Validates the capability statement of a directory to ensure it meets mCSD requirements.
Logs the process and returns True if valid, False otherwise.
Checks if a directory has been marked as deleted.
"""
logger.info(f"Checking capability statement for {dir_dto.id}")
try:
fhir_api=FhirApi(
timeout=5,
backoff=5,
auth=NullAuthenticator(),
base_url=dir_dto.endpoint_address,
request_count=5,
fill_required_fields=False,
retries=5,
)
if not fhir_api.validate_capability_statement():
logger.warning(
f"Directory {dir_dto.id} at {dir_dto.endpoint_address} does not support mCSD requirements"
)
return False
except Exception as e:
logger.error(f"Error checking capability statement for {dir_dto.id}: {e}")
return False
return True
return False
39 changes: 18 additions & 21 deletions app/services/directory_provider/factory.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import json
from typing import List

from app.models.directory.dto import DirectoryDto
from app.services.api.authenticators.authenticator import Authenticator
from app.services.api.fhir_api import FhirApi
from app.services.directory_provider.capability_provider import CapabilityProvider
from app.services.directory_provider.fhir_provider import FhirDirectoryProvider
from app.services.entity.directory_info_service import DirectoryInfoService
from app.services.api.directory_api_service import DirectoryApiService
from app.services.directory_provider.caching_provider import CachingDirectoryProvider
from app.services.directory_provider.json_provider import DirectoryJsonProvider
from app.services.directory_provider.directory_provider import DirectoryProvider
from app.config import Config
Expand All @@ -24,20 +21,22 @@ def __init__(self, config: Config, auth: Authenticator, directory_info_service:
self.__directory_info_service = directory_info_service

def create(self) -> DirectoryProvider:
provider: DirectoryProvider|None = None

# Use JSON file-based provider if directories_file_path is provided
if (
self.__directory_config.directories_file_path is not None
and len(self.__directory_config.directories_file_path) > 1
):
return DirectoryJsonProvider(
directories=self._read_directories_file(
self.__directory_config.directories_file_path
),
provider = DirectoryJsonProvider(
json_path=self.__directory_config.directories_file_path,
directory_info_service=self.__directory_info_service,
validate_capability_statement=self.__mcsd_config.check_capability_statement,
)

elif self.__directory_config.directories_provider_url is not None:
# Use API-based provider if directories_provider_url is provided

# Api service to interact with the FHIR server
api_service = DirectoryApiService(
fhir_api=FhirApi(
timeout=self.__directory_config.timeout,
Expand All @@ -53,23 +52,21 @@ def create(self) -> DirectoryProvider:
),
provider_url=self.__directory_config.directories_provider_url,
)
return CachingDirectoryProvider(
api_service=api_service,

provider = FhirDirectoryProvider(
api_provider=api_service,
directory_info_service=self.__directory_info_service,
validate_capability_statement=self.__mcsd_config.check_capability_statement,
)

else:
raise ValueError(
"Configuration error: Either 'directories_file_path' or 'directories_provider_url' must be provided. "
f"Provided values - directories_file_path: {self.__directory_config.directories_file_path}, "
f"directories_provider_url: {self.__directory_config.directories_provider_url}."
)

def _read_directories_file(self, directory_urls_path: str) -> List[DirectoryDto]:
try:
with open(directory_urls_path) as f:
data = json.load(f)
return [DirectoryDto(**item) for item in data["directories"]]
except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
raise ValueError(f"Error processing directory URLs file: {e}")
# Wrap with capability filter provider
return CapabilityProvider(
inner=provider,
validate_capability_statement=self.__mcsd_config.check_capability_statement,
)
Loading