Skip to content

Commit

Permalink
Move data stores to separate module and add helper methods
Browse files Browse the repository at this point in the history
  • Loading branch information
petechd committed Oct 18, 2023
1 parent f6b1b51 commit a31f5c1
Show file tree
Hide file tree
Showing 29 changed files with 173 additions and 149 deletions.
12 changes: 5 additions & 7 deletions app/data_models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from app.data_models.answer import Answer, AnswerValueTypes
from app.data_models.answer_store import AnswerStore
from app.data_models.fulfilment_request import FulfilmentRequest
from app.data_models.list_store import ListStore
from app.data_models.progress import CompletionStatus
from app.data_models.questionnaire_store import (
AnswerStore,
ListStore,
ProgressStore,
QuestionnaireStore,
SupplementaryDataStore,
)
from app.data_models.progress_store import ProgressStore
from app.data_models.questionnaire_store import QuestionnaireStore
from app.data_models.session_data import SessionData
from app.data_models.session_store import SessionStore
from app.data_models.supplementary_data_store import SupplementaryDataStore

__all__ = [
"Answer",
Expand Down
134 changes: 134 additions & 0 deletions app/data_models/data_stores.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
from dataclasses import dataclass, field
from typing import Iterable, MutableMapping

from ordered_set import OrderedSet

from app.data_models.answer_store import AnswerStore
from app.data_models.list_store import ListStore
from app.data_models.metadata_proxy import MetadataProxy
from app.data_models.progress_store import ProgressStore
from app.data_models.supplementary_data_store import SupplementaryDataStore
from app.questionnaire import (
Location,
QuestionnaireSchema,
placeholder_renderer,
value_source_resolver,
)
from app.questionnaire.relationship_location import RelationshipLocation
from app.questionnaire.rules import rule_evaluator
from app.utilities.types import LocationType


@dataclass
class DataStores:
# self.metadata is a read-only view over self._metadata
metadata: MetadataProxy | None = None
response_metadata: MutableMapping = field(default_factory=dict)
list_store: ListStore = field(default_factory=ListStore)
answer_store: AnswerStore = field(default_factory=AnswerStore)
progress_store: ProgressStore = field(default_factory=ProgressStore)
supplementary_data_store: SupplementaryDataStore = field(
default_factory=SupplementaryDataStore
)

def rule_evaluator(
self,
*,
schema: QuestionnaireSchema,
location: Location,
routing_path_block_ids: Iterable[str] | None = None,
) -> rule_evaluator.RuleEvaluator:
return rule_evaluator.RuleEvaluator(
schema=schema,
answer_store=self.answer_store,
list_store=self.list_store,
metadata=self.metadata,
response_metadata=self.response_metadata,
progress_store=self.progress_store,
location=location,
routing_path_block_ids=routing_path_block_ids,
supplementary_data_store=self.supplementary_data_store,
)

def value_source_resolver(
self,
*,
schema: QuestionnaireSchema,
location: Location | RelationshipLocation | None,
list_item_id: str | None,
routing_path_block_ids: OrderedSet[str] | None = None,
assess_routing_path: bool = True,
use_default_answer: bool = True,
escape_answer_values: bool = True,
) -> value_source_resolver.ValueSourceResolver:
return value_source_resolver.ValueSourceResolver(
answer_store=self.answer_store,
list_store=self.list_store,
metadata=self.metadata,
schema=schema,
location=location,
list_item_id=list_item_id,
escape_answer_values=escape_answer_values,
response_metadata=self.response_metadata,
use_default_answer=use_default_answer,
assess_routing_path=assess_routing_path,
routing_path_block_ids=routing_path_block_ids,
progress_store=self.progress_store,
supplementary_data_store=self.supplementary_data_store,
)

def placeholder_renderer(
self,
language: str,
schema: QuestionnaireSchema,
location: LocationType | None = None,
placeholder_preview_mode: bool | None = False,
) -> placeholder_renderer.PlaceholderRenderer:
return placeholder_renderer.PlaceholderRenderer(
language=language,
answer_store=self.answer_store,
list_store=self.list_store,
metadata=self.metadata,
response_metadata=self.response_metadata,
schema=schema,
progress_store=self.progress_store,
supplementary_data_store=self.supplementary_data_store,
location=location,
placeholder_preview_mode=placeholder_preview_mode,
)

def set_supplementary_store(
self, supplementary_data: MutableMapping, list_mappings: dict
) -> None:
self.supplementary_data_store = SupplementaryDataStore(
supplementary_data=supplementary_data, list_mappings=list_mappings
)

def deserialize_stores(self, json_data: dict, metadata: MutableMapping) -> None:
self.metadata = self.metadata_from_dict(metadata=metadata)
self.response_metadata = json_data.get("RESPONSE_METADATA", {})
# Type ignore: ListStore deserialize method only accepts Iterable[ListModelDictType]
self.list_store = ListStore.deserialize(json_data.get("LISTS")) # type: ignore
self.supplementary_data_store = SupplementaryDataStore.deserialize(
json_data.get("SUPPLEMENTARY_DATA", {})
)
self.progress_store = ProgressStore(json_data.get("PROGRESS"))
self.answer_store = AnswerStore(json_data.get("ANSWERS"))

def serialize_stores(self) -> dict:
return {
"ANSWERS": list(self.answer_store),
"SUPPLEMENTARY_DATA": self.supplementary_data_store.serialize(),
"LISTS": self.list_store.serialize(),
"PROGRESS": self.progress_store.serialize(),
"RESPONSE_METADATA": self.response_metadata,
}

@staticmethod
def metadata_from_dict(metadata: MutableMapping) -> MetadataProxy:
return MetadataProxy.from_dict(metadata)

def delete(self) -> None:
self.response_metadata = {}
self.answer_store.clear()
self.progress_store.clear()
124 changes: 8 additions & 116 deletions app/data_models/questionnaire_store.py
Original file line number Diff line number Diff line change
@@ -1,113 +1,19 @@
from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from typing import TYPE_CHECKING, Iterable, MutableMapping, Optional
from typing import TYPE_CHECKING, MutableMapping, Optional

from ordered_set import OrderedSet

from app.data_models.answer_store import AnswerStore
from app.data_models.list_store import ListStore
from app.data_models.metadata_proxy import MetadataProxy
from app.data_models.progress_store import ProgressStore
from app.data_models.supplementary_data_store import SupplementaryDataStore
from app.questionnaire import (
Location,
QuestionnaireSchema,
placeholder_renderer,
value_source_resolver,
)
from app.questionnaire.relationship_location import RelationshipLocation
from app.questionnaire.rules import rule_evaluator
from app.data_models.data_stores import DataStores
from app.questionnaire.rules.utils import parse_iso_8601_datetime
from app.utilities.json import json_dumps, json_loads
from app.utilities.types import LocationType, SupplementaryDataListMapping
from app.utilities.types import SupplementaryDataListMapping

if TYPE_CHECKING:
from app.storage.encrypted_questionnaire_storage import ( # pragma: no cover
EncryptedQuestionnaireStorage,
)


@dataclass
class DataStores:
# self.metadata is a read-only view over self._metadata
metadata: MetadataProxy | None = None
response_metadata: MutableMapping = field(default_factory=dict)
list_store: ListStore = field(default_factory=ListStore)
answer_store: AnswerStore = field(default_factory=AnswerStore)
progress_store: ProgressStore = field(default_factory=ProgressStore)
supplementary_data_store: SupplementaryDataStore = field(
default_factory=SupplementaryDataStore
)

def rule_evaluator(
self,
*,
schema: QuestionnaireSchema,
location: Location,
routing_path_block_ids: Iterable[str] | None = None,
) -> rule_evaluator.RuleEvaluator:
return rule_evaluator.RuleEvaluator(
schema=schema,
answer_store=self.answer_store,
list_store=self.list_store,
metadata=self.metadata,
response_metadata=self.response_metadata,
progress_store=self.progress_store,
location=location,
routing_path_block_ids=routing_path_block_ids,
supplementary_data_store=self.supplementary_data_store,
)

def value_source_resolver(
self,
*,
schema: QuestionnaireSchema,
location: Location | RelationshipLocation | None,
list_item_id: str | None,
routing_path_block_ids: OrderedSet[str] | None = None,
assess_routing_path: bool = True,
use_default_answer: bool = True,
escape_answer_values: bool = True,
) -> value_source_resolver.ValueSourceResolver:
return value_source_resolver.ValueSourceResolver(
answer_store=self.answer_store,
list_store=self.list_store,
metadata=self.metadata,
schema=schema,
location=location,
list_item_id=list_item_id,
escape_answer_values=escape_answer_values,
response_metadata=self.response_metadata,
use_default_answer=use_default_answer,
assess_routing_path=assess_routing_path,
routing_path_block_ids=routing_path_block_ids,
progress_store=self.progress_store,
supplementary_data_store=self.supplementary_data_store,
)

def placeholder_renderer(
self,
language: str,
schema: QuestionnaireSchema,
location: LocationType | None = None,
placeholder_preview_mode: bool | None = False,
) -> placeholder_renderer.PlaceholderRenderer:
return placeholder_renderer.PlaceholderRenderer(
language=language,
answer_store=self.answer_store,
list_store=self.list_store,
metadata=self.metadata,
response_metadata=self.response_metadata,
schema=schema,
progress_store=self.progress_store,
supplementary_data_store=self.supplementary_data_store,
location=location,
placeholder_preview_mode=placeholder_preview_mode,
)


class QuestionnaireStore:
LATEST_VERSION = 1

Expand Down Expand Up @@ -144,8 +50,6 @@ def set_metadata(self, to_set: MutableMapping) -> QuestionnaireStore:
Metadata should normally be read only.
"""
self._metadata = to_set
self.data_stores.metadata = MetadataProxy.from_dict(self._metadata)

return self

def set_supplementary_data(self, to_set: MutableMapping) -> None:
Expand All @@ -166,7 +70,7 @@ def set_supplementary_data(self, to_set: MutableMapping) -> None:
for list_name, list_data in to_set.get("items", {}).items()
}

self.data_stores.supplementary_data_store = SupplementaryDataStore(
self.data_stores.set_supplementary_store(
supplementary_data=to_set, list_mappings=list_mappings
)

Expand Down Expand Up @@ -229,32 +133,20 @@ def _remove_old_supplementary_lists_and_answers(

def _deserialize(self, data: str) -> None:
json_data = json_loads(data)
self.data_stores.progress_store = ProgressStore(json_data.get("PROGRESS"))
self.set_metadata(json_data.get("METADATA", {}))
self.data_stores.supplementary_data_store = SupplementaryDataStore.deserialize(
json_data.get("SUPPLEMENTARY_DATA", {})
)
self.data_stores.answer_store = AnswerStore(json_data.get("ANSWERS"))
self.data_stores.list_store = ListStore.deserialize(json_data.get("LISTS"))
self.data_stores.response_metadata = json_data.get("RESPONSE_METADATA", {})
self.data_stores.deserialize_stores(json_data, metadata=self._metadata)

def serialize(self) -> str:
data = {
serialized_stores = self.data_stores.serialize_stores()
data = serialized_stores | {
"METADATA": self._metadata,
"ANSWERS": list(self.data_stores.answer_store),
"SUPPLEMENTARY_DATA": self.data_stores.supplementary_data_store.serialize(),
"LISTS": self.data_stores.list_store.serialize(),
"PROGRESS": self.data_stores.progress_store.serialize(),
"RESPONSE_METADATA": self.data_stores.response_metadata,
}
return json_dumps(data)

def delete(self) -> None:
self._storage.delete()
self._metadata.clear()
self.data_stores.response_metadata = {}
self.data_stores.answer_store.clear()
self.data_stores.progress_store.clear()
self.data_stores.delete()

def save(self) -> None:
data = self.serialize()
Expand Down
2 changes: 1 addition & 1 deletion app/forms/questionnaire_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from werkzeug.datastructures import ImmutableMultiDict, MultiDict
from wtforms import validators

from app.data_models.questionnaire_store import DataStores
from app.data_models.data_stores import DataStores
from app.forms import error_messages
from app.forms.field_handlers import DateHandler, FieldHandler, get_field_handler
from app.forms.validators import DateRangeCheck, MutuallyExclusiveCheck, SumCheck
Expand Down
2 changes: 1 addition & 1 deletion app/questionnaire/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from flask import url_for

from app.data_models.questionnaire_store import DataStores
from app.data_models.data_stores import DataStores
from app.questionnaire import QuestionnaireSchema
from app.questionnaire.location import Location, SectionKey
from app.questionnaire.path_finder import PathFinder
Expand Down
2 changes: 1 addition & 1 deletion app/submitter/converter_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

from app.authentication.auth_payload_versions import AuthPayloadVersion
from app.data_models import QuestionnaireStore
from app.data_models.data_stores import DataStores
from app.data_models.metadata_proxy import MetadataProxy, NoMetadataException
from app.data_models.questionnaire_store import DataStores
from app.questionnaire.questionnaire_schema import (
DEFAULT_LANGUAGE_CODE,
QuestionnaireSchema,
Expand Down
2 changes: 1 addition & 1 deletion app/views/contexts/calculated_summary_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from werkzeug.datastructures import ImmutableDict

from app.data_models.questionnaire_store import DataStores
from app.data_models.data_stores import DataStores
from app.jinja_filters import format_number, format_percentage, format_unit
from app.questionnaire.questionnaire_schema import (
QuestionnaireSchema,
Expand Down
2 changes: 1 addition & 1 deletion app/views/contexts/context.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from abc import ABC

from app.data_models.questionnaire_store import DataStores
from app.data_models.data_stores import DataStores
from app.questionnaire.questionnaire_schema import QuestionnaireSchema
from app.questionnaire.router import Router

Expand Down
2 changes: 1 addition & 1 deletion app/views/contexts/preview_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from flask_babel import lazy_gettext

from app.data_models.questionnaire_store import DataStores
from app.data_models.data_stores import DataStores
from app.questionnaire import QuestionnaireSchema
from app.views.contexts import Context
from app.views.contexts.section_preview_context import SectionPreviewContext
Expand Down
2 changes: 1 addition & 1 deletion app/views/contexts/section_preview_context.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from app.data_models.questionnaire_store import DataStores
from app.data_models.data_stores import DataStores
from app.questionnaire import QuestionnaireSchema
from app.views.contexts.context import Context
from app.views.contexts.preview import PreviewGroup
Expand Down
2 changes: 1 addition & 1 deletion app/views/contexts/section_summary_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from werkzeug.datastructures import ImmutableDict

from app.data_models.questionnaire_store import DataStores
from app.data_models.data_stores import DataStores
from app.questionnaire import Location, QuestionnaireSchema
from app.questionnaire.questionnaire_schema import LIST_COLLECTORS_WITH_REPEATING_BLOCKS
from app.questionnaire.routing_path import RoutingPath
Expand Down
Loading

0 comments on commit a31f5c1

Please sign in to comment.