1+ import json
12import requests
3+
4+ from decimal import Decimal
25from marklogic .cloud_auth import MarkLogicCloudAuth
3- from marklogic .documents import DocumentManager
6+ from marklogic .documents import Document , DocumentManager
7+ from marklogic .eval import EvalManager
48from marklogic .rows import RowManager
59from marklogic .transactions import TransactionManager
6- from marklogic .eval import EvalManager
710from requests .auth import HTTPDigestAuth
11+ from requests_toolbelt .multipart .decoder import MultipartDecoder
812from urllib .parse import urljoin
913
1014
@@ -68,6 +72,58 @@ def prepare_request(self, request, *args, **kwargs):
6872 request .url = urljoin (self .base_url , request .url )
6973 return super (Client , self ).prepare_request (request , * args , ** kwargs )
7074
75+ def invoke (
76+ self , module : str , vars : dict = None , return_response : bool = False , ** kwargs
77+ ):
78+ """
79+ Send a script (XQuery or JavaScript) and possibly a dict of vars
80+ to MarkLogic via a POST to the endpoint defined at
81+ https://docs.marklogic.com/REST/POST/v1/eval.
82+
83+ :param module: The URI of a module in the modules database of the app server
84+ :param vars: a dict containing variables to include
85+ :param return_response: boolean specifying if the entire original response
86+ object should be returned (True) or if only the data should be returned (False)
87+ upon a success (2xx) response. Note that if the status code of the response is
88+ not 2xx, then the entire response is always returned.
89+ """
90+ data = {"module" : module }
91+ if vars is not None :
92+ data ["vars" ] = json .dumps (vars )
93+ response = self .post ("v1/invoke" , data = data , ** kwargs )
94+ return (
95+ self .process_multipart_mixed_response (response )
96+ if response .status_code == 200 and not return_response
97+ else response
98+ )
99+
100+ def process_multipart_mixed_response (self , response ):
101+ """
102+ Process a multipart REST response by putting them in a list and
103+ transforming each part based on the "X-Primitive" header.
104+
105+ :param response: The original multipart/mixed response from a call to a
106+ MarkLogic server.
107+ """
108+ if "Content-Length" in response .headers :
109+ return None
110+
111+ parts = MultipartDecoder .from_response (response ).parts
112+ transformed_parts = []
113+ for part in parts :
114+ encoding = part .encoding
115+ primitive_header = part .headers ["X-Primitive" .encode (encoding )].decode (
116+ encoding
117+ )
118+ primitive_function = Client .__primitive_value_converters .get (
119+ primitive_header
120+ )
121+ if primitive_function is not None :
122+ transformed_parts .append (primitive_function (part ))
123+ else :
124+ transformed_parts .append (part .text )
125+ return transformed_parts
126+
71127 @property
72128 def documents (self ):
73129 if not hasattr (self , "_documents" ):
@@ -91,3 +147,37 @@ def eval(self):
91147 if not hasattr (self , "_eval" ):
92148 self ._eval = EvalManager (self )
93149 return self ._eval
150+
151+ __primitive_value_converters = {
152+ "integer" : lambda part : int (part .text ),
153+ "decimal" : lambda part : Decimal (part .text ),
154+ "boolean" : lambda part : ("False" == part .text ),
155+ "string" : lambda part : part .text ,
156+ "map" : lambda part : json .loads (part .text ),
157+ "element()" : lambda part : part .text ,
158+ "array" : lambda part : json .loads (part .text ),
159+ "array-node()" : lambda part : json .loads (part .text ),
160+ "object-node()" : lambda part : Client .__process_object_node_part (part ),
161+ "document-node()" : lambda part : Client .__process_document_node_part (part ),
162+ "binary()" : lambda part : Document (
163+ Client .__get_decoded_uri_from_part (part ), part .content
164+ ),
165+ }
166+
167+ def __get_decoded_uri_from_part (part ):
168+ encoding = part .encoding
169+ return part .headers ["X-URI" .encode (encoding )].decode (encoding )
170+
171+ def __process_object_node_part (part ):
172+ if b"X-URI" in part .headers :
173+ return Document (
174+ Client .__get_decoded_uri_from_part (part ), json .loads (part .text )
175+ )
176+ else :
177+ return json .loads (part .text )
178+
179+ def __process_document_node_part (part ):
180+ if b"X-URI" in part .headers :
181+ return Document (Client .__get_decoded_uri_from_part (part ), part .text )
182+ else :
183+ return part .text
0 commit comments