Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7644bd6

Browse files
authoredJun 30, 2022
WebDav support (#51)
* 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
1 parent 0000ee6 commit 7644bd6

File tree

6 files changed

+181
-8
lines changed

6 files changed

+181
-8
lines changed
 

‎CHANGE.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
+++++++++++
22
LabKey Python Client API News
33
+++++++++++
4+
What's New in the LabKey 2.3.0 package
5+
==============================
6+
7+
*Release date: 06/30/2022*
8+
- Add "hostname" property to ServerContext
9+
- Add "base_url" property to ServerContext
10+
- Add "webdav_client" method to ServerContext
11+
- This method returns a webdavclient3 Client instance
12+
- Add "webdav_path" method to ServerContext
13+
- Add docs for WebDav support
14+
- Add unit tests for ServerContext
415

516
What's New in the LabKey 2.2.0 package
617
==============================
18+
19+
*Release date: 08/11/2021*
720
- Add `domain.get_domain_details` API to domain module.
821
- Support saving domain options via `domain.save`.
922
- Fix `ConditionalFormat.to_json()` to match server response.

‎README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ Security API - [sample code](samples/security_example.py)
3636

3737
- Available for administrating and configuring user accounts and permissions.
3838

39+
WebDav
40+
41+
- Documentation and example code can be found [here](docs/webdav.md).
42+
3943
## Installation
4044
To install, simply use `pip`:
4145

‎docs/webdav.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# WebDav Support
2+
3+
Our Python API includes some convenience methods for creating "webdavclient3" clients, and building webdav file paths.
4+
5+
### Creating a WebDav client
6+
First, make sure you have the [webdavclient3](https://github.com/ezhov-evgeny/webdav-client-python-3) library installed:
7+
8+
```bash
9+
$ pip install webdavclient3
10+
```
11+
12+
Then you can use your `APIWrapper` to create a client:
13+
14+
```python
15+
from labkey.api_wrapper import APIWrapper
16+
17+
domain = "localhost:8080"
18+
container = "MyContainer"
19+
api = APIWrapper(domain, container)
20+
webdav_client = api.server_context.webdav_client()
21+
```
22+
23+
The `webdav_client` method has a single optional argument, `webdav_options`, a dict that you can use to pass any options
24+
that you would pass to the [webdavclient3](https://github.com/ezhov-evgeny/webdav-client-python-3#webdav-api) library.
25+
If you are using API Key authentication with your APIWrapper we will automatically configure the WebDav Client to use
26+
API Key authentication with your API Key. If you are using a `.netrc` file for authentication it should automatically
27+
detect your `.netrc` file and authenticate using those credentials.
28+
29+
30+
### The webdav_path utility method
31+
If you are using the `webdavclient3` library you'll still need to know the appropriate WebDav path in order to access
32+
your files. We provide a utility method, `webdav_path` to make it easier to construct LabKey WebDav paths. The method
33+
takes two keyword arguments, `container_path`, and `file_name`.
34+
35+
```python
36+
from labkey.api_wrapper import APIWrapper
37+
38+
domain = "localhost:8080"
39+
container = "MyContainer"
40+
api = APIWrapper(domain, container)
41+
webdav_client = api.server_context.webdav_client()
42+
43+
# Constructs a webdav path to "MyContainer"
44+
path = api.server_context.webdav_path()
45+
print(webdav_client.info(path))
46+
# Constructs a webdav path to the "data.txt" file in "MyContainer"
47+
path = api.server_context.webdav_path(file_name='data.txt')
48+
print(webdav_client.info(path))
49+
# Constructs a webdav path to the "data.txt" file in "other_container"
50+
path = api.server_context.webdav_path(container_path="other_container", file_name="data.txt")
51+
print(webdav_client.info(path))
52+
```

‎labkey/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@
1616
from labkey import domain, query, experiment, security, utils
1717

1818
__title__ = "labkey"
19-
__version__ = "2.2.0"
19+
__version__ = "2.3.0"
2020
__author__ = "LabKey"
2121
__license__ = "Apache License 2.0"

‎labkey/server_context.py

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,81 @@ def __init__(
8181
def __repr__(self):
8282
return f"<ServerContext [ {self._domain} | {self._context_path} | {self._container_path} ]>"
8383

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

87-
url = self._scheme + self._domain
88+
@property
89+
def base_url(self) -> str:
90+
base_url = self.hostname
8891

8992
if self._context_path is not None:
90-
url += sep + self._context_path
93+
base_url += "/" + self._context_path
94+
95+
return base_url
96+
97+
def build_url(self, controller: str, action: str, container_path: str = None) -> str:
98+
url = self.base_url
9199

92100
if container_path is not None:
93-
url += sep + container_path
101+
url += "/" + container_path
94102
elif self._container_path is not None:
95-
url += sep + self._container_path
103+
url += "/" + self._container_path
96104

97-
url += sep + controller + "-" + action
105+
url += "/" + controller + "-" + action
98106

99107
return url
100108

109+
def webdav_path(self, container_path: str = None, file_name: str = None):
110+
path = "/_webdav"
111+
container_path = container_path or self._container_path
112+
113+
if container_path is not None:
114+
if container_path.endswith("/"):
115+
# trim the slash
116+
container_path = container_path[0:-1]
117+
118+
if not container_path.startswith("/"):
119+
path += "/"
120+
121+
path += container_path
122+
123+
path += "/@files"
124+
125+
if file_name is not None:
126+
if not file_name.startswith("/"):
127+
path += "/"
128+
129+
path += file_name
130+
131+
return path
132+
133+
def webdav_client(self, webdav_options: dict = None):
134+
# We localize the import of webdav3 here so it is an optional dependency. Only users who want to use webdav will
135+
# need to pip install webdavclient3
136+
from webdav3.client import Client
137+
138+
options = {
139+
"webdav_hostname": self.base_url,
140+
}
141+
142+
if self._api_key is not None:
143+
options["webdav_login"] = "apikey"
144+
options["webdav_password"] = f"apikey|{self._api_key}"
145+
146+
if webdav_options is not None:
147+
options = {
148+
**options,
149+
**webdav_options,
150+
}
151+
152+
client = Client(options)
153+
154+
if self._verify_ssl is False:
155+
client.verify = False # Set verify to false if using localhost without HTTPS
156+
157+
return client
158+
101159
def handle_request_exception(self, exception):
102160
if type(exception) in [RequestAuthorizationError, QueryNotFoundError, ServerNotFoundError]:
103161
raise exception

‎test/unit/test_server_context.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from labkey.server_context import ServerContext
2+
import pytest
3+
4+
5+
@pytest.fixture(scope="session")
6+
def server_context():
7+
return ServerContext("example.com", "test_container", "test_context_path")
8+
9+
10+
@pytest.fixture(scope="session")
11+
def server_context_no_context_path():
12+
return ServerContext("example.com", "test_container")
13+
14+
15+
@pytest.fixture(scope="session")
16+
def server_context_no_ssl():
17+
return ServerContext("example.com", "test_container", "test_context_path", use_ssl=False)
18+
19+
20+
def test_base_url(server_context, server_context_no_context_path, server_context_no_ssl):
21+
assert server_context.base_url == "https://example.com/test_context_path"
22+
assert server_context_no_context_path.base_url == "https://example.com"
23+
assert server_context_no_ssl.base_url == "http://example.com/test_context_path"
24+
25+
26+
def test_build_url(server_context):
27+
assert (
28+
server_context.build_url("query", "getQuery.api")
29+
== "https://example.com/test_context_path/test_container/query-getQuery.api"
30+
)
31+
assert (
32+
server_context.build_url("query", "getQuery.api", "different_container")
33+
== "https://example.com/test_context_path/different_container/query-getQuery.api"
34+
)
35+
36+
37+
def test_webdav_path(server_context, server_context_no_context_path, server_context_no_ssl):
38+
assert server_context.webdav_path() == "/_webdav/test_container/@files"
39+
assert (
40+
server_context.webdav_path(file_name="test.jpg")
41+
== "/_webdav/test_container/@files/test.jpg"
42+
)
43+
assert (
44+
server_context.webdav_path("my_container/with_subfolder", "data.txt")
45+
== "/_webdav/my_container/with_subfolder/@files/data.txt"
46+
)

0 commit comments

Comments
 (0)
Please sign in to comment.