Skip to content

Commit

Permalink
Merge pull request #68 from marklogic/develop
Browse files Browse the repository at this point in the history
Release 1.2.0
  • Loading branch information
BillFarber authored Jun 10, 2024
2 parents be1e6e6 + 4185178 commit 54c4613
Show file tree
Hide file tree
Showing 21 changed files with 391 additions and 29 deletions.
17 changes: 9 additions & 8 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"python.formatting.provider": "black",
"python.linting.enabled": true,
"python.linting.flake8Enabled": true,
"python.linting.flake8Args": [
"--max-line-length=88"
],
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true
},
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"flake8.args": [
"--max-line-length=120"
]
}
23 changes: 22 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ To try this out locally:
- `source .venv/bin/activate` to use that virtual environment.
- `poetry install` to install project dependencies.

VSCode is recommended for development. You can try [these instructions](https://www.pythoncheatsheet.org/blog/python-projects-with-poetry-and-vscode-part-1)
VSCode is recommended for development.
- For formatting, the project uses the [Black](https://github.com/psf/black) code formatter, and the
[Black Formatter VSCode extension](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter)
is recommended.
- For linting, the project uses the [Flake8](https://flake8.pycqa.org/en/latest/) linter and the
[Flake8 extension](https://marketplace.visualstudio.com/items?itemName=ms-python.flake8) is recommended.
These tools are included in the project by pyproject.toml and the settings are in .vscode/settings.json.

You can also get additional information at
[these instructions](https://www.pythoncheatsheet.org/blog/python-projects-with-poetry-and-vscode-part-1)
for getting setup in VSCode with linting and formatting enabled.

## Running the tests
Expand Down Expand Up @@ -67,6 +76,18 @@ project's documentation:

python -i shell/docs.py


## Testing updates in a different local project
If you are using this in another project and making changes for it, use the following command to make the
changes to this local project immediately reflected in a dependent project:
```poetry add <local-path-to-this-project>/marklogic-python-client/```

Using this method will allow you to very easily test changes to this project, in a different local project.

Keep in mind that you probably do not want to check that version of the pyproject.toml file into version
control since it is only useful locally.


## Testing the documentation locally

The docs for this project are stored in the `./docs` directory as a set of Markdown files. These are published via
Expand Down
39 changes: 39 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@Library('shared-libraries') _
pipeline{
agent none;
environment{
JAVA_HOME_DIR="/home/builder/java/jdk-11.0.2"
GRADLE_DIR =".gradle"
}
options {
checkoutToSubdirectory 'marklogic-python-client'
buildDiscarder logRotator(artifactDaysToKeepStr: '7', artifactNumToKeepStr: '', daysToKeepStr: '30', numToKeepStr: '')
}
stages{
stage('tests'){
agent {label 'devExpLinuxPool'}
steps{
script{
copyRPM 'Latest','11'
setUpML '$WORKSPACE/xdmp/src/Mark*.rpm'
sh label:'deploy project', script: '''#!/bin/bash
export JAVA_HOME=$JAVA_HOME_DIR
export GRADLE_USER_HOME=$WORKSPACE/$GRADLE_DIR
export PATH=$GRADLE_USER_HOME:$JAVA_HOME/bin:$PATH
cd marklogic-python-client/test-app
./gradlew -i mlDeploy -PmlPassword=admin
'''
sh label:'Run tests', script: '''#!/bin/bash
cd marklogic-python-client
python -m venv .venv;
source .venv/bin/activate;
pip install poetry;
poetry install;
pytest --junitxml=TestReport.xml || true
'''
junit 'marklogic-python-client/TestReport.xml'
}
}
}
}
}
6 changes: 6 additions & 0 deletions docs/creating-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ The `Client` class is the primary API to interact with in the MarkLogic Python c
found in both the `Session` class and the `requests` API. You can therefore use a `Client` object in the same manner
as you'd use either the `Session` class or the `requests` API.

## Table of contents
{: .no_toc .text-delta }

- TOC
{:toc}

## Creating a client

A `Client` instance can be created either by providing a base URL for all requests along with authentication:
Expand Down
20 changes: 20 additions & 0 deletions docs/eval.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ execution of custom code, whether via an inline script or an existing module in
The MarkLogic Python client supports execution of custom code by simplifying the submission of custom code
and converting the multipart response into more useful Python data types.

## Table of contents
{: .no_toc .text-delta }

- TOC
{:toc}

## Setup

The examples below all depend on the instructions in the [setup guide](example-setup.md) having already been performed.
Expand Down Expand Up @@ -117,3 +123,17 @@ processing of the response or debugging requests.
The `client.eval` and `client.invoke` functions both support referencing a
[REST API transaction](https://docs.marklogic.com/REST/client/transaction-management) via the `tx`
argument. See [the guide on transactions](transactions.md) for further information.

## Providing additional arguments

The `client.eval` and `client.invoke` methods each provide a `**kwargs` argument, so you can pass in any other arguments you would
normally pass to `requests`. For example:

```
client.eval(javascript="fn.currentDateTime()", params={"database": "Documents"})
client.invoke("/sample.sjs", params={"database": "Documents"})
```

Please see [the eval endpoint documentation](https://docs.marklogic.com/REST/POST/v1/eval)
and [the invoke endpoint documentation](https://docs.marklogic.com/REST/POST/v1/invoke) for
information on additional parameters.
6 changes: 6 additions & 0 deletions docs/managing-documents/reading.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ The [GET /v1/documents](https://docs.marklogic.com/REST/GET/v1/documents) endpoi
reading multiple documents with metadata via a multipart/mixed HTTP response. The MarkLogic Python client simplifies
handling the response by converting it into a list of `Document` instances via the `client.documents.read` method.

## Table of contents
{: .no_toc .text-delta }

- TOC
{:toc}

## Setup for examples

The examples below all assume that you have created a new MarkLogic user named "python-user" as described in the
Expand Down
6 changes: 6 additions & 0 deletions docs/managing-documents/searching.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ returning content and metadata for each matching document. Similar to reading mu
HTTP response. The MarkLogic Python client simplifies use of this operation by returning a list of `Document` instances
via the `client.documents.search` method.

## Table of contents
{: .no_toc .text-delta }

- TOC
{:toc}

## Setup for examples

The examples below all assume that you have created a new MarkLogic user named "python-user" as described in the
Expand Down
8 changes: 8 additions & 0 deletions docs/managing-documents/writing.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ writing multiple documents with metadata via a multipart HTTP request. The MarkL
simplifies the use of this endpoint via the `client.documents.write` method and the `Document`
class.

## Table of contents
{: .no_toc .text-delta }

- TOC
{:toc}

## Setup

The examples below all assume that you have created a new MarkLogic user named "python-user" as described in the
[setup guide](../example-setup.md). In addition, each of the examples below requires the following `Client` instance to be created
first:
Expand Down
22 changes: 21 additions & 1 deletion docs/rows.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ The [MarkLogic REST rows service](https://docs.marklogic.com/REST/client/row-man
operations for querying for rows via several query languages. The MarkLogic Python client simplifies submitting queries
for rows and converting responses into useful data structures.

## Table of contents
{: .no_toc .text-delta }

- TOC
{:toc}

## Setup

The examples below require documents to be loaded along with a
Expand Down Expand Up @@ -178,4 +184,18 @@ Printing the `df` object will yield the following:
1 Davis Miles 1926-05-26
2 Armstrong Louis 1901-08-04
3 Coltrane John 1926-09-23
```
```

## Providing additional arguments

The `client.rows.query` method provides a `**kwargs` argument, so you can pass in any other arguments you would
normally pass to `requests`. For example:

```
response = client.rows.query("op.fromView('example', 'musician')", params={"database": "Documents"})
```

Please see [the rows endpoint documentation](https://docs.marklogic.com/REST/POST/v1/rows) for
information on additional parameters. If you are submitting a GraphQL query, then see
[the GraphQL endpoint documentation](https://docs.marklogic.com/REST/POST/v1/rows/graphql) for
information on parameters for that endpoint.
8 changes: 8 additions & 0 deletions docs/transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ via a `Transaction` class that is also a
thereby allowing it to handle committing or rolling back the transaction without any user
involvement.

## Table of contents
{: .no_toc .text-delta }

- TOC
{:toc}

## Using a transaction

The following example demonstrates writing documents via multiple calls to MarkLogic,
all within the same REST API transaction; the example depends on first following the
instructions in the [setup guide](example-setup.md):
Expand Down
33 changes: 20 additions & 13 deletions marklogic/documents.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
from collections import OrderedDict
from email.message import Message
from typing import Union

from marklogic.transactions import Transaction
Expand Down Expand Up @@ -262,23 +263,29 @@ def _extract_values_from_header(part) -> dict:
Returns a dict containing values about the document content or metadata.
"""
encoding = part.encoding
disposition = part.headers["Content-Disposition".encode(encoding)].decode(encoding)
disposition_values = {}
for item in disposition.split(";"):
tokens = item.split("=")
# The first item will be "attachment" and can be ignored.
if len(tokens) == 2:
disposition_values[tokens[0].strip()] = tokens[1]
disposition = part.headers["Content-Disposition".encode(encoding)].decode(
encoding
)

content_type = None
if part.headers.get("Content-Type".encode(encoding)):
content_type = part.headers["Content-Type".encode(encoding)].decode(encoding)
content_type = part.headers["Content-Type".encode(encoding)].decode(
encoding
)

uri = disposition_values["filename"]
if uri.startswith('"'):
uri = uri[1:]
if uri.endswith('"'):
uri = uri[:-1]
content_disposition_header = part.headers[
"Content-Disposition".encode(encoding)
].decode(encoding)
msg = Message()
msg["content-disposition"] = content_disposition_header
uri = msg.get_filename()

disposition_values = {}
for item in disposition.replace(uri, "").split(";"):
tokens = item.split("=")
key = tokens[0].strip()
if key in ["category", "versionId"]:
disposition_values[key] = tokens[1]

return {
"uri": uri,
Expand Down
57 changes: 57 additions & 0 deletions marklogic/rows.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,63 @@ def query(
not 2xx, then the entire response is always returned.
"""
path = "v1/rows/graphql" if graphql else "v1/rows"
return self.__send_request(
path,
dsl,
plan,
sql,
sparql,
graphql,
format,
tx,
return_response,
**kwargs,
)

def update(
self,
dsl: str = None,
plan: dict = None,
format: str = "json",
tx: Transaction = None,
return_response: bool = False,
**kwargs,
):
"""
Sends an update query to an endpoint at the MarkLogic rows service defined at
https://docs.marklogic.com/REST/client/row-management. One of 'dsl' or
'plan' must be defined. This feature requires the use of MarkLogic version
11.2 or later.
For more information about Optic Update and using the Optic DSL,
see https://docs.marklogic.com/guide/app-dev/OpticAPI.
:param dsl: an Optic DSL query
:param plan: a serialized Optic query
:param tx: optional REST transaction in which to service this request.
: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.
"""
path = "v1/rows/update"
return self.__send_request(
path, dsl, plan, None, None, None, format, tx, return_response, **kwargs
)

def __send_request(
self,
path: str = None,
dsl: str = None,
plan: dict = None,
sql: str = None,
sparql: str = None,
graphql: str = None,
format: str = "json",
tx: Transaction = None,
return_response: bool = False,
**kwargs,
):
headers = kwargs.pop("headers", {})
data = None
if graphql:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "marklogic-python-client"
version = "1.1.0"
version = "1.2.0"
description = "Python client for MarkLogic, built on the requests library"
authors = ["MarkLogic <general@developer.marklogic.com>"]
readme = "README.md"
Expand Down
1 change: 1 addition & 0 deletions test-app/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.gradle
gradle-local.properties
build
docker
18 changes: 18 additions & 0 deletions test-app/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: '3.8'
name: marklogic_python

services:

marklogic:
image: "marklogicdb/marklogic-db:11.2.0-centos-1.1.2"
platform: linux/amd64
environment:
- INSTALL_CONVERTERS=true
- MARKLOGIC_INIT=true
- MARKLOGIC_ADMIN_USERNAME=admin
- MARKLOGIC_ADMIN_PASSWORD=admin
volumes:
- ./docker/marklogic/logs:/var/opt/MarkLogic/Logs
ports:
- "8000-8002:8000-8002"
- "8030-8031:8030-8031"
1 change: 1 addition & 0 deletions test-app/src/main/ml-data/doc2;copy.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<hello>semicolon</hello>
1 change: 1 addition & 0 deletions test-app/src/main/ml-data/doc2=copy.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<hello>equal</hello>
Loading

0 comments on commit 54c4613

Please sign in to comment.