Skip to content
Draft
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
10 changes: 5 additions & 5 deletions src/ere/adapters/mock_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ class MockResolver:
"""

def process_request(self, request: ERERequest) -> EREResponse:
request_id = getattr(request, "ereRequestId", "unknown")
request_id = getattr(request, "ere_request_id", "unknown")
log.warning(
"MockResolver.process_request: returning placeholder error response "
"for request_id=%s — wire a real resolver to enable resolution.",
request_id,
)
return EREErrorResponse(
ereRequestId=request_id,
errorTitle="Mock resolver — not implemented",
errorDetail=(
ere_request_id=request_id,
error_title="Mock resolver — not implemented",
error_detail=(
"This ERE instance is running with the MockResolver placeholder. "
"No resolution logic has been configured."
),
errorType="NotImplementedError",
error_type="NotImplementedError",
timestamp=datetime.now(timezone.utc).isoformat(),
)

Expand Down
6 changes: 3 additions & 3 deletions src/ere/adapters/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ def __init__(

def push_request(self, request: ERERequest):
log.debug(
f"Redis ERE client, pushing request id: {request.ereRequestId} to channel: {self.request_channel_id}"
f"Redis ERE client, pushing request id: {request.ere_request_id} to channel: {self.request_channel_id}"
)
msg_json_str = _linkml_dumper.dumps(request)
self._redis_client.lpush(self.request_channel_id, msg_json_str)
log.debug(f"Redis ERE client, request id: {request.ereRequestId} sent")
log.debug(f"Redis ERE client, request id: {request.ere_request_id} sent")

def subscribe_responses(self) -> Generator[EREResponse, None, None]:
while True:
Expand All @@ -83,7 +83,7 @@ def subscribe_responses(self) -> Generator[EREResponse, None, None]:
_, raw_msg = self._redis_client.brpop(self.response_channel_id)
response = get_response_from_message(raw_msg, self.character_encoding)
log.debug(
f"Redis ERE client, received response id: {response.ereRequestId}"
f"Redis ERE client, received response id: {response.ere_request_id}"
)
yield response
except (ConnectionError, TimeoutError) as ex:
Expand Down
6 changes: 3 additions & 3 deletions src/ere/services/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ async def _pull_request(self) -> ERERequest:
)

request = get_request_from_message(raw_msg, self.character_encoding)
log.debug(f"RedisResolutionService, pulled request id: {request.ereRequestId}")
log.debug(f"RedisResolutionService, pulled request id: {request.ere_request_id}")
return request

def _push_response(self, response: EREResponse):
log.debug(
f"RedisResolutionService, pushing response id: {response.ereRequestId} to channel: {self.response_channel_id}"
f"RedisResolutionService, pushing response id: {response.ere_request_id} to channel: {self.response_channel_id}"
)
msg_json_str = _linkml_dumper.dumps(response)
self._redis_client.lpush(self.response_channel_id, msg_json_str)
log.debug(f"RedisResolutionService, response id: {response.ereRequestId} sent")
log.debug(f"RedisResolutionService, response id: {response.ere_request_id} sent")
133 changes: 69 additions & 64 deletions test/_test_ere_abstracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,18 @@
from pyparsing import Path
from rdflib import Graph

from ere.entrypoints import AbstractClient
from ere.models.core import (
from ere.adapters.redis import AbstractClient
from erspec.models.ere import (
EntityMentionResolutionRequest,
EntityMentionResolutionResponse,
EREErrorResponse,
# FullRebuildRequest, # TODO: Uncomment when available in erspec
# FullRebuildResponse, # TODO: Uncomment when available in erspec
)
from erspec.models.core import (
EntityMention,
EntityMentionIdentifier,
ClusterReference,
EREErrorResponse,
FullRebuildRequest,
FullRebuildResponse,
)


Expand All @@ -62,30 +64,30 @@ def test_known_entity_resolution(mock_ere_client: AbstractClient):
)

test_entity_mention = EntityMention(
identifier=EntityMentionIdentifier(
requestId=test_entity_uri,
sourceId="test-module",
entityType=f"{ORG_NS}Organization",
identifiedBy=EntityMentionIdentifier(
request_id=test_entity_uri,
source_id="test-module",
entity_type=f"{ORG_NS}Organization",
),
# Not important here, the mock resolver just looks up static test data
# TODO: validation of ID/content match
contentType="text/turtle",
content_type="text/turtle",
content="<foo>",
)

test_req = EntityMentionResolutionRequest(
entityMention=test_entity_mention,
ereRequestId="test-known-entity-resolution-001",
entity_mention=test_entity_mention,
ere_request_id="test-known-entity-resolution-001",
timestamp=create_timestamp(),
)

mock_ere_client.push_request(test_req)
entity_resolution = catch_response(
mock_ere_client, test_req.ereRequestId, EntityMentionResolutionResponse
mock_ere_client, test_req.ere_request_id, EntityMentionResolutionResponse
)

assert_that(
entity_resolution.entityMentionId,
entity_resolution.entity_mention_id,
"Resolution response has the source entity mention ID",
).is_equal_to(test_entity_mention.identifier)

Expand All @@ -110,26 +112,26 @@ def test_unknown_entity_resolution(mock_ere_client: AbstractClient):
test_entity_uri = f"{ORG_NS}foo_organization_999"

test_entity_mention = EntityMention(
identifier=EntityMentionIdentifier(
requestId=test_entity_uri,
sourceId="test-module",
entityType=f"{ORG_NS}Organization",
identifiedBy=EntityMentionIdentifier(
request_id=test_entity_uri,
source_id="test-module",
entity_type=f"{ORG_NS}Organization",
),
# Not important here, the mock resolver just looks up static test data
# TODO: validation of ID/content match
contentType="text/turtle",
content_type="text/turtle",
content="<foo>",
)

test_req = EntityMentionResolutionRequest(
entityMention=test_entity_mention,
ereRequestId="test-unknown-entity-resolution-001",
entity_mention=test_entity_mention,
ere_request_id="test-unknown-entity-resolution-001",
timestamp=create_timestamp(),
)

mock_ere_client.push_request(test_req)
entity_resolution = catch_response(
mock_ere_client, test_req.ereRequestId, EntityMentionResolutionResponse
mock_ere_client, test_req.ere_request_id, EntityMentionResolutionResponse
)

candidate_clusters = entity_resolution.candidates
Expand All @@ -148,39 +150,42 @@ def test_unknown_entity_resolution(mock_ere_client: AbstractClient):
).is_equal_to(1)


def test_ere_acknowledges_rebuild_request(mock_ere_client: AbstractClient):
"""
Scenario: The ERE acknowledges a rebuild request
"""

rebuild_request = FullRebuildRequest(
ereRequestId="test-ere-acknowledges-rebuild-request-001",
timestamp=create_timestamp(),
)

mock_ere_client.push_request(rebuild_request)

# Does all the assertions we want here
catch_response(mock_ere_client, rebuild_request.ereRequestId, FullRebuildResponse)


def test_ere_still_working_after_rebuild(mock_ere_client: AbstractClient):
"""
Scenario: The ERE keeps resolving entities as usually after a rebuild request
"""

# First, send a rebuild request
rebuild_request = FullRebuildRequest(
ereRequestId="test-ere-still-working-after-rebuild-001",
timestamp=create_timestamp(),
)

mock_ere_client.push_request(rebuild_request)
catch_response(mock_ere_client, rebuild_request.ereRequestId, FullRebuildResponse)

# Now just repeat previous tests
test_known_entity_resolution(mock_ere_client)
test_unknown_entity_resolution(mock_ere_client)
# TODO: Uncomment when FullRebuildRequest/Response are available in erspec
# def test_ere_acknowledges_rebuild_request(mock_ere_client: AbstractClient):
# """
# Scenario: The ERE acknowledges a rebuild request
# """
#
# rebuild_request = FullRebuildRequest(
# ere_request_id="test-ere-acknowledges-rebuild-request-001",
# timestamp=create_timestamp(),
# )
#
# mock_ere_client.push_request(rebuild_request)
#
# # Does all the assertions we want here
# catch_response(mock_ere_client, rebuild_request.ere_request_id, FullRebuildResponse)


