From 299fa3b6b4b7c9f55d038704ae922f8d751c2426 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Mon, 12 Aug 2019 16:41:31 -0400 Subject: [PATCH] simplify awxkit dependencies - remove flake8 as an install requirements (it's only used for tests) - vendor toposort, which is Apache 2.0 licensed (and very small) - change websocket-client to a setuptools optional dependency, which you can install via: pip install "./awxkit[websockets]" - add `jq` and `tabulate` under an additional optional setuptools dependency: pip install "./awxkit[formatting]" - remove `cryptography`, which is only used for random RSA generation (unused by the CLI) --- awxkit/awxkit/api/mixins/has_create.py | 3 +- awxkit/awxkit/api/pages/credentials.py | 8 +- awxkit/awxkit/{utils.py => utils/__init__.py} | 0 awxkit/awxkit/utils/toposort.py | 82 +++++++++++++++++++ awxkit/awxkit/ws.py | 5 +- awxkit/requirements.txt | 4 - awxkit/setup.py | 4 + awxkit/test/test_dependency_resolver.py | 2 +- awxkit/tox.ini | 1 + 9 files changed, 96 insertions(+), 13 deletions(-) rename awxkit/awxkit/{utils.py => utils/__init__.py} (100%) create mode 100644 awxkit/awxkit/utils/toposort.py diff --git a/awxkit/awxkit/api/mixins/has_create.py b/awxkit/awxkit/api/mixins/has_create.py index 672a3efd5f..43a2810697 100644 --- a/awxkit/awxkit/api/mixins/has_create.py +++ b/awxkit/awxkit/api/mixins/has_create.py @@ -1,9 +1,8 @@ from collections import defaultdict import inspect -from toposort import toposort - from awxkit.utils import get_class_if_instance, class_name_to_kw_arg, is_proper_subclass, super_dir_set +from awxkit.utils.toposort import toposort # HasCreate dependency resolution and creation utilities diff --git a/awxkit/awxkit/api/pages/credentials.py b/awxkit/awxkit/api/pages/credentials.py index cfa3594aae..af78eb141e 100644 --- a/awxkit/awxkit/api/pages/credentials.py +++ b/awxkit/awxkit/api/pages/credentials.py @@ -1,10 +1,6 @@ import logging -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa - from awxkit.utils import ( cloud_types, filter_by_class, @@ -47,6 +43,10 @@ credential_input_fields = ( def generate_private_key(): + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import rsa + key = rsa.generate_private_key( public_exponent=65537, key_size=4096, diff --git a/awxkit/awxkit/utils.py b/awxkit/awxkit/utils/__init__.py similarity index 100% rename from awxkit/awxkit/utils.py rename to awxkit/awxkit/utils/__init__.py diff --git a/awxkit/awxkit/utils/toposort.py b/awxkit/awxkit/utils/toposort.py new file mode 100644 index 0000000000..4874cd7ec9 --- /dev/null +++ b/awxkit/awxkit/utils/toposort.py @@ -0,0 +1,82 @@ +####################################################################### +# Implements a topological sort algorithm. +# +# Copyright 2014 True Blade Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Notes: +# Based on http://code.activestate.com/recipes/578272-topological-sort +# with these major changes: +# Added unittests. +# Deleted doctests (maybe not the best idea in the world, but it cleans +# up the docstring). +# Moved functools import to the top of the file. +# Changed assert to a ValueError. +# Changed iter[items|keys] to [items|keys], for python 3 +# compatibility. I don't think it matters for python 2 these are +# now lists instead of iterables. +# Copy the input so as to leave it unmodified. +# Renamed function from toposort2 to toposort. +# Handle empty input. +# Switch tests to use set literals. +# +######################################################################## + +from functools import reduce as _reduce + +__all__ = ['toposort', 'CircularDependencyError'] + + +class CircularDependencyError(ValueError): + def __init__(self, data): + # Sort the data just to make the output consistent, for use in + # error messages. That's convenient for doctests. + s = 'Circular dependencies exist among these items: {{{}}}'.format(', '.join('{!r}:{!r}'.format(key, value) for key, value in sorted(data.items()))) # noqa + super(CircularDependencyError, self).__init__(s) + self.data = data + + +def toposort(data): + """Dependencies are expressed as a dictionary whose keys are items +and whose values are a set of dependent items. Output is a list of +sets in topological order. The first set consists of items with no +dependences, each subsequent set consists of items that depend upon +items in the preceeding sets. +""" + + # Special case empty input. + if len(data) == 0: + return + + # Copy the input so as to leave it unmodified. + data = data.copy() + + # Ignore self dependencies. + for k, v in data.items(): + v.discard(k) + # Find all items that don't depend on anything. + extra_items_in_deps = _reduce(set.union, data.values()) - set(data.keys()) + # Add empty dependences where needed. + data.update({item: set() for item in extra_items_in_deps}) + while True: + ordered = set(item for item, dep in data.items() if len(dep) == 0) + if not ordered: + break + yield ordered + data = { + item: (dep - ordered) + for item, dep in data.items() if item not in ordered + } + if len(data) != 0: + raise CircularDependencyError(data) diff --git a/awxkit/awxkit/ws.py b/awxkit/awxkit/ws.py index 224663ea25..4c39906914 100644 --- a/awxkit/awxkit/ws.py +++ b/awxkit/awxkit/ws.py @@ -7,8 +7,6 @@ import json import ssl import urllib.parse -import websocket - from awxkit.config import config @@ -53,6 +51,9 @@ class WSClient(object): # Subscription group types def __init__(self, token=None, hostname='', port=443, secure=True, session_id=None, csrftoken=None): + # delay this import, because this is an optional dependency + import websocket + if not hostname: result = urllib.parse.urlparse(config.base_url) secure = result.scheme == 'https' diff --git a/awxkit/requirements.txt b/awxkit/requirements.txt index ee3930f7dc..b662004a18 100644 --- a/awxkit/requirements.txt +++ b/awxkit/requirements.txt @@ -1,8 +1,4 @@ PyYAML>=5.1 -cryptography -flake8 python-dateutil requests termcolor -toposort -websocket-client>=0.54.0 diff --git a/awxkit/setup.py b/awxkit/setup.py index 200889424a..2a13705b3b 100644 --- a/awxkit/setup.py +++ b/awxkit/setup.py @@ -68,6 +68,10 @@ setup( include_package_data=True, install_requires=requirements, python_requires=">= 3.5", + extras_require={ + 'formatting': ['jq', 'tabulate'], + 'websockets': ['websocket-client>0.54.0'], + }, entry_points={ 'console_scripts': [ 'akit=awxkit.scripts.basic_session:load_interactive', diff --git a/awxkit/test/test_dependency_resolver.py b/awxkit/test/test_dependency_resolver.py index 05c92a8c95..4a7e85e3c4 100644 --- a/awxkit/test/test_dependency_resolver.py +++ b/awxkit/test/test_dependency_resolver.py @@ -1,7 +1,7 @@ -from toposort import CircularDependencyError import pytest from awxkit.utils import filter_by_class +from awxkit.utils.toposort import CircularDependencyError from awxkit.api.mixins import has_create diff --git a/awxkit/tox.ini b/awxkit/tox.ini index 763d226e68..d2769d66c8 100644 --- a/awxkit/tox.ini +++ b/awxkit/tox.ini @@ -12,6 +12,7 @@ passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH setenv = PYTHONPATH = {toxinidir}:{env:PYTHONPATH:}:. deps = + websocket-client coverage pytest pytest-mock