Skip to content

Commit

Permalink
Merge branch 'master' into fix-upload-links
Browse files Browse the repository at this point in the history
  • Loading branch information
mattkram committed Aug 28, 2024
2 parents 806b5a0 + a319064 commit ed99696
Show file tree
Hide file tree
Showing 20 changed files with 385 additions and 34 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/check-master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ jobs:
with:
auto-update-conda: true
python-version: ${{ matrix.python-version }}
miniconda-version: "latest"

- name: Install latest conda
run: |
conda update -n base -q conda
- 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 python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-extra.txt
pip install -r requirements-dev.txt
python setup.py develop --no-deps
Expand Down Expand Up @@ -107,14 +108,15 @@ jobs:
with:
auto-update-conda: true
python-version: ${{ matrix.python-version }}
miniconda-version: "latest"

- name: Install latest conda
run: |
conda update -n base -q conda
- 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 python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-extra.txt
pip install -r requirements-dev.txt
python setup.py develop --no-deps
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

We [keep a changelog.](http://keepachangelog.com/)

## [Unreleased]

* [PR 717](https://github.com/anaconda/anaconda-client/pull/717) - Drop dependency on `six`

## 1.12.3 - 2024-02-22

### Tickets closed
Expand Down
26 changes: 23 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,36 @@ Commands:
endef
export HELP

# Conda-related paths
conda_env_dir ?= ./env

# Command aliases
CONDA_EXE ?= conda
CONDA_RUN := $(CONDA_EXE) run --prefix $(conda_env_dir) --no-capture-output

.PHONY: help init lint lint-bandit lint-mypy lint-pycodestyle lint-pylint test test-pytest

help:
@echo "$${HELP}"

init:
@if [ -z "$${CONDA_SHLVL:+x}" ]; then echo "Conda is not installed." && exit 1; fi
@conda create -y -n anaconda_client python=3.12 --file requirements.txt --file requirements-extra.txt
@conda run -n anaconda_client pip install -r requirements-dev.txt
@echo "\n\nConda environment has been created. To activate run \"conda activate anaconda_client\"."
@conda create \
--channel defaults \
--channel anaconda-cloud \
--yes \
--prefix $(conda_env_dir) \
python=3.11 \
pip \
--file requirements.txt \
--file requirements-extra.txt
@conda run \
--prefix $(conda_env_dir) \
pip install -r requirements-dev.txt
@conda run \
--prefix $(conda_env_dir) \
pip install -e . --no-deps
@echo "\n\nConda environment has been created. To activate run \"conda activate $(conda_env_dir)\"."

check: lint test

Expand Down
1 change: 0 additions & 1 deletion autotest/data/test_env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ dependencies:
- ripgrep=12.1.1=0
- ruamel_yaml=0.15.100=py38h27cfd23_0
- setuptools=52.0.0=py38h06a4308_0
- six=1.16.0=pyhd3eb1b0_0
- soupsieve=2.2.1=pyhd3eb1b0_0
- sqlite=3.35.4=hdfb4753_0
- tk=8.6.10=hbc83047_0
Expand Down
2 changes: 1 addition & 1 deletion binstar_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
import logging
import os
import platform as _platform
from urllib.parse import quote

import defusedxml.ElementTree as ET
import requests
from pkg_resources import parse_version as pv
from six.moves.urllib.parse import quote
from tqdm import tqdm

from . import errors
Expand Down
1 change: 0 additions & 1 deletion binstar_client/commands/authorizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

import pytz
from dateutil.parser import parse as parse_date
from six.moves import input

from binstar_client import errors
from binstar_client.utils import get_server_api
Expand Down
4 changes: 1 addition & 3 deletions binstar_client/commands/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
import platform
import socket
import sys

from six.moves import input
from six.moves.urllib.parse import urlparse
from urllib.parse import urlparse

from binstar_client import errors
from binstar_client.utils import get_config, get_server_api, store_token, bool_input
Expand Down
202 changes: 202 additions & 0 deletions binstar_client/plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
"""Defines the subcommand plugins for the new CLI defined in anaconda-cli-base.
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.
"""

import logging
import warnings
from argparse import ArgumentParser
from typing import Any
from typing import Callable

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,
)

# All subcommands in anaconda-client
ALL_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 = {
"upload",
}
# Any subcommands that should emit deprecation warnings, and show as deprecated in the help
DEPRECATED_SUBCOMMANDS = {
"notebook",
}

# The logger
log = logging.getLogger(__name__)
warnings.simplefilter("always")

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: # 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
if subcommand_parser is None:
return ""
description = subcommand_parser.description
if description is None:
return ""
return description.strip()


def _deprecate(name: str, func: Callable) -> Callable:
"""Mark a named subcommand as deprecated.
Args:
name: The name of the subcommand.
f: The subcommand callable.
"""
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} ..."'
)
log.warning(msg)
return func(ctx)

return new_func


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.
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)
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:
deprecated_text = typer.style("(deprecated)", fg=typer.colors.RED, bold=True)
help_text = f"{deprecated_text} {help_text}"
func = _deprecate(name, _subcommand)
else:
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},
)(func)

# 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,
},
)(func)


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 ALL_SUBCOMMANDS:
# 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),
is_deprecated=(name in DEPRECATED_SUBCOMMANDS),
mount_to_main=(name not in {"login", "logout", "whoami"}),
is_hidden_on_main=(name not in NON_HIDDEN_SUBCOMMANDS),
)


load_legacy_subcommands()
21 changes: 10 additions & 11 deletions binstar_client/requests_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@

from __future__ import annotations

__all__ = ['NullAuth', 'encode_multipart_formdata_stream', 'stream_multipart']

from io import BytesIO, StringIO
from itertools import chain
import logging
import typing

import requests
import six
from requests.auth import AuthBase
from urllib3.filepost import choose_boundary

__all__ = ['NullAuth', 'encode_multipart_formdata_stream', 'stream_multipart']

logger = logging.getLogger('binstar.requests_ext')


Expand All @@ -30,15 +29,15 @@ def iter_fields(
return iter(fields)


class NullAuth(requests.auth.AuthBase): # pylint: disable=too-few-public-methods
class NullAuth(AuthBase): # pylint: disable=too-few-public-methods
"""force requests to ignore the ``.netrc``
Some sites do not support regular authentication, but we still
want to store credentials in the ``.netrc`` file and submit them
as form elements. Without this, requests would otherwise use the
.netrc which leads, on some sites, to a 401 error.
https://github.com/kennethreitz/requests/issues/2773
https://github.com/psf/requests/issues/2773
Use with::
Expand Down Expand Up @@ -68,9 +67,9 @@ def encode_multipart_formdata_stream(fields, boundary=None):
body = []

def body_write(item):
if isinstance(item, six.binary_type):
if isinstance(item, bytes):
item = BytesIO(item)
elif isinstance(item, six.text_type):
elif isinstance(item, str):
item = StringIO(item)
body.append(item)

Expand Down Expand Up @@ -102,10 +101,10 @@ def body_write_encode(item):
% (fieldname))
body_write(b'\r\n')

if isinstance(data, six.integer_types):
data = six.text_type(data) # Backwards compatibility
if isinstance(data, (int,)):
data = str(data) # Backwards compatibility

if isinstance(data, six.text_type):
if isinstance(data, str):
body_write_encode(data)
else:
body_write(data)
Expand Down
Loading

0 comments on commit ed99696

Please sign in to comment.