From ed98cb98b74131c6631f1afdc32c69d9068a6f7b Mon Sep 17 00:00:00 2001 From: mattkram Date: Sun, 7 Jul 2024 22:50:36 -0500 Subject: [PATCH 01/34] Remove anaconda entrypoint --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 5262862e..54b9905d 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,6 @@ packages=setuptools.find_packages(include=['binstar_client', 'binstar_client.*']), entry_points={ 'console_scripts': [ - 'anaconda = binstar_client.scripts.cli:main', 'binstar = binstar_client.scripts.cli:main', 'conda-server = binstar_client.scripts.cli:main', ], From a9c1c2964e6ed5b93536065030ab5caabd5737d8 Mon Sep 17 00:00:00 2001 From: mattkram Date: Mon, 8 Jul 2024 00:02:46 -0500 Subject: [PATCH 02/34] Add a plugin to anaconda-cli-base --- binstar_client/plugins.py | 161 ++++++++++++++++++++++++++++++++++++++ setup.py | 3 + 2 files changed, 164 insertions(+) create mode 100644 binstar_client/plugins.py diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py new file mode 100644 index 00000000..523178e6 --- /dev/null +++ b/binstar_client/plugins.py @@ -0,0 +1,161 @@ +"""Wrappers and functions to handle loading of legacy anaconda-client subcommands into the new CLI. + +A one-stop-shop for maintaining compatibility and helping to gracefully migrate & deprecate. + +""" + +import logging +import sys +from argparse import ArgumentParser +from typing import Any +from typing import Callable +from typing import List +from typing import Optional +from typing import Set + +from binstar_client import commands as command_module +from binstar_client.scripts.cli import ( + _add_subparser_modules as add_subparser_modules, +) +from binstar_client.scripts.cli import main as binstar_main + +from anaconda_cli_base.cli import app as main_app +from typer import Context, Typer + +# All subcommands in anaconda-client +LEGACY_SUBCOMMANDS = { + "auth", + "channel", + "config", + "copy", + "download", + "groups", + "label", + "login", + "logout", + "move", + "notebook", + "package", + "remove", + "search", + "show", + "update", + "upload", + "whoami", +} +# These subcommands will be shown in the top-level help +NON_HIDDEN_SUBCOMMANDS = { + "auth", + "channel", + "config", + "copy", + "download", + "groups", + "label", + "login", + "logout", + "move", + "notebook", + "package", + "remove", + "search", + "show", + "update", + "upload", + "whoami", +} +# Any subcommands that should emit deprecation warnings, and show as deprecated in the help +DEPRECATED_SUBCOMMANDS: Set[str] = set() + +# The logger +log = logging.getLogger(__name__) + +app = Typer( + add_completion=False, + name="org", + help="Interact with anaconda.org", + no_args_is_help=True, +) + + +def _get_help_text(parser: ArgumentParser, name: str) -> str: + """Extract the help text from the anaconda-client CLI Argument Parser.""" + if parser._subparsers is None: + return "" + if parser._subparsers._actions is None: + return "" + if parser._subparsers._actions[1].choices is None: + return "" + subcommand_parser = dict(parser._subparsers._actions[1].choices).get(name) + if subcommand_parser is None: + return "" + description = subcommand_parser.description + if description is None: + return "" + return description.strip() + + +def _deprecate(name: str, f: Callable) -> Callable: + def new_f(ctx: Context) -> Any: + if name in DEPRECATED_SUBCOMMANDS: + log.warning( + "The existing anaconda-client commands will be deprecated. To maintain compatibility, " + "please either pin `anaconda-client<2` or update your system call with the `org` prefix, " + f'e.g. "anaconda org {name} ..."' + ) + return f(ctx) + + return new_f + + +def subcommand_function(ctx: Context) -> None: + # Here, we are using the ctx instead of sys.argv because the test invoker doesn't + # use sys.argv + args = [] + if ctx.info_name is not None: + args.append(ctx.info_name) + args.extend(ctx.args) + legacy_main(args=args) + + +def load_legacy_subcommands() -> None: + """Load each of the legacy subcommands into its own typer subcommand. + + This allows them to be called from the new CLI, without having to manually migrate. + + """ + + parser = ArgumentParser() + add_subparser_modules(parser, command_module) + + for name in LEGACY_SUBCOMMANDS: + + # Define all of the subcommands in the typer app + # TODO: Can we load the arguments, or at least the docstring to make the help nicer? + help_text = _get_help_text(parser, name) + app.command( + name=name, + help=help_text, + context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, + )(subcommand_function) + + # Mount some CLI subcommands at the top-level, but optionally emit a deprecation warning + help_text = f"anaconda.org: {help_text + ' ' if help_text else ''}(alias for 'anaconda org {name}')" + if name in DEPRECATED_SUBCOMMANDS: + help_text = f"(deprecated) {help_text}" + main_app.command( + name=name, + help=help_text, + hidden=name not in NON_HIDDEN_SUBCOMMANDS, + context_settings={ + "allow_extra_args": True, + "ignore_unknown_options": True, + }, + )(_deprecate(name, subcommand_function)) + + +def legacy_main(args: Optional[List[str]] = None) -> None: + binstar_main(args if args is not None else sys.argv[1:], allow_plugin_main=False) + + +load_legacy_subcommands() diff --git a/setup.py b/setup.py index 54b9905d..c93b871d 100644 --- a/setup.py +++ b/setup.py @@ -49,5 +49,8 @@ 'binstar = binstar_client.scripts.cli:main', 'conda-server = binstar_client.scripts.cli:main', ], + 'anaconda_cli.subcommand': [ + 'org = binstar_client.plugins:app', + ] }, ) From eff478f01c4e92ed0b441d9b4324c3ee98765b00 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 9 Jul 2024 15:59:32 -0500 Subject: [PATCH 03/34] Register main CLI as anaconda_cli.main plugin --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index c93b871d..cedd0dc0 100644 --- a/setup.py +++ b/setup.py @@ -49,6 +49,9 @@ 'binstar = binstar_client.scripts.cli:main', 'conda-server = binstar_client.scripts.cli:main', ], + 'anaconda_cli.main': [ + 'anaconda-client = binstar_client.scripts.cli:main', + ], 'anaconda_cli.subcommand': [ 'org = binstar_client.plugins:app', ] From 1a4cbb06c3d543e202f14d9eeb052e21fb652b32 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 9 Jul 2024 21:04:48 -0500 Subject: [PATCH 04/34] Don't mount login or logout at the top level if using the typer app These are implemented inside anaconda-cloud-cli, which will delegate back to anaconda_client --- binstar_client/plugins.py | 45 +++++++++++++-------------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index 523178e6..a8190e48 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -19,7 +19,6 @@ ) from binstar_client.scripts.cli import main as binstar_main -from anaconda_cli_base.cli import app as main_app from typer import Context, Typer # All subcommands in anaconda-client @@ -45,24 +44,7 @@ } # These subcommands will be shown in the top-level help NON_HIDDEN_SUBCOMMANDS = { - "auth", - "channel", - "config", - "copy", - "download", - "groups", - "label", - "login", - "logout", - "move", - "notebook", - "package", - "remove", - "search", - "show", - "update", "upload", - "whoami", } # Any subcommands that should emit deprecation warnings, and show as deprecated in the help DEPRECATED_SUBCOMMANDS: Set[str] = set() @@ -125,6 +107,8 @@ def load_legacy_subcommands() -> None: """ + from anaconda_cli_base.cli import app as main_app + parser = ArgumentParser() add_subparser_modules(parser, command_module) @@ -140,18 +124,19 @@ def load_legacy_subcommands() -> None: )(subcommand_function) # Mount some CLI subcommands at the top-level, but optionally emit a deprecation warning - help_text = f"anaconda.org: {help_text + ' ' if help_text else ''}(alias for 'anaconda org {name}')" - if name in DEPRECATED_SUBCOMMANDS: - help_text = f"(deprecated) {help_text}" - main_app.command( - name=name, - help=help_text, - hidden=name not in NON_HIDDEN_SUBCOMMANDS, - context_settings={ - "allow_extra_args": True, - "ignore_unknown_options": True, - }, - )(_deprecate(name, subcommand_function)) + if name not in {"login", "logout"}: + help_text = f"anaconda.org: {help_text + ' ' if help_text else ''}(alias for 'anaconda org {name}')" + if name in DEPRECATED_SUBCOMMANDS: + help_text = f"(deprecated) {help_text}" + main_app.command( + name=name, + help=help_text, + hidden=name not in NON_HIDDEN_SUBCOMMANDS, + context_settings={ + "allow_extra_args": True, + "ignore_unknown_options": True, + }, + )(_deprecate(name, subcommand_function)) def legacy_main(args: Optional[List[str]] = None) -> None: From 758a258523d4c1fdfef2a0250afd913cbf10b79a Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 9 Jul 2024 21:12:43 -0500 Subject: [PATCH 05/34] Remove "anaconda_cli.main" plugin --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index cedd0dc0..c93b871d 100644 --- a/setup.py +++ b/setup.py @@ -49,9 +49,6 @@ 'binstar = binstar_client.scripts.cli:main', 'conda-server = binstar_client.scripts.cli:main', ], - 'anaconda_cli.main': [ - 'anaconda-client = binstar_client.scripts.cli:main', - ], 'anaconda_cli.subcommand': [ 'org = binstar_client.plugins:app', ] From 19266362115828fa8aed20c1eb89498a1079c442 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 9 Jul 2024 21:13:49 -0500 Subject: [PATCH 06/34] Add descriptive docstring --- binstar_client/plugins.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index a8190e48..980c8594 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -1,6 +1,16 @@ -"""Wrappers and functions to handle loading of legacy anaconda-client subcommands into the new CLI. +"""Defines the subcommand plugins for the new CLI defined in anaconda-cli-base. -A one-stop-shop for maintaining compatibility and helping to gracefully migrate & deprecate. +We define a new subcommand called `anaconda org`, which nests all existing +anaconda-client subcommands beneath it. Additionally, we mount all of the +existing subcommands, with the exception of "login" and "logout" at the top +level of the CLI, although some of these are mounted silently. This is done to +maintain backwards compatibility while we work to deprecate some of them. + +Rather than re-write all the CLI code in anaconda-client, we opt to dynamically +register each subcommand in the `load_legacy_subcommands` function. + +Note: This module should not be imported, except as defined as a plugin +entrypoint in setup.py. """ From 035844312afcec669a4981a15e5d2343a714c1c7 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 9 Jul 2024 21:14:45 -0500 Subject: [PATCH 07/34] Move and rename some helper functions --- binstar_client/plugins.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index 980c8594..1cc82e7b 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -100,14 +100,18 @@ def new_f(ctx: Context) -> Any: return new_f -def subcommand_function(ctx: Context) -> None: +def _legacy_main(args: Optional[List[str]] = None) -> None: + binstar_main(args if args is not None else sys.argv[1:], allow_plugin_main=False) + + +def _subcommand_function(ctx: Context) -> None: # Here, we are using the ctx instead of sys.argv because the test invoker doesn't # use sys.argv args = [] if ctx.info_name is not None: args.append(ctx.info_name) args.extend(ctx.args) - legacy_main(args=args) + _legacy_main(args=args) def load_legacy_subcommands() -> None: @@ -131,7 +135,7 @@ def load_legacy_subcommands() -> None: name=name, help=help_text, context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, - )(subcommand_function) + )(_subcommand_function) # Mount some CLI subcommands at the top-level, but optionally emit a deprecation warning if name not in {"login", "logout"}: @@ -146,11 +150,7 @@ def load_legacy_subcommands() -> None: "allow_extra_args": True, "ignore_unknown_options": True, }, - )(_deprecate(name, subcommand_function)) - - -def legacy_main(args: Optional[List[str]] = None) -> None: - binstar_main(args if args is not None else sys.argv[1:], allow_plugin_main=False) + )(_deprecate(name, _subcommand_function)) load_legacy_subcommands() From 14e37cd7790a969a8fa3835824ba70172e1377d0 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 9 Jul 2024 21:18:44 -0500 Subject: [PATCH 08/34] Tidy --- binstar_client/plugins.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index 1cc82e7b..934fec9b 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -25,7 +25,7 @@ from binstar_client import commands as command_module from binstar_client.scripts.cli import ( - _add_subparser_modules as add_subparser_modules, + _add_subparser_modules as add_subparser_modules, main as binstar_main, ) from binstar_client.scripts.cli import main as binstar_main @@ -100,18 +100,21 @@ def new_f(ctx: Context) -> Any: return new_f -def _legacy_main(args: Optional[List[str]] = None) -> None: - binstar_main(args if args is not None else sys.argv[1:], allow_plugin_main=False) +def _subcommand(ctx: Context) -> None: + """A common function to use for all subcommands. + In a proper typer/click app, this is the function that is decorated. -def _subcommand_function(ctx: Context) -> None: - # Here, we are using the ctx instead of sys.argv because the test invoker doesn't - # use sys.argv + We use the typer.Context object to extract the args passed into the CLI, and then delegate + to the binstar_main function. + + """ args = [] + # Ensure we capture the subcommand name if there is one if ctx.info_name is not None: args.append(ctx.info_name) args.extend(ctx.args) - _legacy_main(args=args) + binstar_main(args, allow_plugin_main=False) def load_legacy_subcommands() -> None: @@ -135,7 +138,7 @@ def load_legacy_subcommands() -> None: name=name, help=help_text, context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, - )(_subcommand_function) + )(_subcommand) # Mount some CLI subcommands at the top-level, but optionally emit a deprecation warning if name not in {"login", "logout"}: @@ -150,7 +153,7 @@ def load_legacy_subcommands() -> None: "allow_extra_args": True, "ignore_unknown_options": True, }, - )(_deprecate(name, _subcommand_function)) + )(_deprecate(name, _subcommand)) load_legacy_subcommands() From e865a81dbf7be6e1bac381b33054b480d5bedf43 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 9 Jul 2024 21:21:03 -0500 Subject: [PATCH 09/34] Add a docstring --- binstar_client/plugins.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index 934fec9b..dcc50ce1 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -88,6 +88,13 @@ def _get_help_text(parser: ArgumentParser, name: str) -> str: def _deprecate(name: str, f: Callable) -> Callable: + """Mark a named subcommand as deprecated. + + Args: + name: The name of the subcommand. + f: The subcommand callable. + + """ def new_f(ctx: Context) -> Any: if name in DEPRECATED_SUBCOMMANDS: log.warning( From 0e6dac72248549634d5be23901255ce05f4c6d95 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 9 Jul 2024 21:44:00 -0500 Subject: [PATCH 10/34] Refactor subcommand mounting into helper function --- binstar_client/plugins.py | 98 ++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index dcc50ce1..11a04c98 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -29,6 +29,7 @@ ) from binstar_client.scripts.cli import main as binstar_main +from anaconda_cli_base.cli import app as main_app from typer import Context, Typer # All subcommands in anaconda-client @@ -57,7 +58,9 @@ "upload", } # Any subcommands that should emit deprecation warnings, and show as deprecated in the help -DEPRECATED_SUBCOMMANDS: Set[str] = set() +DEPRECATED_SUBCOMMANDS: str = { + "notebook", +} # The logger log = logging.getLogger(__name__) @@ -96,12 +99,11 @@ def _deprecate(name: str, f: Callable) -> Callable: """ def new_f(ctx: Context) -> Any: - if name in DEPRECATED_SUBCOMMANDS: - log.warning( - "The existing anaconda-client commands will be deprecated. To maintain compatibility, " - "please either pin `anaconda-client<2` or update your system call with the `org` prefix, " - f'e.g. "anaconda org {name} ..."' - ) + log.warning( + "The existing anaconda-client commands will be deprecated. To maintain compatibility, " + "please either pin `anaconda-client<2` or update your system call with the `org` prefix, " + f'e.g. "anaconda org {name} ..."' + ) return f(ctx) return new_f @@ -124,43 +126,77 @@ def _subcommand(ctx: Context) -> None: binstar_main(args, allow_plugin_main=False) +def _mount_subcommand( + *, + name: str, + help_text: str, + is_deprecated: bool, + mount_to_main: bool, + is_hidden_on_main: bool, +) -> None: + """Mount an existing subcommand to the `anaconda org` typer application. + + Args: + name: The name of the subcommand. + help_text: The help text for the subcommand + is_deprecated: If True, mark the subcommand as deprecated. This will cause a warning to be + emitted, and also add "(deprecated)" to the help text. + mount_to_main: If True, also mount the subcommand to the main typer app. + is_hidden_on_main: If True, the subcommand is registered as a hidden subcommand of the main CLI + for backwards-compatibility + + """ + if is_deprecated: + help_text = f"(deprecated) {help_text}" + f = _deprecate(name, _subcommand) + else: + f = _subcommand + + # Mount the subcommand to the `anaconda org` application. + app.command( + name=name, + help=help_text, + context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, + )(f) + + # Exit early if we are not mounting to the main `anaconda` app + if not mount_to_main: + return + + # Mount some CLI subcommands at the top-level, but optionally emit a deprecation warning + help_text = f"anaconda.org: {help_text + ' ' if help_text else ''}(alias for 'anaconda org {name}')" + + main_app.command( + name=name, + help=help_text, + hidden=is_hidden_on_main, + context_settings={ + "allow_extra_args": True, + "ignore_unknown_options": True, + }, + )(f) + + def load_legacy_subcommands() -> None: """Load each of the legacy subcommands into its own typer subcommand. This allows them to be called from the new CLI, without having to manually migrate. """ - - from anaconda_cli_base.cli import app as main_app - parser = ArgumentParser() add_subparser_modules(parser, command_module) + # Define all of the subcommands in the typer app for name in LEGACY_SUBCOMMANDS: - - # Define all of the subcommands in the typer app # TODO: Can we load the arguments, or at least the docstring to make the help nicer? help_text = _get_help_text(parser, name) - app.command( + _mount_subcommand( name=name, - help=help_text, - context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, - )(_subcommand) - - # Mount some CLI subcommands at the top-level, but optionally emit a deprecation warning - if name not in {"login", "logout"}: - help_text = f"anaconda.org: {help_text + ' ' if help_text else ''}(alias for 'anaconda org {name}')" - if name in DEPRECATED_SUBCOMMANDS: - help_text = f"(deprecated) {help_text}" - main_app.command( - name=name, - help=help_text, - hidden=name not in NON_HIDDEN_SUBCOMMANDS, - context_settings={ - "allow_extra_args": True, - "ignore_unknown_options": True, - }, - )(_deprecate(name, _subcommand)) + help_text=help_text, + is_deprecated=(name in DEPRECATED_SUBCOMMANDS), + mount_to_main=(name not in {"login", "logout"}), + is_hidden_on_main=(name not in NON_HIDDEN_SUBCOMMANDS), + ) load_legacy_subcommands() From 9c16f0d1b5cecdf07c6b05ff47643357f57cb4a9 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 9 Jul 2024 21:44:57 -0500 Subject: [PATCH 11/34] In-line call to _get_help_text --- binstar_client/plugins.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index 11a04c98..1fd178de 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -186,13 +186,11 @@ def load_legacy_subcommands() -> None: parser = ArgumentParser() add_subparser_modules(parser, command_module) - # Define all of the subcommands in the typer app for name in LEGACY_SUBCOMMANDS: # TODO: Can we load the arguments, or at least the docstring to make the help nicer? - help_text = _get_help_text(parser, name) _mount_subcommand( name=name, - help_text=help_text, + help_text=_get_help_text(parser, name), is_deprecated=(name in DEPRECATED_SUBCOMMANDS), mount_to_main=(name not in {"login", "logout"}), is_hidden_on_main=(name not in NON_HIDDEN_SUBCOMMANDS), From e53eb6840277d84d7d1f5d7bdc7e0705d562dd66 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 9 Jul 2024 21:45:10 -0500 Subject: [PATCH 12/34] Rename constant --- binstar_client/plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index 1fd178de..6ca66dc8 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -33,7 +33,7 @@ from typer import Context, Typer # All subcommands in anaconda-client -LEGACY_SUBCOMMANDS = { +ALL_SUBCOMMANDS = { "auth", "channel", "config", @@ -186,7 +186,7 @@ def load_legacy_subcommands() -> None: parser = ArgumentParser() add_subparser_modules(parser, command_module) - for name in LEGACY_SUBCOMMANDS: + for name in ALL_SUBCOMMANDS: # TODO: Can we load the arguments, or at least the docstring to make the help nicer? _mount_subcommand( name=name, From 40231efe017aabfa6d450d0f01389e41a5d14b7c Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 10 Jul 2024 09:26:54 -0500 Subject: [PATCH 13/34] Make deprecated text red and bold --- binstar_client/plugins.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index 6ca66dc8..a9a28922 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -30,6 +30,8 @@ from binstar_client.scripts.cli import main as binstar_main from anaconda_cli_base.cli import app as main_app +import typer +import typer.colors from typer import Context, Typer # All subcommands in anaconda-client @@ -147,7 +149,8 @@ def _mount_subcommand( """ if is_deprecated: - help_text = f"(deprecated) {help_text}" + deprecated_text = typer.style("(deprecated)", fg=typer.colors.RED, bold=True) + help_text = f"{deprecated_text} {help_text}" f = _deprecate(name, _subcommand) else: f = _subcommand From b8c7e1dcde3247d436494731da18e0b4709927db Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 10 Jul 2024 10:35:37 -0500 Subject: [PATCH 14/34] Fix type annotation --- binstar_client/plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index a9a28922..85e9bd46 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -60,7 +60,7 @@ "upload", } # Any subcommands that should emit deprecation warnings, and show as deprecated in the help -DEPRECATED_SUBCOMMANDS: str = { +DEPRECATED_SUBCOMMANDS = { "notebook", } From 37c88f646b36c92f2609684338c1b7eb96baf43a Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 10 Jul 2024 10:51:34 -0500 Subject: [PATCH 15/34] Bump version and add anaconda-cli-base as dependency --- binstar_client/__about__.py | 2 +- conda.recipe/meta.yaml | 1 + requirements.txt | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/binstar_client/__about__.py b/binstar_client/__about__.py index 301d5b71..932207c3 100644 --- a/binstar_client/__about__.py +++ b/binstar_client/__about__.py @@ -4,4 +4,4 @@ __all__ = ['__version__'] -__version__ = '1.12.3' +__version__ = '1.13.0' diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 058b0c51..67e94810 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -35,6 +35,7 @@ requirements: - setuptools >=58.0.4 - tqdm >=4.56.0 - urllib3 >=1.26.4 + - anaconda-cli-base >0.2.3 test: requires: diff --git a/requirements.txt b/requirements.txt index 81222c4c..21fa1364 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,4 @@ requests-toolbelt>=0.9.1 setuptools>=58.0.4 tqdm>=4.56.0 urllib3>=1.26.4 +anaconda-cli-base>0.2.3 \ No newline at end of file From 8bd73167e7ffb27822cbb5f52ded88e27070d659 Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 10 Jul 2024 12:59:13 -0500 Subject: [PATCH 16/34] Remove entrypoint from recipe --- conda.recipe/meta.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 67e94810..8f0b1e3e 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -11,7 +11,6 @@ build: number: 0 script: {{ PYTHON }} -m pip install --no-build-isolation --no-deps . entry_points: - - anaconda = binstar_client.scripts.cli:main - binstar = binstar_client.scripts.cli:main - conda-server = binstar_client.scripts.cli:main From 0ae5b337b18031d107abe9ae670432dd5a4c51aa Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 10 Jul 2024 12:59:22 -0500 Subject: [PATCH 17/34] Use local path instead of git_url --- conda.recipe/meta.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 8f0b1e3e..1d0cf6cc 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -5,7 +5,7 @@ package: version: {{ data.get('version') }} source: - git_url: ../ + path: .. build: number: 0 From fcc486be547e895fbd990ef32ff13ea0b5612edf Mon Sep 17 00:00:00 2001 From: Albert DeFusco Date: Tue, 20 Aug 2024 13:00:09 -0400 Subject: [PATCH 18/34] test cli plugin --- binstar_client/plugins.py | 2 +- tests/test_cli.py | 69 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 tests/test_cli.py diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index 85e9bd46..3192cf9c 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -195,7 +195,7 @@ def load_legacy_subcommands() -> None: name=name, help_text=_get_help_text(parser, name), is_deprecated=(name in DEPRECATED_SUBCOMMANDS), - mount_to_main=(name not in {"login", "logout"}), + mount_to_main=(name not in {"login", "logout", "whoami"}), is_hidden_on_main=(name not in NON_HIDDEN_SUBCOMMANDS), ) diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..7451e667 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,69 @@ +from importlib import reload +from typing import Generator + +import pytest +from pytest import MonkeyPatch +from typer.testing import CliRunner +import anaconda_cli_base.cli +import binstar_client.plugins +from binstar_client.plugins import ALL_SUBCOMMANDS, NON_HIDDEN_SUBCOMMANDS + +BASE_COMMANDS = {"login", "logout", "whoami"} +HIDDEN_SUBCOMMANDS = ALL_SUBCOMMANDS - BASE_COMMANDS - NON_HIDDEN_SUBCOMMANDS + + +@pytest.fixture(autouse=True) +def enable_base_cli_plugin(monkeypatch: MonkeyPatch) -> Generator[None, None, None]: + monkeypatch.setenv("ANACONDA_CLI_FORCE_NEW", "1") + monkeypatch.delenv("ANACONDA_CLIENT_FORCE_STANDALONE", raising=False) + reload(anaconda_cli_base.cli) + reload(binstar_client.plugins) + yield + + +def test_entrypoint() -> None: + groups = [g.name for g in anaconda_cli_base.cli.app.registered_groups] + assert "org" in groups + + +@pytest.mark.parametrize("cmd", ALL_SUBCOMMANDS) +def test_org_subcommands(cmd: str) -> None: + org = next((group for group in anaconda_cli_base.cli.app.registered_groups if group.name == "org"), None) + assert org is not None + + subcmd = next((subcmd for subcmd in org.typer_instance.registered_commands if subcmd.name == cmd), None) + assert subcmd is not None + assert subcmd.hidden is False + + runner = CliRunner() + result = runner.invoke(anaconda_cli_base.cli.app, ["org", cmd, "-h"]) + assert result.exit_code == 0 + assert result.stdout.startswith("usage") + + +@pytest.mark.parametrize("cmd", HIDDEN_SUBCOMMANDS) +def test_hidden_commands(cmd: str) -> None: + subcmd = next((subcmd for subcmd in anaconda_cli_base.cli.app.registered_commands if subcmd.name == cmd), None) + assert subcmd is not None + assert subcmd.hidden is True + assert subcmd.help is not None + assert subcmd.help.startswith("anaconda.org") + + runner = CliRunner() + result = runner.invoke(anaconda_cli_base.cli.app, [cmd, "-h"]) + assert result.exit_code == 0 + assert result.stdout.startswith("usage") + + +@pytest.mark.parametrize("cmd", NON_HIDDEN_SUBCOMMANDS) +def test_non_hidden_commands(cmd: str) -> None: + subcmd = next((subcmd for subcmd in anaconda_cli_base.cli.app.registered_commands if subcmd.name == cmd), None) + assert subcmd is not None + assert subcmd.hidden is False + assert subcmd.help is not None + assert subcmd.help.startswith("anaconda.org") + + runner = CliRunner() + result = runner.invoke(anaconda_cli_base.cli.app, [cmd, "-h"]) + assert result.exit_code == 0 + assert result.stdout.startswith("usage") From 7ee18c1f2300529d225c54f452e685eac41f1b1e Mon Sep 17 00:00:00 2001 From: Albert DeFusco Date: Tue, 20 Aug 2024 13:58:30 -0400 Subject: [PATCH 19/34] temporarily add anaconda-cloud/label/dev --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index d8d606a9..20fac05a 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ init: @if [ -z "$${CONDA_SHLVL:+x}" ]; then echo "Conda is not installed." && exit 1; fi @conda create \ --channel defaults \ + --channel anaconda-cloud/label/dev \ --yes \ --prefix $(conda_env_dir) \ python=3.11 \ From fcb990ffea4b9b3bd0aab6e28dd37cd2d2b2c8c0 Mon Sep 17 00:00:00 2001 From: Albert DeFusco Date: Tue, 20 Aug 2024 14:03:02 -0400 Subject: [PATCH 20/34] and in ci --- .github/workflows/check-master.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-master.yml b/.github/workflows/check-master.yml index 29f722d9..eac5b739 100644 --- a/.github/workflows/check-master.yml +++ b/.github/workflows/check-master.yml @@ -42,7 +42,7 @@ jobs: - name: Install dependencies run: | - conda install python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-extra.txt + conda install -c defaults -c anaconda-cloud/label/dev python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-extra.txt pip install -r requirements-dev.txt python setup.py develop --no-deps @@ -116,7 +116,7 @@ jobs: - name: Install dependencies run: | - conda install python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-extra.txt + conda install -c defaults -c anaconda-cloud/label/dev python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-extra.txt pip install -r requirements-dev.txt python setup.py develop --no-deps From e9f2b063767268f3cacdcd627b48d4376f6e4815 Mon Sep 17 00:00:00 2001 From: Albert DeFusco Date: Tue, 20 Aug 2024 15:26:47 -0400 Subject: [PATCH 21/34] pylint --- binstar_client/plugins.py | 50 +++++++++++++++++++-------------------- tests/test_cli.py | 29 +++++++++++++++++++++-- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index 3192cf9c..4259c612 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -15,24 +15,20 @@ """ import logging -import sys +import warnings from argparse import ArgumentParser from typing import Any from typing import Callable -from typing import List -from typing import Optional -from typing import Set + +import typer +import typer.colors +from anaconda_cli_base.cli import app as main_app +from typer import Context, Typer from binstar_client import commands as command_module from binstar_client.scripts.cli import ( _add_subparser_modules as add_subparser_modules, main as binstar_main, ) -from binstar_client.scripts.cli import main as binstar_main - -from anaconda_cli_base.cli import app as main_app -import typer -import typer.colors -from typer import Context, Typer # All subcommands in anaconda-client ALL_SUBCOMMANDS = { @@ -66,6 +62,7 @@ # The logger log = logging.getLogger(__name__) +warnings.simplefilter("always") app = Typer( add_completion=False, @@ -77,13 +74,13 @@ def _get_help_text(parser: ArgumentParser, name: str) -> str: """Extract the help text from the anaconda-client CLI Argument Parser.""" - if parser._subparsers is None: + if parser._subparsers is None: # pylint: disable=protected-access return "" - if parser._subparsers._actions is None: + if parser._subparsers._actions is None: # pylint: disable=protected-access return "" - if parser._subparsers._actions[1].choices is None: + if parser._subparsers._actions[1].choices is None: # pylint: disable=protected-access return "" - subcommand_parser = dict(parser._subparsers._actions[1].choices).get(name) + subcommand_parser = dict(parser._subparsers._actions[1].choices).get(name) # pylint: disable=protected-access if subcommand_parser is None: return "" description = subcommand_parser.description @@ -92,7 +89,7 @@ def _get_help_text(parser: ArgumentParser, name: str) -> str: return description.strip() -def _deprecate(name: str, f: Callable) -> Callable: +def _deprecate(name: str, func: Callable) -> Callable: """Mark a named subcommand as deprecated. Args: @@ -100,15 +97,16 @@ def _deprecate(name: str, f: Callable) -> Callable: f: The subcommand callable. """ - def new_f(ctx: Context) -> Any: - log.warning( - "The existing anaconda-client commands will be deprecated. To maintain compatibility, " - "please either pin `anaconda-client<2` or update your system call with the `org` prefix, " + def new_func(ctx: Context) -> Any: + msg = ( + f"The existing anaconda-client commands will be deprecated. To maintain compatibility, " + f"please either pin `anaconda-client<2` or update your system call with the `org` prefix, " f'e.g. "anaconda org {name} ..."' ) - return f(ctx) + log.warning(msg) + return func(ctx) - return new_f + return new_func def _subcommand(ctx: Context) -> None: @@ -151,16 +149,16 @@ def _mount_subcommand( if is_deprecated: deprecated_text = typer.style("(deprecated)", fg=typer.colors.RED, bold=True) help_text = f"{deprecated_text} {help_text}" - f = _deprecate(name, _subcommand) + func = _deprecate(name, _subcommand) else: - f = _subcommand + func = _subcommand # Mount the subcommand to the `anaconda org` application. app.command( name=name, help=help_text, context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, - )(f) + )(func) # Exit early if we are not mounting to the main `anaconda` app if not mount_to_main: @@ -177,7 +175,7 @@ def _mount_subcommand( "allow_extra_args": True, "ignore_unknown_options": True, }, - )(f) + )(func) def load_legacy_subcommands() -> None: @@ -190,7 +188,7 @@ def load_legacy_subcommands() -> None: add_subparser_modules(parser, command_module) for name in ALL_SUBCOMMANDS: - # TODO: Can we load the arguments, or at least the docstring to make the help nicer? + # TODO: Can we load the arguments, or at least the docstring to make the help nicer? # pylint: disable=fixme _mount_subcommand( name=name, help_text=_get_help_text(parser, name), diff --git a/tests/test_cli.py b/tests/test_cli.py index 7451e667..6b3bbcea 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,12 +1,16 @@ +"""Test entrypoint to anaconda-cli-base""" + from importlib import reload +import logging from typing import Generator import pytest +from pytest import LogCaptureFixture from pytest import MonkeyPatch from typer.testing import CliRunner import anaconda_cli_base.cli import binstar_client.plugins -from binstar_client.plugins import ALL_SUBCOMMANDS, NON_HIDDEN_SUBCOMMANDS +from binstar_client.plugins import ALL_SUBCOMMANDS, NON_HIDDEN_SUBCOMMANDS, DEPRECATED_SUBCOMMANDS BASE_COMMANDS = {"login", "logout", "whoami"} HIDDEN_SUBCOMMANDS = ALL_SUBCOMMANDS - BASE_COMMANDS - NON_HIDDEN_SUBCOMMANDS @@ -14,6 +18,8 @@ @pytest.fixture(autouse=True) def enable_base_cli_plugin(monkeypatch: MonkeyPatch) -> Generator[None, None, None]: + """Make sure that we get a clean app with plugins loaded""" + monkeypatch.setenv("ANACONDA_CLI_FORCE_NEW", "1") monkeypatch.delenv("ANACONDA_CLIENT_FORCE_STANDALONE", raising=False) reload(anaconda_cli_base.cli) @@ -22,12 +28,16 @@ def enable_base_cli_plugin(monkeypatch: MonkeyPatch) -> Generator[None, None, No def test_entrypoint() -> None: - groups = [g.name for g in anaconda_cli_base.cli.app.registered_groups] + """Has the entrypoint been loaded?""" + + groups = [grp.name for grp in anaconda_cli_base.cli.app.registered_groups] assert "org" in groups @pytest.mark.parametrize("cmd", ALL_SUBCOMMANDS) def test_org_subcommands(cmd: str) -> None: + """anaconda org """ + org = next((group for group in anaconda_cli_base.cli.app.registered_groups if group.name == "org"), None) assert org is not None @@ -43,6 +53,8 @@ def test_org_subcommands(cmd: str) -> None: @pytest.mark.parametrize("cmd", HIDDEN_SUBCOMMANDS) def test_hidden_commands(cmd: str) -> None: + """anaconda """ + subcmd = next((subcmd for subcmd in anaconda_cli_base.cli.app.registered_commands if subcmd.name == cmd), None) assert subcmd is not None assert subcmd.hidden is True @@ -57,6 +69,8 @@ def test_hidden_commands(cmd: str) -> None: @pytest.mark.parametrize("cmd", NON_HIDDEN_SUBCOMMANDS) def test_non_hidden_commands(cmd: str) -> None: + """anaconda login""" + subcmd = next((subcmd for subcmd in anaconda_cli_base.cli.app.registered_commands if subcmd.name == cmd), None) assert subcmd is not None assert subcmd.hidden is False @@ -67,3 +81,14 @@ def test_non_hidden_commands(cmd: str) -> None: result = runner.invoke(anaconda_cli_base.cli.app, [cmd, "-h"]) assert result.exit_code == 0 assert result.stdout.startswith("usage") + + +@pytest.mark.parametrize("cmd", DEPRECATED_SUBCOMMANDS) +def test_deprecated_message(cmd: str, caplog: LogCaptureFixture) -> None: + """anaconda warning""" + + with caplog.at_level(logging.WARNING): + runner = CliRunner() + result = runner.invoke(anaconda_cli_base.cli.app, [cmd, "-h"]) + assert result.exit_code == 0 + assert "commands will be deprecated" in caplog.records[0].msg From 3035668a40ec05139694a2fbb75bbd103771ff87 Mon Sep 17 00:00:00 2001 From: Albert DeFusco Date: Tue, 20 Aug 2024 15:46:28 -0400 Subject: [PATCH 22/34] mypy --- binstar_client/plugins.py | 5 +++-- tests/test_cli.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/binstar_client/plugins.py b/binstar_client/plugins.py index 4259c612..963244a7 100644 --- a/binstar_client/plugins.py +++ b/binstar_client/plugins.py @@ -76,8 +76,9 @@ def _get_help_text(parser: ArgumentParser, name: str) -> str: """Extract the help text from the anaconda-client CLI Argument Parser.""" if parser._subparsers is None: # pylint: disable=protected-access return "" - if parser._subparsers._actions is None: # pylint: disable=protected-access - return "" + # MyPy says this was unreachable + # if parser._subparsers._actions is None: # pylint: disable=protected-access + # return "" if parser._subparsers._actions[1].choices is None: # pylint: disable=protected-access return "" subcommand_parser = dict(parser._subparsers._actions[1].choices).get(name) # pylint: disable=protected-access diff --git a/tests/test_cli.py b/tests/test_cli.py index 6b3bbcea..c6b0e0cd 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -41,6 +41,7 @@ def test_org_subcommands(cmd: str) -> None: org = next((group for group in anaconda_cli_base.cli.app.registered_groups if group.name == "org"), None) assert org is not None + assert org.typer_instance subcmd = next((subcmd for subcmd in org.typer_instance.registered_commands if subcmd.name == cmd), None) assert subcmd is not None assert subcmd.hidden is False From cfbde6ee0668a5f19617ae1dbb9c9783007eb110 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 27 Aug 2024 13:24:05 -0500 Subject: [PATCH 23/34] Remove RichHandler from root logger because of formatting conflict --- binstar_client/utils/logging_utils.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/binstar_client/utils/logging_utils.py b/binstar_client/utils/logging_utils.py index 26af2de0..26557b37 100644 --- a/binstar_client/utils/logging_utils.py +++ b/binstar_client/utils/logging_utils.py @@ -49,6 +49,21 @@ def format(self, record: logging.LogRecord) -> str: self._style._fmt = self.FORMAT_CUSTOM.get(record.levelno, self.FORMAT_DEFAULT) return super().format(record) +def _purge_rich_handler_from_logging_root() -> None: + # Remove all handlers associated with the root logger object. + # We do this since anaconda-cli-base defines the RichHandler, which conflicts with anaconda-client's logging + # We can remove this once we clean up logging. + for handler in logging.root.handlers[:]: + try: + from rich.logging import RichHandler + except: + pass + else: + # Only remove the root RichHandler, and only if rich is installed + # This should always happen, but just being super careful here. + if isinstance(handler, RichHandler): + logging.root.removeHandler(handler) + def setup_logging( logger: logging.Logger, @@ -57,6 +72,7 @@ def setup_logging( disable_ssl_warnings: bool = False ) -> None: """Configure logging for the application.""" + _purge_rich_handler_from_logging_root() logger.setLevel(logging.DEBUG) os.makedirs(config.USER_LOGDIR, exist_ok=True) From 149bb4342ff0f6f68251ab206656770c53976080 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 27 Aug 2024 15:21:37 -0500 Subject: [PATCH 24/34] Satisfy the linter --- binstar_client/utils/logging_utils.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/binstar_client/utils/logging_utils.py b/binstar_client/utils/logging_utils.py index 26557b37..a0a53452 100644 --- a/binstar_client/utils/logging_utils.py +++ b/binstar_client/utils/logging_utils.py @@ -49,20 +49,22 @@ def format(self, record: logging.LogRecord) -> str: self._style._fmt = self.FORMAT_CUSTOM.get(record.levelno, self.FORMAT_DEFAULT) return super().format(record) + +try: + from rich.logging import RichHandler +except (ImportError, ModuleNotFoundError): + RichHandler = None + + def _purge_rich_handler_from_logging_root() -> None: # Remove all handlers associated with the root logger object. # We do this since anaconda-cli-base defines the RichHandler, which conflicts with anaconda-client's logging # We can remove this once we clean up logging. for handler in logging.root.handlers[:]: - try: - from rich.logging import RichHandler - except: - pass - else: - # Only remove the root RichHandler, and only if rich is installed - # This should always happen, but just being super careful here. - if isinstance(handler, RichHandler): - logging.root.removeHandler(handler) + # Only remove the root RichHandler, and only if rich is installed + # This should always happen, but just being super careful here. + if RichHandler is not None and isinstance(handler, RichHandler): + logging.root.removeHandler(handler) def setup_logging( From dd38dd26e05585fb5aefae51f2bded654b2acbd2 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 27 Aug 2024 15:25:05 -0500 Subject: [PATCH 25/34] Ignore type hinting for optional import --- binstar_client/utils/logging_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binstar_client/utils/logging_utils.py b/binstar_client/utils/logging_utils.py index a0a53452..daa8006c 100644 --- a/binstar_client/utils/logging_utils.py +++ b/binstar_client/utils/logging_utils.py @@ -53,7 +53,7 @@ def format(self, record: logging.LogRecord) -> str: try: from rich.logging import RichHandler except (ImportError, ModuleNotFoundError): - RichHandler = None + RichHandler = None # type: ignore def _purge_rich_handler_from_logging_root() -> None: From 3e38b0fb792f40af54449c06889f764d312a7d93 Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 28 Aug 2024 12:29:22 -0500 Subject: [PATCH 26/34] Revert change to version number Save this for the release --- binstar_client/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binstar_client/__about__.py b/binstar_client/__about__.py index 932207c3..301d5b71 100644 --- a/binstar_client/__about__.py +++ b/binstar_client/__about__.py @@ -4,4 +4,4 @@ __all__ = ['__version__'] -__version__ = '1.13.0' +__version__ = '1.12.3' From cf281dd17cdf06ba540301555de31196d6923fba Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 28 Aug 2024 13:45:21 -0500 Subject: [PATCH 27/34] Use anaconda-cloud channel instead of the dev label --- .github/workflows/check-master.yml | 4 ++-- Makefile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-master.yml b/.github/workflows/check-master.yml index eac5b739..34864ba0 100644 --- a/.github/workflows/check-master.yml +++ b/.github/workflows/check-master.yml @@ -42,7 +42,7 @@ jobs: - name: Install dependencies run: | - conda install -c defaults -c anaconda-cloud/label/dev python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-extra.txt + conda install -c defaults -c anaconda-cloud python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-extra.txt pip install -r requirements-dev.txt python setup.py develop --no-deps @@ -116,7 +116,7 @@ jobs: - name: Install dependencies run: | - conda install -c defaults -c anaconda-cloud/label/dev python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-extra.txt + conda install -c defaults -c anaconda-cloud python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-extra.txt pip install -r requirements-dev.txt python setup.py develop --no-deps diff --git a/Makefile b/Makefile index 20fac05a..107e565d 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ init: @if [ -z "$${CONDA_SHLVL:+x}" ]; then echo "Conda is not installed." && exit 1; fi @conda create \ --channel defaults \ - --channel anaconda-cloud/label/dev \ + --channel anaconda-cloud \ --yes \ --prefix $(conda_env_dir) \ python=3.11 \ From 948073473e0a7f4a6b445464f06e8f50ac666b77 Mon Sep 17 00:00:00 2001 From: mattkram Date: Tue, 27 Aug 2024 12:54:36 -0500 Subject: [PATCH 28/34] xfail tests related to anaconda-project (cherry picked from commit 3dc5194bed9efbb7e019279a14bddd68b7dc4196) --- tests/test_upload.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_upload.py b/tests/test_upload.py index 5d96d64d..052de030 100644 --- a/tests/test_upload.py +++ b/tests/test_upload.py @@ -7,6 +7,8 @@ import json import unittest.mock +import pytest + from binstar_client import errors from tests.fixture import CLITestCase, main from tests.urlmock import urlpatch @@ -221,6 +223,7 @@ def test_upload_file(self, registry): registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) + @pytest.mark.xfail(reason="anaconda-project removed") @urlpatch def test_upload_project(self, registry): # there's redundant work between anaconda-client which checks auth and anaconda-project also checks auth; @@ -241,6 +244,7 @@ def test_upload_project(self, registry): registry.assertAllCalled() + @pytest.mark.xfail(reason="anaconda-project removed") @urlpatch def test_upload_notebook_as_project(self, registry): registry.register(method='HEAD', path='/', status=200) @@ -289,6 +293,7 @@ def test_upload_notebook_as_package(self, registry): registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) + @pytest.mark.xfail(reason="anaconda-project removed") @urlpatch def test_upload_project_specifying_user(self, registry): registry.register(method='HEAD', path='/', status=200) @@ -306,6 +311,7 @@ def test_upload_project_specifying_user(self, registry): registry.assertAllCalled() + @pytest.mark.xfail(reason="anaconda-project removed") @urlpatch def test_upload_project_specifying_token(self, registry): registry.register(method='HEAD', path='/', status=200) From 8d0f29d3961deb0f7d0669de0a3ee322886c8e46 Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 28 Aug 2024 13:46:20 -0500 Subject: [PATCH 29/34] Exclude anaconda-project from extras --- requirements-extra.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/requirements-extra.txt b/requirements-extra.txt index 3ef95284..64313da9 100644 --- a/requirements-extra.txt +++ b/requirements-extra.txt @@ -1,5 +1,9 @@ # Additional requirements for complete experience -anaconda-project>=0.9.1 -ruamel.yaml # Required by anaconda-project +# Disabling these extras since they break CI and the server doesn't support +# projects anyway. The problem is that anaconda-project has a circular +# dependency back onto anaconda-client. + +# anaconda-project>=0.9.1 +# ruamel.yaml # Required by anaconda-project pillow>=8.2 From 660cd88f50349174ff245a3dcd7890bb6e8782d9 Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 28 Aug 2024 13:51:48 -0500 Subject: [PATCH 30/34] Bump dependency on anaconda-cli-base --- conda.recipe/meta.yaml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 1d0cf6cc..d44c1917 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -34,7 +34,7 @@ requirements: - setuptools >=58.0.4 - tqdm >=4.56.0 - urllib3 >=1.26.4 - - anaconda-cli-base >0.2.3 + - anaconda-cli-base >=0.3.0 test: requires: diff --git a/requirements.txt b/requirements.txt index 21fa1364..258d0825 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,4 @@ requests-toolbelt>=0.9.1 setuptools>=58.0.4 tqdm>=4.56.0 urllib3>=1.26.4 -anaconda-cli-base>0.2.3 \ No newline at end of file +anaconda-cli-base>=0.3.0 \ No newline at end of file From b9d1a2553449b2d24f2a38b547c73edc9beb660b Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 28 Aug 2024 13:57:59 -0500 Subject: [PATCH 31/34] Make quotes consistent --- tests/test_upload.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_upload.py b/tests/test_upload.py index 052de030..578b3cf4 100644 --- a/tests/test_upload.py +++ b/tests/test_upload.py @@ -223,7 +223,7 @@ def test_upload_file(self, registry): registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) - @pytest.mark.xfail(reason="anaconda-project removed") + @pytest.mark.xfail(reason='anaconda-project removed') @urlpatch def test_upload_project(self, registry): # there's redundant work between anaconda-client which checks auth and anaconda-project also checks auth; @@ -244,7 +244,7 @@ def test_upload_project(self, registry): registry.assertAllCalled() - @pytest.mark.xfail(reason="anaconda-project removed") + @pytest.mark.xfail(reason='anaconda-project removed') @urlpatch def test_upload_notebook_as_project(self, registry): registry.register(method='HEAD', path='/', status=200) @@ -293,7 +293,7 @@ def test_upload_notebook_as_package(self, registry): registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) - @pytest.mark.xfail(reason="anaconda-project removed") + @pytest.mark.xfail(reason='anaconda-project removed') @urlpatch def test_upload_project_specifying_user(self, registry): registry.register(method='HEAD', path='/', status=200) @@ -311,7 +311,7 @@ def test_upload_project_specifying_user(self, registry): registry.assertAllCalled() - @pytest.mark.xfail(reason="anaconda-project removed") + @pytest.mark.xfail(reason='anaconda-project removed') @urlpatch def test_upload_project_specifying_token(self, registry): registry.register(method='HEAD', path='/', status=200) From 9e336b4804eeec9da9b18428d355175fe7e408dd Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 28 Aug 2024 14:28:11 -0500 Subject: [PATCH 32/34] Add anaconda-project back to extras just to be completely safe --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index c93b871d..2a08cb02 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,11 @@ requirement.split('#', 1)[0].strip() for requirement in stream ))) +# This is temporarily here so we don't pull in the incompatible dependency in CI +# and during local development as we move to 0.13.0. But to not change the behavior +# around the "full" extra at all. We will soon explicitly drop this dependency. +extras_require.append("anaconda-project>=0.9.1") + __about__ = {} with open(os.path.join(root, 'binstar_client', '__about__.py'), 'rt', encoding='utf-8') as stream: exec(stream.read(), __about__) From 11712a361599e86c7a2ad5cb1d12460a11eb2c50 Mon Sep 17 00:00:00 2001 From: mattkram Date: Wed, 28 Aug 2024 14:30:32 -0500 Subject: [PATCH 33/34] Fix line ending --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 258d0825..ade73baf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,4 @@ requests-toolbelt>=0.9.1 setuptools>=58.0.4 tqdm>=4.56.0 urllib3>=1.26.4 -anaconda-cli-base>=0.3.0 \ No newline at end of file +anaconda-cli-base>=0.3.0 From 39889826e7180920ec69badde9c758a068f276bd Mon Sep 17 00:00:00 2001 From: Albert DeFusco Date: Wed, 28 Aug 2024 14:40:42 -0500 Subject: [PATCH 34/34] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2a08cb02..aa15db46 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ ))) # This is temporarily here so we don't pull in the incompatible dependency in CI -# and during local development as we move to 0.13.0. But to not change the behavior +# and during local development as we move to 1.13.0. But to not change the behavior # around the "full" extra at all. We will soon explicitly drop this dependency. extras_require.append("anaconda-project>=0.9.1")