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
7 changes: 7 additions & 0 deletions marklogic/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import requests
from marklogic.cloud_auth import MarkLogicCloudAuth
from marklogic.documents import DocumentManager
from marklogic.rows import RowManager
from requests.auth import HTTPDigestAuth
from urllib.parse import urljoin

Expand Down Expand Up @@ -70,3 +71,9 @@ def documents(self):
if not hasattr(self, "_documents"):
self._documents = DocumentManager(self)
return self._documents

@property
def rows(self):
if not hasattr(self, "_rows"):
self._rows = RowManager(self)
return self._rows
43 changes: 43 additions & 0 deletions marklogic/rows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import json
from requests import Session

"""
Defines a RowManager class to simplify usage of the "/v1/rows" & "/v1/rows/graphql" REST
endpoints defined at https://docs.marklogic.com/REST/POST/v1/rows/graphql
"""


class RowManager:
"""
Provides a method to simplify sending a GraphQL request to the GraphQL rows endpoint.
"""
def __init__(self, session: Session):
self._session = session

def graphql(self, graphql_query, return_response=False, *args, **kwargs):
"""
Send a GraphQL query to MarkLogic via a POST to the endpoint defined at
https://docs.marklogic.com/REST/POST/v1/rows/graphql

:param graphql_query: a GraphQL query string. Note - this is the query string
only, not the entire query JSON object. See the following for more information:
https://spec.graphql.org/October2021/#sec-Overview
https://graphql.org/learn/queries/
:param return_response: boolean specifying if the entire original response
object should be returned (True) or if only the data should be returned (False)
upon a success (2xx) response. Note that if the status code of the response is
not 2xx, then the entire response is always returned.
"""
headers = kwargs.pop("headers", {})
headers["Content-Type"] = "application/graphql"
response = self._session.post(
"v1/rows/graphql",
headers=headers,
data=json.dumps({"query": graphql_query}),
**kwargs
)
return (
response.json()
if response.status_code == 200 and not return_response
else response
)
4 changes: 4 additions & 0 deletions test-app/src/main/ml-config/databases/content-database.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"database-name": "%%DATABASE%%",
"schema-database": "%%SCHEMAS_DATABASE%%"
}
3 changes: 3 additions & 0 deletions test-app/src/main/ml-config/databases/modules-database.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"database-name": "%%MODULES_DATABASE%%"
}
3 changes: 3 additions & 0 deletions test-app/src/main/ml-config/databases/schemas-database.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"database-name": "%%SCHEMAS_DATABASE%%"
}
2 changes: 1 addition & 1 deletion test-app/src/main/ml-data/collections.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
*=test-data
*=test-data,search-test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*=test-data
11 changes: 11 additions & 0 deletions test-app/src/main/ml-data/musicians/musician1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"musician": {
"lastName": "Armstrong",
"firstName": "Louis",
"dob": "1901-08-04",
"instrument": [
"trumpet",
"vocal"
]
}
}
11 changes: 11 additions & 0 deletions test-app/src/main/ml-data/musicians/musician2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"musician": {
"lastName": "Byron",
"firstName": "Don",
"dob": "1958-11-08",
"instrument": [
"clarinet",
"saxophone"
]
}
}
10 changes: 10 additions & 0 deletions test-app/src/main/ml-data/musicians/musician3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"musician": {
"lastName": "Coltrane",
"firstName": "John",
"dob": "1926-09-23",
"instrument": [
"saxophone"
]
}
}
10 changes: 10 additions & 0 deletions test-app/src/main/ml-data/musicians/musician4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"musician": {
"lastName": "Davis",
"firstName": "Miles",
"dob": "1926-05-26",
"instrument": [
"trumpet"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*=python-tester,read,python-tester,update
28 changes: 28 additions & 0 deletions test-app/src/main/ml-schemas/tde/musician.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"template": {
"context": "/musician",
"rows": [
{
"schemaName": "test",
"viewName": "musician",
"columns": [
{
"name": "lastName",
"scalarType": "string",
"val": "lastName"
},
{
"name": "firstName",
"scalarType": "string",
"val": "firstName"
},
{
"name": "dob",
"scalarType": "date",
"val": "dob"
}
]
}
]
}
}
25 changes: 25 additions & 0 deletions tests/test_graphql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
def test_graphql(client):
data = client.rows.graphql("query musicianQuery { test_musician { lastName firstName dob } }")
musicians = data["data"]["test_musician"]
assert 4 == len(musicians)
assert 1 == len([m for m in musicians if m["lastName"] == "Armstrong"])


def test_graphql_return_response(client):
response = client.rows.graphql("query musicianQuery { test_musician { lastName firstName dob } }", return_response=True)
assert 200 == response.status_code
data = response.json()
musicians = data["data"]["test_musician"]
assert 4 == len(musicians)
assert 1 == len([m for m in musicians if m["lastName"] == "Armstrong"])


def test_graphql_bad_graphql(client):
response = client.rows.graphql("query musicianQuery { test_musician { lastName firstName dob } ")
assert 1 == len(response['errors'])
assert 'GRAPHQL-PARSE: Error parsing the GraphQL request string => \nquery musicianQuery { test_musician { lastName firstName dob } ' == response['errors'][0]['message']


def test_graphql_bad_user(not_rest_user_client):
response = not_rest_user_client.rows.graphql("query musicianQuery { test_musician { lastName firstName dob } }")
assert 403 == response.status_code
10 changes: 6 additions & 4 deletions tests/test_read_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ def test_read_only_collections(client: Client):

doc1 = docs[0]
assert doc1.uri == "/doc1.json"
assert len(doc1.collections) == 1
assert doc1.collections[0] == "test-data"
assert len(doc1.collections) == 2
assert "test-data" in doc1.collections
assert "search-test" in doc1.collections
assert doc1.content is None
assert doc1.permissions is None
assert doc1.quality is None
Expand All @@ -101,8 +102,9 @@ def test_read_only_collections(client: Client):

doc2 = docs[1]
assert doc2.uri == "/doc2.xml"
assert len(doc2.collections) == 1
assert doc2.collections[0] == "test-data"
assert len(doc2.collections) == 2
assert "test-data" in doc1.collections
assert "search-test" in doc1.collections
assert doc2.content is None
assert doc2.permissions is None
assert doc2.quality is None
Expand Down
10 changes: 7 additions & 3 deletions tests/test_search_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,21 @@ def test_search_options(client: Client):

def test_collection(client: Client):
docs = client.documents.search(
categories=["content", "collections"], collections=["test-data"]
categories=["content", "collections"], collections=["search-test"]
)
assert len(docs) == 2

doc1 = next(doc for doc in docs if doc.uri == "/doc1.json")
assert doc1.content is not None
assert doc1.collections[0] == "test-data"
assert len(doc1.collections) == 2
assert "test-data" in doc1.collections
assert "search-test" in doc1.collections

doc2 = next(doc for doc in docs if doc.uri == "/doc2.xml")
assert doc2.content is not None
assert doc2.collections[0] == "test-data"
assert len(doc1.collections) == 2
assert "test-data" in doc1.collections
assert "search-test" in doc1.collections


def test_not_rest_user(not_rest_user_client: Client):
Expand Down