From 461f69f79727e535036057fcc69940899e731ff8 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 15 Aug 2025 13:44:53 +0200 Subject: [PATCH 01/30] prepare another setting variable to store plugin coupling thightness --- qiita_client/plugin.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/qiita_client/plugin.py b/qiita_client/plugin.py index 7ee87b4..36c37a2 100644 --- a/qiita_client/plugin.py +++ b/qiita_client/plugin.py @@ -136,13 +136,32 @@ def __init__(self, name, description, can_be_submitted_to_ebi, class BaseQiitaPlugin(object): - def __init__(self, name, version, description, publications=None): + def __init__(self, name, version, description, publications=None, plugincoupling='filesystem'): logger.debug('Entered BaseQiitaPlugin.__init__()') self.name = name self.version = version self.description = description self.publications = dumps(publications) if publications else "" + # Depending on your compute architecture, there are multiple options available how + # "thight" plugins are coupled to the central Qiita master/workers + # --- filesystem --- + # The default scenario is "filesystem", i.e. plugins as well as master/worker have + # unrestricted direct access to a shared filesystem, e.g. a larger volume / directory, + # defined in the server configuration as base_data_dir + # --- https --- + # A second scenario is that your plugins execute as independent jobs on another machine, + # e.g. as docker containers or other cloud techniques. Intentionally, you don't want to + # use a shared filesystem, but you have to make sure necessary input files are + # provided to the containerized plugin before execution and resulting files are + # transfered back to the central Qiita master/worker. In this case, files are + # pulled / pushed through functions qiita_client.fetch_file_from_central and + # qiita_client.push_file_to_central, respectivey. + # Actually, all files need to be decorated with this function. The decision how + # data are transferred is then made within these two functions according to the + # "plugincoupling" setting. + self.plugincoupling = plugincoupling + # Will hold the different commands self.task_dict = {} @@ -178,7 +197,8 @@ def generate_config(self, env_script, start_script, server_cert=None): f.write(CONF_TEMPLATE % (self.name, self.version, self.description, env_script, start_script, self._plugin_type, self.publications, - server_cert, client_id, client_secret)) + server_cert, client_id, client_secret, + self.plugincoupling)) def _register_command(self, command): """Registers a command in the plugin @@ -314,9 +334,11 @@ class QiitaTypePlugin(BaseQiitaPlugin): _plugin_type = "artifact definition" def __init__(self, name, version, description, validate_func, - html_generator_func, artifact_types, publications=None): + html_generator_func, artifact_types, publications=None, + plugincoupling='filesystem'): super(QiitaTypePlugin, self).__init__(name, version, description, - publications=publications) + publications=publications, + plugincoupling=plugincoupling) logger.debug('Entered QiitaTypePlugin.__init__()') self.artifact_types = artifact_types @@ -378,6 +400,7 @@ def register_command(self, command): START_SCRIPT = %s PLUGIN_TYPE = %s PUBLICATIONS = %s +PLUGINCOUPLING = %s [oauth2] SERVER_CERT = %s From fefc02b9c9ee654cdbc55d050da06d02c3866645 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 15 Aug 2025 13:45:36 +0200 Subject: [PATCH 02/30] provide two functions to retrieve and push file content to qiita master --- qiita_client/qiita_client.py | 59 ++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 553fae8..57954d7 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -6,6 +6,7 @@ # The full license is in the file LICENSE, distributed with this software. # ----------------------------------------------------------------------------- +import os import time import requests import threading @@ -253,13 +254,16 @@ def _fetch_token(self): # Timeout, etc. and logs them logger.debug(str(e)) - def _request_oauth2(self, req, *args, **kwargs): + def _request_oauth2(self, req, rettype, *args, **kwargs): """Executes a request using OAuth2 authorization Parameters ---------- req : function The request to execute + rettype : string + The return type of the function, either "json" or + if e.g. files are transferred "content" args : tuple The request args kwargs : dict @@ -309,13 +313,16 @@ def _request_oauth2(self, req, *args, **kwargs): r = req(*args, **kwargs) return r - def _request_retry(self, req, url, **kwargs): + def _request_retry(self, req, url, rettype='json', **kwargs): """Executes a request retrying it 2 times in case of failure Parameters ---------- req : function The request to execute + rettype : string + The return type of the function, either "json" (default) or + if e.g. files are transferred "content" url : str The url to access in the server kwargs : dict @@ -323,7 +330,7 @@ def _request_retry(self, req, url, **kwargs): Returns ------- - dict or None + dict or None or plain content IF rettype='content' The JSON information in the request response, if any Raises @@ -358,7 +365,7 @@ def _request_retry(self, req, url, **kwargs): retries = MAX_RETRIES while retries > 0: retries -= 1 - r = self._request_oauth2(req, url, verify=self._verify, **kwargs) + r = self._request_oauth2(req, rettype, url, verify=self._verify, **kwargs) r.close() # There are some error codes that the specification says that they # shouldn't be retried @@ -374,7 +381,13 @@ def _request_retry(self, req, url, **kwargs): "Message: %s" % (req.__name__, url, r.status_code, r.text)) elif 0 <= (r.status_code - 200) < 100: try: - return r.json() + if rettype is None or rettype == 'json': + return r.json() + else: + if rettype == 'content': + return r.content + else: + raise ValueError("return type rettype='%s' cannot be understand. Choose from 'json' (default) or 'content!" % rettype) except ValueError: return None stime = randint(MIN_TIME_SLEEP, MAX_TIME_SLEEP) @@ -386,13 +399,16 @@ def _request_retry(self, req, url, **kwargs): "Request '%s %s' did not succeed. Status code: %d. Message: %s" % (req.__name__, url, r.status_code, r.text)) - def get(self, url, **kwargs): + def get(self, url, rettype='json', **kwargs): """Execute a get request against the Qiita server Parameters ---------- url : str The url to access in the server + rettype : string + The return type of the function, either "json" (default) or + if e.g. files are transferred "content" kwargs : dict The request kwargs @@ -402,7 +418,7 @@ def get(self, url, **kwargs): The JSON response from the server """ logger.debug('Entered QiitaClient.get()') - return self._request_retry(self._session.get, url, **kwargs) + return self._request_retry(self._session.get, url, rettype=rettype, **kwargs) def post(self, url, **kwargs): """Execute a post request against the Qiita server @@ -420,7 +436,7 @@ def post(self, url, **kwargs): The JSON response from the server """ logger.debug('Entered QiitaClient.post(%s)' % url) - return self._request_retry(self._session.post, url, **kwargs) + return self._request_retry(self._session.post, url, rettype='json', **kwargs) def patch(self, url, op, path, value=None, from_p=None, **kwargs): """Executes a JSON patch request against the Qiita server @@ -479,7 +495,7 @@ def patch(self, url, op, path, value=None, from_p=None, **kwargs): # we made sure that data is correctly formatted here kwargs['data'] = data - return self._request_retry(self._session.patch, url, **kwargs) + return self._request_retry(self._session.patch, url, rettype='json', **kwargs) # The functions are shortcuts for common functionality that all plugins # need to implement. @@ -502,7 +518,7 @@ def http_patch(self, url, **kwargs): The JSON response from the server """ logger.debug('Entered QiitaClient.http_patch()') - return self._request_retry(self._session.patch, url, **kwargs) + return self._request_retry(self._session.patch, url, rettype='json', **kwargs) def start_heartbeat(self, job_id): """Create and start a thread that would send heartbeats to the server @@ -714,3 +730,26 @@ def _process_files_per_sample_fastq(self, files, prep_info, prep_info = prep_info.filter(items=sample_names.keys(), axis=0) return sample_names, prep_info + + def fetch_file_from_central(self, filepath): + logger.debug('Requesting file %s from qiita server.' % filepath) + + # actual call to Qiita central to obtain file content + content = self.get('/cloud/fetch_file_from_central/' + filepath, rettype='content') + + # create necessary directory locally + os.makedirs(os.path.dirname(filepath), exist_ok=True) + + # write retrieved file content + with open(filepath, 'wb') as f: + f.write(content) + + return filepath + + def push_file_to_central(self, filepath): + logger.debug('Submitting file %s to qiita server.' % filepath) + + self.post('/cloud/push_file_to_central/', + files={os.path.dirname(filepath): open(filepath, 'rb')}) + + return filepath \ No newline at end of file From a04beeb832586c69f45300cdf163f8a57c89dabb Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 15 Aug 2025 16:29:40 +0200 Subject: [PATCH 03/30] add a configurable parameter to define plugincoupling for plugin --- qiita_client/plugin.py | 27 +++++++++++++++-------- qiita_client/qiita_client.py | 42 ++++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/qiita_client/plugin.py b/qiita_client/plugin.py index 36c37a2..417efd0 100644 --- a/qiita_client/plugin.py +++ b/qiita_client/plugin.py @@ -136,7 +136,8 @@ def __init__(self, name, description, can_be_submitted_to_ebi, class BaseQiitaPlugin(object): - def __init__(self, name, version, description, publications=None, plugincoupling='filesystem'): + _ALLOWED_PLUGIN_COUPLINGS = ['filesystem', 'https'] # default must be first element + def __init__(self, name, version, description, publications=None, plugincoupling=_ALLOWED_PLUGIN_COUPLINGS[0]): logger.debug('Entered BaseQiitaPlugin.__init__()') self.name = name self.version = version @@ -160,6 +161,8 @@ def __init__(self, name, version, description, publications=None, plugincoupling # Actually, all files need to be decorated with this function. The decision how # data are transferred is then made within these two functions according to the # "plugincoupling" setting. + if plugincoupling not in self._ALLOWED_PLUGIN_COUPLINGS: + raise ValueError("valid plugincoupling values are ['%s'], but you provided %s" % ("', '".join(self._ALLOWED_PLUGIN_COUPLINGS), plugincoupling)) self.plugincoupling = plugincoupling # Will hold the different commands @@ -170,7 +173,7 @@ def __init__(self, name, version, description, publications=None, plugincoupling 'QIITA_PLUGINS_DIR', join(expanduser('~'), '.qiita_plugins')) self.conf_fp = join(conf_dir, "%s_%s.conf" % (self.name, self.version)) - def generate_config(self, env_script, start_script, server_cert=None): + def generate_config(self, env_script, start_script, server_cert=None, plugin_couling=_ALLOWED_PLUGIN_COUPLINGS[0]): """Generates the plugin configuration file Parameters @@ -184,6 +187,9 @@ def generate_config(self, env_script, start_script, server_cert=None): If the Qiita server used does not have a valid certificate, the path to the Qiita certificate so the plugin can connect over HTTPS to it + plugin_coupling : str + Type of coupling of plugin to central for file exchange. + Valid values are 'filesystem' and 'https'. """ logger.debug('Entered BaseQiitaPlugin.generate_config()') sr = SystemRandom() @@ -198,7 +204,7 @@ def generate_config(self, env_script, start_script, server_cert=None): env_script, start_script, self._plugin_type, self.publications, server_cert, client_id, client_secret, - self.plugincoupling)) + plugin_couling)) def _register_command(self, command): """Registers a command in the plugin @@ -208,8 +214,7 @@ def _register_command(self, command): command: QiitaCommand The command to be added to the plugin """ - logger.debug( - f'Entered BaseQiitaPlugin._register_command({command.name})') + logger.debug('Entered BaseQiitaPlugin._register_command(%s)' % command.name) self.task_dict[command.name] = command def _register(self, qclient): @@ -271,7 +276,8 @@ def __call__(self, server_url, job_id, output_dir): # this value will prevent underlying libraries # from validating the server's cert using # certifi's pem cache. - ca_cert=config.get('oauth2', 'SERVER_CERT')) + ca_cert=config.get('oauth2', 'SERVER_CERT'), + plugincoupling=config.get('network', 'PLUGINCOUPLING')) if job_id == 'register': self._register(qclient) @@ -335,7 +341,7 @@ class QiitaTypePlugin(BaseQiitaPlugin): def __init__(self, name, version, description, validate_func, html_generator_func, artifact_types, publications=None, - plugincoupling='filesystem'): + plugincoupling=BaseQiitaPlugin._ALLOWED_PLUGIN_COUPLINGS[0]): super(QiitaTypePlugin, self).__init__(name, version, description, publications=publications, plugincoupling=plugincoupling) @@ -400,9 +406,12 @@ def register_command(self, command): START_SCRIPT = %s PLUGIN_TYPE = %s PUBLICATIONS = %s -PLUGINCOUPLING = %s [oauth2] SERVER_CERT = %s CLIENT_ID = %s -CLIENT_SECRET = %s""" +CLIENT_SECRET = %s + +[network] +PLUGINCOUPLING = %s +""" diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 57954d7..97a5069 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -183,7 +183,7 @@ class QiitaClient(object): get post """ - def __init__(self, server_url, client_id, client_secret, ca_cert=None): + def __init__(self, server_url, client_id, client_secret, ca_cert=None, plugincoupling='filesystem'): self._server_url = server_url self._session = requests.Session() @@ -219,6 +219,8 @@ def __init__(self, server_url, client_id, client_secret, ca_cert=None): self._token = None self._fetch_token() + self._plugincoupling = plugincoupling + def _fetch_token(self): """Retrieves an access token from the Qiita server @@ -732,24 +734,36 @@ def _process_files_per_sample_fastq(self, files, prep_info, return sample_names, prep_info def fetch_file_from_central(self, filepath): - logger.debug('Requesting file %s from qiita server.' % filepath) + if self._plugincoupling == 'filesystem': + return filepath + + if self._plugincoupling == 'https': + logger.debug('Requesting file %s from qiita server.' % filepath) + + # actual call to Qiita central to obtain file content + content = self.get('/cloud/fetch_file_from_central/' + filepath, rettype='content') - # actual call to Qiita central to obtain file content - content = self.get('/cloud/fetch_file_from_central/' + filepath, rettype='content') + # create necessary directory locally + os.makedirs(os.path.dirname(filepath), exist_ok=True) - # create necessary directory locally - os.makedirs(os.path.dirname(filepath), exist_ok=True) + # write retrieved file content + with open(filepath, 'wb') as f: + f.write(content) - # write retrieved file content - with open(filepath, 'wb') as f: - f.write(content) + return filepath - return filepath + raise ValueError("File communication protocol '%s' as defined in plugins configuration is NOT defined." % self._plugincoupling) def push_file_to_central(self, filepath): - logger.debug('Submitting file %s to qiita server.' % filepath) + if self._plugincoupling == 'filesystem': + return filepath + + if self._plugincoupling == 'https': + logger.debug('Submitting file %s to qiita server.' % filepath) + + self.post('/cloud/push_file_to_central/', + files={os.path.dirname(filepath): open(filepath, 'rb')}) - self.post('/cloud/push_file_to_central/', - files={os.path.dirname(filepath): open(filepath, 'rb')}) + return filepath - return filepath \ No newline at end of file + raise ValueError("File communication protocol '%s' as defined in plugins configuration is NOT defined." % self._plugincoupling) From 8f9fbec689a8e631828c3e80da6661da7900795a Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 15 Aug 2025 16:39:46 +0200 Subject: [PATCH 04/30] codestyle --- qiita_client/plugin.py | 56 ++++++++++++++++++++++-------------- qiita_client/qiita_client.py | 38 +++++++++++++++++------- 2 files changed, 62 insertions(+), 32 deletions(-) diff --git a/qiita_client/plugin.py b/qiita_client/plugin.py index 417efd0..b0cc4c6 100644 --- a/qiita_client/plugin.py +++ b/qiita_client/plugin.py @@ -136,33 +136,44 @@ def __init__(self, name, description, can_be_submitted_to_ebi, class BaseQiitaPlugin(object): - _ALLOWED_PLUGIN_COUPLINGS = ['filesystem', 'https'] # default must be first element - def __init__(self, name, version, description, publications=None, plugincoupling=_ALLOWED_PLUGIN_COUPLINGS[0]): + # default must be first element + _ALLOWED_PLUGIN_COUPLINGS = ['filesystem', 'https'] + + def __init__(self, name, version, description, publications=None, + plugincoupling=_ALLOWED_PLUGIN_COUPLINGS[0]): logger.debug('Entered BaseQiitaPlugin.__init__()') self.name = name self.version = version self.description = description self.publications = dumps(publications) if publications else "" - # Depending on your compute architecture, there are multiple options available how - # "thight" plugins are coupled to the central Qiita master/workers + # Depending on your compute architecture, there are multiple options + # available how "thight" plugins are coupled to the central + # Qiita master/workers # --- filesystem --- - # The default scenario is "filesystem", i.e. plugins as well as master/worker have - # unrestricted direct access to a shared filesystem, e.g. a larger volume / directory, - # defined in the server configuration as base_data_dir + # The default scenario is "filesystem", i.e. plugins as well as + # master/worker have unrestricted direct access to a shared filesystem, + # e.g. a larger volume / directory, defined in the server configuration + # as base_data_dir # --- https --- - # A second scenario is that your plugins execute as independent jobs on another machine, - # e.g. as docker containers or other cloud techniques. Intentionally, you don't want to - # use a shared filesystem, but you have to make sure necessary input files are - # provided to the containerized plugin before execution and resulting files are - # transfered back to the central Qiita master/worker. In this case, files are - # pulled / pushed through functions qiita_client.fetch_file_from_central and + # A second scenario is that your plugins execute as independent jobs on + # another machine, e.g. as docker containers or other cloud techniques. + # Intentionally, you don't want to use a shared filesystem, but you + # have to make sure necessary input files are provided to the + # containerized plugin before execution and resulting files are + # transfered back to the central Qiita master/worker. In this case, + # files are pulled / pushed through functions + # qiita_client.fetch_file_from_central and # qiita_client.push_file_to_central, respectivey. - # Actually, all files need to be decorated with this function. The decision how - # data are transferred is then made within these two functions according to the - # "plugincoupling" setting. + # Actually, all files need to be decorated with this function. + # The decision how data are transferred is then made within these two + # functions according to the "plugincoupling" setting. if plugincoupling not in self._ALLOWED_PLUGIN_COUPLINGS: - raise ValueError("valid plugincoupling values are ['%s'], but you provided %s" % ("', '".join(self._ALLOWED_PLUGIN_COUPLINGS), plugincoupling)) + raise ValueError( + ("valid plugincoupling values are ['%s'], but you " + "provided %s") % ( + "', '".join(self._ALLOWED_PLUGIN_COUPLINGS), + plugincoupling)) self.plugincoupling = plugincoupling # Will hold the different commands @@ -173,7 +184,8 @@ def __init__(self, name, version, description, publications=None, plugincoupling 'QIITA_PLUGINS_DIR', join(expanduser('~'), '.qiita_plugins')) self.conf_fp = join(conf_dir, "%s_%s.conf" % (self.name, self.version)) - def generate_config(self, env_script, start_script, server_cert=None, plugin_couling=_ALLOWED_PLUGIN_COUPLINGS[0]): + def generate_config(self, env_script, start_script, server_cert=None, + plugin_couling=_ALLOWED_PLUGIN_COUPLINGS[0]): """Generates the plugin configuration file Parameters @@ -188,7 +200,7 @@ def generate_config(self, env_script, start_script, server_cert=None, plugin_cou path to the Qiita certificate so the plugin can connect over HTTPS to it plugin_coupling : str - Type of coupling of plugin to central for file exchange. + Type of coupling of plugin to central for file exchange. Valid values are 'filesystem' and 'https'. """ logger.debug('Entered BaseQiitaPlugin.generate_config()') @@ -214,7 +226,8 @@ def _register_command(self, command): command: QiitaCommand The command to be added to the plugin """ - logger.debug('Entered BaseQiitaPlugin._register_command(%s)' % command.name) + logger.debug('Entered BaseQiitaPlugin._register_command(%s)' % + command.name) self.task_dict[command.name] = command def _register(self, qclient): @@ -277,7 +290,8 @@ def __call__(self, server_url, job_id, output_dir): # from validating the server's cert using # certifi's pem cache. ca_cert=config.get('oauth2', 'SERVER_CERT'), - plugincoupling=config.get('network', 'PLUGINCOUPLING')) + plugincoupling=config.get('network', + 'PLUGINCOUPLING')) if job_id == 'register': self._register(qclient) diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 97a5069..6e079b1 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -183,7 +183,8 @@ class QiitaClient(object): get post """ - def __init__(self, server_url, client_id, client_secret, ca_cert=None, plugincoupling='filesystem'): + def __init__(self, server_url, client_id, client_secret, ca_cert=None, + plugincoupling='filesystem'): self._server_url = server_url self._session = requests.Session() @@ -367,7 +368,8 @@ def _request_retry(self, req, url, rettype='json', **kwargs): retries = MAX_RETRIES while retries > 0: retries -= 1 - r = self._request_oauth2(req, rettype, url, verify=self._verify, **kwargs) + r = self._request_oauth2( + req, rettype, url, verify=self._verify, **kwargs) r.close() # There are some error codes that the specification says that they # shouldn't be retried @@ -389,7 +391,10 @@ def _request_retry(self, req, url, rettype='json', **kwargs): if rettype == 'content': return r.content else: - raise ValueError("return type rettype='%s' cannot be understand. Choose from 'json' (default) or 'content!" % rettype) + raise ValueError( + ("return type rettype='%s' cannot be " + "understand. Choose from 'json' (default) " + "or 'content!") % rettype) except ValueError: return None stime = randint(MIN_TIME_SLEEP, MAX_TIME_SLEEP) @@ -420,7 +425,8 @@ def get(self, url, rettype='json', **kwargs): The JSON response from the server """ logger.debug('Entered QiitaClient.get()') - return self._request_retry(self._session.get, url, rettype=rettype, **kwargs) + return self._request_retry( + self._session.get, url, rettype=rettype, **kwargs) def post(self, url, **kwargs): """Execute a post request against the Qiita server @@ -438,7 +444,8 @@ def post(self, url, **kwargs): The JSON response from the server """ logger.debug('Entered QiitaClient.post(%s)' % url) - return self._request_retry(self._session.post, url, rettype='json', **kwargs) + return self._request_retry( + self._session.post, url, rettype='json', **kwargs) def patch(self, url, op, path, value=None, from_p=None, **kwargs): """Executes a JSON patch request against the Qiita server @@ -497,7 +504,8 @@ def patch(self, url, op, path, value=None, from_p=None, **kwargs): # we made sure that data is correctly formatted here kwargs['data'] = data - return self._request_retry(self._session.patch, url, rettype='json', **kwargs) + return self._request_retry( + self._session.patch, url, rettype='json', **kwargs) # The functions are shortcuts for common functionality that all plugins # need to implement. @@ -520,7 +528,8 @@ def http_patch(self, url, **kwargs): The JSON response from the server """ logger.debug('Entered QiitaClient.http_patch()') - return self._request_retry(self._session.patch, url, rettype='json', **kwargs) + return self._request_retry( + self._session.patch, url, rettype='json', **kwargs) def start_heartbeat(self, job_id): """Create and start a thread that would send heartbeats to the server @@ -741,7 +750,9 @@ def fetch_file_from_central(self, filepath): logger.debug('Requesting file %s from qiita server.' % filepath) # actual call to Qiita central to obtain file content - content = self.get('/cloud/fetch_file_from_central/' + filepath, rettype='content') + content = self.get( + '/cloud/fetch_file_from_central/' + filepath, + rettype='content') # create necessary directory locally os.makedirs(os.path.dirname(filepath), exist_ok=True) @@ -752,7 +763,9 @@ def fetch_file_from_central(self, filepath): return filepath - raise ValueError("File communication protocol '%s' as defined in plugins configuration is NOT defined." % self._plugincoupling) + raise ValueError( + ("File communication protocol '%s' as defined in plugins " + "configuration is NOT defined.") % self._plugincoupling) def push_file_to_central(self, filepath): if self._plugincoupling == 'filesystem': @@ -761,9 +774,12 @@ def push_file_to_central(self, filepath): if self._plugincoupling == 'https': logger.debug('Submitting file %s to qiita server.' % filepath) - self.post('/cloud/push_file_to_central/', + self.post( + '/cloud/push_file_to_central/', files={os.path.dirname(filepath): open(filepath, 'rb')}) return filepath - raise ValueError("File communication protocol '%s' as defined in plugins configuration is NOT defined." % self._plugincoupling) + raise ValueError( + ("File communication protocol '%s' as defined in plugins " + "configuration is NOT defined.") % self._plugincoupling) From 8d0844dc05868bbeac370d62aa235699c0ec9610 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 15 Aug 2025 16:41:51 +0200 Subject: [PATCH 05/30] adapting test to new configuration option --- qiita_client/tests/test_plugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qiita_client/tests/test_plugin.py b/qiita_client/tests/test_plugin.py index ab87f6b..fcf8920 100644 --- a/qiita_client/tests/test_plugin.py +++ b/qiita_client/tests/test_plugin.py @@ -147,7 +147,10 @@ def html_generator_func(a, b, c, d): 'PUBLICATIONS = \n', '\n', '[oauth2]\n', - 'SERVER_CERT = \n'] + 'SERVER_CERT = \n', + '\n', + '[network]\n', + 'plugincoupling = \n'] # We will test the last 2 lines independently since they're variable # in each test run self.assertEqual(conf[:-2], exp_lines) From 47a4551af0805e0ff5d17e7572d7e743f83b6a17 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 15 Aug 2025 16:54:55 +0200 Subject: [PATCH 06/30] start working on tests --- qiita_client/tests/test_qiita_client.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/qiita_client/tests/test_qiita_client.py b/qiita_client/tests/test_qiita_client.py index 12689e8..02e7b1f 100644 --- a/qiita_client/tests/test_qiita_client.py +++ b/qiita_client/tests/test_qiita_client.py @@ -385,6 +385,17 @@ def test_artifact_and_preparation_files(self): self.assertEqual(fobs, fexp) self.assertEqual(piobs.shape, (2, 1)) + def test_fetch_file_from_central(self): + self.tester._plugincoupling = 'filesystem' + + ainfo = self.tester.get("/qiita_db/artifacts/%s/" % 1) + print("STEFAN", ainfo) + + # fp_query = '/home/runner/work/qiita/qiita/qiita_db/support_files/ + # test_data/templates/FASTA_QUAL_preprocessing_sample_template.txt' + # fp_res = fetch_file_from_central('') + pass + if __name__ == '__main__': main() From 8d220216339cf69b583c26a26d85e5215ecc0a1a Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 15 Aug 2025 17:04:01 +0200 Subject: [PATCH 07/30] check spelling --- qiita_client/tests/test_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiita_client/tests/test_plugin.py b/qiita_client/tests/test_plugin.py index fcf8920..8057139 100644 --- a/qiita_client/tests/test_plugin.py +++ b/qiita_client/tests/test_plugin.py @@ -150,7 +150,7 @@ def html_generator_func(a, b, c, d): 'SERVER_CERT = \n', '\n', '[network]\n', - 'plugincoupling = \n'] + 'PLUGINCOUPLING = \n'] # We will test the last 2 lines independently since they're variable # in each test run self.assertEqual(conf[:-2], exp_lines) From e032053f4532bcd1d692018a5ec7f7ce4aa1239b Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 15 Aug 2025 17:09:31 +0200 Subject: [PATCH 08/30] fixing existing test --- qiita_client/tests/test_plugin.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/qiita_client/tests/test_plugin.py b/qiita_client/tests/test_plugin.py index 8057139..ba4f80a 100644 --- a/qiita_client/tests/test_plugin.py +++ b/qiita_client/tests/test_plugin.py @@ -147,15 +147,14 @@ def html_generator_func(a, b, c, d): 'PUBLICATIONS = \n', '\n', '[oauth2]\n', - 'SERVER_CERT = \n', - '\n', - '[network]\n', - 'PLUGINCOUPLING = \n'] + 'SERVER_CERT = \n'] # We will test the last 2 lines independently since they're variable # in each test run - self.assertEqual(conf[:-2], exp_lines) - self.assertTrue(conf[-2].startswith('CLIENT_ID = ')) - self.assertTrue(conf[-1].startswith('CLIENT_SECRET = ')) + self.assertEqual(conf[:-5], exp_lines) + self.assertTrue(conf[-5].startswith('CLIENT_ID = ')) + self.assertTrue(conf[-4].startswith('CLIENT_SECRET = ')) + self.assertTrue(conf[-2].startswith('[network]')) + self.assertTrue(conf[-2].startswith('PLUGINCOUPLING = ')) def test_call(self): def validate_func(a, b, c, d): From 7d5b09846ee9ed9b20cd6db13e6d0a0493b62311 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 15 Aug 2025 17:14:28 +0200 Subject: [PATCH 09/30] wrong list index --- qiita_client/tests/test_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiita_client/tests/test_plugin.py b/qiita_client/tests/test_plugin.py index ba4f80a..43a8eca 100644 --- a/qiita_client/tests/test_plugin.py +++ b/qiita_client/tests/test_plugin.py @@ -154,7 +154,7 @@ def html_generator_func(a, b, c, d): self.assertTrue(conf[-5].startswith('CLIENT_ID = ')) self.assertTrue(conf[-4].startswith('CLIENT_SECRET = ')) self.assertTrue(conf[-2].startswith('[network]')) - self.assertTrue(conf[-2].startswith('PLUGINCOUPLING = ')) + self.assertTrue(conf[-1].startswith('PLUGINCOUPLING = ')) def test_call(self): def validate_func(a, b, c, d): From 349ab443624346ad65e44c207b40f9efc88b8568 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Mon, 18 Aug 2025 12:19:46 +0200 Subject: [PATCH 10/30] initial tests + docstrings --- qiita_client/qiita_client.py | 64 +++++++++++++++++++++---- qiita_client/tests/test_qiita_client.py | 36 ++++++++++++-- 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 6e079b1..4985fc5 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -7,6 +7,7 @@ # ----------------------------------------------------------------------------- import os +import shutil import time import requests import threading @@ -742,11 +743,53 @@ def _process_files_per_sample_fastq(self, files, prep_info, return sample_names, prep_info - def fetch_file_from_central(self, filepath): + def fetch_file_from_central(self, filepath, prefix=''): + """Moves content of a file from Qiita's central base_data_dir to a + local plugin file-system. + + By default, this is exactly the same location, i.e. the return + filepath is identical to the requested one and nothing is moved / + copied. + However, for less tight plugin couplings, file content can be + transferred via https for situations where the plugin does not have + native access to Qiita's overall base_data_dir. + + Parameters + ---------- + filepath : str + The filepath in Qiita's central base_data_dir to the requested + file content + prefix : str + Primarily for testing: prefix the target filepath with this + filepath prefix to + a) in 'filesystem' mode: create an actual file copy (for testing) + If prefix='', nothing will be copied/moved + b) in 'https' mode: flexibility to locate files differently in + plugin local file system. + + Returns + ------- + str : the filepath of the requested file within the local file system + """ + target_filepath = filepath + if prefix != '': + # strip off root + if filepath.startswith(os.path.abspath(os.sep)): + target_filepath = target_filepath[ + len(os.path.abspath(os.sep)):] + # prefix filepath with given prefix + target_filepath = os.path.join(prefix, target_filepath) + if self._plugincoupling == 'filesystem': - return filepath + if prefix != '': + # create necessary directory locally + os.makedirs(os.path.dirname(target_filepath), exist_ok=True) + + shutil.copyfile(filepath, target_filepath) + + return target_filepath - if self._plugincoupling == 'https': + elif self._plugincoupling == 'https': logger.debug('Requesting file %s from qiita server.' % filepath) # actual call to Qiita central to obtain file content @@ -755,23 +798,24 @@ def fetch_file_from_central(self, filepath): rettype='content') # create necessary directory locally - os.makedirs(os.path.dirname(filepath), exist_ok=True) + os.makedirs(os.path.dirname(target_filepath), exist_ok=True) # write retrieved file content - with open(filepath, 'wb') as f: + with open(target_filepath, 'wb') as f: f.write(content) - return filepath + return target_filepath - raise ValueError( - ("File communication protocol '%s' as defined in plugins " - "configuration is NOT defined.") % self._plugincoupling) + else: + raise ValueError( + ("File communication protocol '%s' as defined in plugins " + "configuration is NOT defined.") % self._plugincoupling) def push_file_to_central(self, filepath): if self._plugincoupling == 'filesystem': return filepath - if self._plugincoupling == 'https': + elif self._plugincoupling == 'https': logger.debug('Submitting file %s to qiita server.' % filepath) self.post( diff --git a/qiita_client/tests/test_qiita_client.py b/qiita_client/tests/test_qiita_client.py index 02e7b1f..a59bdd8 100644 --- a/qiita_client/tests/test_qiita_client.py +++ b/qiita_client/tests/test_qiita_client.py @@ -7,6 +7,7 @@ # ----------------------------------------------------------------------------- from unittest import TestCase, main +import filecmp from os import remove, close from os.path import basename, exists from tempfile import mkstemp @@ -389,12 +390,37 @@ def test_fetch_file_from_central(self): self.tester._plugincoupling = 'filesystem' ainfo = self.tester.get("/qiita_db/artifacts/%s/" % 1) - print("STEFAN", ainfo) + fp = ainfo['files']['raw_forward_seqs'][0]['filepath'] - # fp_query = '/home/runner/work/qiita/qiita/qiita_db/support_files/ - # test_data/templates/FASTA_QUAL_preprocessing_sample_template.txt' - # fp_res = fetch_file_from_central('') - pass + # mode: filesystem, prefix='': no copy, directly return given fp + fp_obs = self.tester.fetch_file_from_central(fp) + self.assertEqual(fp, fp_obs) + + # mode: filesystem, prefix='/karl': make file copy + self.clean_up_files.append('/karl' + fp) + fp_obs = self.tester.fetch_file_from_central(fp, prefix='/karl') + self.assertEqual('/karl' + fp, fp_obs) + self.assertTrue(filecmp.cmp(fp, fp_obs, shallow=False)) + + # non existing mode + with self.assertRaises(ValueError): + self.tester._plugincoupling = 'foo' + self.tester.fetch_file_from_central(fp) + + def test_push_file_to_central(self): + self.tester._plugincoupling = 'filesystem' + + ainfo = self.tester.get("/qiita_db/artifacts/%s/" % 1) + fp = ainfo['files']['raw_forward_seqs'][0]['filepath'] + + # mode: filesystem + fp_obs = self.tester.push_file_to_central(fp) + self.assertEqual(fp, fp_obs) + + # non existing mode + with self.assertRaises(ValueError): + self.tester._plugincoupling = 'foo' + self.tester.push_file_to_central(fp) if __name__ == '__main__': From 1fdf1e5a16bdd5271f1d260c80c8b8cfa3197cf7 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Mon, 18 Aug 2025 12:27:19 +0200 Subject: [PATCH 11/30] use a prefix user has access to --- qiita_client/qiita_client.py | 19 +++++++++++++++++++ qiita_client/tests/test_qiita_client.py | 9 +++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 4985fc5..0c3b277 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -812,6 +812,25 @@ def fetch_file_from_central(self, filepath, prefix=''): "configuration is NOT defined.") % self._plugincoupling) def push_file_to_central(self, filepath): + """Pushs filecontent to Qiita's central base_data_dir directory. + + By default, plugin and Qiita's central base_data_dir filesystems are + identical. In this case, no files are touched and the filepath is + directly returned. + If however, plugincoupling is set to 'https', the content of the file + is sent via https POST to Qiita's master/worker, which has to receive + and store in an appropriate location. + + Parameters + ---------- + filepath : str + The filepath of the files whos content shall be send to Qiita's + central base_data_dir + + Returns + ------- + The given filepath - to be transparent in plugin code. + """ if self._plugincoupling == 'filesystem': return filepath diff --git a/qiita_client/tests/test_qiita_client.py b/qiita_client/tests/test_qiita_client.py index a59bdd8..580250c 100644 --- a/qiita_client/tests/test_qiita_client.py +++ b/qiita_client/tests/test_qiita_client.py @@ -9,7 +9,7 @@ from unittest import TestCase, main import filecmp from os import remove, close -from os.path import basename, exists +from os.path import basename, exists, expanduser, join from tempfile import mkstemp from json import dumps import pandas as pd @@ -397,9 +397,10 @@ def test_fetch_file_from_central(self): self.assertEqual(fp, fp_obs) # mode: filesystem, prefix='/karl': make file copy - self.clean_up_files.append('/karl' + fp) - fp_obs = self.tester.fetch_file_from_central(fp, prefix='/karl') - self.assertEqual('/karl' + fp, fp_obs) + prefix = join(expanduser("~"), '/karl') + self.clean_up_files.append(prefix + fp) + fp_obs = self.tester.fetch_file_from_central(fp, prefix=prefix) + self.assertEqual(prefix + fp, fp_obs) self.assertTrue(filecmp.cmp(fp, fp_obs, shallow=False)) # non existing mode From 748f72729c120d1241e539fe742db49805126de6 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Mon, 18 Aug 2025 12:32:19 +0200 Subject: [PATCH 12/30] remove / --- qiita_client/tests/test_qiita_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiita_client/tests/test_qiita_client.py b/qiita_client/tests/test_qiita_client.py index 580250c..587c4fb 100644 --- a/qiita_client/tests/test_qiita_client.py +++ b/qiita_client/tests/test_qiita_client.py @@ -397,7 +397,7 @@ def test_fetch_file_from_central(self): self.assertEqual(fp, fp_obs) # mode: filesystem, prefix='/karl': make file copy - prefix = join(expanduser("~"), '/karl') + prefix = join(expanduser("~"), 'karl') self.clean_up_files.append(prefix + fp) fp_obs = self.tester.fetch_file_from_central(fp, prefix=prefix) self.assertEqual(prefix + fp, fp_obs) From 8f2680496dae8a7787484fa2b5256c95e381641a Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Tue, 19 Aug 2025 12:50:01 +0200 Subject: [PATCH 13/30] capitalize comments --- qiita_client/qiita_client.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 0c3b277..12ec361 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -744,7 +744,7 @@ def _process_files_per_sample_fastq(self, files, prep_info, return sample_names, prep_info def fetch_file_from_central(self, filepath, prefix=''): - """Moves content of a file from Qiita's central base_data_dir to a + """Moves content of a file from Qiita's central BASE_DATA_DIR to a local plugin file-system. By default, this is exactly the same location, i.e. the return @@ -752,12 +752,12 @@ def fetch_file_from_central(self, filepath, prefix=''): copied. However, for less tight plugin couplings, file content can be transferred via https for situations where the plugin does not have - native access to Qiita's overall base_data_dir. + native access to Qiita's overall BASE_DATA_DIR. Parameters ---------- filepath : str - The filepath in Qiita's central base_data_dir to the requested + The filepath in Qiita's central BASE_DATA_DIR to the requested file content prefix : str Primarily for testing: prefix the target filepath with this @@ -812,9 +812,9 @@ def fetch_file_from_central(self, filepath, prefix=''): "configuration is NOT defined.") % self._plugincoupling) def push_file_to_central(self, filepath): - """Pushs filecontent to Qiita's central base_data_dir directory. + """Pushs filecontent to Qiita's central BASE_DATA_DIR directory. - By default, plugin and Qiita's central base_data_dir filesystems are + By default, plugin and Qiita's central BASE_DATA_DIR filesystems are identical. In this case, no files are touched and the filepath is directly returned. If however, plugincoupling is set to 'https', the content of the file @@ -825,7 +825,7 @@ def push_file_to_central(self, filepath): ---------- filepath : str The filepath of the files whos content shall be send to Qiita's - central base_data_dir + central BASE_DATA_DIR Returns ------- From 509b3d5763aeaa616c95304e2caeb0a4c7d5d71d Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Tue, 19 Aug 2025 12:50:25 +0200 Subject: [PATCH 14/30] use qiita branch --- .github/workflows/qiita-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/qiita-ci.yml b/.github/workflows/qiita-ci.yml index d09ac5e..1559611 100644 --- a/.github/workflows/qiita-ci.yml +++ b/.github/workflows/qiita-ci.yml @@ -52,8 +52,9 @@ jobs: # we need to download qiita directly so we have "easy" access to # all config files - wget https://github.com/biocore/qiita/archive/dev.zip - unzip dev.zip + # wget https://github.com/biocore/qiita/archive/dev.zip + # unzip dev.zip + git clone -b uncouplePlugins https://github.com/jlab/qiita.git # pull out the port so we can modify the configuration file easily pgport=${{ job.services.postgres.ports[5432] }} From 0cc6a50ebef4eb7163301bbbe146dd755236a3be Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Tue, 19 Aug 2025 12:53:03 +0200 Subject: [PATCH 15/30] specify directory name --- .github/workflows/qiita-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qiita-ci.yml b/.github/workflows/qiita-ci.yml index 1559611..aefc5dd 100644 --- a/.github/workflows/qiita-ci.yml +++ b/.github/workflows/qiita-ci.yml @@ -54,7 +54,7 @@ jobs: # all config files # wget https://github.com/biocore/qiita/archive/dev.zip # unzip dev.zip - git clone -b uncouplePlugins https://github.com/jlab/qiita.git + git clone -b uncouplePlugins https://github.com/jlab/qiita.git qiita-dev # pull out the port so we can modify the configuration file easily pgport=${{ job.services.postgres.ports[5432] }} From 738ab79a6b84417a5d4e4ad70abf0bfb3d012629 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Tue, 19 Aug 2025 12:59:41 +0200 Subject: [PATCH 16/30] add testing https protocol --- qiita_client/tests/test_qiita_client.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/qiita_client/tests/test_qiita_client.py b/qiita_client/tests/test_qiita_client.py index 587c4fb..15e4f77 100644 --- a/qiita_client/tests/test_qiita_client.py +++ b/qiita_client/tests/test_qiita_client.py @@ -408,6 +408,14 @@ def test_fetch_file_from_central(self): self.tester._plugincoupling = 'foo' self.tester.fetch_file_from_central(fp) + # change transfer mode to https + self.tester._plugincoupling = 'https' + prefix = join(expanduser("~"), 'kurt') + self.clean_up_files.append(prefix + fp) + fp_obs = self.tester.fetch_file_from_central(fp, prefix=prefix) + self.assertEqual(prefix + fp, fp_obs) + self.assertTrue(filecmp.cmp(fp, fp_obs, shallow=False)) + def test_push_file_to_central(self): self.tester._plugincoupling = 'filesystem' @@ -423,6 +431,15 @@ def test_push_file_to_central(self): self.tester._plugincoupling = 'foo' self.tester.push_file_to_central(fp) + # change transfer mode to https + self.tester._plugincoupling = 'https' + fp_source = 'foo.bar' + with open(fp_source, 'w') as f: + f.write("this is a test\n") + self.clean_up_files.append(fp_source) + fp_obs = self.tester.push_file_to_central(fp_source) + self.assertEqual(fp_source, fp_obs) + if __name__ == '__main__': main() From 0849ca7f4a72ee0d8aaea66164b06b9046631770 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Tue, 19 Aug 2025 15:56:29 +0200 Subject: [PATCH 17/30] fix protected --- .github/workflows/qiita-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/qiita-ci.yml b/.github/workflows/qiita-ci.yml index aefc5dd..646bd18 100644 --- a/.github/workflows/qiita-ci.yml +++ b/.github/workflows/qiita-ci.yml @@ -106,6 +106,7 @@ jobs: export NGINX_FILE=`pwd`/qiita-dev/qiita_pet/nginx_example.conf export NGINX_FILE_NEW=`pwd`/qiita-dev/qiita_pet/nginx_example_local.conf sed "s#/home/runner/work/qiita/qiita#${PWD}/qiita-dev/#g" ${NGINX_FILE} > ${NGINX_FILE_NEW} + sed "s#/Users/username/qiita#${PWD}/qiita-dev/#g" ${NGINX_FILE} > ${NGINX_FILE_NEW} nginx -c ${NGINX_FILE_NEW} echo "3. Setting up qiita" From e1cb5b6836def496bfa49a9e314fa0dcf3b5e2d5 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Tue, 19 Aug 2025 17:32:32 +0200 Subject: [PATCH 18/30] operate on file copy --- .github/workflows/qiita-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/qiita-ci.yml b/.github/workflows/qiita-ci.yml index 646bd18..d7dbfa9 100644 --- a/.github/workflows/qiita-ci.yml +++ b/.github/workflows/qiita-ci.yml @@ -105,8 +105,8 @@ jobs: mkdir -p ${CONDA_PREFIX}/var/run/nginx/ export NGINX_FILE=`pwd`/qiita-dev/qiita_pet/nginx_example.conf export NGINX_FILE_NEW=`pwd`/qiita-dev/qiita_pet/nginx_example_local.conf - sed "s#/home/runner/work/qiita/qiita#${PWD}/qiita-dev/#g" ${NGINX_FILE} > ${NGINX_FILE_NEW} - sed "s#/Users/username/qiita#${PWD}/qiita-dev/#g" ${NGINX_FILE} > ${NGINX_FILE_NEW} + sed "s#/home/runner/work/qiita/qiita#${PWD}/qiita-dev#g" ${NGINX_FILE} > ${NGINX_FILE_NEW} + sed -i "s#/Users/username/qiita#${PWD}/qiita-dev/#g" ${NGINX_FILE_NEW} nginx -c ${NGINX_FILE_NEW} echo "3. Setting up qiita" From ba929a2201a7fa8d3ef47283446a6d3d652b14bc Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Tue, 19 Aug 2025 17:40:37 +0200 Subject: [PATCH 19/30] debug --- qiita_client/tests/test_qiita_client.py | 48 +++++++++++++------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/qiita_client/tests/test_qiita_client.py b/qiita_client/tests/test_qiita_client.py index 15e4f77..48ba7bf 100644 --- a/qiita_client/tests/test_qiita_client.py +++ b/qiita_client/tests/test_qiita_client.py @@ -412,33 +412,35 @@ def test_fetch_file_from_central(self): self.tester._plugincoupling = 'https' prefix = join(expanduser("~"), 'kurt') self.clean_up_files.append(prefix + fp) + print("STEFAN", fp, prefix) fp_obs = self.tester.fetch_file_from_central(fp, prefix=prefix) + print("STEFAN2", fp_obs) self.assertEqual(prefix + fp, fp_obs) self.assertTrue(filecmp.cmp(fp, fp_obs, shallow=False)) - def test_push_file_to_central(self): - self.tester._plugincoupling = 'filesystem' - - ainfo = self.tester.get("/qiita_db/artifacts/%s/" % 1) - fp = ainfo['files']['raw_forward_seqs'][0]['filepath'] - - # mode: filesystem - fp_obs = self.tester.push_file_to_central(fp) - self.assertEqual(fp, fp_obs) - - # non existing mode - with self.assertRaises(ValueError): - self.tester._plugincoupling = 'foo' - self.tester.push_file_to_central(fp) - - # change transfer mode to https - self.tester._plugincoupling = 'https' - fp_source = 'foo.bar' - with open(fp_source, 'w') as f: - f.write("this is a test\n") - self.clean_up_files.append(fp_source) - fp_obs = self.tester.push_file_to_central(fp_source) - self.assertEqual(fp_source, fp_obs) + # def test_push_file_to_central(self): + # self.tester._plugincoupling = 'filesystem' + + # ainfo = self.tester.get("/qiita_db/artifacts/%s/" % 1) + # fp = ainfo['files']['raw_forward_seqs'][0]['filepath'] + + # # mode: filesystem + # fp_obs = self.tester.push_file_to_central(fp) + # self.assertEqual(fp, fp_obs) + + # # non existing mode + # with self.assertRaises(ValueError): + # self.tester._plugincoupling = 'foo' + # self.tester.push_file_to_central(fp) + + # # change transfer mode to https + # self.tester._plugincoupling = 'https' + # fp_source = 'foo.bar' + # with open(fp_source, 'w') as f: + # f.write("this is a test\n") + # self.clean_up_files.append(fp_source) + # fp_obs = self.tester.push_file_to_central(fp_source) + # self.assertEqual(fp_source, fp_obs) if __name__ == '__main__': From 1d26830e7e19fca7071203a1b505e1af2baea9ed Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Tue, 19 Aug 2025 17:50:51 +0200 Subject: [PATCH 20/30] avoid // --- .github/workflows/qiita-ci.yml | 2 +- qiita_client/qiita_client.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/qiita-ci.yml b/.github/workflows/qiita-ci.yml index d7dbfa9..8615d3e 100644 --- a/.github/workflows/qiita-ci.yml +++ b/.github/workflows/qiita-ci.yml @@ -106,7 +106,7 @@ jobs: export NGINX_FILE=`pwd`/qiita-dev/qiita_pet/nginx_example.conf export NGINX_FILE_NEW=`pwd`/qiita-dev/qiita_pet/nginx_example_local.conf sed "s#/home/runner/work/qiita/qiita#${PWD}/qiita-dev#g" ${NGINX_FILE} > ${NGINX_FILE_NEW} - sed -i "s#/Users/username/qiita#${PWD}/qiita-dev/#g" ${NGINX_FILE_NEW} + sed -i "s#/Users/username/qiita#${PWD}/qiita-dev#g" ${NGINX_FILE_NEW} nginx -c ${NGINX_FILE_NEW} echo "3. Setting up qiita" diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 12ec361..03d58e3 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -790,6 +790,10 @@ def fetch_file_from_central(self, filepath, prefix=''): return target_filepath elif self._plugincoupling == 'https': + # strip off root + if filepath.startswith(os.path.abspath(os.sep)): + filepath = filepath[len(os.path.abspath(os.sep)):] + logger.debug('Requesting file %s from qiita server.' % filepath) # actual call to Qiita central to obtain file content From 85c40f2217b534426952521899f8a3548e5f6859 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Tue, 19 Aug 2025 17:59:26 +0200 Subject: [PATCH 21/30] remove trailing / --- .github/workflows/qiita-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qiita-ci.yml b/.github/workflows/qiita-ci.yml index 8615d3e..7c52dfb 100644 --- a/.github/workflows/qiita-ci.yml +++ b/.github/workflows/qiita-ci.yml @@ -94,7 +94,7 @@ jobs: conda activate qiita export QIITA_ROOTCA_CERT=`pwd`/qiita-dev/qiita_core/support_files/ci_rootca.crt export QIITA_CONFIG_FP=`pwd`/qiita-dev/qiita_core/support_files/config_test_local.cfg - sed "s#/home/runner/work/qiita/qiita#${PWD}/qiita-dev/#g" `pwd`/qiita-dev/qiita_core/support_files/config_test.cfg > ${QIITA_CONFIG_FP} + sed "s#/home/runner/work/qiita/qiita#${PWD}/qiita-dev#g" `pwd`/qiita-dev/qiita_core/support_files/config_test.cfg > ${QIITA_CONFIG_FP} export REDBIOM_HOST="http://localhost:7379" From 7c46442bfc049e828cfd3b204789869ce39c1f47 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Tue, 19 Aug 2025 18:03:41 +0200 Subject: [PATCH 22/30] remove debug, activate endpoint, reactivate all tests --- .github/workflows/qiita-ci.yml | 1 + qiita_client/tests/test_qiita_client.py | 48 ++++++++++++------------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/.github/workflows/qiita-ci.yml b/.github/workflows/qiita-ci.yml index 7c52dfb..446d2b9 100644 --- a/.github/workflows/qiita-ci.yml +++ b/.github/workflows/qiita-ci.yml @@ -95,6 +95,7 @@ jobs: export QIITA_ROOTCA_CERT=`pwd`/qiita-dev/qiita_core/support_files/ci_rootca.crt export QIITA_CONFIG_FP=`pwd`/qiita-dev/qiita_core/support_files/config_test_local.cfg sed "s#/home/runner/work/qiita/qiita#${PWD}/qiita-dev#g" `pwd`/qiita-dev/qiita_core/support_files/config_test.cfg > ${QIITA_CONFIG_FP} + sed -i "s/ENABLE_HTTPS_PLUGIN_FILETRANSFER = False/ENABLE_HTTPS_PLUGIN_FILETRANSFER = True/" ${QIITA_CONFIG_FP} export REDBIOM_HOST="http://localhost:7379" diff --git a/qiita_client/tests/test_qiita_client.py b/qiita_client/tests/test_qiita_client.py index 48ba7bf..15e4f77 100644 --- a/qiita_client/tests/test_qiita_client.py +++ b/qiita_client/tests/test_qiita_client.py @@ -412,35 +412,33 @@ def test_fetch_file_from_central(self): self.tester._plugincoupling = 'https' prefix = join(expanduser("~"), 'kurt') self.clean_up_files.append(prefix + fp) - print("STEFAN", fp, prefix) fp_obs = self.tester.fetch_file_from_central(fp, prefix=prefix) - print("STEFAN2", fp_obs) self.assertEqual(prefix + fp, fp_obs) self.assertTrue(filecmp.cmp(fp, fp_obs, shallow=False)) - # def test_push_file_to_central(self): - # self.tester._plugincoupling = 'filesystem' - - # ainfo = self.tester.get("/qiita_db/artifacts/%s/" % 1) - # fp = ainfo['files']['raw_forward_seqs'][0]['filepath'] - - # # mode: filesystem - # fp_obs = self.tester.push_file_to_central(fp) - # self.assertEqual(fp, fp_obs) - - # # non existing mode - # with self.assertRaises(ValueError): - # self.tester._plugincoupling = 'foo' - # self.tester.push_file_to_central(fp) - - # # change transfer mode to https - # self.tester._plugincoupling = 'https' - # fp_source = 'foo.bar' - # with open(fp_source, 'w') as f: - # f.write("this is a test\n") - # self.clean_up_files.append(fp_source) - # fp_obs = self.tester.push_file_to_central(fp_source) - # self.assertEqual(fp_source, fp_obs) + def test_push_file_to_central(self): + self.tester._plugincoupling = 'filesystem' + + ainfo = self.tester.get("/qiita_db/artifacts/%s/" % 1) + fp = ainfo['files']['raw_forward_seqs'][0]['filepath'] + + # mode: filesystem + fp_obs = self.tester.push_file_to_central(fp) + self.assertEqual(fp, fp_obs) + + # non existing mode + with self.assertRaises(ValueError): + self.tester._plugincoupling = 'foo' + self.tester.push_file_to_central(fp) + + # change transfer mode to https + self.tester._plugincoupling = 'https' + fp_source = 'foo.bar' + with open(fp_source, 'w') as f: + f.write("this is a test\n") + self.clean_up_files.append(fp_source) + fp_obs = self.tester.push_file_to_central(fp_source) + self.assertEqual(fp_source, fp_obs) if __name__ == '__main__': From d9543c9575f0171620c17f4e87897ed5cf52a905 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Tue, 19 Aug 2025 23:10:29 +0200 Subject: [PATCH 23/30] set path to / of only filename is given --- qiita_client/qiita_client.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 03d58e3..16a7c5e 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -841,9 +841,14 @@ def push_file_to_central(self, filepath): elif self._plugincoupling == 'https': logger.debug('Submitting file %s to qiita server.' % filepath) + # target path, i.e. without filename + dirpath = os.path.dirname(filepath) + if dirpath == "": + dirpath = "/" + self.post( '/cloud/push_file_to_central/', - files={os.path.dirname(filepath): open(filepath, 'rb')}) + files={dirpath: open(filepath, 'rb')}) return filepath From b9ade44b1d1a59425c95771e069ab1441549d8fa Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Wed, 20 Aug 2025 10:26:18 +0200 Subject: [PATCH 24/30] fix typo --- qiita_client/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiita_client/plugin.py b/qiita_client/plugin.py index b0cc4c6..71ff9b0 100644 --- a/qiita_client/plugin.py +++ b/qiita_client/plugin.py @@ -185,7 +185,7 @@ def __init__(self, name, version, description, publications=None, self.conf_fp = join(conf_dir, "%s_%s.conf" % (self.name, self.version)) def generate_config(self, env_script, start_script, server_cert=None, - plugin_couling=_ALLOWED_PLUGIN_COUPLINGS[0]): + plugin_coupling=_ALLOWED_PLUGIN_COUPLINGS[0]): """Generates the plugin configuration file Parameters @@ -216,7 +216,7 @@ def generate_config(self, env_script, start_script, server_cert=None, env_script, start_script, self._plugin_type, self.publications, server_cert, client_id, client_secret, - plugin_couling)) + plugin_coupling)) def _register_command(self, command): """Registers a command in the plugin From e336fbc60d2beee74b04f24574d4bb2636ed4f12 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Wed, 27 Aug 2025 10:54:55 +0200 Subject: [PATCH 25/30] enable environment variable, configuration file and default for "plugincoupling" --- qiita_client/plugin.py | 21 +++++++++++++++++++-- qiita_client/qiita_client.py | 1 + 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/qiita_client/plugin.py b/qiita_client/plugin.py index 71ff9b0..f57ee78 100644 --- a/qiita_client/plugin.py +++ b/qiita_client/plugin.py @@ -282,6 +282,24 @@ def __call__(self, server_url, job_id, output_dir): with open(self.conf_fp, 'U') as conf_file: config.readfp(conf_file) + # the plugin coupling protocoll can be set in three ways + # 1. default is always "filesystem", i.e. first value in + # _ALLOWED_PLUGIN_COUPLINGS. This is to be downward compatible. + # 2. the plugin configuration can hold a section 'network' with an + # option 'PLUGINCOUPLING'. For old config files, this might not + # (yet) be the case. Therefore, we are double checking existance + # of this section and parameter here. + # 3. you can set the environment variable QIITA_PLUGINCOUPLING + # Precedence is 3, 2, 1, i.e. the environment variable overrides the + # other two ways. + plugincoupling = self._ALLOWED_PLUGIN_COUPLINGS[0] + if config.has_section('network') and \ + config.has_option('network', 'PLUGINCOUPLING'): + plugincoupling = config.get('network', 'PLUGINCOUPLING') + if 'QIITA_PLUGINCOUPLING' in environ.keys() and \ + environ['QIITA_PLUGINCOUPLING'] is not None: + plugincoupling = environ['QIITA_PLUGINCOUPLING'] + qclient = QiitaClient(server_url, config.get('oauth2', 'CLIENT_ID'), config.get('oauth2', 'CLIENT_SECRET'), # for this group of tests, confirm optional @@ -290,8 +308,7 @@ def __call__(self, server_url, job_id, output_dir): # from validating the server's cert using # certifi's pem cache. ca_cert=config.get('oauth2', 'SERVER_CERT'), - plugincoupling=config.get('network', - 'PLUGINCOUPLING')) + plugincoupling=plugincoupling) if job_id == 'register': self._register(qclient) diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 16a7c5e..7246eb9 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -221,6 +221,7 @@ def __init__(self, server_url, client_id, client_secret, ca_cert=None, self._token = None self._fetch_token() + # store protocol for plugin coupling self._plugincoupling = plugincoupling def _fetch_token(self): From c2d597647b3a22792a7dcd88408895e81d33ceb6 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Wed, 27 Aug 2025 21:10:39 +0200 Subject: [PATCH 26/30] revert to qiita dev and remove ENABLE_HTTPS_PLUGIN_FILETRANSFER --- .github/workflows/qiita-ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/qiita-ci.yml b/.github/workflows/qiita-ci.yml index 446d2b9..d3c6558 100644 --- a/.github/workflows/qiita-ci.yml +++ b/.github/workflows/qiita-ci.yml @@ -52,9 +52,8 @@ jobs: # we need to download qiita directly so we have "easy" access to # all config files - # wget https://github.com/biocore/qiita/archive/dev.zip - # unzip dev.zip - git clone -b uncouplePlugins https://github.com/jlab/qiita.git qiita-dev + wget https://github.com/biocore/qiita/archive/dev.zip + unzip dev.zip # pull out the port so we can modify the configuration file easily pgport=${{ job.services.postgres.ports[5432] }} @@ -95,7 +94,6 @@ jobs: export QIITA_ROOTCA_CERT=`pwd`/qiita-dev/qiita_core/support_files/ci_rootca.crt export QIITA_CONFIG_FP=`pwd`/qiita-dev/qiita_core/support_files/config_test_local.cfg sed "s#/home/runner/work/qiita/qiita#${PWD}/qiita-dev#g" `pwd`/qiita-dev/qiita_core/support_files/config_test.cfg > ${QIITA_CONFIG_FP} - sed -i "s/ENABLE_HTTPS_PLUGIN_FILETRANSFER = False/ENABLE_HTTPS_PLUGIN_FILETRANSFER = True/" ${QIITA_CONFIG_FP} export REDBIOM_HOST="http://localhost:7379" From 65e6f4805b4362f7d37f23efb1028e67a5302d02 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 29 Aug 2025 08:23:01 +0200 Subject: [PATCH 27/30] address Antonio's great suggestions --- qiita_client/plugin.py | 18 +++++++++--------- qiita_client/qiita_client.py | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/qiita_client/plugin.py b/qiita_client/plugin.py index f57ee78..6f47b2f 100644 --- a/qiita_client/plugin.py +++ b/qiita_client/plugin.py @@ -136,11 +136,11 @@ def __init__(self, name, description, can_be_submitted_to_ebi, class BaseQiitaPlugin(object): - # default must be first element - _ALLOWED_PLUGIN_COUPLINGS = ['filesystem', 'https'] + _DEFAULT_PLUGIN_COUPLINGS = 'filesystem' + _ALLOWED_PLUGIN_COUPLINGS = [_DEFAULT_PLUGIN_COUPLINGS, 'https'] def __init__(self, name, version, description, publications=None, - plugincoupling=_ALLOWED_PLUGIN_COUPLINGS[0]): + plugincoupling=_DEFAULT_PLUGIN_COUPLINGS): logger.debug('Entered BaseQiitaPlugin.__init__()') self.name = name self.version = version @@ -185,7 +185,7 @@ def __init__(self, name, version, description, publications=None, self.conf_fp = join(conf_dir, "%s_%s.conf" % (self.name, self.version)) def generate_config(self, env_script, start_script, server_cert=None, - plugin_coupling=_ALLOWED_PLUGIN_COUPLINGS[0]): + plugin_coupling=_DEFAULT_PLUGIN_COUPLINGS): """Generates the plugin configuration file Parameters @@ -201,7 +201,7 @@ def generate_config(self, env_script, start_script, server_cert=None, HTTPS to it plugin_coupling : str Type of coupling of plugin to central for file exchange. - Valid values are 'filesystem' and 'https'. + Valid values: see _ALLOWED_PLUGIN_COUPLINGS. """ logger.debug('Entered BaseQiitaPlugin.generate_config()') sr = SystemRandom() @@ -283,8 +283,8 @@ def __call__(self, server_url, job_id, output_dir): config.readfp(conf_file) # the plugin coupling protocoll can be set in three ways - # 1. default is always "filesystem", i.e. first value in - # _ALLOWED_PLUGIN_COUPLINGS. This is to be downward compatible. + # 1. default is always "filesystem", i.e. _DEFAULT_PLUGIN_COUPLINGS + # This is to be downward compatible. # 2. the plugin configuration can hold a section 'network' with an # option 'PLUGINCOUPLING'. For old config files, this might not # (yet) be the case. Therefore, we are double checking existance @@ -292,7 +292,7 @@ def __call__(self, server_url, job_id, output_dir): # 3. you can set the environment variable QIITA_PLUGINCOUPLING # Precedence is 3, 2, 1, i.e. the environment variable overrides the # other two ways. - plugincoupling = self._ALLOWED_PLUGIN_COUPLINGS[0] + plugincoupling = self._DEFAULT_PLUGIN_COUPLINGS if config.has_section('network') and \ config.has_option('network', 'PLUGINCOUPLING'): plugincoupling = config.get('network', 'PLUGINCOUPLING') @@ -372,7 +372,7 @@ class QiitaTypePlugin(BaseQiitaPlugin): def __init__(self, name, version, description, validate_func, html_generator_func, artifact_types, publications=None, - plugincoupling=BaseQiitaPlugin._ALLOWED_PLUGIN_COUPLINGS[0]): + plugincoupling=BaseQiitaPlugin._DEFAULT_PLUGIN_COUPLINGS): super(QiitaTypePlugin, self).__init__(name, version, description, publications=publications, plugincoupling=plugincoupling) diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 7246eb9..478945e 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -744,7 +744,7 @@ def _process_files_per_sample_fastq(self, files, prep_info, return sample_names, prep_info - def fetch_file_from_central(self, filepath, prefix=''): + def fetch_file_from_central(self, filepath, prefix=None): """Moves content of a file from Qiita's central BASE_DATA_DIR to a local plugin file-system. @@ -764,7 +764,7 @@ def fetch_file_from_central(self, filepath, prefix=''): Primarily for testing: prefix the target filepath with this filepath prefix to a) in 'filesystem' mode: create an actual file copy (for testing) - If prefix='', nothing will be copied/moved + If prefix=None, nothing will be copied/moved b) in 'https' mode: flexibility to locate files differently in plugin local file system. @@ -773,7 +773,7 @@ def fetch_file_from_central(self, filepath, prefix=''): str : the filepath of the requested file within the local file system """ target_filepath = filepath - if prefix != '': + if (prefix is not None) and (prefix != ""): # strip off root if filepath.startswith(os.path.abspath(os.sep)): target_filepath = target_filepath[ From 7b6ca636008b67858a2bf503d16c9a892de5a18e Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 29 Aug 2025 08:41:17 +0200 Subject: [PATCH 28/30] promoting comment to part of readme --- README.md | 29 +++++++++++++++++++++++++++++ qiita_client/plugin.py | 10 ---------- qiita_client/qiita_client.py | 4 ++-- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 2f7eb4d..21f7855 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,32 @@ Also, if Qiita is running with the default server SSL certificate, you need to e export QIITA_ROOT_CA=/qiita_core/support_files/ci_rootca.crt ``` + +Configure for cloud computing +----------------------------- +In the default scenario, Qiita main and Qiita plugins are executed on the same +machines, maybe spread across a Slurm or other grid compute cluster, but main +and plugins have direct access to all files in `BASE_DATA_DIR`. + +This can be different, if you set up Qiita within a cloud compute environment, +where main and plugins do **not** share one file system. In this case, input- +files must first be transferred from main to plugin, then plugin can do its +processing and resulting files must be transferred back to main, once +processing is finished. To achieve this, the qiita_client, as it is part of +each plugin, provides the two functions for this file transfer +`fetch_file_from_central` and `push_file_to_central`. According to +`self._plugincoupling`, these functions operate on different "protocols"; +as of 2025-08-29, either "filesystem" or "https". Switch to **"https"** for +cloud environments, default is **filesystem**. + +The plugin coupling protocoll can be set in three ways + + 1. default is always "filesystem", i.e. _DEFAULT_PLUGIN_COUPLINGS + This is to be downward compatible. + 2. the plugin configuration can hold a section 'network' with an + option 'PLUGINCOUPLING'. For old config files, this might not + (yet) be the case. Therefore, we are double checking existance + of this section and parameter here. + 3. you can set the environment variable QIITA_PLUGINCOUPLING + Precedence is 3, 2, 1, i.e. the environment variable overrides the + other two ways. diff --git a/qiita_client/plugin.py b/qiita_client/plugin.py index 6f47b2f..d455e48 100644 --- a/qiita_client/plugin.py +++ b/qiita_client/plugin.py @@ -282,16 +282,6 @@ def __call__(self, server_url, job_id, output_dir): with open(self.conf_fp, 'U') as conf_file: config.readfp(conf_file) - # the plugin coupling protocoll can be set in three ways - # 1. default is always "filesystem", i.e. _DEFAULT_PLUGIN_COUPLINGS - # This is to be downward compatible. - # 2. the plugin configuration can hold a section 'network' with an - # option 'PLUGINCOUPLING'. For old config files, this might not - # (yet) be the case. Therefore, we are double checking existance - # of this section and parameter here. - # 3. you can set the environment variable QIITA_PLUGINCOUPLING - # Precedence is 3, 2, 1, i.e. the environment variable overrides the - # other two ways. plugincoupling = self._DEFAULT_PLUGIN_COUPLINGS if config.has_section('network') and \ config.has_option('network', 'PLUGINCOUPLING'): diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 478945e..091733e 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -773,7 +773,7 @@ def fetch_file_from_central(self, filepath, prefix=None): str : the filepath of the requested file within the local file system """ target_filepath = filepath - if (prefix is not None) and (prefix != ""): + if not prefix: # strip off root if filepath.startswith(os.path.abspath(os.sep)): target_filepath = target_filepath[ @@ -782,7 +782,7 @@ def fetch_file_from_central(self, filepath, prefix=None): target_filepath = os.path.join(prefix, target_filepath) if self._plugincoupling == 'filesystem': - if prefix != '': + if not prefix: # create necessary directory locally os.makedirs(os.path.dirname(target_filepath), exist_ok=True) From 296e32c4910de8365e7085781223cf485c47faaf Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 29 Aug 2025 08:47:13 +0200 Subject: [PATCH 29/30] fix formatting --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 21f7855..a921528 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,12 @@ cloud environments, default is **filesystem**. The plugin coupling protocoll can be set in three ways - 1. default is always "filesystem", i.e. _DEFAULT_PLUGIN_COUPLINGS - This is to be downward compatible. - 2. the plugin configuration can hold a section 'network' with an - option 'PLUGINCOUPLING'. For old config files, this might not - (yet) be the case. Therefore, we are double checking existance - of this section and parameter here. - 3. you can set the environment variable QIITA_PLUGINCOUPLING - Precedence is 3, 2, 1, i.e. the environment variable overrides the - other two ways. +1. default is always `filesystem`, i.e. `_DEFAULT_PLUGIN_COUPLINGS` + This is to be downward compatible. +2. the plugin configuration can hold a section `network` with an + option `PLUGINCOUPLING`. For old config files, this might not + (yet) be the case. Therefore, we are double checking existance + of this section and parameter here. +3. you can set the environment variable `QIITA_PLUGINCOUPLING` + Precedence is 3, 2, 1, i.e. the environment variable overrides + the other two ways. \ No newline at end of file From 384e97de643653e75b20f42b9c1516bfb6ec5af6 Mon Sep 17 00:00:00 2001 From: Stefan Janssen Date: Fri, 29 Aug 2025 08:51:07 +0200 Subject: [PATCH 30/30] "not prefix" did not match properly --- qiita_client/qiita_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiita_client/qiita_client.py b/qiita_client/qiita_client.py index 091733e..4b34613 100644 --- a/qiita_client/qiita_client.py +++ b/qiita_client/qiita_client.py @@ -773,7 +773,7 @@ def fetch_file_from_central(self, filepath, prefix=None): str : the filepath of the requested file within the local file system """ target_filepath = filepath - if not prefix: + if (prefix is not None) and (prefix != ""): # strip off root if filepath.startswith(os.path.abspath(os.sep)): target_filepath = target_filepath[ @@ -782,7 +782,7 @@ def fetch_file_from_central(self, filepath, prefix=None): target_filepath = os.path.join(prefix, target_filepath) if self._plugincoupling == 'filesystem': - if not prefix: + if (prefix is not None) and (prefix != ""): # create necessary directory locally os.makedirs(os.path.dirname(target_filepath), exist_ok=True)