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
4 changes: 2 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@
"ms-python.flake8",
"eamodio.gitlens",
"github.vscode-pull-request-github",
"dbaeumer.vscode-eslint",
"lfm.vscode-makefile-term",
"streetsidesoftware.code-spell-checker",
"timonwong.shellcheck",
"github.vscode-github-actions"
"github.vscode-github-actions",
"tamasfe.even-better-toml"
],
"settings": {
"python.defaultInterpreterPath": "/home/vscode/.asdf/shims/python",
Expand Down
6 changes: 3 additions & 3 deletions src/eps_spine_shared/common/dynamodb_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ def __init__(

resource_args = {"service_name": SERVICE_NAME, "region_name": REGION_NAME}
if aws_endpoint_url:
log_object.write_log("DDB0003", None, {"awsEndpointUrl": aws_endpoint_url})
self.log_object.write_log("DDB0003", None, {"awsEndpointUrl": aws_endpoint_url})
resource_args["endpoint_url"] = aws_endpoint_url
else:
log_object.write_log("DDB0004", None)
self.log_object.write_log("DDB0004", None)

self.resource = session.resource(**resource_args)
self.table = self.resource.Table(table_name)
Expand All @@ -82,7 +82,7 @@ def __init__(
self.deserialiser = TypeDeserializer()
self.serialiser = TypeSerializer()
except Exception as ex:
log_object.write_log("DDB0000", sys.exc_info(), {"error": str(ex)})
self.log_object.write_log("DDB0000", sys.exc_info(), {"error": str(ex)})
raise ex

self.log_object.write_log("DDB0001", None, {"tableName": table_name})
Expand Down
32 changes: 15 additions & 17 deletions src/eps_spine_shared/common/dynamodb_datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,29 +80,27 @@ class EpsDynamoDbDataStore:
DEFAULT_EXPIRY_DAYS = 56
MAX_NEXT_ACTIVITY_DATE = "99991231"

def __init__(
self,
log_object,
aws_endpoint_url,
table_name,
role_arn=None,
role_session_name=None,
sts_endpoint_url=None,
):
def __init__(self, log_object, system_config):
"""
Instantiate the DynamoDB client.
"""
self.log_object = EpsLogger(log_object)
self.client = EpsDynamoDbClient(
log_object,
aws_endpoint_url,
table_name,
role_arn,
role_session_name,
sts_endpoint_url,
system_config["ddb aws endpoint url"],
system_config["datastore table name"],
system_config["datastore role arn"],
system_config["process name"],
system_config["sts endpoint url"],
)
self.indexes = EpsDynamoDbIndex(log_object, self.client)

def testConnection(self):
"""
Placeholder test connection, returns constant value
"""
return True

def base64_decode_document_content(self, internal_id, document):
"""
base64 decode document content in order to store as binary type in DynamoDB.
Expand Down Expand Up @@ -595,7 +593,7 @@ def _fetch_next_sequence_number(self, internal_id, key, max_sequence_number, rea
self.client.insert_items(internal_id, [item], is_update, False)
break
except EpsDataStoreError as e:
if e.errorTopic == EpsDataStoreError.CONDITIONAL_UPDATE_FAILURE and tries < 25:
if e.error_topic == EpsDataStoreError.CONDITIONAL_UPDATE_FAILURE and tries < 25:
sequence_number = item[Attribute.SEQUENCE_NUMBER.name]
item[Attribute.SEQUENCE_NUMBER.name] = (
sequence_number + 1 if sequence_number < max_sequence_number else 1
Expand Down Expand Up @@ -759,13 +757,13 @@ def delete_record(self, internal_id, record_key):

@timer
def return_pids_due_for_next_activity(
self, _internal_id, next_activity_start, next_activity_end
self, _internal_id, next_activity_start, next_activity_end, shard=None
):
"""
Returns all the epsRecord keys for prescriptions whose nextActivity is the same as that provided,
and whose next activity date is within the date range provided.
"""
return self.indexes.query_next_activity_date(next_activity_start, next_activity_end)
return self.indexes.query_next_activity_date(next_activity_start, next_activity_end, shard)

@timer
def return_prescription_ids_for_nom_pharm(self, _internal_id, nominated_pharmacy_index_term):
Expand Down
10 changes: 8 additions & 2 deletions src/eps_spine_shared/common/dynamodb_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ def query_claim_id(self, claim_id):

return [item[Key.PK.name] for item in items]

def query_next_activity_date(self, range_start, range_end):
def query_next_activity_date(self, range_start, range_end, shard=None):
"""
Yields the epsRecord keys which match the supplied nextActivity and date range for the nextActivity index.

Expand All @@ -332,6 +332,10 @@ def query_next_activity_date(self, range_start, range_end):
if not valid:
return []

if shard or shard == "":
yield from self._query_next_activity_date_shard(next_activity, sk_expression, shard)
return

shards = [None] + list(range(1, NEXT_ACTIVITY_DATE_PARTITIONS + 1))

for shard in shards:
Expand All @@ -342,7 +346,9 @@ def _query_next_activity_date_shard(self, next_activity, sk_expression, shard):
Return a generator for the epsRecord keys which match the supplied nextActivity and date range
for a given pk shard.
"""
expected_next_activity = next_activity if shard is None else f"{next_activity}.{shard}"
expected_next_activity = (
next_activity if shard is None or shard == "" else f"{next_activity}.{shard}"
)
pk_expression = BotoKey(Attribute.NEXT_ACTIVITY.name).eq(expected_next_activity)

return self.client.query_index_yield(
Expand Down
2 changes: 1 addition & 1 deletion src/eps_spine_shared/common/prescription/line_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def expire(self, parent_prescription):
if current_status not in LineItemStatus.EXPIRY_IMMUTABLE_STATES:
new_status = LineItemStatus.EXPIRY_LOOKUP[current_status]
self.update_status(new_status)
parent_prescription.logObject.write_log(
parent_prescription.log_object.write_log(
"EPS0072b",
None,
{
Expand Down
9 changes: 5 additions & 4 deletions src/eps_spine_shared/common/prescription/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ def _handle_missing_issue(self, issue_number):
{"internalID": self.internal_id, "prescriptionID": self.id, "issue": issue_number},
)
# Re-raise this as SpineBusinessError with equivalent errorCode from ErrorBase1722.
raise EpsBusinessError(EpsErrorBase.PRESCRIPTION_NOT_FOUND)
raise EpsBusinessError(EpsErrorBase.MISSING_ISSUE)

@property
def id(self):
Expand Down Expand Up @@ -2621,7 +2621,7 @@ def release_next_instance(
next_issue_number_str = self._find_next_future_issue_number(current_issue_number_str)
if next_issue_number_str is None:
# give up if there is no next issue
self.pendingInstanceChange = None
self.pending_instance_change = None
return

# update the issue
Expand Down Expand Up @@ -2677,7 +2677,7 @@ def release_next_instance(
)

# mark so that we know to update the prescription's current issue number
self.pendingInstanceChange = next_issue_number_str
self.pending_instance_change = next_issue_number_str

def add_release_document_ref(self, rel_req_document_ref):
"""
Expand Down Expand Up @@ -2969,7 +2969,7 @@ def fetch_release_response_parameters(self):
for line_item in self.current_issue.line_items:
line_item_ref = "lineItem" + str(line_item.order)
item_status = (
line_item.previousStatus
line_item.previous_status
if line_item.status == LineItemStatus.WITH_DISPENSER
else line_item.status
)
Expand Down Expand Up @@ -3044,6 +3044,7 @@ def force_current_instance_increment(self):
new_current_issue_number = False
for i in range(self.current_issue_number, self.max_repeats + 1):
try:
self.prescription_record[fields.FIELD_INSTANCES][str(i)]
new_current_issue_number = i
break
except KeyError:
Expand Down
196 changes: 131 additions & 65 deletions src/eps_spine_shared/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,135 @@

from botocore.exceptions import NoCredentialsError

# Try to import spine error classes. If successful, we are on spine and should use wrapper classes.
on_spine = False
try:
# from spinecore.prescriptions.common.errors.errorbaseprescriptionsearch \
# import ErrorBasePrescSearch # pyright: ignore[reportMissingImports]
from spinecore.common.aws.awscommon import ( # pyright: ignore[reportMissingImports]
NoCredentialsErrorWithRetry,
)
from spinecore.common.errors import ( # pyright: ignore[reportMissingImports]
SpineBusinessError,
SpineSystemError,
)

class EpsNoCredentialsErrorWithRetry(NoCredentialsError):
"""
Extends NoCredentialsError to provide information about retry attempts.
To be caught in Spine application code and re-raised as NoCredentialsErrorWithRetry.
"""

fmt = "Unable to locate credentials after {attempts} attempts"


class EpsSystemError(Exception):
"""
Exception to be raised if an unexpected system error occurs.
To be caught in Spine application code and re-raised as SpineSystemError.
"""

MESSAGE_FAILURE = "messageFailure"
DEVELOPMENT_FAILURE = "developmentFailure"
SYSTEM_FAILURE = "systemFailure"
IMMEDIATE_REQUEUE = "immediateRequeue"
RETRY_EXPIRED = "retryExpired"
PUBLISHER_HANDLES_REQUEUE = "publisherHandlesRequeue"
UNRELIABLE_MESSAGE = "unreliableMessage"

def __init__(self, errorTopic, *args): # noqa: B042
"""
errorTopic is the topic to be used when writing the WDO to the error exchange
"""
super(EpsSystemError, self).__init__(*args)
self.errorTopic = errorTopic


class EpsBusinessError(Exception):
"""
Exception to be raised by a message worker if an expected error condition is hit,
one that is expected to cause a HL7 error response with a set errorCode.
To be caught in Spine application code and re-raised as SpineBusinessError.
"""

def __init__(self, errorCode, suppInfo=None, messageId=None): # noqa: B042
super(EpsBusinessError, self).__init__()
self.errorCode = errorCode
self.supplementaryInformation = suppInfo
self.messageId = messageId

def __str__(self):
if self.supplementaryInformation:
return "{} {}".format(self.errorCode, self.supplementaryInformation)
return str(self.errorCode)


class EpsErrorBase(Enum):
"""
To be used in Spine application code to remap to ErrorBases.
"""

INVALID_LINE_STATE_TRANSITION = 1
ITEM_NOT_FOUND = 2
MAX_REPEAT_MISMATCH = 3
NOT_CANCELLED_EXPIRED = 4
NOT_CANCELLED_CANCELLED = 5
NOT_CANCELLED_NOT_DISPENSED = 6
NOT_CANCELLED_DISPENSED = 7
NOT_CANCELLED_WITH_DISPENSER = 8
NOT_CANCELLED_WITH_DISPENSER_ACTIVE = 9
PRESCRIPTION_NOT_FOUND = 10
# from spinecore.prescriptions.common.errors.errorbase1634 \
# import ErrorBase1634 # pyright: ignore[reportMissingImports]
from spinecore.prescriptions.common.errors.errorbase1719 import ( # pyright: ignore[reportMissingImports]
ErrorBase1719,
)
from spinecore.prescriptions.common.errors.errorbase1722 import ( # pyright: ignore[reportMissingImports]
ErrorBase1722,
)

on_spine = True
except ImportError:
pass


if on_spine:

class EpsNoCredentialsErrorWithRetry:
"""
Wrapper for NoCredentialsErrorWithRetry
"""

def __init__(self, *args):
raise NoCredentialsErrorWithRetry(*args)

class EpsSystemError:
"""
Wrapper for SpineSystemError
"""

def __init__(self, *args):
raise SpineSystemError(*args)

class EpsBusinessError:
"""
Wrapper for SpineBusinessError
"""

def __init__(self, *args):
raise SpineBusinessError(*args)

class EpsErrorBase:
"""
Wrapper for ErrorBases
"""

MISSING_ISSUE = ErrorBase1722.PRESCRIPTION_NOT_FOUND
ITEM_NOT_FOUND = ErrorBase1722.ITEM_NOT_FOUND
INVALID_LINE_STATE_TRANSITION = ErrorBase1722.INVALID_LINE_STATE_TRANSITION
MAX_REPEAT_MISMATCH = ErrorBase1722.MAX_REPEAT_MISMATCH
NOT_CANCELLED_EXPIRED = ErrorBase1719.NOT_CANCELLED_EXPIRED
NOT_CANCELLED_CANCELLED = ErrorBase1719.NOT_CANCELLED_CANCELLED
NOT_CANCELLED_NOT_DISPENSED = ErrorBase1719.NOT_CANCELLED_NOT_DISPENSED
NOT_CANCELLED_DISPENSED = ErrorBase1719.NOT_CANCELLED_DISPENSED
NOT_CANCELLED_WITH_DISPENSER = ErrorBase1719.NOT_CANCELLED_WITH_DISPENSER
NOT_CANCELLED_WITH_DISPENSER_ACTIVE = ErrorBase1719.NOT_CANCELLED_WITH_DISPENSER_ACTIVE
PRESCRIPTION_NOT_FOUND = ErrorBase1719.PRESCRIPTION_NOT_FOUND

else:

class EpsNoCredentialsErrorWithRetry(NoCredentialsError):
"""
Extends NoCredentialsError to provide information about retry attempts.
"""

fmt = "Unable to locate credentials after {attempts} attempts"

class EpsSystemError(Exception):
"""
Exception to be raised if an unexpected system error occurs.
"""

MESSAGE_FAILURE = "messageFailure"
DEVELOPMENT_FAILURE = "developmentFailure"
SYSTEM_FAILURE = "systemFailure"
IMMEDIATE_REQUEUE = "immediateRequeue"
RETRY_EXPIRED = "retryExpired"
PUBLISHER_HANDLES_REQUEUE = "publisherHandlesRequeue"
UNRELIABLE_MESSAGE = "unreliableMessage"

def __init__(self, error_topic, *args): # noqa: B042
"""
error_topic is the topic to be used when writing the WDO to the error exchange
"""
super(EpsSystemError, self).__init__(*args)
self.error_topic = error_topic

class EpsBusinessError(Exception):
"""
Exception to be raised by a message worker if an expected error condition is hit,
one that is expected to cause a HL7 error response with a set errorCode.
"""

def __init__(self, error_code, supp_info=None, message_id=None): # noqa: B042
super(EpsBusinessError, self).__init__()
self.error_code = error_code
self.supplementary_information = supp_info
self.message_id = message_id

def __str__(self):
if self.supplementary_information:
return "{} {}".format(self.error_code, self.supplementary_information)
return str(self.error_code)

class EpsErrorBase(Enum):
"""
To be used in Spine application code to remap to ErrorBases.
"""

INVALID_LINE_STATE_TRANSITION = 1
ITEM_NOT_FOUND = 2
MAX_REPEAT_MISMATCH = 3
NOT_CANCELLED_EXPIRED = 4
NOT_CANCELLED_CANCELLED = 5
NOT_CANCELLED_NOT_DISPENSED = 6
NOT_CANCELLED_DISPENSED = 7
NOT_CANCELLED_WITH_DISPENSER = 8
NOT_CANCELLED_WITH_DISPENSER_ACTIVE = 9
PRESCRIPTION_NOT_FOUND = 10
MISSING_ISSUE = 11
File renamed without changes.
Loading