From 414156af8aa6f9dd42cc96abe5c183262cbdd44c Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 15 Aug 2022 17:55:24 +0200 Subject: [PATCH 1/8] Fix data_range_{min,max} parsing All datetime values established via our sqlalchemy models don't include `tzinfo`. Fixes https://github.com/galaxyproject/galaxy/issues/14094#issuecomment-1215166187. In general this is a bad thing, we should definitely be honoring timezone info, but the rest of the app just assumes UTC, so this will work. --- lib/galaxy/schema/schema.py | 9 ++++++--- lib/galaxy/schema/types.py | 15 +++++++++++++++ lib/galaxy/webapps/galaxy/api/jobs.py | 5 +++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/schema/schema.py b/lib/galaxy/schema/schema.py index b863546c343..cbbad2608ea 100644 --- a/lib/galaxy/schema/schema.py +++ b/lib/galaxy/schema/schema.py @@ -39,7 +39,10 @@ from galaxy.schema.fields import ( EncodedDatabaseIdField, ModelClassField, ) -from galaxy.schema.types import RelativeUrl +from galaxy.schema.types import ( + OffsetNaiveDatetime, + RelativeUrl, +) USER_MODEL_CLASS_NAME = "User" GROUP_MODEL_CLASS_NAME = "Group" @@ -1112,8 +1115,8 @@ class JobIndexQueryPayload(Model): user_id: Optional[DecodedDatabaseIdField] = None tool_ids: Optional[List[str]] = None tool_ids_like: Optional[List[str]] = None - date_range_min: Optional[Union[datetime, date]] = None - date_range_max: Optional[Union[datetime, date]] = None + date_range_min: Optional[Union[OffsetNaiveDatetime, date]] = None + date_range_max: Optional[Union[OffsetNaiveDatetime, date]] = None history_id: Optional[DecodedDatabaseIdField] = None workflow_id: Optional[DecodedDatabaseIdField] = None invocation_id: Optional[DecodedDatabaseIdField] = None diff --git a/lib/galaxy/schema/types.py b/lib/galaxy/schema/types.py index cf39c391d3a..c78c57767c5 100644 --- a/lib/galaxy/schema/types.py +++ b/lib/galaxy/schema/types.py @@ -1,3 +1,6 @@ +from datetime import datetime + +from pydantic.datetime_parse import parse_datetime from typing_extensions import Literal # Relative URLs cannot be validated with AnyUrl, they need a scheme. @@ -5,3 +8,15 @@ from typing_extensions import Literal RelativeUrl = str LatestLiteral = Literal["latest"] + + +class OffsetNaiveDatetime(datetime): + @classmethod + def __get_validators__(cls): + yield cls.validate + + @classmethod + def validate(cls, v): + v = parse_datetime(v) + v = v.replace(tzinfo=None) + return v diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py index d88ba5c40f8..1eb13167b58 100644 --- a/lib/galaxy/webapps/galaxy/api/jobs.py +++ b/lib/galaxy/webapps/galaxy/api/jobs.py @@ -38,6 +38,7 @@ from galaxy.managers.jobs import ( ) from galaxy.schema.fields import EncodedDatabaseIdField from galaxy.schema.schema import JobIndexSortByEnum +from galaxy.schema.types import OffsetNaiveDatetime from galaxy.util import listify from galaxy.web import ( expose_api, @@ -103,13 +104,13 @@ ToolIdLikeQueryParam: Optional[str] = Query( description="Limit listing of jobs to those that match one of the included tool ID sql-like patterns. If none, all are returned", ) -DateRangeMinQueryParam: Optional[Union[datetime, date]] = Query( +DateRangeMinQueryParam: Optional[Union[OffsetNaiveDatetime, date]] = Query( default=None, title="Date Range Minimum", description="Limit listing of jobs to those that are updated after specified date (e.g. '2014-01-01')", ) -DateRangeMaxQueryParam: Optional[Union[datetime, date]] = Query( +DateRangeMaxQueryParam: Optional[Union[OffsetNaiveDatetime, date]] = Query( default=None, title="Date Range Maximum", description="Limit listing of jobs to those that are updated before specified date (e.g. '2014-01-01')", From 3522fd3a49507c2fe5abce397a66d73a12e9c72c Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Mon, 15 Aug 2022 13:20:10 -0400 Subject: [PATCH 2/8] Add log rotation options and update logging documentation --- doc/source/admin/config_logging.rst | 14 ++++++ doc/source/admin/galaxy_options.rst | 50 +++++++++++++++++++-- lib/galaxy/config/__init__.py | 11 ++++- lib/galaxy/config/sample/galaxy.yml.sample | 29 ++++++++++-- lib/galaxy/config/schemas/config_schema.yml | 36 +++++++++++++-- 5 files changed, 126 insertions(+), 14 deletions(-) diff --git a/doc/source/admin/config_logging.rst b/doc/source/admin/config_logging.rst index faeba11ddc5..cd6e4460a9a 100644 --- a/doc/source/admin/config_logging.rst +++ b/doc/source/admin/config_logging.rst @@ -45,6 +45,20 @@ To change the log file name or location, use the ``$GALAXY_LOG`` environment var $ GALAXY_LOG=/path/to/galaxy/logfile sh run.sh --daemon +It is also possible to specify the path to the log file using the ``log_destination`` configuration option in +``galaxy.yml``. Additionally, it is possible to automatically rotate logs once the log file reaches a given size, using +the ``log_rotate_size`` and ``log_rotate_count`` options, which control the size at which the log is rotated, and the +number of rotated logs to keep, respectively: + +.. code-block:: yaml + + galaxy: + # Set log file path + log_destination: /srv/galaxy/log/galaxy.log + # Rotate once log reaches 100 MB + log_rotate_size: 100 MB + # Keep the 10 most recent log files + log_rotate_count: 10 Advanced Configuration ---------------------------- diff --git a/doc/source/admin/galaxy_options.rst b/doc/source/admin/galaxy_options.rst index b38797b620b..4e710bf3eba 100644 --- a/doc/source/admin/galaxy_options.rst +++ b/doc/source/admin/galaxy_options.rst @@ -2576,12 +2576,55 @@ :Type: bool +~~~~~~~~~~~~~~~~~~~ +``log_destination`` +~~~~~~~~~~~~~~~~~~~ + +:Description: + Log destination, defaults to special value "stdout" that logs to + standard output. If set to anything else, then it will be + interpreted as a path that will be used as the log file, and + logging to stdout will be disabled. +:Default: ``stdout`` +:Type: str + + +~~~~~~~~~~~~~~~~~~~ +``log_rotate_size`` +~~~~~~~~~~~~~~~~~~~ + +:Description: + Size of log file at which size it will be rotated as per the + documentation in + https://docs.python.org/library/logging.handlers.html#logging.handlers.RotatingFileHandler + If log_rotate_count is not also set, no log rotation will be + performed. A value of 0 (the default) means no rotation. Size can + be a number of bytes or a human-friendly representation like "100 + MB" or "1G". +:Default: ``0`` +:Type: str + + +~~~~~~~~~~~~~~~~~~~~ +``log_rotate_count`` +~~~~~~~~~~~~~~~~~~~~ + +:Description: + Number of log file backups to keep, per the documentation in + https://docs.python.org/library/logging.handlers.html#logging.handlers.RotatingFileHandler + Any additional rotated log files will automatically be pruned. If + log_rotate_size is not also set, no log rotation will be + performed. A value of 0 (the default) means no rotation. +:Default: ``0`` +:Type: int + + ~~~~~~~~~~~~~ ``log_level`` ~~~~~~~~~~~~~ :Description: - Verbosity of console log messages. Acceptable values can be found + Verbosity of console log messages. Acceptable values can be found here: https://docs.python.org/library/logging.html#logging-levels A custom debug level of "TRACE" is available for even more verbosity. @@ -2594,9 +2637,8 @@ ~~~~~~~~~~~ :Description: - Controls where and how the server logs messages. If unset, the - default is to log all messages to standard output at the level - defined by the `log_level` configuration option. Configuration is + Controls where and how the server logs messages. If set, overrides + all settings in the log_* configuration options. Configuration is described in the documentation at: https://docs.galaxyproject.org/en/master/admin/config_logging.html :Default: ``None`` diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index f8c1bf888ab..5a8aa0b82d9 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -35,6 +35,7 @@ from galaxy.config.schema import AppSchema from galaxy.exceptions import ConfigurationError from galaxy.util import ( listify, + size_to_bytes, string_as_bool, unicodify, ) @@ -1102,6 +1103,8 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): } log_destination = kwargs.get("log_destination") + log_rotate_size = size_to_bytes(unicodify(kwargs.get("log_rotate_size", 0))) + log_rotate_count = int(kwargs.get("log_rotate_count", 0)) galaxy_daemon_log_destination = os.environ.get("GALAXY_DAEMON_LOG") if log_destination == "stdout": LOGGING_CONFIG_DEFAULT["handlers"]["console"] = { @@ -1113,19 +1116,23 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): } elif log_destination: LOGGING_CONFIG_DEFAULT["handlers"]["console"] = { - "class": "logging.FileHandler", + "class": "logging.handlers.RotatingFileHandler", "formatter": "stack", "level": "DEBUG", "filename": log_destination, "filters": ["stack"], + "maxBytes": log_rotate_size, + "backupCount": log_rotate_count, } if galaxy_daemon_log_destination: LOGGING_CONFIG_DEFAULT["handlers"]["files"] = { - "class": "logging.FileHandler", + "class": "logging.handlers.RotatingFileHandler", "formatter": "stack", "level": "DEBUG", "filename": galaxy_daemon_log_destination, "filters": ["stack"], + "maxBytes": log_rotate_size, + "backupCount": log_rotate_count, } LOGGING_CONFIG_DEFAULT["root"]["handlers"].append("files") diff --git a/lib/galaxy/config/sample/galaxy.yml.sample b/lib/galaxy/config/sample/galaxy.yml.sample index ca1d45b86e2..af7aceedef2 100644 --- a/lib/galaxy/config/sample/galaxy.yml.sample +++ b/lib/galaxy/config/sample/galaxy.yml.sample @@ -1360,14 +1360,35 @@ galaxy: # "loggers" section does not appear in this configuration file. #auto_configure_logging: true - # Verbosity of console log messages. Acceptable values can be found + # Log destination, defaults to special value "stdout" that logs to + # standard output. If set to anything else, then it will be + # interpreted as a path that will be used as the log file, and logging + # to stdout will be disabled. + #log_destination: stdout + + # Size of log file at which size it will be rotated as per the + # documentation in + # https://docs.python.org/library/logging.handlers.html#logging.handlers.RotatingFileHandler + # If log_rotate_count is not also set, no log rotation will be + # performed. A value of 0 (the default) means no rotation. Size can be + # a number of bytes or a human-friendly representation like "100 MB" + # or "1G". + #log_rotate_size: '0' + + # Number of log file backups to keep, per the documentation in + # https://docs.python.org/library/logging.handlers.html#logging.handlers.RotatingFileHandler + # Any additional rotated log files will automatically be pruned. If + # log_rotate_size is not also set, no log rotation will be performed. + # A value of 0 (the default) means no rotation. + #log_rotate_count: 0 + + # Verbosity of console log messages. Acceptable values can be found # here: https://docs.python.org/library/logging.html#logging-levels A # custom debug level of "TRACE" is available for even more verbosity. #log_level: DEBUG - # Controls where and how the server logs messages. If unset, the - # default is to log all messages to standard output at the level - # defined by the `log_level` configuration option. Configuration is + # Controls where and how the server logs messages. If set, overrides + # all settings in the log_* configuration options. Configuration is # described in the documentation at: # https://docs.galaxyproject.org/en/master/admin/config_logging.html #logging: null diff --git a/lib/galaxy/config/schemas/config_schema.yml b/lib/galaxy/config/schemas/config_schema.yml index 97fc75b12c8..18efba8fa47 100644 --- a/lib/galaxy/config/schemas/config_schema.yml +++ b/lib/galaxy/config/schemas/config_schema.yml @@ -1871,12 +1871,41 @@ mapping: If true, Galaxy will attempt to configure a simple root logger if a "loggers" section does not appear in this configuration file. + log_destination: + type: str + default: stdout + required: false + desc: | + Log destination, defaults to special value "stdout" that logs to standard output. If set to anything else, + then it will be interpreted as a path that will be used as the log file, and logging to stdout will be + disabled. + + log_rotate_size: + type: str + default: "0" + required: false + desc: | + Size of log file at which size it will be rotated as per the documentation in + https://docs.python.org/library/logging.handlers.html#logging.handlers.RotatingFileHandler + If log_rotate_count is not also set, no log rotation will be performed. A value of 0 (the default) means no + rotation. Size can be a number of bytes or a human-friendly representation like "100 MB" or "1G". + + log_rotate_count: + type: int + default: 0 + required: false + desc: | + Number of log file backups to keep, per the documentation in + https://docs.python.org/library/logging.handlers.html#logging.handlers.RotatingFileHandler + Any additional rotated log files will automatically be pruned. If log_rotate_size is not also set, no log + rotation will be performed. A value of 0 (the default) means no rotation. + log_level: type: str default: DEBUG required: false desc: | - Verbosity of console log messages. Acceptable values can be found here: + Verbosity of console log messages. Acceptable values can be found here: https://docs.python.org/library/logging.html#logging-levels A custom debug level of "TRACE" is available for even more verbosity. @@ -1884,9 +1913,8 @@ mapping: type: map allowempty: true desc: | - Controls where and how the server logs messages. If unset, the default is to log all messages to standard - output at the level defined by the `log_level` configuration option. Configuration is described in the - documentation at: + Controls where and how the server logs messages. If set, overrides all settings in the log_* configuration + options. Configuration is described in the documentation at: https://docs.galaxyproject.org/en/master/admin/config_logging.html database_engine_option_echo: From cd76fef415507e78fe1d1d48ff1099cd78f5fa7f Mon Sep 17 00:00:00 2001 From: Marius van den Beek Date: Tue, 16 Aug 2022 13:11:21 +0200 Subject: [PATCH 3/8] Convert to UTC instead of stripping timezone Co-authored-by: Nicola Soranzo --- lib/galaxy/schema/types.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/galaxy/schema/types.py b/lib/galaxy/schema/types.py index c78c57767c5..49b42126907 100644 --- a/lib/galaxy/schema/types.py +++ b/lib/galaxy/schema/types.py @@ -18,5 +18,4 @@ class OffsetNaiveDatetime(datetime): @classmethod def validate(cls, v): v = parse_datetime(v) - v = v.replace(tzinfo=None) - return v + return v.replace(tzinfo=None) - v.utcoffset() if v.tzinfo else v From b0d9cff6c4ae7052607131b156bcd46af5fd8edc Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 16 Aug 2022 13:11:54 +0200 Subject: [PATCH 4/8] Add unit test That was meant to go into the first commit ... --- test/unit/webapps/api/test_datetime_parsing.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/unit/webapps/api/test_datetime_parsing.py diff --git a/test/unit/webapps/api/test_datetime_parsing.py b/test/unit/webapps/api/test_datetime_parsing.py new file mode 100644 index 00000000000..7b979449ee5 --- /dev/null +++ b/test/unit/webapps/api/test_datetime_parsing.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel + +from galaxy.schema.types import OffsetNaiveDatetime + + +class Time(BaseModel): + time: OffsetNaiveDatetime + + +def test_naive_datetime_parsing(): + with_zulu = Time(time="2022-08-15T11:29:32.853974Z") + without_zulu = Time(time="2022-08-15T11:29:32.853974") + assert with_zulu.time == without_zulu.time + assert not with_zulu.time.tzinfo From d1b37347da664b6b6b9cae98d3ec5d5d1d8a2f8d Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Tue, 16 Aug 2022 12:36:06 -0400 Subject: [PATCH 5/8] Disable translogger middleware by default, use gunicorn.access (actually uvicorn.access) for transaction logging. --- lib/galaxy/config/__init__.py | 6 ++++++ lib/galaxy/webapps/galaxy/buildapp.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index f8c1bf888ab..ff36a65105f 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -99,6 +99,12 @@ LOGGING_CONFIG_DEFAULT: Dict[str, Any] = { "level": "INFO", "qualname": "botocore", }, + "gunicorn.access": { + "level": "INFO", + "qualname": "gunicorn.access", + "propagate": False, + "handlers": ["console"], + }, }, "filters": { "stack": { diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 110e582a28e..90ab6cd8174 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -1390,7 +1390,7 @@ def wrap_in_middleware(app, global_conf, application_stack, **local_conf): # Error middleware app = wrap_if_allowed(app, stack, ErrorMiddleware, args=(conf,)) # Transaction logging (apache access.log style) - if asbool(conf.get("use_translogger", True)): + if asbool(conf.get("use_translogger", False)): from galaxy.web.framework.middleware.translogger import TransLogger app = wrap_if_allowed(app, stack, TransLogger) From 6eb034706f34f134b12ca13bab3e631b40c5e785 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 17 Aug 2022 09:46:36 +0200 Subject: [PATCH 6/8] Fix position of cleanenv flag in singularity command Fixes https://github.com/galaxyproject/galaxy/issues/14408#issuecomment-1216742827 --- lib/galaxy/tool_util/deps/singularity_util.py | 4 ++-- test/unit/tool_util/test_singularity_util.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 test/unit/tool_util/test_singularity_util.py diff --git a/lib/galaxy/tool_util/deps/singularity_util.py b/lib/galaxy/tool_util/deps/singularity_util.py index 33d8559cd33..0a97f052210 100644 --- a/lib/galaxy/tool_util/deps/singularity_util.py +++ b/lib/galaxy/tool_util/deps/singularity_util.py @@ -75,10 +75,10 @@ def build_singularity_run_command( sudo=sudo, sudo_cmd=sudo_cmd, ) - if cleanenv: - command_parts.append("--cleanenv") command_parts.append("-s") command_parts.append("exec") + if cleanenv: + command_parts.append("--cleanenv") for volume in volumes: command_parts.extend(["-B", str(volume)]) if home is not None: diff --git a/test/unit/tool_util/test_singularity_util.py b/test/unit/tool_util/test_singularity_util.py new file mode 100644 index 00000000000..8a02b2c97ee --- /dev/null +++ b/test/unit/tool_util/test_singularity_util.py @@ -0,0 +1,18 @@ +from galaxy.tool_util.deps.singularity_util import build_singularity_run_command + + +def test_build_singularity_run_command_defaults(): + cmd = build_singularity_run_command( + container_command="echo hi", + image="busybox", + ) + assert cmd == "singularity -s exec --cleanenv busybox echo hi" + + +def test_build_singularity_run_command_no_cleanenv(): + cmd = build_singularity_run_command( + container_command="echo hi", + image="busybox", + cleanenv=False, + ) + assert cmd == "singularity -s exec busybox echo hi" From 973d0779b65993f0252531533717790fea759870 Mon Sep 17 00:00:00 2001 From: Marius van den Beek Date: Wed, 17 Aug 2022 18:43:57 +0200 Subject: [PATCH 7/8] Include timezone testing Co-authored-by: Nicola Soranzo --- test/unit/webapps/api/test_datetime_parsing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/webapps/api/test_datetime_parsing.py b/test/unit/webapps/api/test_datetime_parsing.py index 7b979449ee5..29ea26b14ff 100644 --- a/test/unit/webapps/api/test_datetime_parsing.py +++ b/test/unit/webapps/api/test_datetime_parsing.py @@ -8,7 +8,7 @@ class Time(BaseModel): def test_naive_datetime_parsing(): - with_zulu = Time(time="2022-08-15T11:29:32.853974Z") - without_zulu = Time(time="2022-08-15T11:29:32.853974") - assert with_zulu.time == without_zulu.time - assert not with_zulu.time.tzinfo + with_tz = Time(time="2022-08-15T11:29:32.853974+02:00") + without_tz = Time(time="2022-08-15T09:29:32.853974") + assert with_tz.time == without_tz.time + assert with_tz.time.tzinfo is None From 69ca03e99a2e1cc33866a092579a4a3cdd1500b4 Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Wed, 17 Aug 2022 13:48:39 -0400 Subject: [PATCH 8/8] Bump Gravity version to 0.13.3 --- lib/galaxy/dependencies/dev-requirements.txt | 2 +- lib/galaxy/dependencies/pinned-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/dependencies/dev-requirements.txt b/lib/galaxy/dependencies/dev-requirements.txt index 69a9f4d96dd..917865e7188 100644 --- a/lib/galaxy/dependencies/dev-requirements.txt +++ b/lib/galaxy/dependencies/dev-requirements.txt @@ -69,7 +69,7 @@ fs==2.4.16 funcsigs==1.0.2 future==0.18.2; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") galaxy-sequence-utils==1.1.5 -gravity==0.13.2; python_version >= "3.6" +gravity==0.13.3; python_version >= "3.6" greenlet==1.1.2; python_version >= "3" and python_full_version < "3.0.0" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7") and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0") or python_version >= "3" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7") and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0") and python_full_version >= "3.5.0" gunicorn==20.1.0; python_version >= "3.5" gxformat2==0.15.0 diff --git a/lib/galaxy/dependencies/pinned-requirements.txt b/lib/galaxy/dependencies/pinned-requirements.txt index 6c3aab42a90..ab447226945 100644 --- a/lib/galaxy/dependencies/pinned-requirements.txt +++ b/lib/galaxy/dependencies/pinned-requirements.txt @@ -57,7 +57,7 @@ fs==2.4.16 funcsigs==1.0.2 future==0.18.2; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") galaxy-sequence-utils==1.1.5 -gravity==0.13.2; python_version >= "3.6" +gravity==0.13.3; python_version >= "3.6" greenlet==1.1.2; python_version >= "3" and python_full_version < "3.0.0" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7") and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0") or python_version >= "3" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.7") and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.0") and python_full_version >= "3.5.0" gunicorn==20.1.0; python_version >= "3.5" gxformat2==0.15.0