Skip to content

Commit 477c03d

Browse files
Merge pull request #9 from DACCS-Climate/add-jupyterlab-functions
add jupyterlab functions
2 parents b5a6a2c + fd79ca4 commit 477c03d

File tree

3 files changed

+100
-1
lines changed

3 files changed

+100
-1
lines changed

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,49 @@ Weaver URL is https://pavics.ouranos.ca/weaver/
105105
>>> # A MarbleService object is returned that can be used wherever a string can be used
106106
>>> print(f"Weaver URL is {MarbleClient()['PAVICS'].weaver}")
107107
Weaver URL is https://pavics.ouranos.ca/weaver/
108+
```
109+
110+
## Jupyterlab functionality
111+
112+
When running in a Marble Jupyterlab environment, the client can take advantage of various environment variables and
113+
Jupyter's API to provide some additional functionality.
114+
115+
> [!WARNING]
116+
> Calling any of the methods described below outside a Marble Jupyterlab environment will raise a
117+
> `JupyterEnvironmentError`.
118+
119+
Get the node your notebook/script is currently running on:
120+
121+
```python
122+
>>> client = MarbleClient()
123+
>>> client.this_node
124+
<marble_client.node.MarbleNode at 0x10c129990>
125+
```
126+
127+
Add session cookies to a `requests.Session` object. This means that any request made with that session variable will
128+
be made as if you were logged in to the current Marble node. This is the recommended way to access protected resources
129+
programmatically in your scripts:
130+
131+
```python
132+
>>> client = MarbleClient()
133+
>>> session = requests.Session
134+
>>> client.this_session(session)
135+
>>> session.cookies.get_dict()
136+
{...} # session cookiejar now includes cookies
137+
```
138+
139+
You can also use the `this_session` method to create a new `requests.Sesssion` object:
140+
141+
```python
142+
>>> client = MarbleClient()
143+
>>> session = client.this_session()
144+
>>> session.cookies.get_dict()
145+
{...} # now includes session cookies
146+
```
147+
148+
You can now make a request to a protected resource on the current node using this session object. You will be able to
149+
access the resource if you have permission:
150+
151+
```python
152+
>>> session.get(f"{client.this_node.url}/some/protected/subpath")
108153
```

marble_client/client.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,36 @@
44
import os
55
import shutil
66
import warnings
7+
from functools import wraps, cache
78
from typing import Optional
9+
from urllib.parse import urlparse
810

911
import dateutil.parser
1012
import requests
1113

1214
from marble_client.constants import CACHE_FNAME, CACHE_META_FNAME, NODE_REGISTRY_URL
13-
from marble_client.exceptions import UnknownNodeError
15+
from marble_client.exceptions import UnknownNodeError, JupyterEnvironmentError
1416
from marble_client.node import MarbleNode
1517

1618
__all__ = ["MarbleClient"]
1719

1820

21+
def check_jupyterlab(f):
22+
"""
23+
Wraps the function f by first checking if the current script is running in a
24+
Marble Jupyterlab environment and raising a JupyterEnvironmentError if not.
25+
26+
This is used as a pre-check for functions that only work in a Marble Jupyterlab
27+
environment.
28+
"""
29+
@wraps(f)
30+
def wrapper(*args, **kwargs):
31+
if os.getenv("PAVICS_HOST_URL"):
32+
return f(*args, **kwargs)
33+
raise JupyterEnvironmentError("Not in a Marble jupyterlab environment")
34+
return wrapper
35+
36+
1937
class MarbleClient:
2038
def __init__(self, fallback: Optional[bool] = True) -> None:
2139
"""Constructor method
@@ -51,6 +69,38 @@ def __init__(self, fallback: Optional[bool] = True) -> None:
5169
def nodes(self) -> dict[str, MarbleNode]:
5270
return self._nodes
5371

72+
@property
73+
@cache
74+
@check_jupyterlab
75+
def this_node(self) -> MarbleNode:
76+
"""
77+
Return the node where this script is currently running.
78+
79+
Note that this function only works in a Marble Jupyterlab environment.
80+
"""
81+
host_url = urlparse(os.getenv("PAVICS_HOST_URL"))
82+
for node in self.nodes.values():
83+
if urlparse(node.url).hostname == host_url.hostname:
84+
return node
85+
raise UnknownNodeError(f"No node found in the registry with the url {host_url}")
86+
87+
@check_jupyterlab
88+
def this_session(self, session: Optional[requests.Session]) -> requests.Session:
89+
"""
90+
Add the login session cookies of the user who is currently logged in to the session object.
91+
If a session object is not passed as an argument to this function, create a new session
92+
object as well.
93+
94+
Note that this function only works in a Marble Jupyterlab environment.
95+
"""
96+
if session is None:
97+
session = requests.Session()
98+
r = requests.get(f"{os.getenv('JUPYTERHUB_API_URL')}/users/{os.getenv('JUPYTERHUB_USER')}",
99+
headers={"Authorization": f"token {os.getenv('JUPYTERHUB_API_TOKEN')}"})
100+
for name, value in r.json().get("auth_state", {}).get("magpie_cookies", {}).items():
101+
session.cookies.set(name, value)
102+
return session
103+
54104
def __getitem__(self, node: str) -> MarbleNode:
55105
try:
56106
return self.nodes[node]

marble_client/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ class ServiceNotAvailableError(MarbleBaseError):
88

99
class UnknownNodeError(MarbleBaseError):
1010
pass
11+
12+
13+
class JupyterEnvironmentError(MarbleBaseError):
14+
pass

0 commit comments

Comments
 (0)