caselawclient.types
1from dataclasses import dataclass, field 2from datetime import UTC, datetime 3 4from lxml import etree 5 6 7@dataclass 8class DocumentCategory: 9 name: str 10 subcategories: list["DocumentCategory"] = field(default_factory=list) 11 12 13class InvalidDocumentURIException(Exception): 14 """The document URI is not valid.""" 15 16 17class InvalidMarkLogicDocumentURIException(Exception): 18 """The MarkLogic document URI is not valid.""" 19 20 21class MarkLogicDocumentURIString(str): 22 def __new__(cls, content: str) -> "MarkLogicDocumentURIString": 23 # Check that the URI begins with a slash 24 if content[0] != "/": 25 raise InvalidMarkLogicDocumentURIException( 26 f'"{content}" is not a valid MarkLogic document URI; URIs must begin with a slash.' 27 ) 28 29 # Check that the URI ends with ".xml" 30 if not content.endswith(".xml"): 31 raise InvalidMarkLogicDocumentURIException( 32 f'"{content}" is not a valid MarkLogic document URI; URIs must end with ".xml". ' 33 ) 34 35 # If everything is good, return as usual 36 return str.__new__(cls, content) 37 38 def as_document_uri(self) -> "DocumentURIString": 39 return DocumentURIString(self.lstrip("/").rstrip(".xml")) 40 41 42class DocumentURIString(str): 43 """ 44 This class checks that the string is actually a valid Document URI on creation. It does _not_ manipulate the string. 45 """ 46 47 def __new__(cls, content: str) -> "DocumentURIString": 48 # Check that the URI doesn't begin or end with a slash 49 if content[0] == "/" or content[-1] == "/": 50 raise InvalidDocumentURIException( 51 f'"{content}" is not a valid document URI; URIs cannot begin or end with slashes.' 52 ) 53 54 # Check that the URI doesn't contain a full stop 55 if "." in content: 56 raise InvalidDocumentURIException( 57 f'"{content}" is not a valid document URI; URIs cannot contain full stops.' 58 ) 59 60 # If everything is good, return as usual 61 return str.__new__(cls, content) 62 63 def as_marklogic(self) -> MarkLogicDocumentURIString: 64 return MarkLogicDocumentURIString(f"/{self}.xml") 65 66 67class DocumentIdentifierSlug(str): 68 pass 69 70 71class DocumentIdentifierValue(str): 72 pass 73 74 75class SuccessFailureMessageTuple(tuple[bool, list[str]]): 76 """ 77 Return whether an operation has succeeded or failed 78 (and optionally a list of messages associated with that operation). 79 Typically the messages will be exposed to the end-user. 80 Use only where a failure is a routine event (such as during validation). 81 """ 82 83 def __new__(cls, success: bool, messages: list[str]) -> "SuccessFailureMessageTuple": 84 return super().__new__(cls, [success, messages]) 85 86 @property 87 def success(self) -> bool: 88 return self[0] 89 90 @property 91 def messages(self) -> list[str]: 92 return self[1] 93 94 def __repr__(self) -> str: 95 return f"SuccessFailureMessageTuple({self.success!r}, {self.messages!r})" 96 97 def __bool__(self) -> bool: 98 return self.success 99 100 def __or__(self, other: "SuccessFailureMessageTuple") -> "SuccessFailureMessageTuple": 101 """Allows us to write combined_tuple = first_tuple | second_tuple""" 102 return SuccessFailureMessageTuple(self.success and other.success, self.messages + other.messages) 103 104 105def SuccessTuple() -> SuccessFailureMessageTuple: 106 return SuccessFailureMessageTuple(True, []) 107 108 109def FailureTuple(message: str | list[str]) -> SuccessFailureMessageTuple: 110 messages = message if isinstance(message, list) else [message] 111 return SuccessFailureMessageTuple(success=False, messages=messages) 112 113 114@dataclass 115class DocumentLock: 116 document_uri: DocumentURIString 117 owner: str 118 timestamp: datetime 119 timeout: int 120 121 NAMESPACES = { 122 "lock": "http://marklogic.com/xdmp/lock", 123 } 124 125 @classmethod 126 def from_string(cls, xml_string: str) -> "DocumentLock": 127 root = etree.fromstring(xml_string.encode("utf-8")) 128 129 return cls( 130 document_uri=MarkLogicDocumentURIString(root.xpath("/lock/document/text()")[0]).as_document_uri(), 131 owner=root.xpath( 132 "/lock/details/lock:lock/lock:active-locks/lock:active-lock/lock:owner/text()", 133 namespaces=cls.NAMESPACES, 134 )[0], 135 timestamp=datetime.fromtimestamp( 136 int( 137 root.xpath( 138 "/lock/details/lock:lock/lock:active-locks/lock:active-lock/lock:timestamp/text()", 139 namespaces=cls.NAMESPACES, 140 )[0] 141 ), 142 tz=UTC, 143 ), 144 timeout=int( 145 root.xpath( 146 "/lock/details/lock:lock/lock:active-locks/lock:active-lock/lock:timeout/text()", 147 namespaces=cls.NAMESPACES, 148 )[0] 149 ), 150 )
8@dataclass 9class DocumentCategory: 10 name: str 11 subcategories: list["DocumentCategory"] = field(default_factory=list)
The document URI is not valid.
18class InvalidMarkLogicDocumentURIException(Exception): 19 """The MarkLogic document URI is not valid."""
The MarkLogic document URI is not valid.
22class MarkLogicDocumentURIString(str): 23 def __new__(cls, content: str) -> "MarkLogicDocumentURIString": 24 # Check that the URI begins with a slash 25 if content[0] != "/": 26 raise InvalidMarkLogicDocumentURIException( 27 f'"{content}" is not a valid MarkLogic document URI; URIs must begin with a slash.' 28 ) 29 30 # Check that the URI ends with ".xml" 31 if not content.endswith(".xml"): 32 raise InvalidMarkLogicDocumentURIException( 33 f'"{content}" is not a valid MarkLogic document URI; URIs must end with ".xml". ' 34 ) 35 36 # If everything is good, return as usual 37 return str.__new__(cls, content) 38 39 def as_document_uri(self) -> "DocumentURIString": 40 return DocumentURIString(self.lstrip("/").rstrip(".xml"))
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
43class DocumentURIString(str): 44 """ 45 This class checks that the string is actually a valid Document URI on creation. It does _not_ manipulate the string. 46 """ 47 48 def __new__(cls, content: str) -> "DocumentURIString": 49 # Check that the URI doesn't begin or end with a slash 50 if content[0] == "/" or content[-1] == "/": 51 raise InvalidDocumentURIException( 52 f'"{content}" is not a valid document URI; URIs cannot begin or end with slashes.' 53 ) 54 55 # Check that the URI doesn't contain a full stop 56 if "." in content: 57 raise InvalidDocumentURIException( 58 f'"{content}" is not a valid document URI; URIs cannot contain full stops.' 59 ) 60 61 # If everything is good, return as usual 62 return str.__new__(cls, content) 63 64 def as_marklogic(self) -> MarkLogicDocumentURIString: 65 return MarkLogicDocumentURIString(f"/{self}.xml")
This class checks that the string is actually a valid Document URI on creation. It does _not_ manipulate the string.
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
76class SuccessFailureMessageTuple(tuple[bool, list[str]]): 77 """ 78 Return whether an operation has succeeded or failed 79 (and optionally a list of messages associated with that operation). 80 Typically the messages will be exposed to the end-user. 81 Use only where a failure is a routine event (such as during validation). 82 """ 83 84 def __new__(cls, success: bool, messages: list[str]) -> "SuccessFailureMessageTuple": 85 return super().__new__(cls, [success, messages]) 86 87 @property 88 def success(self) -> bool: 89 return self[0] 90 91 @property 92 def messages(self) -> list[str]: 93 return self[1] 94 95 def __repr__(self) -> str: 96 return f"SuccessFailureMessageTuple({self.success!r}, {self.messages!r})" 97 98 def __bool__(self) -> bool: 99 return self.success 100 101 def __or__(self, other: "SuccessFailureMessageTuple") -> "SuccessFailureMessageTuple": 102 """Allows us to write combined_tuple = first_tuple | second_tuple""" 103 return SuccessFailureMessageTuple(self.success and other.success, self.messages + other.messages)
Return whether an operation has succeeded or failed (and optionally a list of messages associated with that operation). Typically the messages will be exposed to the end-user. Use only where a failure is a routine event (such as during validation).
115@dataclass 116class DocumentLock: 117 document_uri: DocumentURIString 118 owner: str 119 timestamp: datetime 120 timeout: int 121 122 NAMESPACES = { 123 "lock": "http://marklogic.com/xdmp/lock", 124 } 125 126 @classmethod 127 def from_string(cls, xml_string: str) -> "DocumentLock": 128 root = etree.fromstring(xml_string.encode("utf-8")) 129 130 return cls( 131 document_uri=MarkLogicDocumentURIString(root.xpath("/lock/document/text()")[0]).as_document_uri(), 132 owner=root.xpath( 133 "/lock/details/lock:lock/lock:active-locks/lock:active-lock/lock:owner/text()", 134 namespaces=cls.NAMESPACES, 135 )[0], 136 timestamp=datetime.fromtimestamp( 137 int( 138 root.xpath( 139 "/lock/details/lock:lock/lock:active-locks/lock:active-lock/lock:timestamp/text()", 140 namespaces=cls.NAMESPACES, 141 )[0] 142 ), 143 tz=UTC, 144 ), 145 timeout=int( 146 root.xpath( 147 "/lock/details/lock:lock/lock:active-locks/lock:active-lock/lock:timeout/text()", 148 namespaces=cls.NAMESPACES, 149 )[0] 150 ), 151 )
126 @classmethod 127 def from_string(cls, xml_string: str) -> "DocumentLock": 128 root = etree.fromstring(xml_string.encode("utf-8")) 129 130 return cls( 131 document_uri=MarkLogicDocumentURIString(root.xpath("/lock/document/text()")[0]).as_document_uri(), 132 owner=root.xpath( 133 "/lock/details/lock:lock/lock:active-locks/lock:active-lock/lock:owner/text()", 134 namespaces=cls.NAMESPACES, 135 )[0], 136 timestamp=datetime.fromtimestamp( 137 int( 138 root.xpath( 139 "/lock/details/lock:lock/lock:active-locks/lock:active-lock/lock:timestamp/text()", 140 namespaces=cls.NAMESPACES, 141 )[0] 142 ), 143 tz=UTC, 144 ), 145 timeout=int( 146 root.xpath( 147 "/lock/details/lock:lock/lock:active-locks/lock:active-lock/lock:timeout/text()", 148 namespaces=cls.NAMESPACES, 149 )[0] 150 ), 151 )