Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6cf977e
adding modality for queries towards pasteur database
karlnyr Apr 11, 2025
f97bf83
Set PubMLST client in query_pubmlst method
karlnyr Apr 11, 2025
e53362a
adding debuggers
karlnyr Apr 14, 2025
ed8efc0
adding loggers
karlnyr Apr 14, 2025
8327f95
Add debug logging for complete service configuration in get_service_c…
karlnyr Apr 14, 2025
12028ac
Add logging for configuration loading in microSALT initialization
karlnyr Apr 14, 2025
4b7994d
adding pasteur to app.config, removing loggers
karlnyr Apr 14, 2025
aa707ab
Refactor URL parsing in get_mlst_scheme to use unified parse_url method
karlnyr Apr 14, 2025
8e687bc
Refactor URL parsing in PubMLSTClient and Referencer to improve clari…
karlnyr Apr 14, 2025
433add1
Refactor credential handling in main function to use service configur…
karlnyr Apr 29, 2025
0dac333
Refactor credential references in configuration and authentication fi…
karlnyr Apr 29, 2025
50bf18c
Refactor credential handling to use CREDENTIALS_KEY constant for impr…
karlnyr Apr 29, 2025
f47f2cf
Enhance main function to support species argument for Pasteur service…
karlnyr Apr 29, 2025
d01f0e0
Update database naming convention for Pasteur service in main function
karlnyr Apr 29, 2025
b73d70c
correct db name
karlnyr Apr 29, 2025
4a12ac7
Remove hardcoded database name and update Pasteur service web URL for…
karlnyr Apr 29, 2025
634a344
update base web url for pasteur
karlnyr Apr 29, 2025
9338d50
Enhance authentication and client setup to support dynamic database c…
karlnyr Apr 29, 2025
8a56468
Update client setup to dynamically determine database name for Pasteu…
karlnyr Apr 29, 2025
d8429d1
fix database string
karlnyr Apr 29, 2025
469881d
fix syntax
karlnyr Apr 29, 2025
0750c1f
Refactor URL mapping and helper functions for improved service config…
karlnyr Apr 29, 2025
df39d68
Move add_prefix_to_rules function to constants.py for better organiza…
karlnyr Apr 29, 2025
0e4f667
fix name of variable
karlnyr Apr 29, 2025
e159b9b
set logging to info
karlnyr Apr 30, 2025
a71ef81
adding to expected
karlnyr Apr 30, 2025
5a798be
testing python container
karlnyr Apr 30, 2025
f54bcf4
remove python setup
karlnyr Apr 30, 2025
419fc79
add pasteur client configuration to configExample.json
karlnyr Apr 30, 2025
6bdeeb7
Apply suggestions from code review
karlnyr May 6, 2025
bfcafd6
syntax
karlnyr May 6, 2025
e6f4691
casing
karlnyr May 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@ env:

jobs:
build-linux:
runs-on: ubuntu-20.04
strategy:
max-parallel: 5

runs-on: ubuntu-latest
container:
image: python:3.6-slim
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.6
uses: actions/setup-python@v3
with:
python-version: '3.6.15'

- name: Create conda/mamba environment using micromamba
uses: mamba-org/setup-micromamba@v1
with:
Expand Down
10 changes: 7 additions & 3 deletions configExample.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
"resistances": "/tmp/MLST/references/resistances",
"_comment": "Download path for NCBI genomes, for alignment usage",
"genomes": "/tmp/MLST/references/genomes",
"_comment": "PubMLST credentials",
"pubmlst_credentials": "/tmp/MLST/credentials"
"_comment": "Credentials",
"credentials": "/tmp/MLST/credentials"
},
"_comment": "Database/Flask configuration",
"database": {
Expand Down Expand Up @@ -75,9 +75,13 @@
"username": "limsuser",
"password": "mypassword"
},
"_comment": "PubMLST credentials",
"_comment": "Credentials",
"pubmlst": {
"client_id": "",
"client_secret": ""
},
"pasteur": {
"client_id": "",
"client_secret": ""
}
}
6 changes: 1 addition & 5 deletions microSALT/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,9 @@

