Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Session Handling #481

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions bioblend/galaxy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"""
from typing import Optional

import requests

from bioblend.galaxy import (
config,
container_resolution,
Expand Down Expand Up @@ -39,6 +41,7 @@ def __init__(
email: Optional[str] = None,
password: Optional[str] = None,
verify: bool = True,
session: requests.sessions.Session() = None,
williamjsmith15 marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
"""
A base representation of a connection to a Galaxy instance, identified
Expand Down Expand Up @@ -106,6 +109,8 @@ def __init__(
self.tool_data = tool_data.ToolDataClient(self)
self.folders = folders.FoldersClient(self)
self.tool_dependencies = tool_dependencies.ToolDependenciesClient(self)
if session is not None:
self.session = session
williamjsmith15 marked this conversation as resolved.
Show resolved Hide resolved

def __repr__(self) -> str:
"""
Expand Down
5 changes: 5 additions & 0 deletions bioblend/galaxy/objects/galaxy_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
Optional,
)

import requests

import bioblend
import bioblend.galaxy
from bioblend.galaxy.datasets import TERMINAL_STATES
Expand Down Expand Up @@ -57,6 +59,7 @@ def __init__(
email: Optional[str] = None,
password: Optional[str] = None,
verify: bool = True,
session: requests.sessions.Session() = None,
williamjsmith15 marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
self.gi = bioblend.galaxy.GalaxyInstance(url, api_key, email, password, verify)
williamjsmith15 marked this conversation as resolved.
Show resolved Hide resolved
self.log = bioblend.log
Expand All @@ -68,6 +71,8 @@ def __init__(
self.invocations = client.ObjInvocationClient(self)
self.tools = client.ObjToolClient(self)
self.jobs = client.ObjJobClient(self)
if session is not None:
self.session = session
williamjsmith15 marked this conversation as resolved.
Show resolved Hide resolved

def _wait_datasets(
self, datasets: Iterable[wrappers.Dataset], polling_interval: float, break_on_error: bool = True
Expand Down
162 changes: 113 additions & 49 deletions bioblend/galaxyclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def __init__(
password: Optional[str] = None,
verify: bool = True,
timeout: Optional[float] = None,
session: requests.sessions.Session() = None,
williamjsmith15 marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
"""
:param verify: Whether to verify the server's TLS certificate
Expand All @@ -54,11 +55,18 @@ def __init__(
for scheme in ("https://", "http://"):
log.warning(f"Missing scheme in url, trying with {scheme}")
with contextlib.suppress(requests.RequestException):
r = requests.get(
scheme + url,
timeout=self.timeout,
verify=self.verify,
)
if session is not None:
r = session.get(
scheme + url,
timeout=self.timeout,
verify=self.verify,
)
else:
r = requests.get(
scheme + url,
timeout=self.timeout,
verify=self.verify,
)
williamjsmith15 marked this conversation as resolved.
Show resolved Hide resolved
r.raise_for_status()
found_scheme = scheme
break
Expand All @@ -83,6 +91,8 @@ def __init__(
self._max_get_attempts = 1
# Delay in seconds between subsequent retries.
self._get_retry_delay = 10.0
# Add session to the client if provided
self.session = session
williamjsmith15 marked this conversation as resolved.
Show resolved Hide resolved

@property
def max_get_attempts(self) -> int:
Expand Down Expand Up @@ -133,7 +143,10 @@ def make_get_request(self, url: str, **kwargs: Any) -> requests.Response:
headers = self.json_headers
kwargs.setdefault("timeout", self.timeout)
kwargs.setdefault("verify", self.verify)
r = requests.get(url, headers=headers, **kwargs)
if self.session is not None:
r = self.session.get(url, headers=headers, **kwargs)
else:
r = requests.get(url, headers=headers, **kwargs)
return r

def make_post_request(
Expand Down Expand Up @@ -173,16 +186,26 @@ def my_dumps(d: dict) -> dict:
data = json.dumps(payload) if payload is not None else None
headers = self.json_headers
post_params = params

r = requests.post(
url,
params=post_params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
if self.session is not None:
r = self.session.post(
url,
params=post_params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
else:
r = requests.post(
url,
params=post_params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
if r.status_code == 200:
try:
return r.json()
Expand Down Expand Up @@ -214,15 +237,26 @@ def make_delete_request(
"""
data = json.dumps(payload) if payload is not None else None
headers = self.json_headers
r = requests.delete(
url,
params=params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
if self.session is not None:
r = self.session.delete(
url,
params=params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
else:
r = requests.delete(
url,
params=params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
return r

def make_put_request(self, url: str, payload: Optional[dict] = None, params: Optional[dict] = None) -> Any:
Expand All @@ -236,15 +270,26 @@ def make_put_request(self, url: str, payload: Optional[dict] = None, params: Opt
"""
data = json.dumps(payload) if payload is not None else None
headers = self.json_headers
r = requests.put(
url,
params=params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
if self.session is not None:
r = self.session.put(
url,
params=params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
else:
r = requests.put(
url,
params=params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
if r.status_code == 200:
try:
return r.json()
Expand Down Expand Up @@ -272,15 +317,26 @@ def make_patch_request(self, url: str, payload: Optional[dict] = None, params: O
"""
data = json.dumps(payload) if payload is not None else None
headers = self.json_headers
r = requests.patch(
url,
params=params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
if self.session is not None:
r = self.session.patch(
url,
params=params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
else:
r = requests.patch(
url,
params=params,
data=data,
headers=headers,
timeout=self.timeout,
allow_redirects=False,
verify=self.verify,
)
if r.status_code == 200:
try:
return r.json()
Expand Down Expand Up @@ -355,12 +411,20 @@ def key(self) -> Optional[str]:
auth_url = f"{self.url}/authenticate/baseauth"
# Use lower level method instead of make_get_request() because we
# need the additional Authorization header.
r = requests.get(
auth_url,
headers=headers,
timeout=self.timeout,
verify=self.verify,
)
if self.session is not None:
r = self.session.get(
auth_url,
headers=headers,
timeout=self.timeout,
verify=self.verify,
)
else:
r = requests.get(
auth_url,
headers=headers,
timeout=self.timeout,
verify=self.verify,
)
if r.status_code != 200:
raise Exception("Failed to authenticate user.")
response = r.json()
Expand Down
94 changes: 94 additions & 0 deletions docs/examples/session_handling/cookie_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""
Cookie handler for Authelia authentication using the Galaxy API
"""

import getpass
import logging as log
import sys
from http.cookiejar import LWPCookieJar
from pathlib import Path
from pprint import pprint

import requests
from galaxy_api import *

AUTH_HOSTNAME = "auth.service.org"
API_HOSTNAME = "galaxy.service.org"
cookie_path = Path(".galaxy_auth.txt")
cookie_jar = LWPCookieJar(cookie_path)


class ExpiredCookies(Exception):
pass


class NoCookies(Exception):
pass


def main():
try:
cookie_jar.load() # raises OSError
if not cookie_jar: # if empty due to expirations
raise ExpiredCookies()
except OSError:
print("No cached session found, please authenticate")
prompt_authentication()
except ExpiredCookies:
print("Session has expired, please authenticate")
prompt_authentication()
run_examples()


def prompt_authentication():
# --------------------------------------------------------------------------
# Prompt for username and password

username = input("Please enter username: ")
password = getpass.getpass(f"Please enter password for {username}: ")

# --------------------------------------------------------------------------
# Prepare authentication packet and authenticate session using Authelia

login_body = {
"username": username,
"password": password,
"requestMethod": "GET",
"keepMeLoggedIn": True,
"targetURL": API_HOSTNAME,
}

with requests.sessions.Session() as session:
williamjsmith15 marked this conversation as resolved.
Show resolved Hide resolved
session.cookies = cookie_jar
session.verify = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the default:

Suggested change
session.verify = True


auth = session.post(f"https://{AUTH_HOSTNAME}/api/firstfactor", json=login_body)

response = session.get(f"https://{AUTH_HOSTNAME}/api/user/info")
if response.status_code != 200:
print("Authentication failed")
sys.exit()
else:
pprint(response.json())
session.cookies.save()


def run_examples():
GALAXY_KEY = "user_api_key"
WORKFLOW_NAME = "workflow_name"
Comment on lines +79 to +80
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move these 2 constants to the top with the other constants?

with requests.sessions.Session() as session:
williamjsmith15 marked this conversation as resolved.
Show resolved Hide resolved
session.cookies = cookie_jar
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
session.cookies = cookie_jar
session.cookies = cookie_jar
gi = GalaxyInstance(url=f"https://{API_HOSTNAME}", key=GALAXY_KEY, session=session)


print("Running demo to demonstrate how to use the Galaxy API with Authelia")

print("Getting workflows from Galaxy")
response = get_workflows(f"https://{API_HOSTNAME}", GALAXY_KEY, session=session)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
response = get_workflows(f"https://{API_HOSTNAME}", GALAXY_KEY, session=session)
response = get_workflows(gi)

print(response)

print("Getting inputs for a workflow")
response = get_inputs(f"https://{API_HOSTNAME}", GALAXY_KEY, WORKFLOW_NAME, session=session)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
response = get_inputs(f"https://{API_HOSTNAME}", GALAXY_KEY, WORKFLOW_NAME, session=session)
response = get_inputs(gi, WORKFLOW_NAME)

print(response)


if __name__ == "__main__":
main()
Loading