From 5fd5c41ebd1780dc895c24d9ec978f63dc848c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr> Date: Tue, 10 Sep 2024 22:00:50 +0200 Subject: [PATCH 01/10] add: push file --- dinamis_sdk/__init__.py | 4 +++- dinamis_sdk/s3.py | 46 +++++++++++++++++++++++++++++++++-------- dinamis_sdk/upload.py | 34 ++++++++++++++++++++++++++++++ tests/test_push.py | 13 ++++++++++++ 4 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 dinamis_sdk/upload.py create mode 100644 tests/test_push.py diff --git a/dinamis_sdk/__init__.py b/dinamis_sdk/__init__.py index dfa1b29..a20433c 100644 --- a/dinamis_sdk/__init__.py +++ b/dinamis_sdk/__init__.py @@ -7,6 +7,8 @@ from dinamis_sdk.s3 import ( sign_urls, sign_item, sign_asset, - sign_item_collection + sign_item_collection, + sign_url_put ) # noqa from dinamis_sdk import auth # noqa +from dinamis_sdk.upload import push diff --git a/dinamis_sdk/s3.py b/dinamis_sdk/s3.py index f544a13..b445a4b 100644 --- a/dinamis_sdk/s3.py +++ b/dinamis_sdk/s3.py @@ -28,11 +28,11 @@ import pydantic from .utils import ( log, - SIGNED_URL_TTL_MARGIN, - CREDENTIALS, + SIGNED_URL_TTL_MARGIN, + CREDENTIALS, MAX_URLS, - S3_SIGNING_ENDPOINT, - S3_STORAGE_DOMAIN, + S3_SIGNING_ENDPOINT, + S3_STORAGE_DOMAIN, SIGNED_URL_DURATION_SECONDS, BYPASS_API ) @@ -154,7 +154,10 @@ def sign_string(url: str, copy: bool = True) -> str: return sign_urls(urls=[url])[url] -def sign_urls(urls: List[str]) -> Dict[str, str]: +def _generic_sign_urls( + urls: List[str], + route: str +) -> Dict[str, str]: """Sign URLs with a S3 Token. Signing URL allows read access to files in storage. @@ -165,6 +168,7 @@ def sign_urls(urls: List[str]) -> Dict[str, str]: Single URLs can be found on a STAC Item's Asset ``href`` value. Only URLs to assets in S3 Storage are signed, other URLs are returned unmodified. + route: API route Returns: dict of signed HREF: key = original URL, value = signed URL @@ -193,18 +197,38 @@ def sign_urls(urls: List[str]) -> Dict[str, str]: not_signed_urls = [url for url in urls if url not in signed_urls] signed_urls.update({ url: signed_url.href - for url, signed_url in get_signed_urls(not_signed_urls).items() + for url, signed_url in _generic_get_signed_urls( + urls=not_signed_urls, + route=route + ).items() }) return signed_urls +def sign_urls(urls: List[str]) -> Dict[str, str]: + return _generic_sign_urls(urls=urls, route="sign_urls") + + +def sign_urls_put(urls: List[str]) -> Dict[str, str]: + return _generic_sign_urls(urls=urls, route="sign_urls_put") + + +def sign_url_put(url: str) -> str: + """ + Sign a single URL for put + """ + urls = sign_urls_put([url]) + return urls[url] + + def _repl_vrt(match: re.Match) -> str: # replace all blob-storages URLs with a signed version. url = match.string[slice(*match.span())] return sign_urls(url)[url] -def sign_vrt_string(vrt: str, copy: bool = True) -> str: # pylint: disable = W0613 # noqa: E501 +def sign_vrt_string(vrt: str, + copy: bool = True) -> str: # pylint: disable = W0613 # noqa: E501 """Sign a VRT-like string containing URLs from the storage. Signing URLs allows read access to files in storage. @@ -413,8 +437,9 @@ def sign_mapping(mapping: Mapping, copy: bool = True) -> Mapping: sign_reference_file = sign_mapping -def get_signed_urls( +def _generic_get_signed_urls( urls: List[str], + route: str, retry_total: int = 10, retry_backoff_factor: float = .8 ) -> Dict[str, SignedURL]: @@ -426,6 +451,7 @@ def get_signed_urls( Args: urls: urls + route: route (API) retry_total (int): The number of allowable retry attempts for REST API calls. Use retry_total=0 to disable retries. A backoff factor to apply between attempts. @@ -501,8 +527,10 @@ def get_signed_urls( params = {"urls": not_signed_urls_chunk} if SIGNED_URL_DURATION_SECONDS: params["duration_seconds"] = SIGNED_URL_DURATION_SECONDS + post_url = f"{S3_SIGNING_ENDPOINT}{route}" + log.debug("POST %s", post_url) response = session.post( - f"{S3_SIGNING_ENDPOINT}sign_urls", + post_url, params=params, headers=headers, timeout=10 diff --git a/dinamis_sdk/upload.py b/dinamis_sdk/upload.py new file mode 100644 index 0000000..df2492d --- /dev/null +++ b/dinamis_sdk/upload.py @@ -0,0 +1,34 @@ +import requests +import urllib3.util.retry +from .s3 import sign_url_put + +def push( + local_filename: str, + target_url: str, + retry_total: int = 5, + retry_backoff_factor: float = .8 +): + """ + Publish a local file to the cloud + + """ + remote_presigned_url = sign_url_put(target_url) + + session = requests.Session() + retry = urllib3.util.retry.Retry( + total=retry_total, + backoff_factor=retry_backoff_factor, + status_forcelist=[404, 429, 500, 502, 503, 504], + allowed_methods=False, + ) + adapter = requests.adapters.HTTPAdapter(max_retries=retry) + session.mount("http://", adapter) + session.mount("https://", adapter) + + with open(local_filename, 'rb') as f: + ret = session.put(remote_presigned_url, data=f) + + if ret.status_code == 200: + return remote_presigned_url + + ret.raise_for_status() diff --git a/tests/test_push.py b/tests/test_push.py new file mode 100644 index 0000000..d7623ea --- /dev/null +++ b/tests/test_push.py @@ -0,0 +1,13 @@ +import dinamis_sdk +import time + +local_filename = "/tmp/toto.txt" + +with open(local_filename, "w") as f: + f.write("hello world") + +pushed = dinamis_sdk.push( + local_filename=local_filename, + target_url="https://s3-data.meso.umontpellier.fr/sm1-gdc-tests/titi.txt" +) +print("Done") -- GitLab From 97ac406688311c4be7f4c9c030a8f049c26f3aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr> Date: Wed, 11 Sep 2024 14:00:58 +0200 Subject: [PATCH 02/10] refactor with settings --- dinamis_sdk/auth.py | 38 +++++++++++++------------- dinamis_sdk/s3.py | 35 +++++++++++------------- dinamis_sdk/settings.py | 14 ++++++++++ dinamis_sdk/upload.py | 16 ++++------- dinamis_sdk/utils.py | 59 ++++++++++++++++++++++------------------- 5 files changed, 84 insertions(+), 78 deletions(-) create mode 100644 dinamis_sdk/settings.py diff --git a/dinamis_sdk/auth.py b/dinamis_sdk/auth.py index 25be15e..d17a158 100644 --- a/dinamis_sdk/auth.py +++ b/dinamis_sdk/auth.py @@ -10,26 +10,28 @@ import requests from pydantic import BaseModel, Field # pylint: disable = no-name-in-module import qrcode import urllib3 -from .utils import log, JWT_FILE, TOKEN_ENDPOINT, AUTH_BASE_URL, TOKEN_SERVER +from .utils import ( + log, JWT_FILE, TOKEN_ENDPOINT, AUTH_BASE_URL, settings, create_session +) class JWT(BaseModel): # pylint: disable = R0903 """JWT model.""" - access_token: str = Field(alias="access_token") - expires_in: int = Field(alias="expires_in") - refresh_token: str = Field(alias="refresh_token") - refresh_expires_in: int = Field(alias="refresh_expires_in") - token_type: str = Field(alias="token_type") + access_token: str + expires_in: int + refresh_token: str + refresh_expires_in: int + token_type: str class DeviceGrantResponse(BaseModel): # pylint: disable = R0903 """Device grant login response model.""" - verification_uri_complete: str = Field(alias="verification_uri_complete") - device_code: str = Field(alias="device_code") - expires_in: int = Field(alias="expires_in") - interval: int = Field(alias="interval") + verification_uri_complete: str + device_code: str + expires_in: int + interval: int class GrantMethodBase: @@ -251,24 +253,20 @@ class OAuth2Session: class TokenServer: """Token server.""" - def __init__(self, endpoint: str, total_retry=5, backoff_factor=0.8): + def __init__(self, endpoint: str, retry_total=5, retry_backoff_factor=0.8): self.endpoint = endpoint - self.session = requests.Session() - retry = urllib3.util.retry.Retry( - total=total_retry, - backoff_factor=backoff_factor, - status_forcelist=[404, 429, 500, 502, 503, 504], + self.session = create_session( + retry_total=retry_total, + retry_backoff_factor=retry_backoff_factor ) - adapter = requests.adapters.HTTPAdapter(max_retries=retry) - self.session.mount("https://", adapter) - self.session.mount("http://", adapter) log.info("Using Token Server: %s", self.endpoint) def get_access_token(self) -> str: return self.session.get(self.endpoint, timeout=10).json() -session = TokenServer(TOKEN_SERVER) if TOKEN_SERVER else OAuth2Session() +session = TokenServer(settings.dinamis_sdk_token_server) \ + if settings.dinamis_sdk_token_server else OAuth2Session() def _get_access_token(): diff --git a/dinamis_sdk/s3.py b/dinamis_sdk/s3.py index b445a4b..324d652 100644 --- a/dinamis_sdk/s3.py +++ b/dinamis_sdk/s3.py @@ -28,13 +28,12 @@ import pydantic from .utils import ( log, - SIGNED_URL_TTL_MARGIN, + settings, CREDENTIALS, MAX_URLS, S3_SIGNING_ENDPOINT, S3_STORAGE_DOMAIN, - SIGNED_URL_DURATION_SECONDS, - BYPASS_API + create_session ) _PYDANTIC_2_0 = packaging.version.parse( @@ -494,27 +493,25 @@ def _generic_get_signed_urls( log.debug("URL %s already in cache", url) ttl = signed_url_in_cache.ttl() log.debug("URL %s TTL is %s", url, ttl) - if ttl > SIGNED_URL_TTL_MARGIN: - log.debug("Using cache (%s > %s)", ttl, SIGNED_URL_TTL_MARGIN) + if ttl > settings.dinamis_sdk_ttl_margin: + log.debug( + "Using cache (%s > %s)", + ttl, + settings.dinamis_sdk_ttl_margin + ) signed_urls[url] = signed_url_in_cache not_signed_urls = [url for url in urls if url not in signed_urls] log.debug("Already signed URLs:\n %s", signed_urls) log.debug("Not signed URLs:\n %s", not_signed_urls) if not_signed_urls: - # Refresh the token if there's less than SIGNED_URL_TTL_MARGIN seconds - # remaining, in order to give a small amount of time to do stuff with - # the url - session = requests.Session() - retry = urllib3.util.retry.Retry( - total=retry_total, - backoff_factor=retry_backoff_factor, - status_forcelist=[404, 429, 500, 502, 503, 504], - allowed_methods=False, + # Refresh the token if there's less than + # `settings.dinamis_sdk_ttl_margin seconds` remaining, in order to + # give a small amount of time to do stuff with the url + session = create_session( + retry_backoff_factor=retry_backoff_factor, + retry_total=retry_total ) - adapter = requests.adapters.HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) n_urls = len(not_signed_urls) log.debug("Number of URLs to sign: %s", n_urls) n_chunks = math.ceil(n_urls / MAX_URLS) @@ -525,8 +522,8 @@ def _generic_get_signed_urls( chunk_end = min(chunk_start + MAX_URLS, n_urls) not_signed_urls_chunk = not_signed_urls[chunk_start:chunk_end] params = {"urls": not_signed_urls_chunk} - if SIGNED_URL_DURATION_SECONDS: - params["duration_seconds"] = SIGNED_URL_DURATION_SECONDS + if settings.dinamis_sdk_url_duration: + params["duration_seconds"] = settings.dinamis_sdk_url_duration post_url = f"{S3_SIGNING_ENDPOINT}{route}" log.debug("POST %s", post_url) response = session.post( diff --git a/dinamis_sdk/settings.py b/dinamis_sdk/settings.py new file mode 100644 index 0000000..eb4c137 --- /dev/null +++ b/dinamis_sdk/settings.py @@ -0,0 +1,14 @@ +""" +Environment settings +""" +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + """ + Holds settings + """ + dinamis_sdk_ttl_margin: int = 1800 + dinamis_sdk_url_duration: int = None + dinamis_sdk_bypass_api: str = None + dinamis_sdk_token_server: str = None diff --git a/dinamis_sdk/upload.py b/dinamis_sdk/upload.py index df2492d..6be1d43 100644 --- a/dinamis_sdk/upload.py +++ b/dinamis_sdk/upload.py @@ -1,6 +1,6 @@ -import requests -import urllib3.util.retry from .s3 import sign_url_put +from .utils import create_session + def push( local_filename: str, @@ -14,16 +14,10 @@ def push( """ remote_presigned_url = sign_url_put(target_url) - session = requests.Session() - retry = urllib3.util.retry.Retry( - total=retry_total, - backoff_factor=retry_backoff_factor, - status_forcelist=[404, 429, 500, 502, 503, 504], - allowed_methods=False, + session = create_session( + retry_total=retry_total, + retry_backoff_factor=retry_backoff_factor ) - adapter = requests.adapters.HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.mount("https://", adapter) with open(local_filename, 'rb') as f: ret = session.put(remote_presigned_url, data=f) diff --git a/dinamis_sdk/utils.py b/dinamis_sdk/utils.py index ac636ec..a121525 100644 --- a/dinamis_sdk/utils.py +++ b/dinamis_sdk/utils.py @@ -5,33 +5,16 @@ import os import appdirs from pydantic import BaseModel # pylint: disable = no-name-in-module import requests +from .settings import Settings +import urllib3.util.retry -# Env vars -ENV_TTL_MARGIN = "DINAMIS_SDK_TTL_MARGIN" -ENV_DURATION_SECS = "DINAMIS_SDK_DURATION_SECONDS" -ENV_BYPASS_API = "DINAMIS_SDK_BYPASS_API" + +settings = Settings() logging.basicConfig(level=os.environ.get("LOGLEVEL") or "INFO") log = logging.getLogger("dinamis_sdk") -def _get_seconds(env_var_name: str, default: int = None) -> int: - val = os.environ.get(env_var_name) - if val: - if val.isdigit(): - log.info( - "Using %s = %s seconds", - env_var_name, - val - ) - return int(val) - return default - - -# Signed TTL margin default to 1800 seconds (30 minutes), or env. var. -SIGNED_URL_TTL_MARGIN = _get_seconds(ENV_TTL_MARGIN, 1800) -SIGNED_URL_DURATION_SECONDS = _get_seconds(ENV_DURATION_SECS) - MAX_URLS = 64 S3_STORAGE_DOMAIN = "meso.umontpellier.fr" S3_SIGNING_ENDPOINT = \ @@ -85,16 +68,36 @@ def retrieve_token_endpoint(s3_signing_endpoint: str = S3_SIGNING_ENDPOINT): return oauth2_defs["flows"]["password"]["tokenUrl"] -BYPASS_API = os.environ.get(ENV_BYPASS_API) -if BYPASS_API: - S3_SIGNING_ENDPOINT = BYPASS_API +if settings.dinamis_sdk_bypass_api: + S3_SIGNING_ENDPOINT = settings.dinamis_sdk_bypass_api # Token endpoint is typically something like: https://keycloak-dinamis.apps.okd # .crocc.meso.umontpellier.fr/auth/realms/dinamis/protocol/openid-connect/token -TOKEN_ENDPOINT = None if BYPASS_API else retrieve_token_endpoint() +TOKEN_ENDPOINT = None if settings.dinamis_sdk_bypass_api \ + else retrieve_token_endpoint() # Auth base URL is typically something like: https://keycloak-dinamis.apps.okd. # crocc.meso.umontpellier.fr/auth/realms/dinamis/protocol/openid-connect -AUTH_BASE_URL = None if BYPASS_API else TOKEN_ENDPOINT.rsplit('/', 1)[0] +AUTH_BASE_URL = None if settings.dinamis_sdk_bypass_api \ + else TOKEN_ENDPOINT.rsplit('/', 1)[0] + + +def create_session( + retry_total: int = 5, + retry_backoff_factor: float = .8 +): + """ + Create a session for requests + + """ + session = requests.Session() + retry = urllib3.util.retry.Retry( + total=retry_total, + backoff_factor=retry_backoff_factor, + status_forcelist=[404, 429, 500, 502, 503, 504], + allowed_methods=False, + ) + adapter = requests.adapters.HTTPAdapter(max_retries=retry) + session.mount("http://", adapter) + session.mount("https://", adapter) -# Token server (optional) -TOKEN_SERVER = os.environ.get("DINAMIS_SDK_TOKEN_SERVER") + return session -- GitLab From 8e1e45e4c38039bc29eddeb1b849c2ee35b699e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr> Date: Wed, 11 Sep 2024 14:00:58 +0200 Subject: [PATCH 03/10] bump version --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a549ba0..4d4db85 100644 --- a/setup.py +++ b/setup.py @@ -7,12 +7,13 @@ install_requires = [ "pystac", "pystac_client", "pydantic>=1.7.3", + "pydantic_settings", "packaging" ] setup( name="dinamis-sdk", - version="0.1.13", + version="0.2.0", description="DINAMIS SDK", python_requires=">=3.8", author="Remi Cresson", -- GitLab From 691bfc0f0c99454181ac399f114538cfc7d487ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr> Date: Wed, 11 Sep 2024 14:08:13 +0200 Subject: [PATCH 04/10] refactor with settings --- dinamis_sdk/settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dinamis_sdk/settings.py b/dinamis_sdk/settings.py index eb4c137..a0a8f78 100644 --- a/dinamis_sdk/settings.py +++ b/dinamis_sdk/settings.py @@ -9,6 +9,6 @@ class Settings(BaseSettings): Holds settings """ dinamis_sdk_ttl_margin: int = 1800 - dinamis_sdk_url_duration: int = None - dinamis_sdk_bypass_api: str = None - dinamis_sdk_token_server: str = None + dinamis_sdk_url_duration: int = 0 + dinamis_sdk_bypass_api: str = "" + dinamis_sdk_token_server: str = "" -- GitLab From 9b82538766544b143cfb0932c3bfc7bd7e28ecb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr> Date: Wed, 11 Sep 2024 14:37:12 +0200 Subject: [PATCH 05/10] refactor with settings --- dinamis_sdk/__init__.py | 1 + dinamis_sdk/auth.py | 8 +++++--- dinamis_sdk/s3.py | 21 +++++++++------------ dinamis_sdk/upload.py | 4 ++++ dinamis_sdk/utils.py | 2 +- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/dinamis_sdk/__init__.py b/dinamis_sdk/__init__.py index a20433c..d062092 100644 --- a/dinamis_sdk/__init__.py +++ b/dinamis_sdk/__init__.py @@ -1,4 +1,5 @@ """Dinamis SDK module.""" +# flake8: noqa import pkg_resources __version__ = pkg_resources.require("dinamis-sdk")[0].version from dinamis_sdk.s3 import ( diff --git a/dinamis_sdk/auth.py b/dinamis_sdk/auth.py index d17a158..9e1a03b 100644 --- a/dinamis_sdk/auth.py +++ b/dinamis_sdk/auth.py @@ -7,9 +7,8 @@ import time from abc import abstractmethod from typing import Dict import requests -from pydantic import BaseModel, Field # pylint: disable = no-name-in-module +from pydantic import BaseModel # pylint: disable = no-name-in-module import qrcode -import urllib3 from .utils import ( log, JWT_FILE, TOKEN_ENDPOINT, AUTH_BASE_URL, settings, create_session ) @@ -235,7 +234,7 @@ class OAuth2Session: log.debug( "Credentials from %s still valid", JWT_FILE ) - except Exception as error: + except FileNotFoundError as error: log.warning( "Warning: can't use token from file %s (%s)", JWT_FILE, @@ -262,6 +261,9 @@ class TokenServer: log.info("Using Token Server: %s", self.endpoint) def get_access_token(self) -> str: + """ + Return the access token + """ return self.session.get(self.endpoint, timeout=10).json() diff --git a/dinamis_sdk/s3.py b/dinamis_sdk/s3.py index 324d652..cf5a63a 100644 --- a/dinamis_sdk/s3.py +++ b/dinamis_sdk/s3.py @@ -14,10 +14,7 @@ from functools import singledispatch from typing import Any, Dict, Mapping, TypeVar, cast, List from urllib.parse import urlparse, parse_qs import math -import urllib3.util.retry -import requests -import requests.adapters -from pydantic import BaseModel, Field # pylint: disable = no-name-in-module +from pydantic import BaseModel # pylint: disable = no-name-in-module from pystac import Asset, Item, ItemCollection, STACObjectType, Collection from pystac.serialization.identify import identify_stac_object_type from pystac.utils import datetime_to_str @@ -54,7 +51,7 @@ asset_xpr = re.compile( class URLBase(BaseModel): # pylint: disable = R0903 """Base model for responses.""" - expiry: datetime = Field(alias="expiry") + expiry: datetime class Config: # pylint: disable = R0903 """Config for URLBase model.""" @@ -69,7 +66,7 @@ class URLBase(BaseModel): # pylint: disable = R0903 class SignedURL(URLBase): # pylint: disable = R0903 """Signed URL response.""" - href: str = Field(alias="href") + href: str def ttl(self) -> float: """Return the number of seconds the token is still valid for.""" @@ -79,7 +76,7 @@ class SignedURL(URLBase): # pylint: disable = R0903 class SignedURLBatch(URLBase): # pylint: disable = R0903 """Signed URLs (batch of URLs) response.""" - hrefs: dict = Field(alias="hrefs") + hrefs: dict # Cache of signing requests so we can reuse them @@ -205,17 +202,17 @@ def _generic_sign_urls( def sign_urls(urls: List[str]) -> Dict[str, str]: + """Sign multiple URLs for get""" return _generic_sign_urls(urls=urls, route="sign_urls") def sign_urls_put(urls: List[str]) -> Dict[str, str]: + """Sign multiple URLs for put""" return _generic_sign_urls(urls=urls, route="sign_urls_put") def sign_url_put(url: str) -> str: - """ - Sign a single URL for put - """ + """Sign a single URL for put""" urls = sign_urls_put([url]) return urls[url] @@ -477,8 +474,8 @@ def _generic_get_signed_urls( "dinamis-secret-key": CREDENTIALS.secret_key }) log.debug("Using credentials (access/secret keys)") - elif BYPASS_API: - log.debug("Using bypass API %s", BYPASS_API) + elif settings.dinamis_sdk_bypass_api: + log.debug("Using bypass API %s", settings.dinamis_sdk_bypass_api) else: from .auth import get_access_token access_token = get_access_token() diff --git a/dinamis_sdk/upload.py b/dinamis_sdk/upload.py index 6be1d43..a027b5d 100644 --- a/dinamis_sdk/upload.py +++ b/dinamis_sdk/upload.py @@ -1,3 +1,6 @@ +""" +This module is used to upload files using HTTP requests. +""" from .s3 import sign_url_put from .utils import create_session @@ -26,3 +29,4 @@ def push( return remote_presigned_url ret.raise_for_status() + return "" diff --git a/dinamis_sdk/utils.py b/dinamis_sdk/utils.py index a121525..189f0e5 100644 --- a/dinamis_sdk/utils.py +++ b/dinamis_sdk/utils.py @@ -5,8 +5,8 @@ import os import appdirs from pydantic import BaseModel # pylint: disable = no-name-in-module import requests -from .settings import Settings import urllib3.util.retry +from .settings import Settings settings = Settings() -- GitLab From c46a054ec19de13b67ab2c3d0e1f1e30a7551d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr> Date: Wed, 11 Sep 2024 14:40:26 +0200 Subject: [PATCH 06/10] refactor with settings --- dinamis_sdk/auth.py | 10 ++++------ dinamis_sdk/utils.py | 4 +--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/dinamis_sdk/auth.py b/dinamis_sdk/auth.py index 9e1a03b..6c0c104 100644 --- a/dinamis_sdk/auth.py +++ b/dinamis_sdk/auth.py @@ -58,6 +58,7 @@ class GrantMethodBase: @property def data_base(self) -> Dict[str, str]: """Base payload.""" + return { "client_id": self.client_id, "scope": "openid offline_access" @@ -189,6 +190,7 @@ class OAuth2Session: def refresh_if_needed(self): """Refresh the token if ttl is too short.""" + ttl_margin_seconds = 30 now = datetime.datetime.now() jwt_expires_in = datetime.timedelta(seconds=self.jwt.expires_in) @@ -214,13 +216,8 @@ class OAuth2Session: self.save_token(now) def get_access_token(self) -> str: - """ - Get the access token. - - Returns: - access token + """Return the access token.""" - """ if not self.jwt: # First JWT initialisation if JWT_FILE and os.path.isfile(JWT_FILE): @@ -252,6 +249,7 @@ class OAuth2Session: class TokenServer: """Token server.""" + def __init__(self, endpoint: str, retry_total=5, retry_backoff_factor=0.8): self.endpoint = endpoint self.session = create_session( diff --git a/dinamis_sdk/utils.py b/dinamis_sdk/utils.py index 189f0e5..04e0d43 100644 --- a/dinamis_sdk/utils.py +++ b/dinamis_sdk/utils.py @@ -85,10 +85,8 @@ def create_session( retry_total: int = 5, retry_backoff_factor: float = .8 ): - """ - Create a session for requests + """Create a session for requests.""" - """ session = requests.Session() retry = urllib3.util.retry.Retry( total=retry_total, -- GitLab From 2cf5a96f841880238f34f1f2551262ff61183f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr> Date: Wed, 11 Sep 2024 14:46:52 +0200 Subject: [PATCH 07/10] refactor with settings --- dinamis_sdk/auth.py | 7 +------ dinamis_sdk/s3.py | 6 +++--- dinamis_sdk/settings.py | 9 +++------ 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/dinamis_sdk/auth.py b/dinamis_sdk/auth.py index 6c0c104..bdaf718 100644 --- a/dinamis_sdk/auth.py +++ b/dinamis_sdk/auth.py @@ -58,7 +58,6 @@ class GrantMethodBase: @property def data_base(self) -> Dict[str, str]: """Base payload.""" - return { "client_id": self.client_id, "scope": "openid offline_access" @@ -190,7 +189,6 @@ class OAuth2Session: def refresh_if_needed(self): """Refresh the token if ttl is too short.""" - ttl_margin_seconds = 30 now = datetime.datetime.now() jwt_expires_in = datetime.timedelta(seconds=self.jwt.expires_in) @@ -217,7 +215,6 @@ class OAuth2Session: def get_access_token(self) -> str: """Return the access token.""" - if not self.jwt: # First JWT initialisation if JWT_FILE and os.path.isfile(JWT_FILE): @@ -259,9 +256,7 @@ class TokenServer: log.info("Using Token Server: %s", self.endpoint) def get_access_token(self) -> str: - """ - Return the access token - """ + """Return the access token.""" return self.session.get(self.endpoint, timeout=10).json() diff --git a/dinamis_sdk/s3.py b/dinamis_sdk/s3.py index cf5a63a..13098ea 100644 --- a/dinamis_sdk/s3.py +++ b/dinamis_sdk/s3.py @@ -202,17 +202,17 @@ def _generic_sign_urls( def sign_urls(urls: List[str]) -> Dict[str, str]: - """Sign multiple URLs for get""" + """Sign multiple URLs for get.""" return _generic_sign_urls(urls=urls, route="sign_urls") def sign_urls_put(urls: List[str]) -> Dict[str, str]: - """Sign multiple URLs for put""" + """Sign multiple URLs for put.""" return _generic_sign_urls(urls=urls, route="sign_urls_put") def sign_url_put(url: str) -> str: - """Sign a single URL for put""" + """Sign a single URL for put.""" urls = sign_urls_put([url]) return urls[url] diff --git a/dinamis_sdk/settings.py b/dinamis_sdk/settings.py index a0a8f78..3aba92c 100644 --- a/dinamis_sdk/settings.py +++ b/dinamis_sdk/settings.py @@ -1,13 +1,10 @@ -""" -Environment settings -""" +"""Settings from environment variables.""" from pydantic_settings import BaseSettings class Settings(BaseSettings): - """ - Holds settings - """ + """Holds settings.""" + dinamis_sdk_ttl_margin: int = 1800 dinamis_sdk_url_duration: int = 0 dinamis_sdk_bypass_api: str = "" -- GitLab From 646b340b0e92ec685ef1a41b488f07732cce6c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr> Date: Wed, 11 Sep 2024 14:49:34 +0200 Subject: [PATCH 08/10] refactor with settings --- dinamis_sdk/auth.py | 1 + dinamis_sdk/upload.py | 9 ++------- dinamis_sdk/utils.py | 1 - 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/dinamis_sdk/auth.py b/dinamis_sdk/auth.py index bdaf718..b809ac2 100644 --- a/dinamis_sdk/auth.py +++ b/dinamis_sdk/auth.py @@ -248,6 +248,7 @@ class TokenServer: """Token server.""" def __init__(self, endpoint: str, retry_total=5, retry_backoff_factor=0.8): + """Initialize the token server.""" self.endpoint = endpoint self.session = create_session( retry_total=retry_total, diff --git a/dinamis_sdk/upload.py b/dinamis_sdk/upload.py index a027b5d..ec5bc4f 100644 --- a/dinamis_sdk/upload.py +++ b/dinamis_sdk/upload.py @@ -1,6 +1,4 @@ -""" -This module is used to upload files using HTTP requests. -""" +"""This module is used to upload files using HTTP requests.""" from .s3 import sign_url_put from .utils import create_session @@ -11,10 +9,7 @@ def push( retry_total: int = 5, retry_backoff_factor: float = .8 ): - """ - Publish a local file to the cloud - - """ + """Publish a local file to the cloud.""" remote_presigned_url = sign_url_put(target_url) session = create_session( diff --git a/dinamis_sdk/utils.py b/dinamis_sdk/utils.py index 04e0d43..dcfa7fa 100644 --- a/dinamis_sdk/utils.py +++ b/dinamis_sdk/utils.py @@ -86,7 +86,6 @@ def create_session( retry_backoff_factor: float = .8 ): """Create a session for requests.""" - session = requests.Session() retry = urllib3.util.retry.Retry( total=retry_total, -- GitLab From da0964669ed65c788811bf53f320fec3ac0a3e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr> Date: Wed, 11 Sep 2024 15:03:24 +0200 Subject: [PATCH 09/10] refactor with settings --- dinamis_sdk/utils.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dinamis_sdk/utils.py b/dinamis_sdk/utils.py index dcfa7fa..65e9a29 100644 --- a/dinamis_sdk/utils.py +++ b/dinamis_sdk/utils.py @@ -8,18 +8,21 @@ import requests import urllib3.util.retry from .settings import Settings - +# Settings settings = Settings() -logging.basicConfig(level=os.environ.get("LOGLEVEL") or "INFO") +# Logger +logging.basicConfig() log = logging.getLogger("dinamis_sdk") +log.setLevel(level=os.environ.get('LOGLEVEL', 'INFO').upper()) - +# Constants MAX_URLS = 64 S3_STORAGE_DOMAIN = "meso.umontpellier.fr" S3_SIGNING_ENDPOINT = \ "https://s3-signing-cdos.apps.okd.crocc.meso.umontpellier.fr/" +# Config path CFG_PTH = appdirs.user_config_dir(appname='dinamis_sdk_auth') if not os.path.exists(CFG_PTH): try: @@ -31,9 +34,11 @@ if not os.path.exists(CFG_PTH): else: log.debug("Config path already exist in %s", CFG_PTH) +# JWT File JWT_FILE = os.path.join(CFG_PTH, ".token") if CFG_PTH else None log.debug("JWT file is %s", JWT_FILE) +# Settings file settings_file = os.path.join(CFG_PTH, ".settings") if CFG_PTH else None log.debug("Settings file is %s", settings_file) -- GitLab From bcf4d6c252a037ee520216ac92c011eaf5bb27cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr> Date: Wed, 11 Sep 2024 15:16:41 +0200 Subject: [PATCH 10/10] add test for push --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 86c9856..718cc82 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -87,6 +87,7 @@ Tests: script: - python tests/test_spot-6-7-drs.py - python tests/test_super-s2.py + - python tests/test_push.py # --------------------------------- Ship -------------------------------------- -- GitLab