# Ensure PubMLST configuration is included

app.config["pubmlst"] = preset_config.get("pubmlst", {
"client_id": "",
"client_secret": ""
})

app.config["pubmlst"] = preset_config.get("pubmlst", {"client_id": "", "client_secret": ""})

app.config["pasteur"] = preset_config.get("pasteur", {"client_id": "", "client_secret": ""})

# Add extrapaths to config
preset_config["folders"]["expec"] = os.path.abspath(
Expand Down
194 changes: 110 additions & 84 deletions microSALT/utils/pubmlst/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,116 +5,142 @@
from dateutil import parser
from rauth import OAuth1Session

from microSALT import logger
from microSALT import app, logger
from microSALT.utils.pubmlst.exceptions import (
PUBMLSTError,
SessionTokenRequestError,
SessionTokenResponseError,
)
from microSALT.utils.pubmlst.helpers import (
BASE_API,
credentials_path_key,
folders_config,
get_path,
get_service_config,
load_auth_credentials,
pubmlst_session_credentials_file_name,
save_session_token,
)
from microSALT.utils.pubmlst.constants import CREDENTIALS_KEY

session_token_validity = 12 # 12-hour validity
session_expiration_buffer = 60 # 60-second buffer


def get_new_session_token(db: str):
"""Request a new session token using all credentials for a specific database."""
logger.debug(f"Fetching a new session token for database '{db}'...")
class ClientAuthentication:

try:
consumer_key, consumer_secret, access_token, access_secret = load_auth_credentials()
def __init__(self, service: str):
"""Initialize the client with the specified service."""
self.service: str = service
self.service_config: dict = get_service_config(service)
self.base_api: str = self.service_config["base_api"]

url = f"{BASE_API}/db/{db}/oauth/get_session_token"
def get_new_session_token(self, db: str):
"""Request a new session token using all credentials for a specific database."""

session = OAuth1Session(
consumer_key=consumer_key,
consumer_secret=consumer_secret,
access_token=access_token,
access_token_secret=access_secret,
)
try:
consumer_key, consumer_secret, access_token, access_secret = load_auth_credentials(
self.service
)
logger.debug(f"Consumer Key: {consumer_key}")
logger.debug(f"Consumer Secret: {consumer_secret}")
logger.debug(f"Access Token: {access_token}")
logger.debug(f"Access Secret: {access_secret}")

url = f"{self.base_api}/db/{db}/oauth/get_session_token"

response = session.get(url, headers={"User-Agent": "BIGSdb API downloader"})
logger.debug(f"Response Status Code: {response.status_code}")
logger.debug(f"Requesting session token from URL: {url}")

if response.ok:
try:
token_data = response.json()
session_token = token_data.get("oauth_token")
session_secret = token_data.get("oauth_token_secret")
session = OAuth1Session(
consumer_key=consumer_key,
consumer_secret=consumer_secret,
access_token=access_token,
access_token_secret=access_secret,
)

if not session_token or not session_secret:
raise SessionTokenResponseError(
db, "Missing 'oauth_token' or 'oauth_token_secret' in response."
response = session.get(url, headers={"User-Agent": "BIGSdb API downloader"})
logger.debug(f"Response Content: {response.content}")
logger.debug(f"Response Status Code: {response.status_code}")

if response.ok:
try:
token_data = response.json()
session_token = token_data.get("oauth_token")
session_secret = token_data.get("oauth_token_secret")

if not session_token or not session_secret:
raise SessionTokenResponseError(
db, "Missing 'oauth_token' or 'oauth_token_secret' in response."
)

expiration_time = datetime.now() + timedelta(hours=session_token_validity)

save_session_token(
service=self.service,
db=db,
token=session_token,
secret=session_secret,
expiration_date=expiration_time,
)
return session_token, session_secret

except (ValueError, KeyError) as e:
raise SessionTokenResponseError(db, f"Invalid response format: {str(e)}")
else:
raise SessionTokenRequestError(db, response.status_code, response.text)

except PUBMLSTError as e:
logger.error(f"Error during token fetching: {e}")
raise
except Exception as e:
logger.error(f"Unexpected error: {e}")
raise PUBMLSTError(
f"Unexpected error while fetching session token for database '{db}': {e}"
)

expiration_time = datetime.now() + timedelta(hours=session_token_validity)
def load_session_credentials(
self,
db: str,
):
"""Load session token from file for a specific database."""
try:
credentials_file = os.path.join(
get_path(folders_config, CREDENTIALS_KEY),
self.service_config["session_credentials_file_name"],
)

if not os.path.exists(credentials_file):
logger.debug("Session file does not exist. Fetching a new session token.")
return self.get_new_session_token(db)

with open(credentials_file, "r") as f:
try:
all_sessions = json.load(f)
except json.JSONDecodeError as e:
raise SessionTokenResponseError(db, f"Failed to parse session file: {str(e)}")

db_session_data = all_sessions.get("databases", {}).get(db)
if not db_session_data:
logger.debug(
f"No session token found for database '{db}'. Fetching a new session token."
)
return self.get_new_session_token(db)

expiration = parser.parse(db_session_data.get("expiration", ""))
if datetime.now() < expiration - timedelta(seconds=session_expiration_buffer):
logger.debug(f"Using existing session token for database '{db}'.")
session_token = db_session_data.get("token")
session_secret = db_session_data.get("secret")

save_session_token(db, session_token, session_secret, expiration_time)
return session_token, session_secret

except (ValueError, KeyError) as e:
raise SessionTokenResponseError(db, f"Invalid response format: {str(e)}")
else:
raise SessionTokenRequestError(db, response.status_code, response.text)

except PUBMLSTError as e:
logger.error(f"Error during token fetching: {e}")
raise
except Exception as e:
logger.error(f"Unexpected error: {e}")
raise PUBMLSTError(
f"Unexpected error while fetching session token for database '{db}': {e}"
)


def load_session_credentials(db: str):
"""Load session token from file for a specific database."""
try:
credentials_file = os.path.join(
get_path(folders_config, credentials_path_key), pubmlst_session_credentials_file_name
)

if not os.path.exists(credentials_file):
logger.debug("Session file does not exist. Fetching a new session token.")
return get_new_session_token(db)

with open(credentials_file, "r") as f:
try:
all_sessions = json.load(f)
except json.JSONDecodeError as e:
raise SessionTokenResponseError(db, f"Failed to parse session file: {str(e)}")

db_session_data = all_sessions.get("databases", {}).get(db)
if not db_session_data:
logger.debug(
f"No session token found for database '{db}'. Fetching a new session token."
f"Session token for database '{db}' has expired. Fetching a new session token."
)
return self.get_new_session_token(db)

except PUBMLSTError as e:
logger.error(f"PUBMLST-specific error occurred: {e}")
raise
except Exception as e:
logger.error(f"Unexpected error: {e}")
raise PUBMLSTError(
f"Unexpected error while loading session token for database '{db}': {e}"
)
return get_new_session_token(db)

expiration = parser.parse(db_session_data.get("expiration", ""))
if datetime.now() < expiration - timedelta(seconds=session_expiration_buffer):
logger.debug(f"Using existing session token for database '{db}'.")
session_token = db_session_data.get("token")
session_secret = db_session_data.get("secret")

return session_token, session_secret

logger.debug(
f"Session token for database '{db}' has expired. Fetching a new session token."
)
return get_new_session_token(db)

except PUBMLSTError as e:
logger.error(f"PUBMLST-specific error occurred: {e}")
raise
except Exception as e:
logger.error(f"Unexpected error: {e}")
raise PUBMLSTError(f"Unexpected error while loading session token for database '{db}': {e}")
Loading
Loading