Skip to content

Commit

Permalink
WebDav support (#51)
Browse files Browse the repository at this point in the history
* Add `hostname` property to ServerContext
* Add `base_url` property to ServerContext
* Add `webdav_client` method to ServerContext
  * This method returns a webdavclient3 Client instance
* Add `webdav_path` method to ServerContext
* Add docs for WebDav support
* Add unit tests for ServerContext
  • Loading branch information
labkey-alan authored Jun 30, 2022
1 parent 0000ee6 commit 7644bd6
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 8 deletions.
13 changes: 13 additions & 0 deletions CHANGE.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
+++++++++++
LabKey Python Client API News
+++++++++++
What's New in the LabKey 2.3.0 package
==============================

*Release date: 06/30/2022*
- Add "hostname" property to ServerContext
- Add "base_url" property to ServerContext
- Add "webdav_client" method to ServerContext
- This method returns a webdavclient3 Client instance
- Add "webdav_path" method to ServerContext
- Add docs for WebDav support
- Add unit tests for ServerContext

What's New in the LabKey 2.2.0 package
==============================

*Release date: 08/11/2021*
- Add `domain.get_domain_details` API to domain module.
- Support saving domain options via `domain.save`.
- Fix `ConditionalFormat.to_json()` to match server response.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ Security API - [sample code](samples/security_example.py)

- Available for administrating and configuring user accounts and permissions.

WebDav

- Documentation and example code can be found [here](docs/webdav.md).

## Installation
To install, simply use `pip`:

Expand Down
52 changes: 52 additions & 0 deletions docs/webdav.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# WebDav Support

Our Python API includes some convenience methods for creating "webdavclient3" clients, and building webdav file paths.

### Creating a WebDav client
First, make sure you have the [webdavclient3](https://github.com/ezhov-evgeny/webdav-client-python-3) library installed:

```bash
$ pip install webdavclient3
```

Then you can use your `APIWrapper` to create a client:

```python
from labkey.api_wrapper import APIWrapper

domain = "localhost:8080"
container = "MyContainer"
api = APIWrapper(domain, container)
webdav_client = api.server_context.webdav_client()
```

The `webdav_client` method has a single optional argument, `webdav_options`, a dict that you can use to pass any options
that you would pass to the [webdavclient3](https://github.com/ezhov-evgeny/webdav-client-python-3#webdav-api) library.
If you are using API Key authentication with your APIWrapper we will automatically configure the WebDav Client to use
API Key authentication with your API Key. If you are using a `.netrc` file for authentication it should automatically
detect your `.netrc` file and authenticate using those credentials.


### The webdav_path utility method
If you are using the `webdavclient3` library you'll still need to know the appropriate WebDav path in order to access
your files. We provide a utility method, `webdav_path` to make it easier to construct LabKey WebDav paths. The method
takes two keyword arguments, `container_path`, and `file_name`.

```python
from labkey.api_wrapper import APIWrapper

domain = "localhost:8080"
container = "MyContainer"
api = APIWrapper(domain, container)
webdav_client = api.server_context.webdav_client()

# Constructs a webdav path to "MyContainer"
path = api.server_context.webdav_path()
print(webdav_client.info(path))
# Constructs a webdav path to the "data.txt" file in "MyContainer"
path = api.server_context.webdav_path(file_name='data.txt')
print(webdav_client.info(path))
# Constructs a webdav path to the "data.txt" file in "other_container"
path = api.server_context.webdav_path(container_path="other_container", file_name="data.txt")
print(webdav_client.info(path))
```
2 changes: 1 addition & 1 deletion labkey/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
from labkey import domain, query, experiment, security, utils

__title__ = "labkey"
__version__ = "2.2.0"
__version__ = "2.3.0"
__author__ = "LabKey"
__license__ = "Apache License 2.0"
72 changes: 65 additions & 7 deletions labkey/server_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,23 +81,81 @@ def __init__(
def __repr__(self):
return f"<ServerContext [ {self._domain} | {self._context_path} | {self._container_path} ]>"

def build_url(self, controller: str, action: str, container_path: str = None) -> str:
sep = "/"
@property
def hostname(self) -> str:
return self._scheme + self._domain

url = self._scheme + self._domain
@property
def base_url(self) -> str:
base_url = self.hostname

if self._context_path is not None:
url += sep + self._context_path
base_url += "/" + self._context_path

return base_url

def build_url(self, controller: str, action: str, container_path: str = None) -> str:
url = self.base_url

if container_path is not None:
url += sep + container_path
url += "/" + container_path
elif self._container_path is not None:
url += sep + self._container_path
url += "/" + self._container_path

url += sep + controller + "-" + action
url += "/" + controller + "-" + action

return url

def webdav_path(self, container_path: str = None, file_name: str = None):
path = "/_webdav"
container_path = container_path or self._container_path

if container_path is not None:
if container_path.endswith("/"):
# trim the slash
container_path = container_path[0:-1]

if not container_path.startswith("/"):
path += "/"

path += container_path

path += "/@files"

if file_name is not None:
if not file_name.startswith("/"):
path += "/"

path += file_name

return path

def webdav_client(self, webdav_options: dict = None):
# We localize the import of webdav3 here so it is an optional dependency. Only users who want to use webdav will
# need to pip install webdavclient3
from webdav3.client import Client

options = {
"webdav_hostname": self.base_url,
}

if self._api_key is not None:
options["webdav_login"] = "apikey"
options["webdav_password"] = f"apikey|{self._api_key}"

if webdav_options is not None:
options = {
**options,
**webdav_options,
}

client = Client(options)

if self._verify_ssl is False:
client.verify = False # Set verify to false if using localhost without HTTPS

return client

def handle_request_exception(self, exception):
if type(exception) in [RequestAuthorizationError, QueryNotFoundError, ServerNotFoundError]:
raise exception
Expand Down
46 changes: 46 additions & 0 deletions test/unit/test_server_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from labkey.server_context import ServerContext
import pytest


@pytest.fixture(scope="session")
def server_context():
return ServerContext("example.com", "test_container", "test_context_path")


@pytest.fixture(scope="session")
def server_context_no_context_path():
return ServerContext("example.com", "test_container")


@pytest.fixture(scope="session")
def server_context_no_ssl():
return ServerContext("example.com", "test_container", "test_context_path", use_ssl=False)


def test_base_url(server_context, server_context_no_context_path, server_context_no_ssl):
assert server_context.base_url == "https://example.com/test_context_path"
assert server_context_no_context_path.base_url == "https://example.com"
assert server_context_no_ssl.base_url == "http://example.com/test_context_path"


def test_build_url(server_context):
assert (
server_context.build_url("query", "getQuery.api")
== "https://example.com/test_context_path/test_container/query-getQuery.api"
)
assert (
server_context.build_url("query", "getQuery.api", "different_container")
== "https://example.com/test_context_path/different_container/query-getQuery.api"
)


def test_webdav_path(server_context, server_context_no_context_path, server_context_no_ssl):
assert server_context.webdav_path() == "/_webdav/test_container/@files"
assert (
server_context.webdav_path(file_name="test.jpg")
== "/_webdav/test_container/@files/test.jpg"
)
assert (
server_context.webdav_path("my_container/with_subfolder", "data.txt")
== "/_webdav/my_container/with_subfolder/@files/data.txt"
)

0 comments on commit 7644bd6

Please sign in to comment.