ds-caselaw-marklogic-api-client 26.0.0__py3-none-any.whl → 27.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ds-caselaw-marklogic-api-client might be problematic. Click here for more details.
- caselawclient/Client.py +3 -10
- caselawclient/factories.py +165 -0
- caselawclient/models/documents/__init__.py +9 -23
- caselawclient/models/documents/body.py +6 -9
- caselawclient/models/judgments.py +12 -8
- caselawclient/models/neutral_citation_mixin.py +7 -11
- caselawclient/models/press_summaries.py +11 -7
- caselawclient/models/utilities/dates.py +1 -5
- caselawclient/models/utilities/move.py +12 -63
- caselawclient/responses/search_response.py +2 -2
- caselawclient/responses/search_result.py +6 -4
- {ds_caselaw_marklogic_api_client-26.0.0.dist-info → ds_caselaw_marklogic_api_client-27.0.0.dist-info}/METADATA +2 -2
- {ds_caselaw_marklogic_api_client-26.0.0.dist-info → ds_caselaw_marklogic_api_client-27.0.0.dist-info}/RECORD +15 -14
- {ds_caselaw_marklogic_api_client-26.0.0.dist-info → ds_caselaw_marklogic_api_client-27.0.0.dist-info}/LICENSE.md +0 -0
- {ds_caselaw_marklogic_api_client-26.0.0.dist-info → ds_caselaw_marklogic_api_client-27.0.0.dist-info}/WHEEL +0 -0
caselawclient/Client.py
CHANGED
|
@@ -12,6 +12,7 @@ from xml.etree.ElementTree import Element, ParseError, fromstring
|
|
|
12
12
|
|
|
13
13
|
import environ
|
|
14
14
|
import requests
|
|
15
|
+
from ds_caselaw_utils.types import NeutralCitationString
|
|
15
16
|
from requests.auth import HTTPBasicAuth
|
|
16
17
|
from requests.structures import CaseInsensitiveDict
|
|
17
18
|
from requests_toolbelt.multipart import decoder
|
|
@@ -800,10 +801,7 @@ class MarklogicApiClient:
|
|
|
800
801
|
else None
|
|
801
802
|
)
|
|
802
803
|
|
|
803
|
-
|
|
804
|
-
image_location = os.getenv("XSLT_IMAGE_LOCATION")
|
|
805
|
-
else:
|
|
806
|
-
image_location = ""
|
|
804
|
+
image_location = os.getenv("XSLT_IMAGE_LOCATION", "")
|
|
807
805
|
|
|
808
806
|
show_unpublished = self.verify_show_unpublished(show_unpublished)
|
|
809
807
|
|
|
@@ -1038,12 +1036,7 @@ class MarklogicApiClient:
|
|
|
1038
1036
|
search_parameters.collections = [DOCUMENT_COLLECTION_URI_JUDGMENT]
|
|
1039
1037
|
return self.search_and_decode_response(search_parameters)
|
|
1040
1038
|
|
|
1041
|
-
def
|
|
1042
|
-
"""Move the judgment at old_uri on top of the new citation, which must already exist
|
|
1043
|
-
Compare to update_document_uri"""
|
|
1044
|
-
return move.overwrite_document(old_uri, new_citation, api_client=self)
|
|
1045
|
-
|
|
1046
|
-
def update_document_uri(self, old_uri: str, new_citation: str) -> str:
|
|
1039
|
+
def update_document_uri(self, old_uri: DocumentURIString, new_citation: NeutralCitationString) -> DocumentURIString:
|
|
1047
1040
|
"""
|
|
1048
1041
|
Move the document at old_uri to the correct location based on the neutral citation
|
|
1049
1042
|
The new neutral citation *must* not already exist (that is handled elsewhere)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from typing import Any, Optional, cast
|
|
3
|
+
from unittest.mock import Mock, patch
|
|
4
|
+
|
|
5
|
+
from typing_extensions import TypeAlias
|
|
6
|
+
|
|
7
|
+
from caselawclient.Client import MarklogicApiClient
|
|
8
|
+
from caselawclient.models.documents import Document
|
|
9
|
+
from caselawclient.models.documents.body import DocumentBody
|
|
10
|
+
from caselawclient.models.judgments import Judgment
|
|
11
|
+
from caselawclient.models.press_summaries import PressSummary
|
|
12
|
+
from caselawclient.responses.search_result import SearchResult, SearchResultMetadata
|
|
13
|
+
|
|
14
|
+
DEFAULT_DOCUMENT_BODY_XML = "<akomantoso>This is some XML of a judgment.</akomantoso>"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DocumentBodyFactory:
|
|
18
|
+
# "name_of_attribute": "default value"
|
|
19
|
+
PARAMS_MAP: dict[str, Any] = {
|
|
20
|
+
"name": "Judgment v Judgement",
|
|
21
|
+
"court": "Court of Testing",
|
|
22
|
+
"document_date_as_string": "2023-02-03",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def build(cls, xml_string: str = DEFAULT_DOCUMENT_BODY_XML, **kwargs: Any) -> DocumentBody:
|
|
27
|
+
document_body = DocumentBody(
|
|
28
|
+
xml_bytestring=xml_string.encode(encoding="utf-8"),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
for param_name, default_value in cls.PARAMS_MAP.items():
|
|
32
|
+
value = kwargs.get(param_name, default_value)
|
|
33
|
+
setattr(document_body, param_name, value)
|
|
34
|
+
|
|
35
|
+
return document_body
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DocumentFactory:
|
|
39
|
+
# "name_of_attribute": "default value"
|
|
40
|
+
PARAMS_MAP: dict[str, Any] = {
|
|
41
|
+
"is_published": False,
|
|
42
|
+
"is_sensitive": False,
|
|
43
|
+
"is_anonymised": False,
|
|
44
|
+
"is_failure": False,
|
|
45
|
+
"source_name": "Example Uploader",
|
|
46
|
+
"source_email": "uploader@example.com",
|
|
47
|
+
"consignment_reference": "TDR-12345",
|
|
48
|
+
"assigned_to": "",
|
|
49
|
+
"versions": [],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
target_class: TypeAlias = Document
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def build(
|
|
56
|
+
cls,
|
|
57
|
+
uri: str = "test/2023/123",
|
|
58
|
+
html: str = "<p>This is a judgment.</p>",
|
|
59
|
+
api_client: Optional[MarklogicApiClient] = None,
|
|
60
|
+
**kwargs: Any,
|
|
61
|
+
) -> target_class:
|
|
62
|
+
if not api_client:
|
|
63
|
+
api_client = Mock(spec=MarklogicApiClient)
|
|
64
|
+
api_client.get_judgment_xml_bytestring.return_value = DEFAULT_DOCUMENT_BODY_XML.encode(encoding="utf-8")
|
|
65
|
+
|
|
66
|
+
with patch.object(cls.target_class, "content_as_html") as mock_content_as_html:
|
|
67
|
+
mock_content_as_html.return_value = html
|
|
68
|
+
document = cls.target_class(uri, api_client=api_client)
|
|
69
|
+
|
|
70
|
+
document.body = kwargs.pop("body") if "body" in kwargs else DocumentBodyFactory.build()
|
|
71
|
+
|
|
72
|
+
for param_name, default_value in cls.PARAMS_MAP.items():
|
|
73
|
+
value = kwargs.get(param_name, default_value)
|
|
74
|
+
setattr(document, param_name, value)
|
|
75
|
+
|
|
76
|
+
return document
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class JudgmentFactory(DocumentFactory):
|
|
80
|
+
target_class = Judgment
|
|
81
|
+
|
|
82
|
+
def __init__(self) -> None:
|
|
83
|
+
self.PARAMS_MAP = self.PARAMS_MAP | {
|
|
84
|
+
"neutral_citation": "[2023] Test 123",
|
|
85
|
+
"best_human_identifier": "[2023] Test 123",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
super().__init__()
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def build(
|
|
92
|
+
cls,
|
|
93
|
+
uri: str = "test/2023/123",
|
|
94
|
+
html: str = "<p>This is a judgment.</p>",
|
|
95
|
+
api_client: Optional[MarklogicApiClient] = None,
|
|
96
|
+
**kwargs: Any,
|
|
97
|
+
) -> Judgment:
|
|
98
|
+
return cast(Judgment, super().build(uri, html, api_client, **kwargs))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class PressSummaryFactory(DocumentFactory):
|
|
102
|
+
target_class = PressSummary
|
|
103
|
+
|
|
104
|
+
def __init__(self) -> None:
|
|
105
|
+
self.PARAMS_MAP = self.PARAMS_MAP | {
|
|
106
|
+
"neutral_citation": "[2023] Test 123",
|
|
107
|
+
"best_human_identifier": "[2023] Test 123",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
super().__init__()
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def build(
|
|
114
|
+
cls,
|
|
115
|
+
uri: str = "test/2023/123/press-summary/1",
|
|
116
|
+
html: str = "<p>This is a judgment.</p>",
|
|
117
|
+
api_client: Optional[MarklogicApiClient] = None,
|
|
118
|
+
**kwargs: Any,
|
|
119
|
+
) -> PressSummary:
|
|
120
|
+
return cast(PressSummary, super().build(uri, html, api_client, **kwargs))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class SimpleFactory:
|
|
124
|
+
# "name_of_attribute": "default value"
|
|
125
|
+
PARAMS_MAP: dict[str, Any]
|
|
126
|
+
|
|
127
|
+
target_class: TypeAlias = object
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def build(cls, **kwargs: Any) -> target_class:
|
|
131
|
+
mock_object = Mock(spec=cls.target_class, autospec=True)
|
|
132
|
+
|
|
133
|
+
for param, default in cls.PARAMS_MAP.items():
|
|
134
|
+
if param in kwargs:
|
|
135
|
+
setattr(mock_object.return_value, param, kwargs[param])
|
|
136
|
+
else:
|
|
137
|
+
setattr(mock_object.return_value, param, default)
|
|
138
|
+
|
|
139
|
+
return mock_object()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class SearchResultMetadataFactory(SimpleFactory):
|
|
143
|
+
target_class = SearchResultMetadata
|
|
144
|
+
# "name_of_attribute": "default value"
|
|
145
|
+
PARAMS_MAP = {
|
|
146
|
+
"author": "Fake Name",
|
|
147
|
+
"author_email": "fake.email@gov.invalid",
|
|
148
|
+
"consignment_reference": "TDR-2023-ABC",
|
|
149
|
+
"submission_datetime": datetime.datetime(2023, 2, 3, 9, 12, 34),
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class SearchResultFactory(SimpleFactory):
|
|
154
|
+
target_class = SearchResult
|
|
155
|
+
|
|
156
|
+
# "name_of_attribute": ("name of incoming param", "default value")
|
|
157
|
+
PARAMS_MAP = {
|
|
158
|
+
"uri": "test/2023/123",
|
|
159
|
+
"name": "Judgment v Judgement",
|
|
160
|
+
"neutral_citation": "[2023] Test 123",
|
|
161
|
+
"court": "Court of Testing",
|
|
162
|
+
"date": datetime.date(2023, 2, 3),
|
|
163
|
+
"metadata": SearchResultMetadataFactory.build(),
|
|
164
|
+
"is_failure": False,
|
|
165
|
+
}
|
|
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any, NewType, Optional
|
|
|
5
5
|
|
|
6
6
|
from ds_caselaw_utils import courts
|
|
7
7
|
from ds_caselaw_utils.courts import CourtNotFoundException
|
|
8
|
+
from ds_caselaw_utils.types import NeutralCitationString
|
|
8
9
|
from lxml import html as html_parser
|
|
9
10
|
from requests_toolbelt.multipart import decoder
|
|
10
11
|
|
|
@@ -281,22 +282,15 @@ class Document:
|
|
|
281
282
|
|
|
282
283
|
:return: `True` if this document is in a 'failure' state, otherwise `False`
|
|
283
284
|
"""
|
|
284
|
-
|
|
285
|
-
return True
|
|
286
|
-
return False
|
|
285
|
+
return self.body.failed_to_parse
|
|
287
286
|
|
|
288
287
|
@cached_property
|
|
289
288
|
def is_parked(self) -> bool:
|
|
290
|
-
|
|
291
|
-
return True
|
|
292
|
-
return False
|
|
289
|
+
return "parked" in self.uri
|
|
293
290
|
|
|
294
291
|
@cached_property
|
|
295
292
|
def has_name(self) -> bool:
|
|
296
|
-
|
|
297
|
-
return False
|
|
298
|
-
|
|
299
|
-
return True
|
|
293
|
+
return bool(self.body.name)
|
|
300
294
|
|
|
301
295
|
@cached_property
|
|
302
296
|
def has_valid_court(self) -> bool:
|
|
@@ -373,9 +367,7 @@ class Document:
|
|
|
373
367
|
"""
|
|
374
368
|
Is it sensible to enrich this document?
|
|
375
369
|
"""
|
|
376
|
-
|
|
377
|
-
return True
|
|
378
|
-
return False
|
|
370
|
+
return (self.enriched_recently is False) and self.validates_against_schema
|
|
379
371
|
|
|
380
372
|
@cached_property
|
|
381
373
|
def enriched_recently(self) -> bool:
|
|
@@ -388,9 +380,8 @@ class Document:
|
|
|
388
380
|
return False
|
|
389
381
|
|
|
390
382
|
now = datetime.datetime.now(tz=datetime.timezone.utc)
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return False
|
|
383
|
+
|
|
384
|
+
return now - last_enrichment < MINIMUM_ENRICHMENT_TIME
|
|
394
385
|
|
|
395
386
|
@cached_property
|
|
396
387
|
def validates_against_schema(self) -> bool:
|
|
@@ -451,10 +442,7 @@ class Document:
|
|
|
451
442
|
else:
|
|
452
443
|
raise DocumentNotSafeForDeletion
|
|
453
444
|
|
|
454
|
-
def
|
|
455
|
-
self.api_client.overwrite_document(self.uri, new_citation)
|
|
456
|
-
|
|
457
|
-
def move(self, new_citation: str) -> None:
|
|
445
|
+
def move(self, new_citation: NeutralCitationString) -> None:
|
|
458
446
|
self.api_client.update_document_uri(self.uri, new_citation)
|
|
459
447
|
|
|
460
448
|
def force_reparse(self) -> None:
|
|
@@ -506,6 +494,4 @@ class Document:
|
|
|
506
494
|
"""
|
|
507
495
|
Is it sensible to reparse this document?
|
|
508
496
|
"""
|
|
509
|
-
|
|
510
|
-
return True
|
|
511
|
-
return False
|
|
497
|
+
return self.docx_exists()
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import warnings
|
|
3
3
|
from functools import cached_property
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Optional
|
|
5
5
|
|
|
6
6
|
import pytz
|
|
7
|
+
from ds_caselaw_utils.types import CourtCode
|
|
7
8
|
|
|
8
9
|
from caselawclient.models.utilities.dates import parse_string_date_as_utc
|
|
9
10
|
|
|
10
11
|
from .xml import XML
|
|
11
12
|
|
|
12
|
-
CourtIdentifierString = NewType("CourtIdentifierString", str)
|
|
13
|
-
|
|
14
13
|
|
|
15
14
|
class UnparsableDate(Warning):
|
|
16
15
|
pass
|
|
@@ -56,10 +55,10 @@ class DocumentBody:
|
|
|
56
55
|
)
|
|
57
56
|
|
|
58
57
|
@property
|
|
59
|
-
def court_and_jurisdiction_identifier_string(self) ->
|
|
58
|
+
def court_and_jurisdiction_identifier_string(self) -> CourtCode:
|
|
60
59
|
if self.jurisdiction != "":
|
|
61
|
-
return
|
|
62
|
-
return
|
|
60
|
+
return CourtCode("/".join((self.court, self.jurisdiction)))
|
|
61
|
+
return CourtCode(self.court)
|
|
63
62
|
|
|
64
63
|
@cached_property
|
|
65
64
|
def document_date_as_string(self) -> str:
|
|
@@ -137,6 +136,4 @@ class DocumentBody:
|
|
|
137
136
|
|
|
138
137
|
:return: `True` if there was a complete parser failure, otherwise `False`
|
|
139
138
|
"""
|
|
140
|
-
|
|
141
|
-
return True
|
|
142
|
-
return False
|
|
139
|
+
return "error" in self._xml.root_element
|
|
@@ -2,6 +2,8 @@ import importlib
|
|
|
2
2
|
from functools import cached_property
|
|
3
3
|
from typing import TYPE_CHECKING, Any, Optional
|
|
4
4
|
|
|
5
|
+
from ds_caselaw_utils.types import NeutralCitationString
|
|
6
|
+
|
|
5
7
|
from caselawclient.errors import DocumentNotFoundError
|
|
6
8
|
from caselawclient.models.neutral_citation_mixin import NeutralCitationMixin
|
|
7
9
|
|
|
@@ -20,16 +22,18 @@ class Judgment(NeutralCitationMixin, Document):
|
|
|
20
22
|
document_noun_plural = "judgments"
|
|
21
23
|
|
|
22
24
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
23
|
-
super(
|
|
25
|
+
super().__init__(self.document_noun, *args, **kwargs)
|
|
24
26
|
|
|
25
27
|
@cached_property
|
|
26
|
-
def neutral_citation(self) ->
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
def neutral_citation(self) -> NeutralCitationString:
|
|
29
|
+
return NeutralCitationString(
|
|
30
|
+
self.body.get_xpath_match_string(
|
|
31
|
+
"/akn:akomaNtoso/akn:*/akn:meta/akn:proprietary/uk:cite/text()",
|
|
32
|
+
{
|
|
33
|
+
"uk": "https://caselaw.nationalarchives.gov.uk/akn",
|
|
34
|
+
"akn": "http://docs.oasis-open.org/legaldocml/ns/akn/3.0",
|
|
35
|
+
},
|
|
36
|
+
)
|
|
33
37
|
)
|
|
34
38
|
|
|
35
39
|
@property
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
1
2
|
from functools import cached_property
|
|
2
3
|
from typing import Any
|
|
3
4
|
|
|
4
5
|
from ds_caselaw_utils import neutral_url
|
|
6
|
+
from ds_caselaw_utils.types import NeutralCitationString
|
|
5
7
|
|
|
6
8
|
|
|
7
|
-
class NeutralCitationMixin:
|
|
9
|
+
class NeutralCitationMixin(ABC):
|
|
8
10
|
"""
|
|
9
11
|
A mixin class that provides functionality related to neutral citation.
|
|
10
12
|
|
|
@@ -35,19 +37,13 @@ class NeutralCitationMixin:
|
|
|
35
37
|
super(NeutralCitationMixin, self).__init__(*args, **kwargs)
|
|
36
38
|
|
|
37
39
|
@cached_property
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def neutral_citation(self) -> NeutralCitationString: ...
|
|
40
42
|
|
|
41
43
|
@cached_property
|
|
42
44
|
def has_ncn(self) -> bool:
|
|
43
|
-
|
|
44
|
-
return False
|
|
45
|
-
|
|
46
|
-
return True
|
|
45
|
+
return bool(self.neutral_citation)
|
|
47
46
|
|
|
48
47
|
@cached_property
|
|
49
48
|
def has_valid_ncn(self) -> bool:
|
|
50
|
-
|
|
51
|
-
return False
|
|
52
|
-
|
|
53
|
-
return True
|
|
49
|
+
return self.has_ncn and neutral_url(self.neutral_citation) is not None
|
|
@@ -4,6 +4,8 @@ import importlib
|
|
|
4
4
|
from functools import cached_property
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Optional
|
|
6
6
|
|
|
7
|
+
from ds_caselaw_utils.types import NeutralCitationString
|
|
8
|
+
|
|
7
9
|
from caselawclient.errors import DocumentNotFoundError
|
|
8
10
|
from caselawclient.models.neutral_citation_mixin import NeutralCitationMixin
|
|
9
11
|
|
|
@@ -22,15 +24,17 @@ class PressSummary(NeutralCitationMixin, Document):
|
|
|
22
24
|
document_noun_plural = "press summaries"
|
|
23
25
|
|
|
24
26
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
25
|
-
super(
|
|
27
|
+
super().__init__(self.document_noun, *args, **kwargs)
|
|
26
28
|
|
|
27
29
|
@cached_property
|
|
28
|
-
def neutral_citation(self) ->
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
def neutral_citation(self) -> NeutralCitationString:
|
|
31
|
+
return NeutralCitationString(
|
|
32
|
+
self.body.get_xpath_match_string(
|
|
33
|
+
"/akn:akomaNtoso/akn:doc/akn:preface/akn:p/akn:neutralCitation/text()",
|
|
34
|
+
{
|
|
35
|
+
"akn": "http://docs.oasis-open.org/legaldocml/ns/akn/3.0",
|
|
36
|
+
},
|
|
37
|
+
)
|
|
34
38
|
)
|
|
35
39
|
|
|
36
40
|
@property
|
|
@@ -9,11 +9,7 @@ def parse_string_date_as_utc(iso_string: str, timezone: tzinfo.BaseTzInfo) -> da
|
|
|
9
9
|
ensure that it is converted to a UTC-aware datetime"""
|
|
10
10
|
|
|
11
11
|
mixed_date = isoparse(iso_string)
|
|
12
|
-
|
|
13
|
-
# it is an unaware time
|
|
14
|
-
aware_date = timezone.localize(mixed_date)
|
|
15
|
-
else:
|
|
16
|
-
aware_date = mixed_date
|
|
12
|
+
aware_date = mixed_date if mixed_date.tzinfo else timezone.localize(mixed_date)
|
|
17
13
|
|
|
18
14
|
# make UTC
|
|
19
15
|
utc_date = aware_date.astimezone(UTC)
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
from typing import Any, Optional
|
|
1
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
2
2
|
|
|
3
3
|
import ds_caselaw_utils as caselawutils
|
|
4
|
+
from ds_caselaw_utils.types import NeutralCitationString
|
|
4
5
|
|
|
5
6
|
from caselawclient.errors import MarklogicAPIError
|
|
7
|
+
from caselawclient.models.documents import DocumentURIString
|
|
6
8
|
from caselawclient.models.utilities.aws import copy_assets
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
pass
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from caselawclient.Client import MarklogicApiClient
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
class
|
|
14
|
+
class NeutralCitationToUriError(Exception):
|
|
14
15
|
pass
|
|
15
16
|
|
|
16
17
|
|
|
@@ -18,62 +19,9 @@ class MoveJudgmentError(Exception):
|
|
|
18
19
|
pass
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
def
|
|
22
|
-
source_uri:
|
|
23
|
-
|
|
24
|
-
api_client: Any,
|
|
25
|
-
) -> str:
|
|
26
|
-
"""Move the document at source_uri on top of the new citation, which must already exist
|
|
27
|
-
Compare to update_document_uri
|
|
28
|
-
|
|
29
|
-
:param source_uri: The URI with the contents of the document to be written. (possibly a failure url)
|
|
30
|
-
:param target_citation: The NCN (implying a URL) whose contents will be overwritten
|
|
31
|
-
:param api_client: An instance of MarklogicApiClient used to make the search request
|
|
32
|
-
:return: The URL associated with the `target_citation`
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
new_uri: Optional[str] = caselawutils.neutral_url(target_citation.strip())
|
|
36
|
-
|
|
37
|
-
if new_uri == source_uri:
|
|
38
|
-
raise RuntimeError(
|
|
39
|
-
f"Attempted to overwrite document {source_uri} with itself, which is not permitted.",
|
|
40
|
-
)
|
|
41
|
-
if new_uri is None:
|
|
42
|
-
raise NeutralCitationToUriError(
|
|
43
|
-
f"Unable to form new URI for {source_uri} from neutral citation: {target_citation}",
|
|
44
|
-
)
|
|
45
|
-
if not api_client.document_exists(new_uri):
|
|
46
|
-
raise OverwriteJudgmentError(
|
|
47
|
-
f"The URI {new_uri} generated from {target_citation} does not already exist, so cannot be overwritten",
|
|
48
|
-
)
|
|
49
|
-
old_doc = api_client.get_document_by_uri_or_404(source_uri)
|
|
50
|
-
try:
|
|
51
|
-
old_doc_xml = old_doc.content_as_xml
|
|
52
|
-
api_client.update_document_xml(
|
|
53
|
-
new_uri,
|
|
54
|
-
old_doc_xml,
|
|
55
|
-
annotation=f"overwritten from {source_uri}",
|
|
56
|
-
)
|
|
57
|
-
set_metadata(source_uri, new_uri, api_client)
|
|
58
|
-
# TODO: consider deleting existing public assets at that location
|
|
59
|
-
copy_assets(source_uri, new_uri)
|
|
60
|
-
api_client.set_judgment_this_uri(new_uri)
|
|
61
|
-
except MarklogicAPIError as e:
|
|
62
|
-
raise OverwriteJudgmentError(
|
|
63
|
-
f"Failure when attempting to copy judgment from {source_uri} to {new_uri}: {e}",
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
try:
|
|
67
|
-
api_client.delete_judgment(source_uri)
|
|
68
|
-
except MarklogicAPIError as e:
|
|
69
|
-
raise OverwriteJudgmentError(
|
|
70
|
-
f"Failure when attempting to delete judgment from {source_uri}: {e}",
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
return new_uri
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def update_document_uri(source_uri: str, target_citation: str, api_client: Any) -> str:
|
|
22
|
+
def update_document_uri(
|
|
23
|
+
source_uri: DocumentURIString, target_citation: NeutralCitationString, api_client: "MarklogicApiClient"
|
|
24
|
+
) -> DocumentURIString:
|
|
77
25
|
"""
|
|
78
26
|
Move the document at source_uri to the correct location based on the neutral citation
|
|
79
27
|
The new neutral citation *must* not already exist (that is handled elsewhere)
|
|
@@ -83,7 +31,8 @@ def update_document_uri(source_uri: str, target_citation: str, api_client: Any)
|
|
|
83
31
|
:param api_client: An instance of MarklogicApiClient used to make the search request
|
|
84
32
|
:return: The URL associated with the `target_citation`
|
|
85
33
|
"""
|
|
86
|
-
|
|
34
|
+
new_ncn_based_uri = caselawutils.neutral_url(target_citation)
|
|
35
|
+
new_uri: Optional[DocumentURIString] = DocumentURIString(new_ncn_based_uri) if new_ncn_based_uri else None
|
|
87
36
|
if new_uri is None:
|
|
88
37
|
raise NeutralCitationToUriError(
|
|
89
38
|
f"Unable to form new URI for {source_uri} from neutral citation: {target_citation}",
|
|
@@ -115,7 +64,7 @@ def update_document_uri(source_uri: str, target_citation: str, api_client: Any)
|
|
|
115
64
|
return new_uri
|
|
116
65
|
|
|
117
66
|
|
|
118
|
-
def set_metadata(old_uri:
|
|
67
|
+
def set_metadata(old_uri: DocumentURIString, new_uri: DocumentURIString, api_client: Any) -> None:
|
|
119
68
|
source_organisation = api_client.get_property(old_uri, "source-organisation")
|
|
120
69
|
source_name = api_client.get_property(old_uri, "source-name")
|
|
121
70
|
source_email = api_client.get_property(old_uri, "source-email")
|
|
@@ -24,13 +24,13 @@ class SearchResponse:
|
|
|
24
24
|
self.client = client
|
|
25
25
|
|
|
26
26
|
@property
|
|
27
|
-
def total(self) ->
|
|
27
|
+
def total(self) -> int:
|
|
28
28
|
"""
|
|
29
29
|
The total number of search results.
|
|
30
30
|
|
|
31
31
|
:return: The total number of search results
|
|
32
32
|
"""
|
|
33
|
-
return
|
|
33
|
+
return int(
|
|
34
34
|
self.node.xpath("//search:response/@total", namespaces=self.NAMESPACES)[0],
|
|
35
35
|
)
|
|
36
36
|
|
|
@@ -8,6 +8,7 @@ from typing import Dict, Optional
|
|
|
8
8
|
from dateutil import parser as dateparser
|
|
9
9
|
from dateutil.parser import ParserError
|
|
10
10
|
from ds_caselaw_utils.courts import Court, CourtNotFoundException, courts
|
|
11
|
+
from ds_caselaw_utils.types import CourtCode, JurisdictionCode
|
|
11
12
|
from lxml import etree
|
|
12
13
|
|
|
13
14
|
from caselawclient.Client import MarklogicApiClient
|
|
@@ -193,15 +194,16 @@ class SearchResult:
|
|
|
193
194
|
"""
|
|
194
195
|
:return: The court of the search result
|
|
195
196
|
"""
|
|
196
|
-
court = None
|
|
197
|
+
court: Optional[Court] = None
|
|
197
198
|
court_code = self._get_xpath_match_string("search:extracted/uk:court/text()")
|
|
198
199
|
jurisdiction_code = self._get_xpath_match_string(
|
|
199
200
|
"search:extracted/uk:jurisdiction/text()",
|
|
200
201
|
)
|
|
201
202
|
if jurisdiction_code:
|
|
202
|
-
court_code_with_jurisdiction = "%s/%s" % (court_code, jurisdiction_code)
|
|
203
203
|
try:
|
|
204
|
-
court = courts.
|
|
204
|
+
court = courts.get_court_with_jurisdiction_by_code(
|
|
205
|
+
CourtCode(court_code), JurisdictionCode(jurisdiction_code)
|
|
206
|
+
)
|
|
205
207
|
except CourtNotFoundException:
|
|
206
208
|
logging.warning(
|
|
207
209
|
"Court not found with court code %s and jurisdiction code %s for judgment with NCN %s, falling back to court."
|
|
@@ -209,7 +211,7 @@ class SearchResult:
|
|
|
209
211
|
)
|
|
210
212
|
if court is None:
|
|
211
213
|
try:
|
|
212
|
-
court = courts.get_by_code(court_code)
|
|
214
|
+
court = courts.get_by_code(CourtCode(court_code))
|
|
213
215
|
except CourtNotFoundException:
|
|
214
216
|
logging.warning(
|
|
215
217
|
"Court not found with court code %s for judgment with NCN %s, returning None."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ds-caselaw-marklogic-api-client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 27.0.0
|
|
4
4
|
Summary: An API client for interacting with the underlying data in Find Caselaw.
|
|
5
5
|
Home-page: https://github.com/nationalarchives/ds-caselaw-custom-api-client
|
|
6
6
|
Keywords: national archives,caselaw
|
|
@@ -14,7 +14,7 @@ Requires-Dist: boto3 (>=1.26.112,<2.0.0)
|
|
|
14
14
|
Requires-Dist: certifi (>=2024.8.30,<2024.9.0)
|
|
15
15
|
Requires-Dist: charset-normalizer (>=3.0.0,<4.0.0)
|
|
16
16
|
Requires-Dist: django-environ (>=0.11.0,<0.12.0)
|
|
17
|
-
Requires-Dist: ds-caselaw-utils (>=
|
|
17
|
+
Requires-Dist: ds-caselaw-utils (>=2.0.0,<3.0.0)
|
|
18
18
|
Requires-Dist: idna (>=3.4,<4.0)
|
|
19
19
|
Requires-Dist: lxml (>=5.0.0,<6.0.0)
|
|
20
20
|
Requires-Dist: memoization (>=0.4.0,<0.5.0)
|
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
caselawclient/Client.py,sha256=
|
|
1
|
+
caselawclient/Client.py,sha256=9tNNo_-qNxIn5E3OUJLg3apVvUie8VfyRfPmnzeqc-0,40182
|
|
2
2
|
caselawclient/__init__.py,sha256=DY-caubLDQWWingSdsBWgovDNXh8KcnkI6kwz08eIFk,612
|
|
3
3
|
caselawclient/client_helpers/__init__.py,sha256=fyDNKCdrTb2N0Ks23YDhmvlXKfLTHnYQCXhnZb-QQbg,3832
|
|
4
4
|
caselawclient/client_helpers/search_helpers.py,sha256=R99HyRLeYHgsw2L3DOidEqlKLLvs6Tga5rKTuWQViig,1525
|
|
5
5
|
caselawclient/content_hash.py,sha256=0cPC4OoABq0SC2wYFX9-24DodNigeOqksDxgxQH_hUA,2221
|
|
6
6
|
caselawclient/errors.py,sha256=tV0vs3wYSd331BzmfuRiZV6GAdsd91rtN65ymRaSx3s,3164
|
|
7
|
+
caselawclient/factories.py,sha256=nmKJLlTH5tN6sDCBz3abgJdGnzQlaPXIOm8aBgTZTaU,5260
|
|
7
8
|
caselawclient/models/__init__.py,sha256=kd23EUpvaC7aLHdgk8farqKAQEx3lf7RvNT2jEatvlg,68
|
|
8
|
-
caselawclient/models/documents/__init__.py,sha256=
|
|
9
|
-
caselawclient/models/documents/body.py,sha256=
|
|
9
|
+
caselawclient/models/documents/__init__.py,sha256=EwfkYMr5HnmdtxijKwU6AV27Y4JAmobdNHL9Cp--bBs,16624
|
|
10
|
+
caselawclient/models/documents/body.py,sha256=0o8qL7oJ40VikNAgqS41phQyB8Jtfz93eK1KyR4k3F0,4791
|
|
10
11
|
caselawclient/models/documents/exceptions.py,sha256=Mz1P8uNqf5w6uLnRwJt6xK7efsVqtd5VA-WXUUH7QLk,285
|
|
11
12
|
caselawclient/models/documents/statuses.py,sha256=Cp4dTQmJOtsU41EJcxy5dV1841pGD2PNWH0VrkDEv4Q,579
|
|
12
13
|
caselawclient/models/documents/xml.py,sha256=afEsgcnTThqW_gKYq-VGtFr4ovOoT2J7h2gXX7F8BbE,1267
|
|
13
|
-
caselawclient/models/judgments.py,sha256=
|
|
14
|
-
caselawclient/models/neutral_citation_mixin.py,sha256=
|
|
15
|
-
caselawclient/models/press_summaries.py,sha256=
|
|
14
|
+
caselawclient/models/judgments.py,sha256=SuCNtOD4LElp37df4dvhaD0umTowioWH0sZNmBgFsoE,1739
|
|
15
|
+
caselawclient/models/neutral_citation_mixin.py,sha256=5ktKCPIDidVRwxVTzx5e242O1BxOdP--1dnatZyTbYI,1773
|
|
16
|
+
caselawclient/models/press_summaries.py,sha256=ZJ5ZhamPTsj6-vO1g96aP_syzUC2RlLMhgr3xnI1PoM,1703
|
|
16
17
|
caselawclient/models/utilities/__init__.py,sha256=aL1a2nDacPxninETeaVZKwOxZemgvm73IcpWgMNXoGc,1100
|
|
17
18
|
caselawclient/models/utilities/aws.py,sha256=YQeuFdF5NvhUxo3Ejj3PURDlygA73oq2T42ltuQZ6Oo,8073
|
|
18
|
-
caselawclient/models/utilities/dates.py,sha256=
|
|
19
|
-
caselawclient/models/utilities/move.py,sha256=
|
|
19
|
+
caselawclient/models/utilities/dates.py,sha256=WwORxVjUHM1ZFcBF6Qtwo3Cj0sATsnSECkUZ6ls1N1Q,492
|
|
20
|
+
caselawclient/models/utilities/move.py,sha256=Rsx1eGHVjbGz0WMVDjy8b_5t4Ig8aP55sLudL07MVUs,3621
|
|
20
21
|
caselawclient/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
22
|
caselawclient/responses/__init__.py,sha256=2-5NJn_PXPTje_W4dHeHYaNRN6vXK4UcB9eLLNUAKa4,67
|
|
22
|
-
caselawclient/responses/search_response.py,sha256=
|
|
23
|
-
caselawclient/responses/search_result.py,sha256=
|
|
23
|
+
caselawclient/responses/search_response.py,sha256=Z76Zj4VvM-EV_vdiehv2-Jfkr9HZD3SvCTlRrUB_cyE,1951
|
|
24
|
+
caselawclient/responses/search_result.py,sha256=2yR3FP4CQsVymE7RrOMbh1owjYaRTrqkjMObbIkSlhE,8216
|
|
24
25
|
caselawclient/responses/xsl/search_match.xsl,sha256=4Sv--MrwBd7J48E9aI7jlFSXGlNi4dBqgzJ3bdMJ_ZU,1018
|
|
25
26
|
caselawclient/search_parameters.py,sha256=nR-UC1aWZbdXzXBrVDaHECU4Ro8Zi4JZATtgrpAVsKY,3342
|
|
26
27
|
caselawclient/xml_helpers.py,sha256=qmqdGhwrQ-zhvvB-8akwzWnC2uHponKkEnnRExqkx_A,591
|
|
@@ -64,7 +65,7 @@ caselawclient/xquery/validate_document.xqy,sha256=PgaDcnqCRJPIVqfmWsNlXmCLNKd21q
|
|
|
64
65
|
caselawclient/xquery/xslt.xqy,sha256=w57wNijH3dkwHkpKeAxqjlghVflQwo8cq6jS_sm-erM,199
|
|
65
66
|
caselawclient/xquery/xslt_transform.xqy,sha256=smyFFxqmtkuOzBd2l7uw6K2oAsYctudrP8omdv_XNAM,2463
|
|
66
67
|
caselawclient/xquery_type_dicts.py,sha256=YOrXbEYJU84S-YwergCI12OL5Wrn_wpqMcqWpsQrKek,5590
|
|
67
|
-
ds_caselaw_marklogic_api_client-
|
|
68
|
-
ds_caselaw_marklogic_api_client-
|
|
69
|
-
ds_caselaw_marklogic_api_client-
|
|
70
|
-
ds_caselaw_marklogic_api_client-
|
|
68
|
+
ds_caselaw_marklogic_api_client-27.0.0.dist-info/LICENSE.md,sha256=fGMzyyLuQW-IAXUeDSCrRdsYW536aEWThdbpCjo6ZKg,1108
|
|
69
|
+
ds_caselaw_marklogic_api_client-27.0.0.dist-info/METADATA,sha256=miZ3RSM382gJvUf0AH3Xe9Y5yzEqF3r4OkIDj5rXiZg,4189
|
|
70
|
+
ds_caselaw_marklogic_api_client-27.0.0.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
|
71
|
+
ds_caselaw_marklogic_api_client-27.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|