# TODO: Uncomment when FullRebuildRequest/Response are available in erspec
# # TODO: Uncomment when FullRebuildRequest/Response are available in erspec
# def test_ere_still_working_after_rebuild(mock_ere_client: AbstractClient):
# """
# Scenario: The ERE keeps resolving entities as usually after a rebuild request
# """
#
# # First, send a rebuild request
# rebuild_request = FullRebuildRequest(
# ere_request_id="test-ere-still-working-after-rebuild-001",
# timestamp=create_timestamp(),
# )
#
# mock_ere_client.push_request(rebuild_request)
# catch_response(mock_ere_client, rebuild_request.ere_request_id, FullRebuildResponse)
#
# # Now just repeat previous tests
# test_known_entity_resolution(mock_ere_client)
# test_unknown_entity_resolution(mock_ere_client)


def test_ere_replies_with_error_response_to_malformed_request(
Expand All @@ -191,29 +196,29 @@ def test_ere_replies_with_error_response_to_malformed_request(
"""
# Send a malformed request (content type is unsupported)
malformed_request = EntityMentionResolutionRequest(
ereRequestId="test-bad-resolution-req-001",
entityMention=EntityMention(
identifier=EntityMentionIdentifier(
requestId="", sourceId="test-module", entityType="FooType"
ere_request_id="test-bad-resolution-req-001",
entity_mention=EntityMention(
identifiedBy=EntityMentionIdentifier(
request_id="", source_id="test-module", entity_type="FooType"
), # Malformed part
contentType="text/turtle",
content_type="text/turtle",
content="<foo>",
),
timestamp=create_timestamp(),
)

mock_ere_client.push_request(malformed_request)
error_response = catch_response(
mock_ere_client, malformed_request.ereRequestId, EREErrorResponse
mock_ere_client, malformed_request.ere_request_id, EREErrorResponse
)

assert_that(
error_response.errorTitle, "The response has the expected error title"
error_response.error_title, "The response has the expected error title"
).contains("MockResolver, unsupported entity type")
assert_that(
error_response.errorDetail, "The response has the expected error detail"
error_response.error_detail, "The response has the expected error detail"
).contains("MockResolver, unsupported entity type")
assert_that(error_response.errorType, "The response has an error type").is_equal_to(
assert_that(error_response.error_type, "The response has an error type").is_equal_to(
"ValueError"
)

Expand Down
47 changes: 26 additions & 21 deletions test/_test_ere_pubsub_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
from assertpy import assert_that
from ere_test import EPD_NS, ORG_NS, MockResolver, catch_response, create_timestamp

from ere.entrypoints import AbstractClient
from ere.models.core import (
from ere.adapters.redis import AbstractClient
from erspec.models.ere import (
EntityMentionResolutionRequest,
EntityMentionResolutionResponse,
ERERequest,
EREResponse,
EREErrorResponse,
)
from erspec.models.core import (
ClusterReference,
EntityMention,
EntityMentionIdentifier,
Expand All @@ -39,38 +42,40 @@ def test_known_entity_resolution(mock_ere_client: AbstractClient):
)

expected_cluster = ClusterReference(
clusterId=f"{EPD_NS}id_2023-S-210-662860_ReviewerOrganisation_LLhJHMi9mby8ixbkfyGoWj_Cluster",
confidenceScore=0.98,
cluster_id=f"{EPD_NS}id_2023-S-210-662860_ReviewerOrganisation_LLhJHMi9mby8ixbkfyGoWj_Cluster",
confidence_score=0.98,
similarity_score=0.98,
)
expected_alt_cluster = ClusterReference(
clusterId=f"{EPD_NS}id_2023-S-210-661238_ReviewerOrganisation_LLhJHMi9mby8ixbkfyGoWj_alt_Cluster",
confidenceScore=0.80,
cluster_id=f"{EPD_NS}id_2023-S-210-661238_ReviewerOrganisation_LLhJHMi9mby8ixbkfyGoWj_alt_Cluster",
confidence_score=0.80,
similarity_score=0.80,
)

test_entity_mention = EntityMention(
identifier=EntityMentionIdentifier(
requestId=test_entity_uri,
sourceId="test-module",
entityType=f"{ORG_NS}Organization",
identifiedBy=EntityMentionIdentifier(
request_id=test_entity_uri,
source_id="test-module",
entity_type=f"{ORG_NS}Organization",
),
# Not important here, the mock resolver just looks up static test data
# TODO: validation of ID/content match
contentType="text/turtle",
content_type="text/turtle",
content="<foo>",
)
test_req = EntityMentionResolutionRequest(
entityMention=test_entity_mention,
ereRequestId="test-known-entity-resolution-001",
entity_mention=test_entity_mention,
ere_request_id="test-known-entity-resolution-001",
timestamp=create_timestamp(),
)

mock_ere_client.push_request(test_req)
entity_resolution: EntityMentionResolutionResponse = catch_response(
mock_ere_client, test_req.ereRequestId, EntityMentionResolutionResponse
mock_ere_client, test_req.ere_request_id, EntityMentionResolutionResponse
)

assert_that(
entity_resolution.entityMentionId,
entity_resolution.entity_mention_id,
"Resolution response has the source entity mention ID",
).is_equal_to(test_entity_mention.identifier)

Expand Down Expand Up @@ -131,14 +136,14 @@ def guarded_get() -> ERERequest | None:
log.debug("Service: pulling request from queue")
# Needs to go in a thread, in order to not block the event loop in waiting
request = await asyncio.to_thread(guarded_get)
id = request.ereRequestId if request else "None"
id = request.ere_request_id if request else "None"
log.debug(f"Service: got a request from queue, id: {id}")
return request

def _push_response(self, response: EREResponse):
log.debug(f"Service: pushing response to queue, id: {response.ereRequestId}")
log.debug(f"Service: pushing response to queue, id: {response.ere_request_id}")
_response_queue.put_nowait(response)
log.debug(f"Service: pushed response to queue, id: {response.ereRequestId}")
log.debug(f"Service: pushed response to queue, id: {response.ere_request_id}")


class FooPubSubClient(AbstractClient):
Expand All @@ -150,13 +155,13 @@ class FooPubSubClient(AbstractClient):
"""

def push_request(self, request: ERERequest):
log.debug(f"Client: pushing request to queue, id: {request.ereRequestId}")
log.debug(f"Client: pushing request to queue, id: {request.ere_request_id}")
_request_queue.put_nowait(request)
log.debug(f"Client: pushed request to queue, id: {request.ereRequestId}")
log.debug(f"Client: pushed request to queue, id: {request.ere_request_id}")

def subscribe_responses(self) -> Generator[EREResponse, None, None]:
while True:
log.debug("Client: waiting for response from queue")
response = _response_queue.get()
log.debug(f"Client: got a response from queue, id: {response.ereRequestId}")
log.debug(f"Client: got a response from queue, id: {response.ere_request_id}")
yield response
Loading