diff options
author | Ryan Petrello <rpetrell@redhat.com> | 2021-03-19 17:44:51 +0100 |
---|---|---|
committer | Ryan Petrello <rpetrell@redhat.com> | 2021-03-23 14:39:58 +0100 |
commit | c2ef0a65002b4c945fedfefc121dbc0a0390894f (patch) | |
tree | 52ab6712c9673f16b3f6aa5213f565b0e4008863 /awxkit | |
parent | Merge pull request #9655 from ansible/jakemcdermott-patch-changelog (diff) | |
download | awx-c2ef0a65002b4c945fedfefc121dbc0a0390894f.tar.xz awx-c2ef0a65002b4c945fedfefc121dbc0a0390894f.zip |
move code linting to a stricter pep8-esque auto-formatting tool, black
Diffstat (limited to 'awxkit')
80 files changed, 2572 insertions, 2171 deletions
diff --git a/awxkit/awxkit/__init__.py b/awxkit/awxkit/__init__.py index 23e0598237..c75166cf5a 100644 --- a/awxkit/awxkit/__init__.py +++ b/awxkit/awxkit/__init__.py @@ -1,4 +1,4 @@ -from awxkit.api import pages, client, resources # NOQA -from awxkit.config import config # NOQA -from awxkit import awx # NOQA -from awxkit.ws import WSClient # NOQA +from awxkit.api import pages, client, resources # NOQA +from awxkit.config import config # NOQA +from awxkit import awx # NOQA +from awxkit.ws import WSClient # NOQA diff --git a/awxkit/awxkit/api/__init__.py b/awxkit/awxkit/api/__init__.py index 6f65601438..04f0f38514 100644 --- a/awxkit/awxkit/api/__init__.py +++ b/awxkit/awxkit/api/__init__.py @@ -1,2 +1,2 @@ -from .pages import * # NOQA -from .client import * # NOQA +from .pages import * # NOQA +from .client import * # NOQA diff --git a/awxkit/awxkit/api/client.py b/awxkit/awxkit/api/client.py index 77c71a569b..1cea4a61c2 100644 --- a/awxkit/awxkit/api/client.py +++ b/awxkit/awxkit/api/client.py @@ -49,8 +49,7 @@ class Connection(object): _next = kwargs.get('next') if _next: headers = self.session.headers.copy() - self.post('/api/login/', headers=headers, - data=dict(username=username, password=password, next=_next)) + self.post('/api/login/', headers=headers, data=dict(username=username, password=password, next=_next)) self.session_id = self.session.cookies.get('sessionid') self.uses_session_cookie = True else: @@ -79,8 +78,7 @@ class Connection(object): use_endpoint = use_endpoint[1:] url = '/'.join([self.server, use_endpoint]) - kwargs = dict(verify=self.verify, params=query_parameters, json=json, data=data, - hooks=dict(response=log_elapsed)) + kwargs = dict(verify=self.verify, params=query_parameters, json=json, data=data, hooks=dict(response=log_elapsed)) if headers is not None: kwargs['headers'] = headers diff --git a/awxkit/awxkit/api/mixins/has_copy.py b/awxkit/awxkit/api/mixins/has_copy.py index d05ebd4fcd..79bc5589c0 100644 --- a/awxkit/awxkit/api/mixins/has_copy.py +++ b/awxkit/awxkit/api/mixins/has_copy.py @@ -3,7 +3,6 @@ from awxkit.utils import random_title class HasCopy(object): - def can_copy(self): return self.get_related('copy').can_copy diff --git a/awxkit/awxkit/api/mixins/has_create.py b/awxkit/awxkit/api/mixins/has_create.py index 43a2810697..1e8b24db5a 100644 --- a/awxkit/awxkit/api/mixins/has_create.py +++ b/awxkit/awxkit/api/mixins/has_create.py @@ -24,7 +24,7 @@ def dependency_graph(page, *provided_dependencies): return graph -def optional_dependency_graph(page, *provided_dependencies): +def optional_dependency_graph(page, *provided_dependencies): """Creates a dependency graph for a page including all dependencies and optional_dependencies Any optional provided_dependencies will be included as if they were dependencies, without affecting the value of each keyed page. @@ -104,8 +104,7 @@ def all_instantiated_dependencies(*potential_parents): """ scope_provided_dependencies = [] - instantiated = set([x for x in potential_parents - if not isinstance(x, type) and not isinstance(x, tuple)]) + instantiated = set([x for x in potential_parents if not isinstance(x, type) and not isinstance(x, tuple)]) for potential_parent in [x for x in instantiated if hasattr(x, '_dependency_store')]: for dependency in potential_parent._dependency_store.values(): @@ -178,7 +177,6 @@ class DSAdapter(object): # Hijack json.dumps and simplejson.dumps (used by requests) # to allow HasCreate.create_payload() serialization without impacting payload.ds access def filter_ds_from_payload(dumps): - def _filter_ds_from_payload(obj, *a, **kw): if hasattr(obj, 'get') and isinstance(obj.get('ds'), DSAdapter): filtered = obj.copy() @@ -191,10 +189,12 @@ def filter_ds_from_payload(dumps): import json # noqa + json.dumps = filter_ds_from_payload(json.dumps) try: import simplejson # noqa + simplejson.dumps = filter_ds_from_payload(simplejson.dumps) except ImportError: pass @@ -299,8 +299,7 @@ class HasCreate(object): # remove falsy values provided_and_desired_dependencies = [x for x in provided_and_desired_dependencies if x] # (HasCreate(), True) tells HasCreate._update_dependencies to link - provided_dependencies = [(x, True) for x in provided_and_desired_dependencies - if not isinstance(x, type) and not isinstance(x, tuple)] + provided_dependencies = [(x, True) for x in provided_and_desired_dependencies if not isinstance(x, type) and not isinstance(x, tuple)] # Since dependencies are often declared at runtime, we need to use some introspection # to determine previously created ones for proper dependency store linking. @@ -374,12 +373,7 @@ class HasCreate(object): to_teardown = all_instantiated_dependencies(self) to_teardown_types = set(map(get_class_if_instance, to_teardown)) order = [ - set( - [ - potential for potential in ( - get_class_if_instance(x) for x in group) if potential in to_teardown_types - ] - ) + set([potential for potential in (get_class_if_instance(x) for x in group) if potential in to_teardown_types]) for group in page_creation_order(self, *to_teardown) ] order.reverse() diff --git a/awxkit/awxkit/api/mixins/has_instance_groups.py b/awxkit/awxkit/api/mixins/has_instance_groups.py index 7d06499e21..8a61e7618e 100644 --- a/awxkit/awxkit/api/mixins/has_instance_groups.py +++ b/awxkit/awxkit/api/mixins/has_instance_groups.py @@ -3,7 +3,6 @@ import awxkit.exceptions as exc class HasInstanceGroups(object): - def add_instance_group(self, instance_group): with suppress(exc.NoContent): self.related['instance_groups'].post(dict(id=instance_group.id)) diff --git a/awxkit/awxkit/api/mixins/has_notifications.py b/awxkit/awxkit/api/mixins/has_notifications.py index aced603af2..15d387712b 100644 --- a/awxkit/awxkit/api/mixins/has_notifications.py +++ b/awxkit/awxkit/api/mixins/has_notifications.py @@ -2,29 +2,25 @@ from awxkit.utils import suppress import awxkit.exceptions as exc -notification_endpoints = ("notification_templates", "notification_templates_started", "notification_templates_error", - "notification_templates_success") +notification_endpoints = ("notification_templates", "notification_templates_started", "notification_templates_error", "notification_templates_success") wfjt_notification_endpoints = notification_endpoints + ('notification_templates_approvals',) class HasNotifications(object): - def add_notification_template(self, notification_template, endpoint="notification_templates_success"): from awxkit.api.pages.workflow_job_templates import WorkflowJobTemplate - supported_endpoints = wfjt_notification_endpoints if isinstance(self, WorkflowJobTemplate) \ - else notification_endpoints + + supported_endpoints = wfjt_notification_endpoints if isinstance(self, WorkflowJobTemplate) else notification_endpoints if endpoint not in supported_endpoints: - raise ValueError('Unsupported notification endpoint "{0}". Please use one of {1}.' - .format(endpoint, notification_endpoints)) + raise ValueError('Unsupported notification endpoint "{0}". Please use one of {1}.'.format(endpoint, notification_endpoints)) with suppress(exc.NoContent): self.related[endpoint].post(dict(id=notification_template.id)) def remove_notification_template(self, notification_template, endpoint="notification_templates_success"): from awxkit.api.pages.workflow_job_templates import WorkflowJobTemplate - supported_endpoints = wfjt_notification_endpoints if isinstance(self, WorkflowJobTemplate) \ - else notification_endpoints + + supported_endpoints = wfjt_notification_endpoints if isinstance(self, WorkflowJobTemplate) else notification_endpoints if endpoint not in supported_endpoints: - raise ValueError('Unsupported notification endpoint "{0}". Please use one of {1}.' - .format(endpoint, notification_endpoints)) + raise ValueError('Unsupported notification endpoint "{0}". Please use one of {1}.'.format(endpoint, notification_endpoints)) with suppress(exc.NoContent): self.related[endpoint].post(dict(id=notification_template.id, disassociate=notification_template.id)) diff --git a/awxkit/awxkit/api/mixins/has_status.py b/awxkit/awxkit/api/mixins/has_status.py index db14874b6c..e4bd603327 100644 --- a/awxkit/awxkit/api/mixins/has_status.py +++ b/awxkit/awxkit/api/mixins/has_status.py @@ -40,8 +40,7 @@ class HasStatus(object): if not getattr(self, 'event_processing_finished', True): elapsed = datetime.utcnow() - start_time time_left = timeout - elapsed.total_seconds() - poll_until(lambda: getattr(self.get(), 'event_processing_finished', True), - interval=interval, timeout=time_left, **kwargs) + poll_until(lambda: getattr(self.get(), 'event_processing_finished', True), interval=interval, timeout=time_left, **kwargs) return self def wait_until_started(self, interval=1, timeout=60): @@ -65,9 +64,7 @@ class HasStatus(object): msg = '' else: msg += '\n' - msg += '{0}-{1} has status of {2}, which is not in {3}.'.format( - self.type.title(), self.id, self.status, status_list - ) + msg += '{0}-{1} has status of {2}, which is not in {3}.'.format(self.type.title(), self.id, self.status, status_list) if getattr(self, 'job_explanation', ''): msg += '\njob_explanation: {}'.format(bytes_to_str(self.job_explanation)) if getattr(self, 'result_traceback', ''): @@ -79,10 +76,8 @@ class HasStatus(object): try: data = json.loads(self.job_explanation.replace('Previous Task Failed: ', '')) dep_output = self.connection.get( - '{0}/api/v2/{1}s/{2}/stdout/'.format( - self.endpoint.split('/api')[0], data['job_type'], data['job_id'] - ), - query_parameters=dict(format='txt_download') + '{0}/api/v2/{1}s/{2}/stdout/'.format(self.endpoint.split('/api')[0], data['job_type'], data['job_id']), + query_parameters=dict(format='txt_download'), ).content msg += '\nDependency output:\n{}'.format(bytes_to_str(dep_output)) except Exception as e: diff --git a/awxkit/awxkit/api/mixins/has_survey.py b/awxkit/awxkit/api/mixins/has_survey.py index 07729f805e..e447580595 100644 --- a/awxkit/awxkit/api/mixins/has_survey.py +++ b/awxkit/awxkit/api/mixins/has_survey.py @@ -3,13 +3,11 @@ from awxkit.utils import random_title class HasSurvey(object): def add_survey(self, spec=None, name=None, description=None, required=False, enabled=True): - payload = dict(name=name or 'Survey - {}'.format(random_title()), - description=description or random_title(10), - spec=spec or [dict(required=required, - question_name="What's the password?", - variable="secret", - type="password", - default="foo")]) + payload = dict( + name=name or 'Survey - {}'.format(random_title()), + description=description or random_title(10), + spec=spec or [dict(required=required, question_name="What's the password?", variable="secret", type="password", default="foo")], + ) if enabled != self.survey_enabled: self.patch(survey_enabled=enabled) return self.related.survey_spec.post(payload).get() diff --git a/awxkit/awxkit/api/mixins/has_variables.py b/awxkit/awxkit/api/mixins/has_variables.py index f69b9b38e9..d3cb859582 100644 --- a/awxkit/awxkit/api/mixins/has_variables.py +++ b/awxkit/awxkit/api/mixins/has_variables.py @@ -4,7 +4,6 @@ from awxkit.utils import PseudoNamespace class HasVariables(object): - @property def variables(self): return PseudoNamespace(yaml.safe_load(self.json.variables)) diff --git a/awxkit/awxkit/api/pages/__init__.py b/awxkit/awxkit/api/pages/__init__.py index 1d78d4ba5e..aa55a938e6 100644 --- a/awxkit/awxkit/api/pages/__init__.py +++ b/awxkit/awxkit/api/pages/__init__.py @@ -33,7 +33,7 @@ from .workflow_job_templates import * # NOQA from .workflow_job_template_nodes import * # NOQA from .workflow_jobs import * # NOQA from .workflow_job_nodes import * # NOQA -from .workflow_approvals import * # NOQA +from .workflow_approvals import * # NOQA from .settings import * # NOQA from .instances import * # NOQA from .instance_groups import * # NOQA diff --git a/awxkit/awxkit/api/pages/access_list.py b/awxkit/awxkit/api/pages/access_list.py index f654f84a01..f037fcfa72 100644 --- a/awxkit/awxkit/api/pages/access_list.py +++ b/awxkit/awxkit/api/pages/access_list.py @@ -8,11 +8,16 @@ class AccessList(page.PageList, users.User): pass -page.register_page([resources.organization_access_list, - resources.user_access_list, - resources.inventory_access_list, - resources.group_access_list, - resources.credential_access_list, - resources.project_access_list, - resources.job_template_access_list, - resources.team_access_list], AccessList) +page.register_page( + [ + resources.organization_access_list, + resources.user_access_list, + resources.inventory_access_list, + resources.group_access_list, + resources.credential_access_list, + resources.project_access_list, + resources.job_template_access_list, + resources.team_access_list, + ], + AccessList, +) diff --git a/awxkit/awxkit/api/pages/activity_stream.py b/awxkit/awxkit/api/pages/activity_stream.py index fda9d429e0..0be25e47c9 100644 --- a/awxkit/awxkit/api/pages/activity_stream.py +++ b/awxkit/awxkit/api/pages/activity_stream.py @@ -16,5 +16,4 @@ class ActivityStreams(page.PageList, ActivityStream): pass -page.register_page([resources.activity_stream, - resources.object_activity_stream], ActivityStreams) +page.register_page([resources.activity_stream, resources.object_activity_stream], ActivityStreams) diff --git a/awxkit/awxkit/api/pages/ad_hoc_commands.py b/awxkit/awxkit/api/pages/ad_hoc_commands.py index 374e13c127..39b11d8746 100644 --- a/awxkit/awxkit/api/pages/ad_hoc_commands.py +++ b/awxkit/awxkit/api/pages/ad_hoc_commands.py @@ -24,31 +24,40 @@ class AdHocCommand(HasCreate, UnifiedJob): return self.walk(result.url) def payload(self, inventory, credential, module_name='ping', **kwargs): - payload = PseudoNamespace(inventory=inventory.id, - credential=credential.id, - module_name=module_name) + payload = PseudoNamespace(inventory=inventory.id, credential=credential.id, module_name=module_name) - optional_fields = ('diff_mode', 'extra_vars', 'module_args', 'job_type', 'limit', 'forks', - 'verbosity') + optional_fields = ('diff_mode', 'extra_vars', 'module_args', 'job_type', 'limit', 'forks', 'verbosity') return update_payload(payload, optional_fields, kwargs) - def create_payload(self, module_name='ping', module_args=np, job_type=np, limit=np, verbosity=np, - inventory=Inventory, credential=Credential, **kwargs): + def create_payload(self, module_name='ping', module_args=np, job_type=np, limit=np, verbosity=np, inventory=Inventory, credential=Credential, **kwargs): self.create_and_update_dependencies(inventory, credential) - payload = self.payload(module_name=module_name, module_args=module_args, job_type=job_type, limit=limit, - verbosity=verbosity, inventory=self.ds.inventory, credential=self.ds.credential, - **kwargs) + payload = self.payload( + module_name=module_name, + module_args=module_args, + job_type=job_type, + limit=limit, + verbosity=verbosity, + inventory=self.ds.inventory, + credential=self.ds.credential, + **kwargs + ) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload - def create(self, module_name='ping', module_args=np, job_type=np, limit=np, verbosity=np, - inventory=Inventory, credential=Credential, **kwargs): - - payload = self.create_payload(module_name=module_name, module_args=module_args, - job_type=job_type, limit=limit, verbosity=verbosity, - inventory=inventory, credential=credential, **kwargs) + def create(self, module_name='ping', module_args=np, job_type=np, limit=np, verbosity=np, inventory=Inventory, credential=Credential, **kwargs): + + payload = self.create_payload( + module_name=module_name, + module_args=module_args, + job_type=job_type, + limit=limit, + verbosity=verbosity, + inventory=inventory, + credential=credential, + **kwargs + ) return self.update_identity(AdHocCommands(self.connection).post(payload)) @@ -60,7 +69,7 @@ class AdHocCommands(page.PageList, AdHocCommand): pass -page.register_page([resources.ad_hoc_commands, - resources.inventory_related_ad_hoc_commands, - resources.group_related_ad_hoc_commands, - resources.host_related_ad_hoc_commands], AdHocCommands) +page.register_page( + [resources.ad_hoc_commands, resources.inventory_related_ad_hoc_commands, resources.group_related_ad_hoc_commands, resources.host_related_ad_hoc_commands], + AdHocCommands, +) diff --git a/awxkit/awxkit/api/pages/api.py b/awxkit/awxkit/api/pages/api.py index 4edc07857f..a333314fc1 100644 --- a/awxkit/awxkit/api/pages/api.py +++ b/awxkit/awxkit/api/pages/api.py @@ -90,18 +90,14 @@ class ApiV2(base.Base): return None # Note: doing _page[key] automatically parses json blob strings, which can be a problem. - fields = { - key: _page.json[key] for key in post_fields - if key in _page.json and key not in _page.related and key != 'id' - } + fields = {key: _page.json[key] for key in post_fields if key in _page.json and key not in _page.related and key != 'id'} for key in post_fields: if key in _page.related: related = _page.related[key] else: if post_fields[key]['type'] == 'id' and _page.json.get(key) is not None: - log.warning("Related link %r missing from %s, attempting to reconstruct endpoint.", - key, _page.endpoint) + log.warning("Related link %r missing from %s, attempting to reconstruct endpoint.", key, _page.endpoint) resource = getattr(self, key, None) if resource is None: log.error("Unable to infer endpoint for %r on %s.", key, _page.endpoint) @@ -119,8 +115,7 @@ class ApiV2(base.Base): continue rel_natural_key = rel_endpoint.get_natural_key(self._cache) if rel_natural_key is None: - log.error("Unable to construct a natural key for foreign key %r of object %s.", - key, _page.endpoint) + log.error("Unable to construct a natural key for foreign key %r of object %s.", key, _page.endpoint) return None # This foreign key has unresolvable dependencies fields[key] = rel_natural_key @@ -154,10 +149,7 @@ class ApiV2(base.Base): continue if 'results' in rel_page: - results = ( - x.get_natural_key(self._cache) if by_natural_key else self._export(x, rel_post_fields) - for x in rel_page.results - ) + results = (x.get_natural_key(self._cache) if by_natural_key else self._export(x, rel_post_fields) for x in rel_page.results) related[key] = [x for x in results if x is not None] else: related[key] = rel_page.json @@ -190,8 +182,7 @@ class ApiV2(base.Base): if isinstance(value, int) or value.isdecimal(): return endpoint.get(id=int(value)) options = self._cache.get_options(endpoint) - identifier = next(field for field in options['search_fields'] - if field in ('name', 'username', 'hostname')) + identifier = next(field for field in options['search_fields'] if field in ('name', 'username', 'hostname')) return endpoint.get(**{identifier: value}) def export_assets(self, **kwargs): @@ -214,8 +205,7 @@ class ApiV2(base.Base): # Import methods def _dependent_resources(self, data): - page_resource = {getattr(self, resource)._create().__item_class__: resource - for resource in self.json} + page_resource = {getattr(self, resource)._create().__item_class__: resource for resource in self.json} data_pages = [getattr(self, resource)._create().__item_class__ for resource in EXPORTABLE_RESOURCES] for page_cls in itertools.chain(*has_create.page_creation_order(*data_pages)): diff --git a/awxkit/awxkit/api/pages/applications.py b/awxkit/awxkit/api/pages/applications.py index 843aefec58..18737cd883 100644 --- a/awxkit/awxkit/api/pages/applications.py +++ b/awxkit/awxkit/api/pages/applications.py @@ -12,10 +12,12 @@ class OAuth2Application(HasCreate, base.Base): dependencies = [Organization] def payload(self, **kwargs): - payload = PseudoNamespace(name=kwargs.get('name') or 'OAuth2Application - {}'.format(random_title()), - description=kwargs.get('description') or random_title(10), - client_type=kwargs.get('client_type', 'public'), - authorization_grant_type=kwargs.get('authorization_grant_type', 'password')) + payload = PseudoNamespace( + name=kwargs.get('name') or 'OAuth2Application - {}'.format(random_title()), + description=kwargs.get('description') or random_title(10), + client_type=kwargs.get('client_type', 'public'), + authorization_grant_type=kwargs.get('authorization_grant_type', 'password'), + ) if kwargs.get('organization'): payload.organization = kwargs['organization'].id @@ -35,8 +37,7 @@ class OAuth2Application(HasCreate, base.Base): return self.update_identity(OAuth2Applications(self.connection).post(payload)) -page.register_page((resources.application, - (resources.applications, 'post')), OAuth2Application) +page.register_page((resources.application, (resources.applications, 'post')), OAuth2Application) class OAuth2Applications(page.PageList, OAuth2Application): @@ -51,8 +52,7 @@ class OAuth2AccessToken(HasCreate, base.Base): optional_dependencies = [OAuth2Application] def payload(self, **kwargs): - payload = PseudoNamespace(description=kwargs.get('description') or random_title(10), - scope=kwargs.get('scope', 'write')) + payload = PseudoNamespace(description=kwargs.get('description') or random_title(10), scope=kwargs.get('scope', 'write')) if kwargs.get('oauth_2_application'): payload.application = kwargs['oauth_2_application'].id @@ -73,8 +73,7 @@ class OAuth2AccessToken(HasCreate, base.Base): return self.update_identity(OAuth2AccessTokens(self.connection).post(payload)) -page.register_page((resources.token, - (resources.tokens, 'post')), OAuth2AccessToken) +page.register_page((resources.token, (resources.tokens, 'post')), OAuth2AccessToken) class OAuth2AccessTokens(page.PageList, OAuth2AccessToken): diff --git a/awxkit/awxkit/api/pages/base.py b/awxkit/awxkit/api/pages/base.py index f3c3957c9d..7e9d210ab3 100644 --- a/awxkit/awxkit/api/pages/base.py +++ b/awxkit/awxkit/api/pages/base.py @@ -3,11 +3,7 @@ import logging from requests.auth import HTTPBasicAuth -from awxkit.api.pages import ( - Page, - get_registered_page, - exception_from_status_code -) +from awxkit.api.pages import Page, get_registered_page, exception_from_status_code from awxkit.config import config from awxkit.api.resources import resources import awxkit.exceptions as exc @@ -17,7 +13,6 @@ log = logging.getLogger(__name__) class Base(Page): - def silent_delete(self): """Delete the object. If it's already deleted, ignore the error""" try: @@ -129,14 +124,14 @@ class Base(Page): @property def object_roles(self): from awxkit.api.pages import Roles, Role + url = self.get().json.related.object_roles for obj_role in Roles(self.connection, endpoint=url).get().json.results: yield Role(self.connection, endpoint=obj_role.url).get() def get_authtoken(self, username='', password=''): default_cred = config.credentials.default - payload = dict(username=username or default_cred.username, - password=password or default_cred.password) + payload = dict(username=username or default_cred.username, password=password or default_cred.password) auth_url = resources.authtoken return get_registered_page(auth_url)(self.connection, endpoint=auth_url).post(payload).token @@ -146,9 +141,7 @@ class Base(Page): load_default_authtoken = load_authtoken - def get_oauth2_token(self, username='', password='', client_id=None, - description='AWX CLI', - client_secret=None, scope='write'): + def get_oauth2_token(self, username='', password='', client_id=None, description='AWX CLI', client_secret=None, scope='write'): default_cred = config.credentials.default username = username or default_cred.username password = password or default_cred.password @@ -157,38 +150,21 @@ class Base(Page): HTTPBasicAuth(client_id, client_secret)(req) req.headers['Content-Type'] = 'application/x-www-form-urlencoded' resp = self.connection.post( - '/api/o/token/', - data={ - "grant_type": "password", - "username": username, - "password": password, - "scope": scope - }, - headers=req.headers + '/api/o/token/', data={"grant_type": "password", "username": username, "password": password, "scope": scope}, headers=req.headers ) elif client_id: req.headers['Content-Type'] = 'application/x-www-form-urlencoded' resp = self.connection.post( '/api/o/token/', - data={ - "grant_type": "password", - "username": username, - "password": password, - "client_id": client_id, - "scope": scope - }, - headers=req.headers + data={"grant_type": "password", "username": username, "password": password, "client_id": client_id, "scope": scope}, + headers=req.headers, ) else: HTTPBasicAuth(username, password)(req) resp = self.connection.post( '/api/v2/users/{}/personal_tokens/'.format(username), - json={ - "description": description, - "application": None, - "scope": scope - }, - headers=req.headers + json={"description": description, "application": None, "scope": scope}, + headers=req.headers, ) if resp.ok: result = resp.json() @@ -201,9 +177,9 @@ class Base(Page): def load_session(self, username='', password=''): default_cred = config.credentials.default - self.connection.login(username=username or default_cred.username, - password=password or default_cred.password, - **self.connection.get_session_requirements()) + self.connection.login( + username=username or default_cred.username, password=password or default_cred.password, **self.connection.get_session_requirements() + ) return self def cleanup(self): diff --git a/awxkit/awxkit/api/pages/config.py b/awxkit/awxkit/api/pages/config.py index 56a620da7f..e4ea14765f 100644 --- a/awxkit/awxkit/api/pages/config.py +++ b/awxkit/awxkit/api/pages/config.py @@ -4,22 +4,17 @@ from . import page class Config(base.Base): - @property def is_aws_license(self): - return self.license_info.get('is_aws', False) or \ - 'ami-id' in self.license_info or \ - 'instance-id' in self.license_info + return self.license_info.get('is_aws', False) or 'ami-id' in self.license_info or 'instance-id' in self.license_info @property def is_valid_license(self): - return self.license_info.get('valid_key', False) and \ - 'instance_count' in self.license_info + return self.license_info.get('valid_key', False) and 'instance_count' in self.license_info @property def is_trial_license(self): - return self.is_valid_license and \ - self.license_info.get('trial', False) + return self.is_valid_license and self.license_info.get('trial', False) @property def is_awx_license(self): @@ -27,8 +22,7 @@ class Config(base.Base): @property def is_enterprise_license(self): - return self.is_valid_license and \ - self.license_info.get('license_type', None) == 'enterprise' + return self.is_valid_license and self.license_info.get('license_type', None) == 'enterprise' @property def features(self): @@ -37,7 +31,6 @@ class Config(base.Base): class ConfigAttach(page.Page): - def attach(self, **kwargs): return self.post(json=kwargs).json diff --git a/awxkit/awxkit/api/pages/credential_input_sources.py b/awxkit/awxkit/api/pages/credential_input_sources.py index 72112ec098..c500cfca81 100644 --- a/awxkit/awxkit/api/pages/credential_input_sources.py +++ b/awxkit/awxkit/api/pages/credential_input_sources.py @@ -16,5 +16,4 @@ class CredentialInputSources(page.PageList, CredentialInputSource): pass -page.register_page([resources.credential_input_sources, - resources.related_input_sources], CredentialInputSources) +page.register_page([resources.credential_input_sources, resources.related_input_sources], CredentialInputSources) diff --git a/awxkit/awxkit/api/pages/credentials.py b/awxkit/awxkit/api/pages/credentials.py index e5b80cf7d0..11fadd8711 100644 --- a/awxkit/awxkit/api/pages/credentials.py +++ b/awxkit/awxkit/api/pages/credentials.py @@ -44,7 +44,8 @@ credential_input_fields = ( 'tenant', 'username', 'vault_password', - 'vault_id') + 'vault_id', +) def generate_private_key(): @@ -52,15 +53,9 @@ def generate_private_key(): from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa - key = rsa.generate_private_key( - public_exponent=65537, - key_size=4096, - backend=default_backend() - ) + key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) return key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption() + encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() ).decode('utf-8') @@ -98,11 +93,10 @@ credential_type_name_to_config_kind_map = { 'source control': 'scm', 'machine': 'ssh', 'vault': 'vault', - 'vmware vcenter': 'vmware'} + 'vmware vcenter': 'vmware', +} -config_kind_to_credential_type_name_map = { - kind: name - for name, kind in credential_type_name_to_config_kind_map.items()} +config_kind_to_credential_type_name_map = {kind: name for name, kind in credential_type_name_to_config_kind_map.items()} def kind_and_config_cred_from_credential_type(credential_type): @@ -115,8 +109,7 @@ def kind_and_config_cred_from_credential_type(credential_type): config_cred = config.credentials.network kind = 'net' elif credential_type.kind == 'cloud': - kind = credential_type_name_to_config_kind_map[credential_type.name.lower( - )] + kind = credential_type_name_to_config_kind_map[credential_type.name.lower()] config_kind = kind if kind != 'azure_rm' else 'azure' config_cred = config.credentials.cloud[config_kind] else: @@ -127,11 +120,8 @@ def kind_and_config_cred_from_credential_type(credential_type): return kind, PseudoNamespace() -def get_payload_field_and_value_from_kwargs_or_config_cred( - field, kind, kwargs, config_cred): - if field in ( - 'project_id', - 'project_name'): # Needed to prevent Project kwarg collision +def get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, kwargs, config_cred): + if field in ('project_id', 'project_name'): # Needed to prevent Project kwarg collision config_field = 'project' elif field == 'subscription' and 'azure' in kind: config_field = 'subscription_id' @@ -159,10 +149,8 @@ class CredentialType(HasCreate, base.Base): def payload(self, kind='cloud', **kwargs): payload = PseudoNamespace( - name=kwargs.get('name') or 'CredentialType - {}'.format( - random_title()), - description=kwargs.get('description') or random_title(10), - kind=kind) + name=kwargs.get('name') or 'CredentialType - {}'.format(random_title()), description=kwargs.get('description') or random_title(10), kind=kind + ) fields = ('inputs', 'injectors') update_payload(payload, fields, kwargs) return payload @@ -174,17 +162,13 @@ class CredentialType(HasCreate, base.Base): def create(self, kind='cloud', **kwargs): payload = self.create_payload(kind=kind, **kwargs) - return self.update_identity( - CredentialTypes( - self.connection).post(payload)) + return self.update_identity(CredentialTypes(self.connection).post(payload)) def test(self, data): """Test the credential type endpoint.""" response = self.connection.post(urljoin(str(self.url), 'test/'), data) exception = exception_from_status_code(response.status_code) - exc_str = "%s (%s) received" % ( - http.responses[response.status_code], response.status_code - ) + exc_str = "%s (%s) received" % (http.responses[response.status_code], response.status_code) if exception: raise exception(exc_str, response.json()) elif response.status_code == http.FORBIDDEN: @@ -192,8 +176,7 @@ class CredentialType(HasCreate, base.Base): return response -page.register_page([resources.credential_type, - (resources.credential_types, 'post')], CredentialType) +page.register_page([resources.credential_type, (resources.credential_types, 'post')], CredentialType) class CredentialTypes(page.PageList, CredentialType): @@ -210,27 +193,19 @@ class Credential(HasCopy, HasCreate, base.Base): optional_dependencies = [Organization, User, Team] NATURAL_KEY = ('organization', 'name', 'credential_type') - def payload( - self, - credential_type, - user=None, - team=None, - organization=None, - inputs=None, - **kwargs): + def payload(self, credential_type, user=None, team=None, organization=None, inputs=None, **kwargs): if not any((user, team, organization)): - raise TypeError( - '{0.__class__.__name__} requires user, team, and/or organization instances.'.format(self)) + raise TypeError('{0.__class__.__name__} requires user, team, and/or organization instances.'.format(self)) if inputs is None: inputs = {} payload = PseudoNamespace( - name=kwargs.get('name') or 'Credential - {}'.format( - random_title()), + name=kwargs.get('name') or 'Credential - {}'.format(random_title()), description=kwargs.get('description') or random_title(10), credential_type=credential_type.id, - inputs=inputs) + inputs=inputs, + ) if user: payload.user = user.id if team: @@ -238,38 +213,26 @@ class Credential(HasCopy, HasCreate, base.Base): if organization: payload.organization = organization.id - kind, config_cred = kind_and_config_cred_from_credential_type( - credential_type) + kind, config_cred = kind_and_config_cred_from_credential_type(credential_type) for field in credential_input_fields: - field, value = get_payload_field_and_value_from_kwargs_or_config_cred( - field, kind, inputs or kwargs, config_cred) + field, value = get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, inputs or kwargs, config_cred) if value != not_provided: payload.inputs[field] = value if kind == 'net': - payload.inputs.authorize = inputs.get( - 'authorize', bool(inputs.get('authorize_password'))) + payload.inputs.authorize = inputs.get('authorize', bool(inputs.get('authorize_password'))) if kind in ('ssh', 'net') and 'ssh_key_data' not in payload.inputs: - payload.inputs.ssh_key_data = inputs.get( - 'ssh_key_data', generate_private_key()) + payload.inputs.ssh_key_data = inputs.get('ssh_key_data', generate_private_key()) return payload - def create_payload( - self, - credential_type=CredentialType, - user=None, - team=None, - organization=Organization, - inputs=None, - **kwargs): + def create_payload(self, credential_type=CredentialType, user=None, team=None, organization=Organization, inputs=None, **kwargs): if isinstance(credential_type, int): # if an int was passed, it is assumed to be the pk id of a # credential type - credential_type = CredentialTypes( - self.connection).get(id=credential_type).results.pop() + credential_type = CredentialTypes(self.connection).get(id=credential_type).results.pop() if credential_type == CredentialType: kind = kwargs.pop('kind', 'ssh') @@ -282,57 +245,29 @@ class Credential(HasCopy, HasCreate, base.Base): inputs = config.credentials.cloud['openstack'] else: credential_type_name = config_kind_to_credential_type_name_map[kind] - credential_type = CredentialTypes( - self.connection).get( - managed_by_tower=True, - name__icontains=credential_type_name).results.pop() + credential_type = CredentialTypes(self.connection).get(managed_by_tower=True, name__icontains=credential_type_name).results.pop() - credential_type, organization, user, team = filter_by_class( - (credential_type, CredentialType), (organization, Organization), (user, User), (team, Team)) + credential_type, organization, user, team = filter_by_class((credential_type, CredentialType), (organization, Organization), (user, User), (team, Team)) if not any((user, team, organization)): organization = Organization - self.create_and_update_dependencies( - credential_type, organization, user, team) + self.create_and_update_dependencies(credential_type, organization, user, team) user = self.ds.user if user else None team = self.ds.team if team else None organization = self.ds.organization if organization else None - payload = self.payload( - self.ds.credential_type, - user=user, - team=team, - organization=organization, - inputs=inputs, - **kwargs) + payload = self.payload(self.ds.credential_type, user=user, team=team, organization=organization, inputs=inputs, **kwargs) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload - def create( - self, - credential_type=CredentialType, - user=None, - team=None, - organization=None, - inputs=None, - **kwargs): - payload = self.create_payload( - credential_type=credential_type, - user=user, - team=team, - organization=organization, - inputs=inputs, - **kwargs) - return self.update_identity( - Credentials( - self.connection)).post(payload) + def create(self, credential_type=CredentialType, user=None, team=None, organization=None, inputs=None, **kwargs): + payload = self.create_payload(credential_type=credential_type, user=user, team=team, organization=organization, inputs=inputs, **kwargs) + return self.update_identity(Credentials(self.connection)).post(payload) def test(self, data): """Test the credential endpoint.""" response = self.connection.post(urljoin(str(self.url), 'test/'), data) exception = exception_from_status_code(response.status_code) - exc_str = "%s (%s) received" % ( - http.responses[response.status_code], response.status_code - ) + exc_str = "%s (%s) received" % (http.responses[response.status_code], response.status_code) if exception: raise exception(exc_str, response.json()) elif response.status_code == http.FORBIDDEN: @@ -343,11 +278,7 @@ class Credential(HasCopy, HasCreate, base.Base): def expected_passwords_needed_to_start(self): """Return a list of expected passwords needed to start a job using this credential.""" passwords = [] - for field in ( - 'password', - 'become_password', - 'ssh_key_unlock', - 'vault_password'): + for field in ('password', 'become_password', 'ssh_key_unlock', 'vault_password'): if getattr(self.inputs, field, None) == 'ASK': if field == 'password': passwords.append('ssh_password') @@ -356,9 +287,7 @@ class Credential(HasCopy, HasCreate, base.Base): return passwords -page.register_page([resources.credential, - (resources.credentials, 'post'), - (resources.credential_copy, 'post')], Credential) +page.register_page([resources.credential, (resources.credentials, 'post'), (resources.credential_copy, 'post')], Credential) class Credentials(page.PageList, Credential): @@ -366,9 +295,7 @@ class Credentials(page.PageList, Credential): pass -page.register_page([resources.credentials, - resources.related_credentials], - Credentials) +page.register_page([resources.credentials, resources.related_credentials], Credentials) class CredentialCopy(base.Base): diff --git a/awxkit/awxkit/api/pages/execution_environments.py b/awxkit/awxkit/api/pages/execution_environments.py index 0471b1f1d3..01ee33afb5 100644 --- a/awxkit/awxkit/api/pages/execution_environments.py +++ b/awxkit/awxkit/api/pages/execution_environments.py @@ -46,14 +46,13 @@ class ExecutionEnvironment(HasCreate, HasCopy, base.Base): return payload -page.register_page([resources.execution_environment, - (resources.execution_environments, 'post'), - (resources.organization_execution_environments, 'post')], ExecutionEnvironment) +page.register_page( + [resources.execution_environment, (resources.execution_environments, 'post'), (resources.organization_execution_environments, 'post')], ExecutionEnvironment +) class ExecutionEnvironments(page.PageList, ExecutionEnvironment): pass -page.register_page([resources.execution_environments, - resources.organization_execution_environments], ExecutionEnvironments) +page.register_page([resources.execution_environments, resources.organization_execution_environments], ExecutionEnvironments) diff --git a/awxkit/awxkit/api/pages/instance_groups.py b/awxkit/awxkit/api/pages/instance_groups.py index 31af3e82fe..28aa614300 100644 --- a/awxkit/awxkit/api/pages/instance_groups.py +++ b/awxkit/awxkit/api/pages/instance_groups.py @@ -7,7 +7,6 @@ from . import page class InstanceGroup(HasCreate, base.Base): - def add_instance(self, instance): with suppress(exc.NoContent): self.related.instances.post(dict(id=instance.id)) @@ -17,8 +16,7 @@ class InstanceGroup(HasCreate, base.Base): self.related.instances.post(dict(id=instance.id, disassociate=True)) def payload(self, **kwargs): - payload = PseudoNamespace(name=kwargs.get('name') or - 'Instance Group - {}'.format(random_title())) + payload = PseudoNamespace(name=kwargs.get('name') or 'Instance Group - {}'.format(random_title())) fields = ('policy_instance_percentage', 'policy_instance_minimum', 'policy_instance_list', 'is_container_group') update_payload(payload, fields, kwargs) @@ -35,8 +33,7 @@ class InstanceGroup(HasCreate, base.Base): return self.update_identity(InstanceGroups(self.connection).post(payload)) -page.register_page([resources.instance_group, - (resources.instance_groups, 'post')], InstanceGroup) +page.register_page([resources.instance_group, (resources.instance_groups, 'post')], InstanceGroup) class InstanceGroups(page.PageList, InstanceGroup): @@ -44,5 +41,4 @@ class InstanceGroups(page.PageList, InstanceGroup): pass -page.register_page([resources.instance_groups, - resources.related_instance_groups], InstanceGroups) +page.register_page([resources.instance_groups, resources.related_instance_groups], InstanceGroups) diff --git a/awxkit/awxkit/api/pages/instances.py b/awxkit/awxkit/api/pages/instances.py index 7e8a0cff6d..38695014bf 100644 --- a/awxkit/awxkit/api/pages/instances.py +++ b/awxkit/awxkit/api/pages/instances.py @@ -16,5 +16,4 @@ class Instances(page.PageList, Instance): pass -page.register_page([resources.instances, - resources.related_instances], Instances) +page.register_page([resources.instances, resources.related_instances], Instances) diff --git a/awxkit/awxkit/api/pages/inventory.py b/awxkit/awxkit/api/pages/inventory.py index 31393b9ad4..beeb36a5a3 100644 --- a/awxkit/awxkit/api/pages/inventory.py +++ b/awxkit/awxkit/api/pages/inventory.py @@ -2,23 +2,8 @@ import logging import json import re -from awxkit.api.pages import ( - Credential, - Organization, - Project, - UnifiedJob, - UnifiedJobTemplate -) -from awxkit.utils import ( - filter_by_class, - random_title, - update_payload, - suppress, - not_provided, - PseudoNamespace, - poll_until, - random_utf8 -) +from awxkit.api.pages import Credential, Organization, Project, UnifiedJob, UnifiedJobTemplate +from awxkit.utils import filter_by_class, random_title, update_payload, suppress, not_provided, PseudoNamespace, poll_until, random_utf8 from awxkit.api.mixins import DSAdapter, HasCreate, HasInstanceGroups, HasNotifications, HasVariables, HasCopy from awxkit.api.resources import resources import awxkit.exceptions as exc @@ -68,56 +53,31 @@ class Inventory(HasCopy, HasCreate, HasInstanceGroups, HasVariables, base.Base): def payload(self, organization, **kwargs): payload = PseudoNamespace( - name=kwargs.get('name') or 'Inventory - {}'.format( - random_title()), + name=kwargs.get('name') or 'Inventory - {}'.format(random_title()), description=kwargs.get('description') or random_title(10), - organization=organization.id) + organization=organization.id, + ) - optional_fields = ( - 'host_filter', - 'insights_credential', - 'kind', - 'variables') + optional_fields = ('host_filter', 'insights_credential', 'kind', 'variables') update_payload(payload, optional_fields, kwargs) if 'variables' in payload and isinstance(payload.variables, dict): payload.variables = json.dumps(payload.variables) - if 'insights_credential' in payload and isinstance( - payload.insights_credential, Credential): + if 'insights_credential' in payload and isinstance(payload.insights_credential, Credential): payload.insights_credential = payload.insights_credential.id return payload - def create_payload( - self, - name='', - description='', - organization=Organization, - **kwargs): + def create_payload(self, name='', description='', organization=Organization, **kwargs): self.create_and_update_dependencies(organization) - payload = self.payload( - name=name, - description=description, - organization=self.ds.organization, - **kwargs) + payload = self.payload(name=name, description=description, organization=self.ds.organization, **kwargs) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload - def create( - self, - name='', - description='', - organization=Organization, - **kwargs): - payload = self.create_payload( - name=name, - description=description, - organization=organization, - **kwargs) - return self.update_identity( - Inventories( - self.connection).post(payload)) + def create(self, name='', description='', organization=Organization, **kwargs): + payload = self.create_payload(name=name, description=description, organization=organization, **kwargs) + return self.update_identity(Inventories(self.connection).post(payload)) def add_host(self, host=None): if host is None: @@ -135,17 +95,16 @@ class Inventory(HasCopy, HasCreate, HasInstanceGroups, HasVariables, base.Base): self.get() except exc.NotFound: return True + poll_until(_wait, interval=1, timeout=60) def update_inventory_sources(self, wait=False): response = self.related.update_inventory_sources.post() - source_ids = [entry['inventory_source'] - for entry in response if entry['status'] == 'started'] + source_ids = [entry['inventory_source'] for entry in response if entry['status'] == 'started'] inv_updates = [] for source_id in source_ids: - inv_source = self.related.inventory_sources.get( - id=source_id).results.pop() + inv_source = self.related.inventory_sources.get(id=source_id).results.pop() inv_updates.append(inv_source.related.current_job.get()) if wait: @@ -154,9 +113,7 @@ class Inventory(HasCopy, HasCreate, HasInstanceGroups, HasVariables, base.Base): return inv_updates -page.register_page([resources.inventory, - (resources.inventories, 'post'), - (resources.inventory_copy, 'post')], Inventory) +page.register_page([resources.inventory, (resources.inventories, 'post'), (resources.inventory_copy, 'post')], Inventory) class Inventories(page.PageList, Inventory): @@ -164,8 +121,7 @@ class Inventories(page.PageList, Inventory): pass -page.register_page([resources.inventories, - resources.related_inventories], Inventories) +page.register_page([resources.inventories, resources.related_inventories], Inventories) class InventoryScript(HasCopy, HasCreate, base.Base): @@ -174,77 +130,48 @@ class InventoryScript(HasCopy, HasCreate, base.Base): def payload(self, organization, **kwargs): payload = PseudoNamespace( - name=kwargs.get('name') or 'Inventory Script - {}'.format( - random_title()), + name=kwargs.get('name') or 'Inventory Script - {}'.format(random_title()), description=kwargs.get('description') or random_title(10), organization=organization.id, - script=kwargs.get('script') or self._generate_script()) + script=kwargs.get('script') or self._generate_script(), + ) return payload - def create_payload( - self, - name='', - description='', - organization=Organization, - script='', - **kwargs): + def create_payload(self, name='', description='', organization=Organization, script='', **kwargs): self.create_and_update_dependencies(organization) - payload = self.payload( - name=name, - description=description, - organization=self.ds.organization, - script=script, - **kwargs) + payload = self.payload(name=name, description=description, organization=self.ds.organization, script=script, **kwargs) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload - def create( - self, - name='', - description='', - organization=Organization, - script='', - **kwargs): - payload = self.create_payload( - name=name, - description=description, - organization=organization, - script=script, - **kwargs) - return self.update_identity( - InventoryScripts( - self.connection).post(payload)) + def create(self, name='', description='', organization=Organization, script='', **kwargs): + payload = self.create_payload(name=name, description=description, organization=organization, script=script, **kwargs) + return self.update_identity(InventoryScripts(self.connection).post(payload)) def _generate_script(self): - script = '\n'.join([ - '#!/usr/bin/env python', - '# -*- coding: utf-8 -*-', - 'import json', - 'inventory = dict()', - 'inventory["{0}"] = dict()', - 'inventory["{0}"]["hosts"] = list()', - 'inventory["{0}"]["hosts"].append("{1}")', - 'inventory["{0}"]["hosts"].append("{2}")', - 'inventory["{0}"]["hosts"].append("{3}")', - 'inventory["{0}"]["hosts"].append("{4}")', - 'inventory["{0}"]["hosts"].append("{5}")', - 'inventory["{0}"]["vars"] = dict(ansible_host="127.0.0.1", ansible_connection="local")', - 'print(json.dumps(inventory))' - ]) + script = '\n'.join( + [ + '#!/usr/bin/env python', + '# -*- coding: utf-8 -*-', + 'import json', + 'inventory = dict()', + 'inventory["{0}"] = dict()', + 'inventory["{0}"]["hosts"] = list()', + 'inventory["{0}"]["hosts"].append("{1}")', + 'inventory["{0}"]["hosts"].append("{2}")', + 'inventory["{0}"]["hosts"].append("{3}")', + 'inventory["{0}"]["hosts"].append("{4}")', + 'inventory["{0}"]["hosts"].append("{5}")', + 'inventory["{0}"]["vars"] = dict(ansible_host="127.0.0.1", ansible_connection="local")', + 'print(json.dumps(inventory))', + ] + ) group_name = re.sub(r"[\']", "", "group_{}".format(random_title(non_ascii=False))) - host_names = [ - re.sub( - r"[\':]", - "", - "host_{}".format( - random_utf8())) for _ in range(5)] + host_names = [re.sub(r"[\':]", "", "host_{}".format(random_utf8())) for _ in range(5)] return script.format(group_name, *host_names) -page.register_page([resources.inventory_script, - (resources.inventory_scripts, 'post'), - (resources.inventory_script_copy, 'post')], InventoryScript) +page.register_page([resources.inventory_script, (resources.inventory_scripts, 'post'), (resources.inventory_script_copy, 'post')], InventoryScript) class InventoryScripts(page.PageList, InventoryScript): @@ -272,11 +199,10 @@ class Group(HasCreate, HasVariables, base.Base): def payload(self, inventory, credential=None, **kwargs): payload = PseudoNamespace( - name=kwargs.get('name') or 'Group{}'.format( - random_title( - non_ascii=False)), + name=kwargs.get('name') or 'Group{}'.format(random_title(non_ascii=False)), description=kwargs.get('description') or random_title(10), - inventory=inventory.id) + inventory=inventory.id, + ) if credential: payload.credential = credential.id @@ -288,38 +214,19 @@ class Group(HasCreate, HasVariables, base.Base): return payload - def create_payload( - self, - name='', - description='', - inventory=Inventory, - credential=None, - source_script=None, - **kwargs): - credential, source_script = filter_by_class( - (credential, Credential), (source_script, InventoryScript)) - self.create_and_update_dependencies( - inventory, credential, source_script) + def create_payload(self, name='', description='', inventory=Inventory, credential=None, source_script=None, **kwargs): + credential, source_script = filter_by_class((credential, Credential), (source_script, InventoryScript)) + self.create_and_update_dependencies(inventory, credential, source_script) credential = self.ds.credential if credential else None - payload = self.payload( - inventory=self.ds.inventory, - credential=credential, - name=name, - description=description, - **kwargs) + payload = self.payload(inventory=self.ds.inventory, credential=credential, name=name, description=description, **kwargs) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload def create(self, name='', description='', inventory=Inventory, **kwargs): - payload = self.create_payload( - name=name, - description=description, - inventory=inventory, - **kwargs) + payload = self.create_payload(name=name, description=description, inventory=inventory, **kwargs) parent = kwargs.get('parent', None) # parent must be a Group instance - resource = parent.related.children if parent else Groups( - self.connection) + resource = parent.related.children if parent else Groups(self.connection) return self.update_identity(resource.post(payload)) def add_host(self, host=None): @@ -348,8 +255,7 @@ class Group(HasCreate, HasVariables, base.Base): self.related.children.post(dict(id=group.id, disassociate=True)) -page.register_page([resources.group, - (resources.groups, 'post')], Group) +page.register_page([resources.group, (resources.groups, 'post')], Group) class Groups(page.PageList, Group): @@ -357,12 +263,17 @@ class Groups(page.PageList, Group): pass -page.register_page([resources.groups, - resources.host_groups, - resources.inventory_related_groups, - resources.inventory_related_root_groups, - resources.group_children, - resources.group_potential_children], Groups) +page.register_page( + [ + resources.groups, + resources.host_groups, + resources.inventory_related_groups, + resources.inventory_related_root_groups, + resources.group_children, + resources.group_potential_children, + ], + Groups, +) class Host(HasCreate, HasVariables, base.Base): @@ -372,11 +283,10 @@ class Host(HasCreate, HasVariables, base.Base): def payload(self, inventory, **kwargs): payload = PseudoNamespace( - name=kwargs.get('name') or 'Host{}'.format( - random_title( - non_ascii=False)), + name=kwargs.get('name') or 'Host{}'.format(random_title(non_ascii=False)), description=kwargs.get('description') or random_title(10), - inventory=inventory.id) + inventory=inventory.id, + ) optional_fields = ('enabled', 'instance_id') @@ -385,9 +295,7 @@ class Host(HasCreate, HasVariables, base.Base): variables = kwargs.get('variables', not_provided) if variables is None: - variables = dict( - ansible_host='127.0.0.1', - ansible_connection='local') + variables = dict(ansible_host='127.0.0.1', ansible_connection='local') if variables != not_provided: if isinstance(variables, dict): @@ -396,42 +304,18 @@ class Host(HasCreate, HasVariables, base.Base): return payload - def create_payload( - self, - name='', - description='', - variables=None, - inventory=Inventory, - **kwargs): - self.create_and_update_dependencies( - *filter_by_class((inventory, Inventory))) - payload = self.payload( - inventory=self.ds.inventory, - name=name, - description=description, - variables=variables, - **kwargs) + def create_payload(self, name='', description='', variables=None, inventory=Inventory, **kwargs): + self.create_and_update_dependencies(*filter_by_class((inventory, Inventory))) + payload = self.payload(inventory=self.ds.inventory, name=name, description=description, variables=variables, **kwargs) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload - def create( - self, - name='', - description='', - variables=None, - inventory=Inventory, - **kwargs): - payload = self.create_payload( - name=name, - description=description, - variables=variables, - inventory=inventory, - **kwargs) + def create(self, name='', description='', variables=None, inventory=Inventory, **kwargs): + payload = self.create_payload(name=name, description=description, variables=variables, inventory=inventory, **kwargs) return self.update_identity(Hosts(self.connection).post(payload)) -page.register_page([resources.host, - (resources.hosts, 'post')], Host) +page.register_page([resources.host, (resources.hosts, 'post')], Host) class Hosts(page.PageList, Host): @@ -439,10 +323,7 @@ class Hosts(page.PageList, Host): pass -page.register_page([resources.hosts, - resources.group_related_hosts, - resources.inventory_related_hosts, - resources.inventory_sources_related_hosts], Hosts) +page.register_page([resources.hosts, resources.group_related_hosts, resources.inventory_related_hosts, resources.inventory_sources_related_hosts], Hosts) class FactVersion(base.Base): @@ -454,7 +335,6 @@ page.register_page(resources.host_related_fact_version, FactVersion) class FactVersions(page.PageList, FactVersion): - @property def count(self): return len(self.results) @@ -478,20 +358,13 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate): optional_dependencies = [Credential, InventoryScript, Project] NATURAL_KEY = ('organization', 'name', 'inventory') - def payload( - self, - inventory, - source='custom', - credential=None, - source_script=None, - project=None, - **kwargs): + def payload(self, inventory, source='custom', credential=None, source_script=None, project=None, **kwargs): payload = PseudoNamespace( - name=kwargs.get('name') or 'InventorySource - {}'.format( - random_title()), + name=kwargs.get('name') or 'InventorySource - {}'.format(random_title()), description=kwargs.get('description') or random_title(10), inventory=inventory.id, - source=source) + source=source, + ) if credential: payload.credential = credential.id @@ -509,22 +382,16 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate): 'update_cache_timeout', 'update_on_launch', 'update_on_project_update', - 'verbosity') + 'verbosity', + ) update_payload(payload, optional_fields, kwargs) return payload def create_payload( - self, - name='', - description='', - source='custom', - inventory=Inventory, - credential=None, - source_script=InventoryScript, - project=None, - **kwargs): + self, name='', description='', source='custom', inventory=Inventory, credential=None, source_script=InventoryScript, project=None, **kwargs + ): if source != 'custom' and source_script == InventoryScript: source_script = None if source == 'scm': @@ -532,12 +399,10 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate): if project is None: project = Project - inventory, credential, source_script, project = filter_by_class((inventory, Inventory), - (credential, Credential), - (source_script, InventoryScript), - (project, Project)) - self.create_and_update_dependencies( - inventory, credential, source_script, project) + inventory, credential, source_script, project = filter_by_class( + (inventory, Inventory), (credential, Credential), (source_script, InventoryScript), (project, Project) + ) + self.create_and_update_dependencies(inventory, credential, source_script, project) if credential: credential = self.ds.credential @@ -554,20 +419,12 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate): project=project, name=name, description=description, - **kwargs) + **kwargs + ) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload - def create( - self, - name='', - description='', - source='custom', - inventory=Inventory, - credential=None, - source_script=InventoryScript, - project=None, - **kwargs): + def create(self, name='', description='', source='custom', inventory=Inventory, credential=None, source_script=InventoryScript, project=None, **kwargs): payload = self.create_payload( name=name, description=description, @@ -576,10 +433,9 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate): credential=credential, source_script=source_script, project=project, - **kwargs) - return self.update_identity( - InventorySources( - self.connection).post(payload)) + **kwargs + ) + return self.update_identity(InventorySources(self.connection).post(payload)) def update(self): """Update the inventory_source using related->update endpoint""" @@ -587,45 +443,37 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate): update_pg = self.get_related('update') # assert can_update == True - assert update_pg.can_update, \ - "The specified inventory_source (id:%s) is not able to update (can_update:%s)" % \ - (self.id, update_pg.can_update) + assert update_pg.can_update, "The specified inventory_source (id:%s) is not able to update (can_update:%s)" % (self.id, update_pg.can_update) # start the inventory_update result = update_pg.post() # assert JSON response - assert 'inventory_update' in result.json, \ - "Unexpected JSON response when starting an inventory_update.\n%s" % \ - json.dumps(result.json, indent=2) + assert 'inventory_update' in result.json, "Unexpected JSON response when starting an inventory_update.\n%s" % json.dumps(result.json, indent=2) # locate and return the inventory_update - jobs_pg = self.related.inventory_updates.get( - id=result.json['inventory_update']) - assert jobs_pg.count == 1, \ - "An inventory_update started (id:%s) but job not found in response at %s/inventory_updates/" % \ - (result.json['inventory_update'], self.url) + jobs_pg = self.related.inventory_updates.get(id=result.json['inventory_update']) + assert jobs_pg.count == 1, "An inventory_update started (id:%s) but job not found in response at %s/inventory_updates/" % ( + result.json['inventory_update'], + self.url, + ) return jobs_pg.results[0] @property def is_successful(self): """An inventory_source is considered successful when source != "" and super().is_successful .""" - return self.source != "" and super( - InventorySource, self).is_successful + return self.source != "" and super(InventorySource, self).is_successful def add_credential(self, credential): with suppress(exc.NoContent): - self.related.credentials.post( - dict(id=credential.id, associate=True)) + self.related.credentials.post(dict(id=credential.id, associate=True)) def remove_credential(self, credential): with suppress(exc.NoContent): - self.related.credentials.post( - dict(id=credential.id, disassociate=True)) + self.related.credentials.post(dict(id=credential.id, disassociate=True)) -page.register_page([resources.inventory_source, - (resources.inventory_sources, 'post')], InventorySource) +page.register_page([resources.inventory_source, (resources.inventory_sources, 'post')], InventorySource) class InventorySources(page.PageList, InventorySource): @@ -633,9 +481,7 @@ class InventorySources(page.PageList, InventorySource): pass -page.register_page([resources.inventory_sources, - resources.related_inventory_sources], - InventorySources) +page.register_page([resources.inventory_sources, resources.related_inventory_sources], InventorySources) class InventorySourceGroups(page.PageList, Group): @@ -643,9 +489,7 @@ class InventorySourceGroups(page.PageList, Group): pass -page.register_page( - resources.inventory_sources_related_groups, - InventorySourceGroups) +page.register_page(resources.inventory_sources_related_groups, InventorySourceGroups) class InventorySourceUpdate(base.Base): @@ -653,9 +497,7 @@ class InventorySourceUpdate(base.Base): pass -page.register_page([resources.inventory_sources_related_update, - resources.inventory_related_update_inventory_sources], - InventorySourceUpdate) +page.register_page([resources.inventory_sources_related_update, resources.inventory_related_update_inventory_sources], InventorySourceUpdate) class InventoryUpdate(UnifiedJob): @@ -671,10 +513,7 @@ class InventoryUpdates(page.PageList, InventoryUpdate): pass -page.register_page([resources.inventory_updates, - resources.inventory_source_updates, - resources.project_update_scm_inventory_updates], - InventoryUpdates) +page.register_page([resources.inventory_updates, resources.inventory_source_updates, resources.project_update_scm_inventory_updates], InventoryUpdates) class InventoryUpdateCancel(base.Base): diff --git a/awxkit/awxkit/api/pages/job_templates.py b/awxkit/awxkit/api/pages/job_templates.py index 693112375a..7de4cc902e 100644 --- a/awxkit/awxkit/api/pages/job_templates.py +++ b/awxkit/awxkit/api/pages/job_templates.py @@ -1,13 +1,6 @@ import json -from awxkit.utils import ( - filter_by_class, - not_provided, - random_title, - suppress, - update_payload, - set_payload_foreign_key_args, - PseudoNamespace) +from awxkit.utils import filter_by_class, not_provided, random_title, suppress, update_payload, set_payload_foreign_key_args, PseudoNamespace from awxkit.api.pages import Credential, Inventory, Project, UnifiedJobTemplate from awxkit.api.mixins import HasCreate, HasInstanceGroups, HasNotifications, HasSurvey, HasCopy, DSAdapter from awxkit.api.resources import resources @@ -16,13 +9,7 @@ from . import base from . import page -class JobTemplate( - HasCopy, - HasCreate, - HasInstanceGroups, - HasNotifications, - HasSurvey, - UnifiedJobTemplate): +class JobTemplate(HasCopy, HasCreate, HasInstanceGroups, HasNotifications, HasSurvey, UnifiedJobTemplate): optional_dependencies = [Inventory, Credential, Project] NATURAL_KEY = ('organization', 'name') @@ -38,16 +25,13 @@ class JobTemplate( # return job if result.json['type'] == 'job': jobs_pg = self.get_related('jobs', id=result.json['job']) - assert jobs_pg.count == 1, \ - "job_template launched (id:%s) but job not found in response at %s/jobs/" % \ - (result.json['job'], self.url) + assert jobs_pg.count == 1, "job_template launched (id:%s) but job not found in response at %s/jobs/" % (result.json['job'], self.url) return jobs_pg.results[0] elif result.json['type'] == 'workflow_job': - slice_workflow_jobs = self.get_related( - 'slice_workflow_jobs', id=result.json['id']) - assert slice_workflow_jobs.count == 1, ( - "job_template launched sliced job (id:%s) but not found in related %s/slice_workflow_jobs/" % - (result.json['id'], self.url) + slice_workflow_jobs = self.get_related('slice_workflow_jobs', id=result.json['id']) + assert slice_workflow_jobs.count == 1, "job_template launched sliced job (id:%s) but not found in related %s/slice_workflow_jobs/" % ( + result.json['id'], + self.url, ) return slice_workflow_jobs.results[0] else: @@ -56,10 +40,7 @@ class JobTemplate( def payload(self, job_type='run', playbook='ping.yml', **kwargs): name = kwargs.get('name') or 'JobTemplate - {}'.format(random_title()) description = kwargs.get('description') or random_title(10) - payload = PseudoNamespace( - name=name, - description=description, - job_type=job_type) + payload = PseudoNamespace(name=name, description=description, job_type=job_type) optional_fields = ( 'ask_scm_branch_on_launch', @@ -90,7 +71,8 @@ class JobTemplate( 'job_slice_count', 'webhook_service', 'webhook_credential', - 'scm_branch') + 'scm_branch', + ) update_payload(payload, optional_fields, kwargs) @@ -113,94 +95,53 @@ class JobTemplate( with suppress(exc.NoContent): self.related.labels.post(label) - def create_payload( - self, - name='', - description='', - job_type='run', - playbook='ping.yml', - credential=Credential, - inventory=Inventory, - project=None, - **kwargs): + def create_payload(self, name='', description='', job_type='run', playbook='ping.yml', credential=Credential, inventory=Inventory, project=None, **kwargs): if not project: project = Project if not inventory and not kwargs.get('ask_inventory_on_launch', False): inventory = Inventory - self.create_and_update_dependencies( - * - filter_by_class( - (credential, - Credential), - (inventory, - Inventory), - (project, - Project))) + self.create_and_update_dependencies(*filter_by_class((credential, Credential), (inventory, Inventory), (project, Project))) project = self.ds.project if project else None inventory = self.ds.inventory if inventory else None credential = self.ds.credential if credential else None payload = self.payload( - name=name, - description=description, - job_type=job_type, - playbook=playbook, - credential=credential, - inventory=inventory, - project=project, - **kwargs) + name=name, description=description, job_type=job_type, playbook=playbook, credential=credential, inventory=inventory, project=project, **kwargs + ) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload, credential - def create( - self, - name='', - description='', - job_type='run', - playbook='ping.yml', - credential=Credential, - inventory=Inventory, - project=None, - **kwargs): - payload, credential = self.create_payload(name=name, description=description, job_type=job_type, - playbook=playbook, credential=credential, inventory=inventory, - project=project, **kwargs) - ret = self.update_identity( - JobTemplates( - self.connection).post(payload)) + def create(self, name='', description='', job_type='run', playbook='ping.yml', credential=Credential, inventory=Inventory, project=None, **kwargs): + payload, credential = self.create_payload( + name=name, description=description, job_type=job_type, playbook=playbook, credential=credential, inventory=inventory, project=project, **kwargs + ) + ret = self.update_identity(JobTemplates(self.connection).post(payload)) if credential: with suppress(exc.NoContent): self.related.credentials.post(dict(id=credential.id)) if 'vault_credential' in kwargs: with suppress(exc.NoContent): if not isinstance(kwargs['vault_credential'], int): - raise ValueError( - "Expected 'vault_credential' value to be an integer, the id of the desired vault credential") - self.related.credentials.post( - dict(id=kwargs['vault_credential'])) + raise ValueError("Expected 'vault_credential' value to be an integer, the id of the desired vault credential") + self.related.credentials.post(dict(id=kwargs['vault_credential'])) return ret def add_credential(self, credential): with suppress(exc.NoContent): - self.related.credentials.post( - dict(id=credential.id, associate=True)) + self.related.credentials.post(dict(id=credential.id, associate=True)) def remove_credential(self, credential): with suppress(exc.NoContent): - self.related.credentials.post( - dict(id=credential.id, disassociate=True)) + self.related.credentials.post(dict(id=credential.id, disassociate=True)) def remove_all_credentials(self): for cred in self.related.credentials.get().results: with suppress(exc.NoContent): - self.related.credentials.post( - dict(id=cred.id, disassociate=True)) + self.related.credentials.post(dict(id=cred.id, disassociate=True)) -page.register_page([resources.job_template, - (resources.job_templates, 'post'), - (resources.job_template_copy, 'post')], JobTemplate) +page.register_page([resources.job_template, (resources.job_templates, 'post'), (resources.job_template_copy, 'post')], JobTemplate) class JobTemplates(page.PageList, JobTemplate): @@ -208,8 +149,7 @@ class JobTemplates(page.PageList, JobTemplate): pass -page.register_page([resources.job_templates, - resources.related_job_templates], JobTemplates) +page.register_page([resources.job_templates, resources.related_job_templates], JobTemplates) class JobTemplateCallback(base.Base): diff --git a/awxkit/awxkit/api/pages/jobs.py b/awxkit/awxkit/api/pages/jobs.py index 5c0ed24f28..358009e59b 100644 --- a/awxkit/awxkit/api/pages/jobs.py +++ b/awxkit/awxkit/api/pages/jobs.py @@ -5,7 +5,6 @@ from . import page class Job(UnifiedJob): - def relaunch(self, payload={}): result = self.related.relaunch.post(payload) return self.walk(result.endpoint) @@ -19,9 +18,7 @@ class Jobs(page.PageList, Job): pass -page.register_page([resources.jobs, - resources.job_template_jobs, - resources.system_job_template_jobs], Jobs) +page.register_page([resources.jobs, resources.job_template_jobs, resources.system_job_template_jobs], Jobs) class JobCancel(UnifiedJob): @@ -37,8 +34,7 @@ class JobEvent(base.Base): pass -page.register_page([resources.job_event, - resources.job_job_event], JobEvent) +page.register_page([resources.job_event, resources.job_job_event], JobEvent) class JobEvents(page.PageList, JobEvent): @@ -46,10 +42,7 @@ class JobEvents(page.PageList, JobEvent): pass -page.register_page([resources.job_events, - resources.job_job_events, - resources.job_event_children, - resources.group_related_job_events], JobEvents) +page.register_page([resources.job_events, resources.job_job_events, resources.job_event_children, resources.group_related_job_events], JobEvents) class JobPlay(base.Base): @@ -97,8 +90,7 @@ class JobHostSummaries(page.PageList, JobHostSummary): pass -page.register_page([resources.job_host_summaries, - resources.group_related_job_host_summaries], JobHostSummaries) +page.register_page([resources.job_host_summaries, resources.group_related_job_host_summaries], JobHostSummaries) class JobRelaunch(base.Base): diff --git a/awxkit/awxkit/api/pages/labels.py b/awxkit/awxkit/api/pages/labels.py index 34022f66d2..b6cb88b073 100644 --- a/awxkit/awxkit/api/pages/labels.py +++ b/awxkit/awxkit/api/pages/labels.py @@ -19,43 +19,24 @@ class Label(HasCreate, base.Base): def payload(self, organization, **kwargs): payload = PseudoNamespace( - name=kwargs.get('name') or 'Label - {}'.format( - random_title()), + name=kwargs.get('name') or 'Label - {}'.format(random_title()), description=kwargs.get('description') or random_title(10), - organization=organization.id) + organization=organization.id, + ) return payload - def create_payload( - self, - name='', - description='', - organization=Organization, - **kwargs): + def create_payload(self, name='', description='', organization=Organization, **kwargs): self.create_and_update_dependencies(organization) - payload = self.payload( - organization=self.ds.organization, - name=name, - description=description, - **kwargs) + payload = self.payload(organization=self.ds.organization, name=name, description=description, **kwargs) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload - def create( - self, - name='', - description='', - organization=Organization, - **kwargs): - payload = self.create_payload( - name=name, - description=description, - organization=organization, - **kwargs) + def create(self, name='', description='', organization=Organization, **kwargs): + payload = self.create_payload(name=name, description=description, organization=organization, **kwargs) return self.update_identity(Labels(self.connection).post(payload)) -page.register_page([resources.label, - (resources.labels, 'post')], Label) +page.register_page([resources.label, (resources.labels, 'post')], Label) class Labels(page.PageList, Label): @@ -63,7 +44,4 @@ class Labels(page.PageList, Label): pass -page.register_page([resources.labels, - resources.job_labels, - resources.job_template_labels, - resources.workflow_job_template_labels], Labels) +page.register_page([resources.labels, resources.job_labels, resources.job_template_labels, resources.workflow_job_template_labels], Labels) diff --git a/awxkit/awxkit/api/pages/metrics.py b/awxkit/awxkit/api/pages/metrics.py index 2e3cafaafd..88a57b7139 100644 --- a/awxkit/awxkit/api/pages/metrics.py +++ b/awxkit/awxkit/api/pages/metrics.py @@ -4,12 +4,9 @@ from . import page class Metrics(base.Base): - def get(self, **query_parameters): - request = self.connection.get(self.endpoint, query_parameters, - headers={'Accept': 'application/json'}) + request = self.connection.get(self.endpoint, query_parameters, headers={'Accept': 'application/json'}) return self.page_identity(request) -page.register_page([resources.metrics, - (resources.metrics, 'get')], Metrics) +page.register_page([resources.metrics, (resources.metrics, 'get')], Metrics) diff --git a/awxkit/awxkit/api/pages/notification_templates.py b/awxkit/awxkit/api/pages/notification_templates.py index ff192d4433..0688cc1342 100644 --- a/awxkit/awxkit/api/pages/notification_templates.py +++ b/awxkit/awxkit/api/pages/notification_templates.py @@ -9,16 +9,7 @@ from . import page job_results = ('any', 'error', 'success') -notification_types = ( - 'email', - 'irc', - 'pagerduty', - 'slack', - 'twilio', - 'webhook', - 'mattermost', - 'grafana', - 'rocketchat') +notification_types = ('email', 'irc', 'pagerduty', 'slack', 'twilio', 'webhook', 'mattermost', 'grafana', 'rocketchat') class NotificationTemplate(HasCopy, HasCreate, base.Base): @@ -28,18 +19,17 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base): def test(self): """Create test notification""" - assert 'test' in self.related, \ - "No such related attribute 'test'" + assert 'test' in self.related, "No such related attribute 'test'" # trigger test notification notification_id = self.related.test.post().notification # return notification page - notifications_pg = self.get_related( - 'notifications', id=notification_id).wait_until_count(1) - assert notifications_pg.count == 1, \ - "test notification triggered (id:%s) but notification not found in response at %s/notifications/" % \ - (notification_id, self.url) + notifications_pg = self.get_related('notifications', id=notification_id).wait_until_count(1) + assert notifications_pg.count == 1, "test notification triggered (id:%s) but notification not found in response at %s/notifications/" % ( + notification_id, + self.url, + ) return notifications_pg.results[0] def silent_delete(self): @@ -53,41 +43,25 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base): def payload(self, organization, notification_type='slack', messages=not_provided, **kwargs): payload = PseudoNamespace( - name=kwargs.get('name') or 'NotificationTemplate ({0}) - {1}' .format( - notification_type, - random_title()), + name=kwargs.get('name') or 'NotificationTemplate ({0}) - {1}'.format(notification_type, random_title()), description=kwargs.get('description') or random_title(10), organization=organization.id, - notification_type=notification_type) + notification_type=notification_type, + ) if messages != not_provided: payload['messages'] = messages - notification_configuration = kwargs.get( - 'notification_configuration', {}) + notification_configuration = kwargs.get('notification_configuration', {}) payload.notification_configuration = notification_configuration if payload.notification_configuration == {}: services = config.credentials.notification_services if notification_type == 'email': - fields = ( - 'host', - 'username', - 'password', - 'port', - 'use_ssl', - 'use_tls', - 'sender', - 'recipients') + fields = ('host', 'username', 'password', 'port', 'use_ssl', 'use_tls', 'sender', 'recipients') cred = services.email elif notification_type == 'irc': - fields = ( - 'server', - 'port', - 'use_ssl', - 'password', - 'nickname', - 'targets') + fields = ('server', 'port', 'use_ssl', 'password', 'nickname', 'targets') cred = services.irc elif notification_type == 'pagerduty': fields = ('client_name', 'service_key', 'subdomain', 'token') @@ -96,34 +70,22 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base): fields = ('channels', 'token') cred = services.slack elif notification_type == 'twilio': - fields = ( - 'account_sid', - 'account_token', - 'from_number', - 'to_numbers') + fields = ('account_sid', 'account_token', 'from_number', 'to_numbers') cred = services.twilio elif notification_type == 'webhook': fields = ('url', 'headers') cred = services.webhook elif notification_type == 'mattermost': - fields = ( - 'mattermost_url', - 'mattermost_username', - 'mattermost_channel', - 'mattermost_icon_url', - 'mattermost_no_verify_ssl') + fields = ('mattermost_url', 'mattermost_username', 'mattermost_channel', 'mattermost_icon_url', 'mattermost_no_verify_ssl') cred = services.mattermost elif notification_type == 'grafana': - fields = ('grafana_url', - 'grafana_key') + fields = ('grafana_url', 'grafana_key') cred = services.grafana elif notification_type == 'rocketchat': - fields = ('rocketchat_url', - 'rocketchat_no_verify_ssl') + fields = ('rocketchat_url', 'rocketchat_no_verify_ssl') cred = services.rocketchat else: - raise ValueError( - 'Unknown notification_type {0}'.format(notification_type)) + raise ValueError('Unknown notification_type {0}'.format(notification_type)) for field in fields: if field == 'bot_token': @@ -136,47 +98,21 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base): return payload - def create_payload( - self, - name='', - description='', - notification_type='slack', - organization=Organization, - messages=not_provided, - **kwargs): + def create_payload(self, name='', description='', notification_type='slack', organization=Organization, messages=not_provided, **kwargs): if notification_type not in notification_types: - raise ValueError( - 'Unsupported notification type "{0}". Please use one of {1}.' .format( - notification_type, notification_types)) + raise ValueError('Unsupported notification type "{0}". Please use one of {1}.'.format(notification_type, notification_types)) self.create_and_update_dependencies(organization) payload = self.payload( - organization=self.ds.organization, - notification_type=notification_type, - name=name, - description=description, - messages=messages, - **kwargs) + organization=self.ds.organization, notification_type=notification_type, name=name, description=description, messages=messages, **kwargs + ) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload - def create( - self, - name='', - description='', - notification_type='slack', - organization=Organization, - messages=not_provided, - **kwargs): + def create(self, name='', description='', notification_type='slack', organization=Organization, messages=not_provided, **kwargs): payload = self.create_payload( - name=name, - description=description, - notification_type=notification_type, - organization=organization, - messages=messages, - **kwargs) - return self.update_identity( - NotificationTemplates( - self.connection).post(payload)) + name=name, description=description, notification_type=notification_type, organization=organization, messages=messages, **kwargs + ) + return self.update_identity(NotificationTemplates(self.connection).post(payload)) def associate(self, resource, job_result='any'): """Associates a NotificationTemplate with the provided resource""" @@ -188,15 +124,11 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base): def _associate(self, resource, job_result='any', disassociate=False): if job_result not in job_results: - raise ValueError( - 'Unsupported job_result type "{0}". Please use one of {1}.' .format( - job_result, job_results)) + raise ValueError('Unsupported job_result type "{0}". Please use one of {1}.'.format(job_result, job_results)) result_attr = 'notification_templates_{0}'.format(job_result) if result_attr not in resource.related: - raise ValueError( - 'Unsupported resource "{0}". Does not have a related {1} field.' .format( - resource, result_attr)) + raise ValueError('Unsupported resource "{0}". Does not have a related {1} field.'.format(resource, result_attr)) payload = dict(id=self.id) if disassociate: @@ -206,14 +138,19 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base): getattr(resource.related, result_attr).post(payload) -page.register_page([resources.notification_template, - (resources.notification_templates, 'post'), - (resources.notification_template_copy, 'post'), - resources.notification_template_any, - resources.notification_template_started, - resources.notification_template_error, - resources.notification_template_success, - resources.notification_template_approval], NotificationTemplate) +page.register_page( + [ + resources.notification_template, + (resources.notification_templates, 'post'), + (resources.notification_template_copy, 'post'), + resources.notification_template_any, + resources.notification_template_started, + resources.notification_template_error, + resources.notification_template_success, + resources.notification_template_approval, + ], + NotificationTemplate, +) class NotificationTemplates(page.PageList, NotificationTemplate): @@ -221,14 +158,18 @@ class NotificationTemplates(page.PageList, NotificationTemplate): pass -page.register_page([resources.notification_templates, - resources.related_notification_templates, - resources.notification_templates_any, - resources.notification_templates_started, - resources.notification_templates_error, - resources.notification_templates_success, - resources.notification_templates_approvals], - NotificationTemplates) +page.register_page( + [ + resources.notification_templates, + resources.related_notification_templates, + resources.notification_templates_any, + resources.notification_templates_started, + resources.notification_templates_error, + resources.notification_templates_success, + resources.notification_templates_approvals, + ], + NotificationTemplates, +) class NotificationTemplateCopy(base.Base): @@ -244,6 +185,4 @@ class NotificationTemplateTest(base.Base): pass -page.register_page( - resources.notification_template_test, - NotificationTemplateTest) +page.register_page(resources.notification_template_test, NotificationTemplateTest) diff --git a/awxkit/awxkit/api/pages/notifications.py b/awxkit/awxkit/api/pages/notifications.py index 0342f3565f..f4ee63313e 100644 --- a/awxkit/awxkit/api/pages/notifications.py +++ b/awxkit/awxkit/api/pages/notifications.py @@ -6,10 +6,8 @@ from . import page class Notification(HasStatus, base.Base): - def __str__(self): - items = ['id', 'notification_type', 'status', 'error', 'notifications_sent', - 'subject', 'recipients'] + items = ['id', 'notification_type', 'status', 'error', 'notifications_sent', 'subject', 'recipients'] info = [] for item in [x for x in items if hasattr(self, x)]: info.append('{0}:{1}'.format(item, getattr(self, item))) @@ -40,13 +38,10 @@ page.register_page(resources.notification, Notification) class Notifications(page.PageList, Notification): - def wait_until_count(self, count, interval=10, timeout=60, **kw): """Poll notifications page until it is populated with `count` number of notifications.""" - poll_until(lambda: getattr(self.get(), 'count') == count, - interval=interval, timeout=timeout, **kw) + poll_until(lambda: getattr(self.get(), 'count') == count, interval=interval, timeout=timeout, **kw) return self -page.register_page([resources.notifications, - resources.related_notifications], Notifications) +page.register_page([resources.notifications, resources.related_notifications], Notifications) diff --git a/awxkit/awxkit/api/pages/organizations.py b/awxkit/awxkit/api/pages/organizations.py index b03c9e6a3a..4ef9b8a26e 100644 --- a/awxkit/awxkit/api/pages/organizations.py +++ b/awxkit/awxkit/api/pages/organizations.py @@ -26,22 +26,27 @@ class Organization(HasCreate, HasInstanceGroups, HasNotifications, base.Base): if isinstance(credential, page.Page): credential = credential.json with suppress(exc.NoContent): - self.related.galaxy_credentials.post({ - "id": credential.id, - }) + self.related.galaxy_credentials.post( + { + "id": credential.id, + } + ) def remove_galaxy_credential(self, credential): if isinstance(credential, page.Page): credential = credential.json with suppress(exc.NoContent): - self.related.galaxy_credentials.post({ - "id": credential.id, - "disassociate": True, - }) + self.related.galaxy_credentials.post( + { + "id": credential.id, + "disassociate": True, + } + ) def payload(self, **kwargs): - payload = PseudoNamespace(name=kwargs.get('name') or 'Organization - {}'.format(random_title()), - description=kwargs.get('description') or random_title(10)) + payload = PseudoNamespace( + name=kwargs.get('name') or 'Organization - {}'.format(random_title()), description=kwargs.get('description') or random_title(10) + ) payload = set_payload_foreign_key_args(payload, ('default_environment',), kwargs) @@ -57,8 +62,7 @@ class Organization(HasCreate, HasInstanceGroups, HasNotifications, base.Base): return self.update_identity(Organizations(self.connection).post(payload)) -page.register_page([resources.organization, - (resources.organizations, 'post')], Organization) +page.register_page([resources.organization, (resources.organizations, 'post')], Organization) class Organizations(page.PageList, Organization): @@ -66,6 +70,4 @@ class Organizations(page.PageList, Organization): pass -page.register_page([resources.organizations, - resources.user_organizations, - resources.project_organizations], Organizations) +page.register_page([resources.organizations, resources.user_organizations, resources.project_organizations], Organizations) diff --git a/awxkit/awxkit/api/pages/page.py b/awxkit/awxkit/api/pages/page.py index 3ee1c38490..82872fe6f1 100644 --- a/awxkit/awxkit/api/pages/page.py +++ b/awxkit/awxkit/api/pages/page.py @@ -6,15 +6,7 @@ import re from requests import Response import http.client as http -from awxkit.utils import ( - PseudoNamespace, - is_relative_endpoint, - are_same_endpoint, - super_dir_set, - suppress, - is_list_or_tuple, - to_str -) +from awxkit.utils import PseudoNamespace, is_relative_endpoint, are_same_endpoint, super_dir_set, suppress, is_list_or_tuple, to_str from awxkit.api import utils from awxkit.api.client import Connection from awxkit.api.registry import URLRegistry @@ -41,17 +33,11 @@ def is_license_invalid(response): def is_license_exceeded(response): - if re.match( - r".*license range of.*instances has been exceeded.*", - response.text): + if re.match(r".*license range of.*instances has been exceeded.*", response.text): return True - if re.match( - r".*License count of.*instances has been reached.*", - response.text): + if re.match(r".*License count of.*instances has been reached.*", response.text): return True - if re.match( - r".*License count of.*instances has been exceeded.*", - response.text): + if re.match(r".*License count of.*instances has been exceeded.*", response.text): return True if re.match(r".*License has expired.*", response.text): return True @@ -67,6 +53,7 @@ def is_duplicate_error(response): def register_page(urls, page_cls): if not _page_registry.default: from awxkit.api.pages import Base + _page_registry.setdefault(Base) if not is_list_or_tuple(urls): @@ -108,32 +95,23 @@ class Page(object): if 'endpoint' in kw: self.endpoint = kw['endpoint'] - self.connection = connection or Connection( - config.base_url, kw.get( - 'verify', not config.assume_untrusted)) + self.connection = connection or Connection(config.base_url, kw.get('verify', not config.assume_untrusted)) self.r = kw.get('r', None) - self.json = kw.get( - 'json', objectify_response_json( - self.r) if self.r else {}) + self.json = kw.get('json', objectify_response_json(self.r) if self.r else {}) self.last_elapsed = kw.get('last_elapsed', None) def __getattr__(self, name): if 'json' in self.__dict__ and name in self.json: value = self.json[name] - if not isinstance( - value, - TentativePage) and is_relative_endpoint(value): + if not isinstance(value, TentativePage) and is_relative_endpoint(value): value = TentativePage(value, self.connection) elif isinstance(value, dict): for key, item in value.items(): - if not isinstance( - item, TentativePage) and is_relative_endpoint(item): + if not isinstance(item, TentativePage) and is_relative_endpoint(item): value[key] = TentativePage(item, self.connection) return value - raise AttributeError( - "{!r} object has no attribute {!r}".format( - self.__class__.__name__, name)) + raise AttributeError("{!r} object has no attribute {!r}".format(self.__class__.__name__, name)) def __setattr__(self, name, value): if 'json' in self.__dict__ and name in self.json: @@ -200,20 +178,15 @@ class Page(object): text = response.text if len(text) > 1024: text = text[:1024] + '... <<< Truncated >>> ...' - log.debug( - "Unable to parse JSON response ({0.status_code}): {1} - '{2}'".format(response, e, text)) + log.debug("Unable to parse JSON response ({0.status_code}): {1} - '{2}'".format(response, e, text)) - exc_str = "%s (%s) received" % ( - http.responses[response.status_code], response.status_code) + exc_str = "%s (%s) received" % (http.responses[response.status_code], response.status_code) exception = exception_from_status_code(response.status_code) if exception: raise exception(exc_str, data) - if response.status_code in ( - http.OK, - http.CREATED, - http.ACCEPTED): + if response.status_code in (http.OK, http.CREATED, http.ACCEPTED): # Not all JSON responses include a URL. Grab it from the request # object, if needed. @@ -232,13 +205,7 @@ class Page(object): return self registered_type = get_registered_page(request_path, request_method) - return registered_type( - self.connection, - endpoint=endpoint, - json=data, - last_elapsed=response.elapsed, - r=response, - ds=ds) + return registered_type(self.connection, endpoint=endpoint, json=data, last_elapsed=response.elapsed, r=response, ds=ds) elif response.status_code == http.FORBIDDEN: if is_license_invalid(response): @@ -341,14 +308,16 @@ class Page(object): return natural_key -_exception_map = {http.NO_CONTENT: exc.NoContent, - http.NOT_FOUND: exc.NotFound, - http.INTERNAL_SERVER_ERROR: exc.InternalServerError, - http.BAD_GATEWAY: exc.BadGateway, - http.METHOD_NOT_ALLOWED: exc.MethodNotAllowed, - http.UNAUTHORIZED: exc.Unauthorized, - http.PAYMENT_REQUIRED: exc.PaymentRequired, - http.CONFLICT: exc.Conflict} +_exception_map = { + http.NO_CONTENT: exc.NoContent, + http.NOT_FOUND: exc.NotFound, + http.INTERNAL_SERVER_ERROR: exc.InternalServerError, + http.BAD_GATEWAY: exc.BadGateway, + http.METHOD_NOT_ALLOWED: exc.MethodNotAllowed, + http.UNAUTHORIZED: exc.Unauthorized, + http.PAYMENT_REQUIRED: exc.PaymentRequired, + http.CONFLICT: exc.Conflict, +} def exception_from_status_code(status_code): @@ -380,12 +349,7 @@ class PageList(object): registered_type = self.__item_class__ else: registered_type = get_registered_page(endpoint) - items.append( - registered_type( - self.connection, - endpoint=endpoint, - json=item, - r=self.r)) + items.append(registered_type(self.connection, endpoint=endpoint, json=item, r=self.r)) return items def go_to_next(self): @@ -407,7 +371,6 @@ class PageList(object): class TentativePage(str): - def __new__(cls, endpoint, connection): return super(TentativePage, cls).__new__(cls, to_str(endpoint)) @@ -416,10 +379,7 @@ class TentativePage(str): self.connection = connection def _create(self): - return get_registered_page( - self.endpoint)( - self.connection, - endpoint=self.endpoint) + return get_registered_page(self.endpoint)(self.connection, endpoint=self.endpoint) def get(self, **params): return self._create().get(**params) @@ -436,21 +396,15 @@ class TentativePage(str): page = None # look up users by username not name if 'users' in self: - assert query_parameters.get( - 'username'), 'For this resource, you must call this method with a "username" to look up the object by' + assert query_parameters.get('username'), 'For this resource, you must call this method with a "username" to look up the object by' page = self.get(username=query_parameters['username']) else: - assert query_parameters.get( - 'name'), 'For this resource, you must call this method with a "name" to look up the object by' + assert query_parameters.get('name'), 'For this resource, you must call this method with a "name" to look up the object by' if query_parameters.get('organization'): if isinstance(query_parameters.get('organization'), int): - page = self.get( - name=query_parameters['name'], - organization=query_parameters.get('organization')) + page = self.get(name=query_parameters['name'], organization=query_parameters.get('organization')) else: - page = self.get( - name=query_parameters['name'], - organization=query_parameters.get('organization').id) + page = self.get(name=query_parameters['name'], organization=query_parameters.get('organization').id) else: page = self.get(name=query_parameters['name']) if page and page.results: @@ -476,13 +430,9 @@ class TentativePage(str): if query_parameters.get('name'): if query_parameters.get('organization'): if isinstance(query_parameters.get('organization'), int): - page = self.get( - name=query_parameters['name'], - organization=query_parameters.get('organization')) + page = self.get(name=query_parameters['name'], organization=query_parameters.get('organization')) else: - page = self.get( - name=query_parameters['name'], - organization=query_parameters.get('organization').id) + page = self.get(name=query_parameters['name'], organization=query_parameters.get('organization').id) else: page = self.get(name=query_parameters['name']) diff --git a/awxkit/awxkit/api/pages/projects.py b/awxkit/awxkit/api/pages/projects.py index 047d471c8f..b00109f61b 100644 --- a/awxkit/awxkit/api/pages/projects.py +++ b/awxkit/awxkit/api/pages/projects.py @@ -18,13 +18,11 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate): def payload(self, organization, scm_type='git', **kwargs): payload = PseudoNamespace( - name=kwargs.get('name') or 'Project - {}'.format( - random_title()), + name=kwargs.get('name') or 'Project - {}'.format(random_title()), description=kwargs.get('description') or random_title(10), scm_type=scm_type, - scm_url=kwargs.get('scm_url') or config.project_urls.get( - scm_type, - '')) + scm_url=kwargs.get('scm_url') or config.project_urls.get(scm_type, ''), + ) if organization is not None: payload.organization = organization.id @@ -40,43 +38,25 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate): 'scm_update_cache_timeout', 'scm_update_on_launch', 'scm_refspec', - 'allow_override') + 'allow_override', + ) update_payload(payload, fields, kwargs) payload = set_payload_foreign_key_args(payload, ('execution_environment', 'default_environment'), kwargs) return payload - def create_payload( - self, - name='', - description='', - scm_type='git', - scm_url='', - scm_branch='', - organization=Organization, - credential=None, - **kwargs): + def create_payload(self, name='', description='', scm_type='git', scm_url='', scm_branch='', organization=Organization, credential=None, **kwargs): if credential: if isinstance(credential, Credential): - if credential.ds.credential_type.namespace not in ( - 'scm', 'insights'): + if credential.ds.credential_type.namespace not in ('scm', 'insights'): credential = None # ignore incompatible credential from HasCreate dependency injection elif credential in (Credential,): - credential = ( - Credential, dict( - credential_type=( - True, dict( - kind='scm')))) + credential = (Credential, dict(credential_type=(True, dict(kind='scm')))) elif credential is True: - credential = ( - Credential, dict( - credential_type=( - True, dict( - kind='scm')))) + credential = (Credential, dict(credential_type=(True, dict(kind='scm')))) - self.create_and_update_dependencies( - *filter_by_class((credential, Credential), (organization, Organization))) + self.create_and_update_dependencies(*filter_by_class((credential, Credential), (organization, Organization))) credential = self.ds.credential if credential else None organization = self.ds.organization if organization else None @@ -89,20 +69,12 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate): scm_url=scm_url, scm_branch=scm_branch, credential=credential, - **kwargs) + **kwargs + ) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload - def create( - self, - name='', - description='', - scm_type='git', - scm_url='', - scm_branch='', - organization=Organization, - credential=None, - **kwargs): + def create(self, name='', description='', scm_type='git', scm_url='', scm_branch='', organization=Organization, credential=None, **kwargs): payload = self.create_payload( name=name, description=description, @@ -111,7 +83,8 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate): scm_branch=scm_branch, organization=organization, credential=credential, - **kwargs) + **kwargs + ) self.update_identity(Projects(self.connection).post(payload)) if kwargs.get('wait', True): @@ -127,25 +100,20 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate): update_pg = self.get_related('update') # assert can_update == True - assert update_pg.can_update, \ - "The specified project (id:%s) is not able to update (can_update:%s)" % \ - (self.id, update_pg.can_update) + assert update_pg.can_update, "The specified project (id:%s) is not able to update (can_update:%s)" % (self.id, update_pg.can_update) # start the update result = update_pg.post() # assert JSON response - assert 'project_update' in result.json, \ - "Unexpected JSON response when starting an project_update.\n%s" % \ - json.dumps(result.json, indent=2) + assert 'project_update' in result.json, "Unexpected JSON response when starting an project_update.\n%s" % json.dumps(result.json, indent=2) # locate and return the specific update - jobs_pg = self.get_related( - 'project_updates', - id=result.json['project_update']) - assert jobs_pg.count == 1, \ - "An project_update started (id:%s) but job not found in response at %s/inventory_updates/" % \ - (result.json['project_update'], self.url) + jobs_pg = self.get_related('project_updates', id=result.json['project_update']) + assert jobs_pg.count == 1, "An project_update started (id:%s) but job not found in response at %s/inventory_updates/" % ( + result.json['project_update'], + self.url, + ) return jobs_pg.results[0] @property @@ -154,13 +122,10 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate): 0) scm_type != "" 1) unified_job_template.is_successful """ - return self.scm_type != "" and \ - super(Project, self).is_successful + return self.scm_type != "" and super(Project, self).is_successful -page.register_page([resources.project, - (resources.projects, 'post'), - (resources.project_copy, 'post')], Project) +page.register_page([resources.project, (resources.projects, 'post'), (resources.project_copy, 'post')], Project) class Projects(page.PageList, Project): @@ -168,8 +133,7 @@ class Projects(page.PageList, Project): pass -page.register_page([resources.projects, - resources.related_projects], Projects) +page.register_page([resources.projects, resources.related_projects], Projects) class ProjectUpdate(UnifiedJob): @@ -185,8 +149,7 @@ class ProjectUpdates(page.PageList, ProjectUpdate): pass -page.register_page([resources.project_updates, - resources.project_project_updates], ProjectUpdates) +page.register_page([resources.project_updates, resources.project_project_updates], ProjectUpdates) class ProjectUpdateLaunch(base.Base): diff --git a/awxkit/awxkit/api/pages/roles.py b/awxkit/awxkit/api/pages/roles.py index 2b317dece1..07f0790b60 100644 --- a/awxkit/awxkit/api/pages/roles.py +++ b/awxkit/awxkit/api/pages/roles.py @@ -18,15 +18,11 @@ class Role(base.Base): cache = page.PageCache() natural_key = super(Role, self).get_natural_key(cache=cache) - related_objs = [ - related for name, related in self.related.items() - if name not in ('users', 'teams') - ] + related_objs = [related for name, related in self.related.items() if name not in ('users', 'teams')] if related_objs: related_endpoint = cache.get_page(related_objs[0]) if related_endpoint is None: - log.error("Unable to obtain content_object %s for role %s", - related_objs[0], self.endpoint) + log.error("Unable to obtain content_object %s for role %s", related_objs[0], self.endpoint) return None natural_key['content_object'] = related_endpoint.get_natural_key(cache=cache) @@ -41,6 +37,4 @@ class Roles(page.PageList, Role): pass -page.register_page([resources.roles, - resources.related_roles, - resources.related_object_roles], Roles) +page.register_page([resources.roles, resources.related_roles, resources.related_object_roles], Roles) diff --git a/awxkit/awxkit/api/pages/schedules.py b/awxkit/awxkit/api/pages/schedules.py index 8603b2ad5b..d1520a0be4 100644 --- a/awxkit/awxkit/api/pages/schedules.py +++ b/awxkit/awxkit/api/pages/schedules.py @@ -11,12 +11,10 @@ class Schedule(UnifiedJob): NATURAL_KEY = ('unified_job_template', 'name') -page.register_page([resources.schedule, - resources.related_schedule], Schedule) +page.register_page([resources.schedule, resources.related_schedule], Schedule) class Schedules(page.PageList, Schedule): - def get_zoneinfo(self): return SchedulesZoneInfo(self.connection).get() @@ -33,8 +31,7 @@ class Schedules(page.PageList, Schedule): self.related.credentials.post(dict(id=cred.id, disassociate=True)) -page.register_page([resources.schedules, - resources.related_schedules], Schedules) +page.register_page([resources.schedules, resources.related_schedules], Schedules) class SchedulesPreview(base.Base): @@ -46,7 +43,6 @@ page.register_page(((resources.schedules_preview, 'post'),), SchedulesPreview) class SchedulesZoneInfo(base.Base): - def __getitem__(self, idx): return self.json[idx] diff --git a/awxkit/awxkit/api/pages/settings.py b/awxkit/awxkit/api/pages/settings.py index fbf133dc02..59168d40c5 100644 --- a/awxkit/awxkit/api/pages/settings.py +++ b/awxkit/awxkit/api/pages/settings.py @@ -8,27 +8,31 @@ class Setting(base.Base): pass -page.register_page([resources.setting, - resources.settings_all, - resources.settings_authentication, - resources.settings_changed, - resources.settings_github, - resources.settings_github_org, - resources.settings_github_team, - resources.settings_google_oauth2, - resources.settings_jobs, - resources.settings_ldap, - resources.settings_radius, - resources.settings_saml, - resources.settings_system, - resources.settings_tacacsplus, - resources.settings_ui, - resources.settings_user, - resources.settings_user_defaults], Setting) +page.register_page( + [ + resources.setting, + resources.settings_all, + resources.settings_authentication, + resources.settings_changed, + resources.settings_github, + resources.settings_github_org, + resources.settings_github_team, + resources.settings_google_oauth2, + resources.settings_jobs, + resources.settings_ldap, + resources.settings_radius, + resources.settings_saml, + resources.settings_system, + resources.settings_tacacsplus, + resources.settings_ui, + resources.settings_user, + resources.settings_user_defaults, + ], + Setting, +) class Settings(page.PageList, Setting): - def get_endpoint(self, endpoint): """Helper method used to navigate to a specific settings endpoint. (Pdb) settings_pg.get_endpoint('all') diff --git a/awxkit/awxkit/api/pages/subscriptions.py b/awxkit/awxkit/api/pages/subscriptions.py index 749776c000..8be55ee6fd 100644 --- a/awxkit/awxkit/api/pages/subscriptions.py +++ b/awxkit/awxkit/api/pages/subscriptions.py @@ -3,7 +3,6 @@ from . import page class Subscriptions(page.Page): - def get_possible_licenses(self, **kwargs): return self.post(json=kwargs).json diff --git a/awxkit/awxkit/api/pages/survey_spec.py b/awxkit/awxkit/api/pages/survey_spec.py index 28a870cf84..e9ea99bbff 100644 --- a/awxkit/awxkit/api/pages/survey_spec.py +++ b/awxkit/awxkit/api/pages/survey_spec.py @@ -5,7 +5,6 @@ from awxkit.api.resources import resources class SurveySpec(base.Base): - def get_variable_default(self, var): for item in self.spec: if item.get('variable') == var: @@ -26,5 +25,4 @@ class SurveySpec(base.Base): return required_vars -page.register_page([resources.job_template_survey_spec, - resources.workflow_job_template_survey_spec], SurveySpec) +page.register_page([resources.job_template_survey_spec, resources.workflow_job_template_survey_spec], SurveySpec) diff --git a/awxkit/awxkit/api/pages/system_job_templates.py b/awxkit/awxkit/api/pages/system_job_templates.py index 0c5f13d7cd..cc0d28857a 100644 --- a/awxkit/awxkit/api/pages/system_job_templates.py +++ b/awxkit/awxkit/api/pages/system_job_templates.py @@ -5,16 +5,13 @@ from . import page class SystemJobTemplate(UnifiedJobTemplate, HasNotifications): - def launch(self, payload={}): """Launch the system_job_template using related->launch endpoint.""" result = self.related.launch.post(payload) # return job jobs_pg = self.get_related('jobs', id=result.json['system_job']) - assert jobs_pg.count == 1, \ - "system_job_template launched (id:%s) but unable to find matching " \ - "job at %s/jobs/" % (result.json['job'], self.url) + assert jobs_pg.count == 1, "system_job_template launched (id:%s) but unable to find matching " "job at %s/jobs/" % (result.json['job'], self.url) return jobs_pg.results[0] diff --git a/awxkit/awxkit/api/pages/teams.py b/awxkit/awxkit/api/pages/teams.py index cb5577b5b2..96fafb3341 100644 --- a/awxkit/awxkit/api/pages/teams.py +++ b/awxkit/awxkit/api/pages/teams.py @@ -20,9 +20,11 @@ class Team(HasCreate, base.Base): self.related.users.post(user) def payload(self, organization, **kwargs): - payload = PseudoNamespace(name=kwargs.get('name') or 'Team - {}'.format(random_title()), - description=kwargs.get('description') or random_title(10), - organization=organization.id) + payload = PseudoNamespace( + name=kwargs.get('name') or 'Team - {}'.format(random_title()), + description=kwargs.get('description') or random_title(10), + organization=organization.id, + ) return payload def create_payload(self, name='', description='', organization=Organization, **kwargs): @@ -36,8 +38,7 @@ class Team(HasCreate, base.Base): return self.update_identity(Teams(self.connection).post(payload)) -page.register_page([resources.team, - (resources.teams, 'post')], Team) +page.register_page([resources.team, (resources.teams, 'post')], Team) class Teams(page.PageList, Team): @@ -45,6 +46,4 @@ class Teams(page.PageList, Team): pass -page.register_page([resources.teams, - resources.credential_owner_teams, - resources.related_teams], Teams) +page.register_page([resources.teams, resources.credential_owner_teams, resources.related_teams], Teams) diff --git a/awxkit/awxkit/api/pages/unified_job_templates.py b/awxkit/awxkit/api/pages/unified_job_templates.py index 286ca18ed7..22a7a70106 100644 --- a/awxkit/awxkit/api/pages/unified_job_templates.py +++ b/awxkit/awxkit/api/pages/unified_job_templates.py @@ -26,38 +26,19 @@ class UnifiedJobTemplate(HasStatus, base.Base): # formatting issue where result_stdout contained '%s'. This later caused # a python traceback when attempting to display output from this # method. - items = [ - 'id', - 'name', - 'status', - 'source', - 'last_update_failed', - 'last_updated', - 'result_traceback', - 'job_explanation', - 'job_args'] + items = ['id', 'name', 'status', 'source', 'last_update_failed', 'last_updated', 'result_traceback', 'job_explanation', 'job_args'] info = [] for item in [x for x in items if hasattr(self, x)]: info.append('{0}:{1}'.format(item, getattr(self, item))) output = '<{0.__class__.__name__} {1}>'.format(self, ', '.join(info)) return output.replace('%', '%%') - def add_schedule( - self, - name='', - description='', - enabled=True, - rrule=None, - **kwargs): + def add_schedule(self, name='', description='', enabled=True, rrule=None, **kwargs): if rrule is None: rrule = "DTSTART:30180101T000000Z RRULE:FREQ=YEARLY;INTERVAL=1" payload = dict( - name=name or "{0} Schedule {1}".format( - self.name, - random_title()), - description=description or random_title(10), - enabled=enabled, - rrule=str(rrule)) + name=name or "{0} Schedule {1}".format(self.name, random_title()), description=description or random_title(10), enabled=enabled, rrule=str(rrule) + ) update_payload(payload, self.optional_schedule_fields, kwargs) @@ -70,9 +51,7 @@ class UnifiedJobTemplate(HasStatus, base.Base): 2) not last_update_failed 3) last_updated """ - return super( - UnifiedJobTemplate, - self).is_successful and not self.last_update_failed and self.last_updated is not None + return super(UnifiedJobTemplate, self).is_successful and not self.last_update_failed and self.last_updated is not None page.register_page(resources.unified_job_template, UnifiedJobTemplate) diff --git a/awxkit/awxkit/api/pages/unified_jobs.py b/awxkit/awxkit/api/pages/unified_jobs.py index 20c6175ed3..09dea1ebbb 100644 --- a/awxkit/awxkit/api/pages/unified_jobs.py +++ b/awxkit/awxkit/api/pages/unified_jobs.py @@ -21,8 +21,7 @@ class UnifiedJob(HasStatus, base.Base): # NOTE: I use .replace('%', '%%') to workaround an odd string # formatting issue where result_stdout contained '%s'. This later caused # a python traceback when attempting to display output from this method. - items = ['id', 'name', 'status', 'failed', 'result_stdout', 'result_traceback', - 'job_explanation', 'job_args'] + items = ['id', 'name', 'status', 'failed', 'result_stdout', 'result_traceback', 'job_explanation', 'job_args'] info = [] for item in [x for x in items if hasattr(self, x)]: info.append('{0}:{1}'.format(item, getattr(self, item))) @@ -32,9 +31,7 @@ class UnifiedJob(HasStatus, base.Base): @property def result_stdout(self): if 'result_stdout' not in self.json and 'stdout' in self.related: - return self.connection.get( - self.related.stdout, query_parameters=dict(format='txt_download') - ).content.decode() + return self.connection.get(self.related.stdout, query_parameters=dict(format='txt_download')).content.decode() return self.json.result_stdout.decode() def assert_text_in_stdout(self, expected_text, replace_spaces=None, replace_newlines=' '): @@ -55,9 +52,7 @@ class UnifiedJob(HasStatus, base.Base): stdout = stdout.replace(' ', replace_spaces) if expected_text not in stdout: pretty_stdout = pformat(stdout) - raise AssertionError( - 'Expected "{}", but it was not found in stdout. Full stdout:\n {}'.format(expected_text, pretty_stdout) - ) + raise AssertionError('Expected "{}", but it was not found in stdout. Full stdout:\n {}'.format(expected_text, pretty_stdout)) @property def is_successful(self): @@ -103,7 +98,7 @@ class UnifiedJob(HasStatus, base.Base): # Race condition where job finishes between can_cancel # check and post. if not any("not allowed" in field for field in e.msg.values()): - raise(e) + raise (e) return self.get() @property @@ -114,6 +109,7 @@ class UnifiedJob(HasStatus, base.Base): ```assert dict(extra_var=extra_var_val) in unified_job.job_args``` If you need to ensure the job_args are of awx-provided format use raw unified_job.json.job_args. """ + def attempt_yaml_load(arg): try: return yaml.safe_load(arg) @@ -151,10 +147,7 @@ class UnifiedJob(HasStatus, base.Base): if host_loc.startswith(expected_prefix): return host_loc raise RuntimeError( - 'Could not find a controller private_data_dir for this job. ' - 'Searched for volume mount to {} inside of args {}'.format( - expected_prefix, job_args - ) + 'Could not find a controller private_data_dir for this job. ' 'Searched for volume mount to {} inside of args {}'.format(expected_prefix, job_args) ) @@ -163,7 +156,4 @@ class UnifiedJobs(page.PageList, UnifiedJob): pass -page.register_page([resources.unified_jobs, - resources.instance_related_jobs, - resources.instance_group_related_jobs, - resources.schedules_jobs], UnifiedJobs) +page.register_page([resources.unified_jobs, resources.instance_related_jobs, resources.instance_group_related_jobs, resources.schedules_jobs], UnifiedJobs) diff --git a/awxkit/awxkit/api/pages/users.py b/awxkit/awxkit/api/pages/users.py index 22ab78dd11..f8a4d9cc17 100644 --- a/awxkit/awxkit/api/pages/users.py +++ b/awxkit/awxkit/api/pages/users.py @@ -13,26 +13,13 @@ class User(HasCreate, base.Base): def payload(self, **kwargs): payload = PseudoNamespace( - username=kwargs.get('username') or 'User-{}'.format( - random_title( - non_ascii=False)), + username=kwargs.get('username') or 'User-{}'.format(random_title(non_ascii=False)), password=kwargs.get('password') or config.credentials.default.password, - is_superuser=kwargs.get( - 'is_superuser', - False), - is_system_auditor=kwargs.get( - 'is_system_auditor', - False), - first_name=kwargs.get( - 'first_name', - random_title()), - last_name=kwargs.get( - 'last_name', - random_title()), - email=kwargs.get( - 'email', - '{}@example.com'.format(random_title(5, non_ascii=False)) - ) + is_superuser=kwargs.get('is_superuser', False), + is_system_auditor=kwargs.get('is_system_auditor', False), + first_name=kwargs.get('first_name', random_title()), + last_name=kwargs.get('last_name', random_title()), + email=kwargs.get('email', '{}@example.com'.format(random_title(5, non_ascii=False))), ) return payload @@ -42,8 +29,7 @@ class User(HasCreate, base.Base): return payload def create(self, username='', password='', organization=None, **kwargs): - payload = self.create_payload( - username=username, password=password, **kwargs) + payload = self.create_payload(username=username, password=password, **kwargs) self.password = payload.password self.update_identity(Users(self.connection).post(payload)) @@ -54,8 +40,7 @@ class User(HasCreate, base.Base): return self -page.register_page([resources.user, - (resources.users, 'post')], User) +page.register_page([resources.user, (resources.users, 'post')], User) class Users(page.PageList, User): @@ -63,11 +48,9 @@ class Users(page.PageList, User): pass -page.register_page([resources.users, - resources.organization_admins, - resources.related_users, - resources.credential_owner_users, - resources.user_admin_organizations], Users) +page.register_page( + [resources.users, resources.organization_admins, resources.related_users, resources.credential_owner_users, resources.user_admin_organizations], Users +) class Me(Users): diff --git a/awxkit/awxkit/api/pages/workflow_approvals.py b/awxkit/awxkit/api/pages/workflow_approvals.py index d4ededcdec..bd26aae7e3 100644 --- a/awxkit/awxkit/api/pages/workflow_approvals.py +++ b/awxkit/awxkit/api/pages/workflow_approvals.py @@ -5,7 +5,6 @@ from awxkit import exceptions class WorkflowApproval(UnifiedJob): - def approve(self): try: self.related.approve.post() diff --git a/awxkit/awxkit/api/pages/workflow_job_nodes.py b/awxkit/awxkit/api/pages/workflow_job_nodes.py index 320618323a..4eabaebe16 100644 --- a/awxkit/awxkit/api/pages/workflow_job_nodes.py +++ b/awxkit/awxkit/api/pages/workflow_job_nodes.py @@ -5,7 +5,6 @@ from . import page class WorkflowJobNode(base.Base): - def wait_for_job(self, interval=5, timeout=60, **kw): """Waits until node's job exists""" adjusted_timeout = timeout - seconds_since_date_string(self.created) @@ -30,8 +29,13 @@ class WorkflowJobNodes(page.PageList, WorkflowJobNode): pass -page.register_page([resources.workflow_job_nodes, - resources.workflow_job_workflow_nodes, - resources.workflow_job_node_always_nodes, - resources.workflow_job_node_failure_nodes, - resources.workflow_job_node_success_nodes], WorkflowJobNodes) +page.register_page( + [ + resources.workflow_job_nodes, + resources.workflow_job_workflow_nodes, + resources.workflow_job_node_always_nodes, + resources.workflow_job_node_failure_nodes, + resources.workflow_job_node_success_nodes, + ], + WorkflowJobNodes, +) diff --git a/awxkit/awxkit/api/pages/workflow_job_template_nodes.py b/awxkit/awxkit/api/pages/workflow_job_template_nodes.py index 5494d6063b..8a68476030 100644 --- a/awxkit/awxkit/api/pages/workflow_job_template_nodes.py +++ b/awxkit/awxkit/api/pages/workflow_job_template_nodes.py @@ -15,12 +15,9 @@ class WorkflowJobTemplateNode(HasCreate, base.Base): def payload(self, workflow_job_template, unified_job_template, **kwargs): if not unified_job_template: # May pass "None" to explicitly create an approval node - payload = PseudoNamespace( - workflow_job_template=workflow_job_template.id) + payload = PseudoNamespace(workflow_job_template=workflow_job_template.id) else: - payload = PseudoNamespace( - workflow_job_template=workflow_job_template.id, - unified_job_template=unified_job_template.id) + payload = PseudoNamespace(workflow_job_template=workflow_job_template.id, unified_job_template=unified_job_template.id) optional_fields = ( 'diff_mode', @@ -33,7 +30,8 @@ class WorkflowJobTemplateNode(HasCreate, base.Base): 'verbosity', 'extra_data', 'identifier', - 'all_parents_must_converge') + 'all_parents_must_converge', + ) update_payload(payload, optional_fields, kwargs) @@ -42,45 +40,23 @@ class WorkflowJobTemplateNode(HasCreate, base.Base): return payload - def create_payload( - self, - workflow_job_template=WorkflowJobTemplate, - unified_job_template=JobTemplate, - **kwargs): + def create_payload(self, workflow_job_template=WorkflowJobTemplate, unified_job_template=JobTemplate, **kwargs): if not unified_job_template: self.create_and_update_dependencies(workflow_job_template) - payload = self.payload( - workflow_job_template=self.ds.workflow_job_template, - unified_job_template=None, - **kwargs) + payload = self.payload(workflow_job_template=self.ds.workflow_job_template, unified_job_template=None, **kwargs) else: - self.create_and_update_dependencies( - workflow_job_template, unified_job_template) - payload = self.payload( - workflow_job_template=self.ds.workflow_job_template, - unified_job_template=self.ds.unified_job_template, - **kwargs) + self.create_and_update_dependencies(workflow_job_template, unified_job_template) + payload = self.payload(workflow_job_template=self.ds.workflow_job_template, unified_job_template=self.ds.unified_job_template, **kwargs) payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store) return payload - def create( - self, - workflow_job_template=WorkflowJobTemplate, - unified_job_template=JobTemplate, - **kwargs): - payload = self.create_payload( - workflow_job_template=workflow_job_template, - unified_job_template=unified_job_template, - **kwargs) - return self.update_identity( - WorkflowJobTemplateNodes( - self.connection).post(payload)) + def create(self, workflow_job_template=WorkflowJobTemplate, unified_job_template=JobTemplate, **kwargs): + payload = self.create_payload(workflow_job_template=workflow_job_template, unified_job_template=unified_job_template, **kwargs) + return self.update_identity(WorkflowJobTemplateNodes(self.connection).post(payload)) def _add_node(self, endpoint, unified_job_template, **kwargs): - node = endpoint.post( - dict(unified_job_template=unified_job_template.id, **kwargs)) - node.create_and_update_dependencies( - self.ds.workflow_job_template, unified_job_template) + node = endpoint.post(dict(unified_job_template=unified_job_template.id, **kwargs)) + node.create_and_update_dependencies(self.ds.workflow_job_template, unified_job_template) return node def add_always_node(self, unified_job_template, **kwargs): @@ -94,24 +70,18 @@ class WorkflowJobTemplateNode(HasCreate, base.Base): def add_credential(self, credential): with suppress(exc.NoContent): - self.related.credentials.post( - dict(id=credential.id, associate=True)) + self.related.credentials.post(dict(id=credential.id, associate=True)) def remove_credential(self, credential): with suppress(exc.NoContent): - self.related.credentials.post( - dict(id=credential.id, disassociate=True)) + self.related.credentials.post(dict(id=credential.id, disassociate=True)) def remove_all_credentials(self): for cred in self.related.credentials.get().results: with suppress(exc.NoContent): - self.related.credentials.post( - dict(id=cred.id, disassociate=True)) + self.related.credentials.post(dict(id=cred.id, disassociate=True)) - def make_approval_node( - self, - **kwargs - ): + def make_approval_node(self, **kwargs): if 'name' not in kwargs: kwargs['name'] = 'approval node {}'.format(random_title()) self.related.create_approval_template.post(kwargs) @@ -122,10 +92,10 @@ class WorkflowJobTemplateNode(HasCreate, base.Base): return candidates.results.pop() -page.register_page([resources.workflow_job_template_node, - (resources.workflow_job_template_nodes, 'post'), - (resources.workflow_job_template_workflow_nodes, 'post')], - WorkflowJobTemplateNode) +page.register_page( + [resources.workflow_job_template_node, (resources.workflow_job_template_nodes, 'post'), (resources.workflow_job_template_workflow_nodes, 'post')], + WorkflowJobTemplateNode, +) class WorkflowJobTemplateNodes(page.PageList, WorkflowJobTemplateNode): @@ -133,9 +103,13 @@ class WorkflowJobTemplateNodes(page.PageList, WorkflowJobTemplateNode): pass -page.register_page([resources.workflow_job_template_nodes, - resources.workflow_job_template_workflow_nodes, - resources.workflow_job_template_node_always_nodes, - resources.workflow_job_template_node_failure_nodes, - resources.workflow_job_template_node_success_nodes], - WorkflowJobTemplateNodes) +page.register_page( + [ + resources.workflow_job_template_nodes, + resources.workflow_job_template_workflow_nodes, + resources.workflow_job_template_node_always_nodes, + resources.workflow_job_template_node_failure_nodes, + resources.workflow_job_template_node_success_nodes, + ], + WorkflowJobTemplateNodes, +) diff --git a/awxkit/awxkit/api/pages/workflow_job_templates.py b/awxkit/awxkit/api/pages/workflow_job_templates.py index 17f3b56342..b8254f34d9 100644 --- a/awxkit/awxkit/api/pages/workflow_job_templates.py +++ b/awxkit/awxkit/api/pages/workflow_job_templates.py @@ -26,15 +26,14 @@ class WorkflowJobTemplate(HasCopy, HasCreate, HasNotifications, HasSurvey, Unifi # return job jobs_pg = self.related.workflow_jobs.get(id=result.workflow_job) if jobs_pg.count != 1: - msg = "workflow_job_template launched (id:{}) but job not found in response at {}/workflow_jobs/".format( - result.json['workflow_job'], self.url - ) + msg = "workflow_job_template launched (id:{}) but job not found in response at {}/workflow_jobs/".format(result.json['workflow_job'], self.url) raise exc.UnexpectedAWXState(msg) return jobs_pg.results[0] def payload(self, **kwargs): - payload = PseudoNamespace(name=kwargs.get('name') or 'WorkflowJobTemplate - {}'.format(random_title()), - description=kwargs.get('description') or random_title(10)) + payload = PseudoNamespace( + name=kwargs.get('name') or 'WorkflowJobTemplate - {}'.format(random_title()), description=kwargs.get('description') or random_title(10) + ) optional_fields = ( "allow_simultaneous", @@ -91,9 +90,9 @@ class WorkflowJobTemplate(HasCopy, HasCreate, HasNotifications, HasSurvey, Unifi self.related.labels.post(label) -page.register_page([resources.workflow_job_template, - (resources.workflow_job_templates, 'post'), - (resources.workflow_job_template_copy, 'post')], WorkflowJobTemplate) +page.register_page( + [resources.workflow_job_template, (resources.workflow_job_templates, 'post'), (resources.workflow_job_template_copy, 'post')], WorkflowJobTemplate +) class WorkflowJobTemplates(page.PageList, WorkflowJobTemplate): @@ -101,8 +100,7 @@ class WorkflowJobTemplates(page.PageList, WorkflowJobTemplate): pass -page.register_page([resources.workflow_job_templates, - resources.related_workflow_job_templates], WorkflowJobTemplates) +page.register_page([resources.workflow_job_templates, resources.related_workflow_job_templates], WorkflowJobTemplates) class WorkflowJobTemplateLaunch(base.Base): diff --git a/awxkit/awxkit/api/pages/workflow_jobs.py b/awxkit/awxkit/api/pages/workflow_jobs.py index 36afc94460..200eb0ef30 100644 --- a/awxkit/awxkit/api/pages/workflow_jobs.py +++ b/awxkit/awxkit/api/pages/workflow_jobs.py @@ -4,7 +4,6 @@ from . import page class WorkflowJob(UnifiedJob): - def __str__(self): # TODO: Update after endpoint's fields are finished filling out return super(UnifiedJob, self).__str__() @@ -56,7 +55,4 @@ class WorkflowJobs(page.PageList, WorkflowJob): pass -page.register_page([resources.workflow_jobs, - resources.workflow_job_template_jobs, - resources.job_template_slice_workflow_jobs], - WorkflowJobs) +page.register_page([resources.workflow_jobs, resources.workflow_job_template_jobs, resources.job_template_slice_workflow_jobs], WorkflowJobs) diff --git a/awxkit/awxkit/api/registry.py b/awxkit/awxkit/api/registry.py index 67d6bb23b8..c7cea080b9 100644 --- a/awxkit/awxkit/api/registry.py +++ b/awxkit/awxkit/api/registry.py @@ -8,7 +8,6 @@ log = logging.getLogger(__name__) class URLRegistry(object): - def __init__(self): self.store = defaultdict(dict) self.default = {} @@ -81,8 +80,7 @@ class URLRegistry(object): if method_pattern.pattern == not_provided: exc_msg = '"{0.pattern}" already has methodless registration.'.format(url_pattern) else: - exc_msg = ('"{0.pattern}" already has registered method "{1.pattern}"' - .format(url_pattern, method_pattern)) + exc_msg = '"{0.pattern}" already has registered method "{1.pattern}"'.format(url_pattern, method_pattern) raise TypeError(exc_msg) self.store[url_pattern][method_pattern] = resource diff --git a/awxkit/awxkit/api/resources.py b/awxkit/awxkit/api/resources.py index 997ada1e70..573c96598f 100644 --- a/awxkit/awxkit/api/resources.py +++ b/awxkit/awxkit/api/resources.py @@ -1,4 +1,3 @@ - class Resources(object): _activity = r'activity_stream/\d+/' diff --git a/awxkit/awxkit/api/utils.py b/awxkit/awxkit/api/utils.py index a3e6739b26..119d68aa41 100644 --- a/awxkit/awxkit/api/utils.py +++ b/awxkit/awxkit/api/utils.py @@ -15,12 +15,11 @@ def freeze(key): def parse_description(desc): options = {} - for line in desc[desc.index('POST'):].splitlines(): + for line in desc[desc.index('POST') :].splitlines(): match = descRE.match(line) if not match: continue - options[match.group(1)] = {'type': match.group(2), - 'required': match.group(3) == 'required'} + options[match.group(1)] = {'type': match.group(2), 'required': match.group(3) == 'required'} return options @@ -45,6 +44,5 @@ def get_post_fields(page, cache): if 'POST' in options_page.json['actions']: return options_page.json['actions']['POST'] else: - log.warning( - "Insufficient privileges on %s, inferring POST fields from description.", options_page.endpoint) + log.warning("Insufficient privileges on %s, inferring POST fields from description.", options_page.endpoint) return parse_description(options_page.json['description']) diff --git a/awxkit/awxkit/awx/inventory.py b/awxkit/awxkit/awx/inventory.py index b4872eb3e8..61a31c5754 100644 --- a/awxkit/awxkit/awx/inventory.py +++ b/awxkit/awxkit/awx/inventory.py @@ -17,13 +17,14 @@ def upload_inventory(ansible_runner, nhosts=10, ini=False): copy_content = '''#!/bin/bash cat <<EOF %s -EOF''' % json_inventory(nhosts) +EOF''' % json_inventory( + nhosts + ) # Copy script to test system contacted = ansible_runner.copy(dest=copy_dest, force=True, mode=copy_mode, content=copy_content) for result in contacted.values(): - assert not result.get('failed', False), \ - "Failed to create inventory file: %s" % result + assert not result.get('failed', False), "Failed to create inventory file: %s" % result return copy_dest @@ -49,8 +50,7 @@ def generate_inventory(nhosts=100): group_by_10s = 'group-%07dX.example.com' % (n / 10) group_by_100s = 'group-%06dXX.example.com' % (n / 100) group_by_1000s = 'group-%05dXXX.example.com' % (n / 1000) - for group in [group_evens_odds, group_threes, group_fours, group_fives, group_sixes, group_sevens, - group_eights, group_nines, group_tens, group_by_10s]: + for group in [group_evens_odds, group_threes, group_fours, group_fives, group_sixes, group_sevens, group_eights, group_nines, group_tens, group_by_10s]: if not group: continue if group in inv_list: @@ -58,11 +58,9 @@ def generate_inventory(nhosts=100): else: inv_list[group] = {'hosts': [hostname], 'children': [], 'vars': {'group_prefix': group.split('.')[0]}} if group_by_1000s not in inv_list: - inv_list[group_by_1000s] = {'hosts': [], 'children': [], - 'vars': {'group_prefix': group_by_1000s.split('.')[0]}} + inv_list[group_by_1000s] = {'hosts': [], 'children': [], 'vars': {'group_prefix': group_by_1000s.split('.')[0]}} if group_by_100s not in inv_list: - inv_list[group_by_100s] = {'hosts': [], 'children': [], - 'vars': {'group_prefix': group_by_100s.split('.')[0]}} + inv_list[group_by_100s] = {'hosts': [], 'children': [], 'vars': {'group_prefix': group_by_100s.split('.')[0]}} if group_by_100s not in inv_list[group_by_1000s]['children']: inv_list[group_by_1000s]['children'].append(group_by_100s) if group_by_10s not in inv_list[group_by_100s]['children']: diff --git a/awxkit/awxkit/awx/utils.py b/awxkit/awxkit/awx/utils.py index d25e555ad6..f4aefeeca3 100644 --- a/awxkit/awxkit/awx/utils.py +++ b/awxkit/awxkit/awx/utils.py @@ -31,9 +31,22 @@ def _delete_all(endpoint): def delete_all(v): - for endpoint in (v.unified_jobs, v.job_templates, v.workflow_job_templates, v.notification_templates, - v.projects, v.inventory, v.hosts, v.inventory_scripts, v.labels, v.credentials, - v.teams, v.users, v.organizations, v.schedules): + for endpoint in ( + v.unified_jobs, + v.job_templates, + v.workflow_job_templates, + v.notification_templates, + v.projects, + v.inventory, + v.hosts, + v.inventory_scripts, + v.labels, + v.credentials, + v.teams, + v.users, + v.organizations, + v.schedules, + ): _delete_all(endpoint) diff --git a/awxkit/awxkit/cli/__init__.py b/awxkit/awxkit/cli/__init__.py index 6b20792eef..a73f18510d 100644 --- a/awxkit/awxkit/cli/__init__.py +++ b/awxkit/awxkit/cli/__init__.py @@ -56,14 +56,7 @@ def run(stdout=sys.stdout, stderr=sys.stderr, argv=[]): json.dump(e.msg, sys.stdout) print('') elif cli.get_config('format') == 'yaml': - sys.stdout.write(to_str( - yaml.safe_dump( - e.msg, - default_flow_style=False, - encoding='utf-8', - allow_unicode=True - ) - )) + sys.stdout.write(to_str(yaml.safe_dump(e.msg, default_flow_style=False, encoding='utf-8', allow_unicode=True))) elif cli.get_config('format') == 'human': sys.stdout.write(e.__class__.__name__) print('') diff --git a/awxkit/awxkit/cli/client.py b/awxkit/awxkit/cli/client.py index f14d6df135..46dc4fa333 100755 --- a/awxkit/awxkit/cli/client.py +++ b/awxkit/awxkit/cli/client.py @@ -8,9 +8,7 @@ import sys from requests.exceptions import RequestException from .custom import handle_custom_actions -from .format import (add_authentication_arguments, - add_output_formatting_arguments, - FORMATTERS, format_response) +from .format import add_authentication_arguments, add_output_formatting_arguments, FORMATTERS, format_response from .options import ResourceOptionsParser, UNIQUENESS_RULES from .resource import parse_resource, is_control_resource from awxkit import api, config, utils, exceptions, WSClient # noqa @@ -88,7 +86,9 @@ class CLI(object): token = self.get_config('token') if token: self.root.connection.login( - None, None, token=token, + None, + None, + token=token, ) else: config.use_sessions = True @@ -102,12 +102,14 @@ class CLI(object): if self.get_config('insecure'): config.assume_untrusted = True - config.credentials = utils.PseudoNamespace({ - 'default': { - 'username': self.get_config('username'), - 'password': self.get_config('password'), + config.credentials = utils.PseudoNamespace( + { + 'default': { + 'username': self.get_config('username'), + 'password': self.get_config('password'), + } } - }) + ) _, remainder = self.parser.parse_known_args() if remainder and remainder[0] == 'config': @@ -133,11 +135,7 @@ class CLI(object): try: self.v2 = self.root.get().available_versions.v2.get() except AttributeError: - raise RuntimeError( - 'An error occurred while fetching {}/api/'.format( - self.get_config('host') - ) - ) + raise RuntimeError('An error occurred while fetching {}/api/'.format(self.get_config('host'))) def parse_resource(self, skip_deprecated=False): """Attempt to parse the <resource> (e.g., jobs) specified on the CLI @@ -170,33 +168,15 @@ class CLI(object): _filter = self.get_config('filter') # human format for metrics, settings is special - if ( - self.resource in ('metrics', 'settings') and - self.get_config('format') == 'human' - ): - response.json = { - 'count': len(response.json), - 'results': [ - {'key': k, 'value': v} - for k, v in response.json.items() - ] - } + if self.resource in ('metrics', 'settings') and self.get_config('format') == 'human': + response.json = {'count': len(response.json), 'results': [{'key': k, 'value': v} for k, v in response.json.items()]} _filter = 'key, value' - if ( - self.get_config('format') == 'human' and - _filter == '.' and - self.resource in UNIQUENESS_RULES - ): + if self.get_config('format') == 'human' and _filter == '.' and self.resource in UNIQUENESS_RULES: _filter = ', '.join(UNIQUENESS_RULES[self.resource]) formatted = format_response( - response, - fmt=self.get_config('format'), - filter=_filter, - changed=self.original_action in ( - 'modify', 'create', 'associate', 'disassociate' - ) + response, fmt=self.get_config('format'), filter=_filter, changed=self.original_action in ('modify', 'create', 'associate', 'disassociate') ) if formatted: print(utils.to_str(formatted), file=self.stdout) @@ -219,10 +199,7 @@ class CLI(object): _without_ triggering a SystemExit (argparse's behavior if required arguments are missing) """ - subparsers = self.subparsers[self.resource].add_subparsers( - dest='action', - metavar='action' - ) + subparsers = self.subparsers[self.resource].add_subparsers(dest='action', metavar='action') subparsers.required = True # parse the action from OPTIONS @@ -252,10 +229,7 @@ class CLI(object): if self.resource != 'settings': for method in ('list', 'modify', 'create'): if method in parser.parser.choices: - parser.build_query_arguments( - method, - 'GET' if method == 'list' else 'POST' - ) + parser.build_query_arguments(method, 'GET' if method == 'list' else 'POST') if from_sphinx: parsed, extra = self.parser.parse_known_args(self.argv) else: @@ -263,10 +237,7 @@ class CLI(object): if extra and self.verbose: # If extraneous arguments were provided, warn the user - cprint('{}: unrecognized arguments: {}'.format( - self.parser.prog, - ' '.join(extra) - ), 'yellow', file=self.stdout) + cprint('{}: unrecognized arguments: {}'.format(self.parser.prog, ' '.join(extra)), 'yellow', file=self.stdout) # build a dictionary of all of the _valid_ flags specified on the # command line so we can pass them on to the underlying awxkit call @@ -275,14 +246,7 @@ class CLI(object): # everything else is a flag used as a query argument for the HTTP # request we'll make (e.g., --username="Joe", --verbosity=3) parsed = parsed.__dict__ - parsed = dict( - (k, v) for k, v in parsed.items() - if ( - v is not None and - k not in ('help', 'resource') and - not k.startswith('conf.') - ) - ) + parsed = dict((k, v) for k, v in parsed.items() if (v is not None and k not in ('help', 'resource') and not k.startswith('conf.'))) # if `id` is one of the arguments, it's a detail view if 'id' in parsed: @@ -290,9 +254,7 @@ class CLI(object): # determine the awxkit method to call action = self.original_action = parsed.pop('action') - page, action = handle_custom_actions( - self.resource, action, page - ) + page, action = handle_custom_actions(self.resource, action, page) self.method = { 'list': 'get', 'modify': 'patch', @@ -327,13 +289,7 @@ class CLI(object): action='store_true', help='prints usage information for the awx tool', ) - self.parser.add_argument( - '--version', - dest='conf.version', - action='version', - help='display awx CLI version', - version=__version__ - ) + self.parser.add_argument('--version', dest='conf.version', action='version', help='display awx CLI version', version=__version__) add_authentication_arguments(self.parser, env) add_output_formatting_arguments(self.parser, env) diff --git a/awxkit/awxkit/cli/custom.py b/awxkit/awxkit/cli/custom.py index 38d97d8895..d2b9bc6590 100644 --- a/awxkit/awxkit/cli/custom.py +++ b/awxkit/awxkit/cli/custom.py @@ -16,7 +16,6 @@ def handle_custom_actions(resource, action, page): class CustomActionRegistryMeta(CustomRegistryMeta): - @property def name(self): return ' '.join([self.resource, self.action]) @@ -45,33 +44,16 @@ class CustomAction(metaclass=CustomActionRegistryMeta): class Launchable(object): - def add_arguments(self, parser, resource_options_parser, with_pk=True): from .options import pk_or_name + if with_pk: - parser.choices[self.action].add_argument( - 'id', - type=functools.partial( - pk_or_name, None, self.resource, page=self.page - ), - help='' - ) - parser.choices[self.action].add_argument( - '--monitor', action='store_true', - help='If set, prints stdout of the launched job until it finishes.' - ) - parser.choices[self.action].add_argument( - '--timeout', type=int, - help='If set with --monitor or --wait, time out waiting on job completion.' # noqa - ) - parser.choices[self.action].add_argument( - '--wait', action='store_true', - help='If set, waits until the launched job finishes.' - ) + parser.choices[self.action].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='') + parser.choices[self.action].add_argument('--monitor', action='store_true', help='If set, prints stdout of the launched job until it finishes.') + parser.choices[self.action].add_argument('--timeout', type=int, help='If set with --monitor or --wait, time out waiting on job completion.') # noqa + parser.choices[self.action].add_argument('--wait', action='store_true', help='If set, waits until the launched job finishes.') - launch_time_options = self.page.connection.options( - self.page.endpoint + '1/{}/'.format(self.action) - ) + launch_time_options = self.page.connection.options(self.page.endpoint + '1/{}/'.format(self.action)) if launch_time_options.ok: launch_time_options = launch_time_options.json()['actions']['POST'] resource_options_parser.options['LAUNCH'] = launch_time_options @@ -118,24 +100,15 @@ class ProjectCreate(CustomAction): resource = 'projects' def add_arguments(self, parser, resource_options_parser): - parser.choices[self.action].add_argument( - '--monitor', action='store_true', - help=('If set, prints stdout of the project update until ' - 'it finishes.') - ) - parser.choices[self.action].add_argument( - '--wait', action='store_true', - help='If set, waits until the new project has updated.' - ) + parser.choices[self.action].add_argument('--monitor', action='store_true', help=('If set, prints stdout of the project update until ' 'it finishes.')) + parser.choices[self.action].add_argument('--wait', action='store_true', help='If set, waits until the new project has updated.') def post(self, kwargs): should_monitor = kwargs.pop('monitor', False) wait = kwargs.pop('wait', False) response = self.page.post(kwargs) if should_monitor or wait: - update = response.related.project_updates.get( - order_by='-created' - ).results[0] + update = response.related.project_updates.get(order_by='-created').results[0] monitor( update, self.page.connection.session, @@ -154,9 +127,7 @@ class AdhocCommandLaunch(Launchable, CustomAction): resource = 'ad_hoc_commands' def add_arguments(self, parser, resource_options_parser): - Launchable.add_arguments( - self, parser, resource_options_parser, with_pk=False - ) + Launchable.add_arguments(self, parser, resource_options_parser, with_pk=False) def perform(self, **kwargs): monitor_kwargs = { @@ -182,22 +153,14 @@ class HasStdout(object): def add_arguments(self, parser, resource_options_parser): from .options import pk_or_name - parser.choices['stdout'].add_argument( - 'id', - type=functools.partial( - pk_or_name, None, self.resource, page=self.page - ), - help='' - ) + + parser.choices['stdout'].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='') def perform(self): fmt = 'txt_download' if color_enabled(): fmt = 'ansi_download' - return self.page.connection.get( - self.page.get().related.stdout, - query_parameters=dict(format=fmt) - ).content.decode('utf-8') + return self.page.connection.get(self.page.get().related.stdout, query_parameters=dict(format=fmt)).content.decode('utf-8') class JobStdout(HasStdout, CustomAction): @@ -222,13 +185,8 @@ class AssociationMixin(object): def add_arguments(self, parser, resource_options_parser): from .options import pk_or_name - parser.choices[self.action].add_argument( - 'id', - type=functools.partial( - pk_or_name, None, self.resource, page=self.page - ), - help='' - ) + + parser.choices[self.action].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='') group = parser.choices[self.action].add_mutually_exclusive_group(required=True) for param, endpoint in self.targets.items(): field, model_name = endpoint @@ -237,7 +195,6 @@ class AssociationMixin(object): help_text = 'The ID (or name) of the {} to {}'.format(model_name, self.action) class related_page(object): - def __init__(self, connection, resource): self.conn = connection self.resource = { @@ -256,20 +213,15 @@ class AssociationMixin(object): group.add_argument( '--{}'.format(param), metavar='', - type=functools.partial( - pk_or_name, None, param, - page=related_page(self.page.connection, param) - ), - help=help_text + type=functools.partial(pk_or_name, None, param, page=related_page(self.page.connection, param)), + help=help_text, ) def perform(self, **kwargs): for k, v in kwargs.items(): endpoint, _ = self.targets[k] try: - self.page.get().related[endpoint].post( - {'id': v, self.action: True} - ) + self.page.get().related[endpoint].post({'id': v, self.action: True}) except NoContent: # we expect to enter this block because these endpoints return # HTTP 204 on success @@ -279,18 +231,9 @@ class AssociationMixin(object): class NotificationAssociateMixin(AssociationMixin): targets = { - 'start_notification': [ - 'notification_templates_started', - 'notification_template' - ], - 'success_notification': [ - 'notification_templates_success', - 'notification_template' - ], - 'failure_notification': [ - 'notification_templates_error', - 'notification_template' - ], + 'start_notification': ['notification_templates_started', 'notification_template'], + 'success_notification': ['notification_templates_success', 'notification_template'], + 'failure_notification': ['notification_templates_error', 'notification_template'], } @@ -306,12 +249,16 @@ class JobTemplateNotificationDisAssociation(NotificationAssociateMixin, CustomAc targets = NotificationAssociateMixin.targets.copy() -JobTemplateNotificationAssociation.targets.update({ - 'credential': ['credentials', None], -}) -JobTemplateNotificationDisAssociation.targets.update({ - 'credential': ['credentials', None], -}) +JobTemplateNotificationAssociation.targets.update( + { + 'credential': ['credentials', None], + } +) +JobTemplateNotificationDisAssociation.targets.update( + { + 'credential': ['credentials', None], + } +) class WorkflowJobTemplateNotificationAssociation(NotificationAssociateMixin, CustomAction): @@ -326,12 +273,16 @@ class WorkflowJobTemplateNotificationDisAssociation(NotificationAssociateMixin, targets = NotificationAssociateMixin.targets.copy() -WorkflowJobTemplateNotificationAssociation.targets.update({ - 'approval_notification': ['notification_templates_approvals', 'notification_template'], -}) -WorkflowJobTemplateNotificationDisAssociation.targets.update({ - 'approval_notification': ['notification_templates_approvals', 'notification_template'], -}) +WorkflowJobTemplateNotificationAssociation.targets.update( + { + 'approval_notification': ['notification_templates_approvals', 'notification_template'], + } +) +WorkflowJobTemplateNotificationDisAssociation.targets.update( + { + 'approval_notification': ['notification_templates_approvals', 'notification_template'], + } +) class ProjectNotificationAssociation(NotificationAssociateMixin, CustomAction): @@ -366,14 +317,18 @@ class OrganizationNotificationDisAssociation(NotificationAssociateMixin, CustomA targets = NotificationAssociateMixin.targets.copy() -OrganizationNotificationAssociation.targets.update({ - 'approval_notification': ['notification_templates_approvals', 'notification_template'], - 'galaxy_credential': ['galaxy_credentials', 'credential'], -}) -OrganizationNotificationDisAssociation.targets.update({ - 'approval_notification': ['notification_templates_approvals', 'notification_template'], - 'galaxy_credential': ['galaxy_credentials', 'credential'], -}) +OrganizationNotificationAssociation.targets.update( + { + 'approval_notification': ['notification_templates_approvals', 'notification_template'], + 'galaxy_credential': ['galaxy_credentials', 'credential'], + } +) +OrganizationNotificationDisAssociation.targets.update( + { + 'approval_notification': ['notification_templates_approvals', 'notification_template'], + 'galaxy_credential': ['galaxy_credentials', 'credential'], + } +) class SettingsList(CustomAction): @@ -381,9 +336,7 @@ class SettingsList(CustomAction): resource = 'settings' def add_arguments(self, parser, resource_options_parser): - parser.choices['list'].add_argument( - '--slug', help='optional setting category/slug', default='all' - ) + parser.choices['list'].add_argument('--slug', help='optional setting category/slug', default='all') def perform(self, slug): self.page.endpoint = self.page.endpoint + '{}/'.format(slug) @@ -409,30 +362,18 @@ class RoleMixin(object): if not RoleMixin.roles: for resource, flag in self.has_roles: - options = self.page.__class__( - self.page.endpoint.replace(self.resource, resource), - self.page.connection - ).options() - RoleMixin.roles[flag] = [ - role.replace('_role', '') - for role in options.json.get('object_roles', []) - ] + options = self.page.__class__(self.page.endpoint.replace(self.resource, resource), self.page.connection).options() + RoleMixin.roles[flag] = [role.replace('_role', '') for role in options.json.get('object_roles', [])] possible_roles = set() for v in RoleMixin.roles.values(): possible_roles.update(v) - resource_group = parser.choices[self.action].add_mutually_exclusive_group( - required=True - ) + resource_group = parser.choices[self.action].add_mutually_exclusive_group(required=True) parser.choices[self.action].add_argument( 'id', - type=functools.partial( - pk_or_name, None, self.resource, page=self.page - ), - help='The ID (or name) of the {} to {} access to/from'.format( - self.resource, self.action - ) + type=functools.partial(pk_or_name, None, self.resource, page=self.page), + help='The ID (or name) of the {} to {} access to/from'.format(self.resource, self.action), ) for _type in RoleMixin.roles.keys(): if _type == 'team' and self.resource == 'team': @@ -440,7 +381,6 @@ class RoleMixin(object): continue class related_page(object): - def __init__(self, connection, resource): self.conn = connection if resource == 'inventories': @@ -453,19 +393,12 @@ class RoleMixin(object): resource_group.add_argument( '--{}'.format(_type), - type=functools.partial( - pk_or_name, None, _type, - page=related_page( - self.page.connection, - dict((v, k) for k, v in self.has_roles)[_type] - ) - ), + type=functools.partial(pk_or_name, None, _type, page=related_page(self.page.connection, dict((v, k) for k, v in self.has_roles)[_type])), metavar='ID', help='The ID (or name) of the target {}'.format(_type), ) parser.choices[self.action].add_argument( - '--role', type=str, choices=possible_roles, required=True, - help='The name of the role to {}'.format(self.action) + '--role', type=str, choices=possible_roles, required=True, help='The name of the role to {}'.format(self.action) ) def perform(self, **kwargs): @@ -474,17 +407,10 @@ class RoleMixin(object): role = kwargs['role'] if role not in RoleMixin.roles[flag]: options = ', '.join(RoleMixin.roles[flag]) - raise ValueError( - "invalid choice: '{}' must be one of {}".format( - role, options - ) - ) + raise ValueError("invalid choice: '{}' must be one of {}".format(role, options)) value = kwargs[flag] target = '/api/v2/{}/{}'.format(resource, value) - detail = self.page.__class__( - target, - self.page.connection - ).get() + detail = self.page.__class__(target, self.page.connection).get() object_roles = detail['summary_fields']['object_roles'] actual_role = object_roles[role + '_role'] params = {'id': actual_role['id']} @@ -530,15 +456,8 @@ class SettingsModify(CustomAction): resource = 'settings' def add_arguments(self, parser, resource_options_parser): - options = self.page.__class__( - self.page.endpoint + 'all/', self.page.connection - ).options() - parser.choices['modify'].add_argument( - 'key', - choices=sorted(options['actions']['PUT'].keys()), - metavar='key', - help='' - ) + options = self.page.__class__(self.page.endpoint + 'all/', self.page.connection).options() + parser.choices['modify'].add_argument('key', choices=sorted(options['actions']['PUT'].keys()), metavar='key', help='') parser.choices['modify'].add_argument('value', help='') def perform(self, key, value): @@ -563,13 +482,8 @@ class HasMonitor(object): def add_arguments(self, parser, resource_options_parser): from .options import pk_or_name - parser.choices[self.action].add_argument( - 'id', - type=functools.partial( - pk_or_name, None, self.resource, page=self.page - ), - help='' - ) + + parser.choices[self.action].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='') def perform(self, **kwargs): response = self.page.get() diff --git a/awxkit/awxkit/cli/docs/source/conf.py b/awxkit/awxkit/cli/docs/source/conf.py index 7f4cf47185..75eb627103 100644 --- a/awxkit/awxkit/cli/docs/source/conf.py +++ b/awxkit/awxkit/cli/docs/source/conf.py @@ -27,9 +27,7 @@ author = 'Ansible by Red Hat' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ - 'awxkit.cli.sphinx' -] +extensions = ['awxkit.cli.sphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/awxkit/awxkit/cli/format.py b/awxkit/awxkit/cli/format.py index d35c61efbb..adf61337a4 100644 --- a/awxkit/awxkit/cli/format.py +++ b/awxkit/awxkit/cli/format.py @@ -48,24 +48,21 @@ def add_output_formatting_arguments(parser, env): dest='conf.format', choices=FORMATTERS.keys(), default=env.get('TOWER_FORMAT', 'json'), - help=( - 'specify a format for the input and output' - ), + help=('specify a format for the input and output'), ) formatting.add_argument( '--filter', dest='conf.filter', default='.', metavar='TEXT', - help=( - 'specify an output filter (only valid with jq or human format)' - ), + help=('specify an output filter (only valid with jq or human format)'), ) formatting.add_argument( '--conf.color', metavar='BOOLEAN', help='Display colorized output. Defaults to True', - default=env.get('TOWER_COLOR', 't'), type=strtobool, + default=env.get('TOWER_COLOR', 't'), + type=strtobool, ) formatting.add_argument( '-v', @@ -73,7 +70,7 @@ def add_output_formatting_arguments(parser, env): dest='conf.verbose', help='print debug-level logs, including requests made', default=strtobool(env.get('TOWER_VERBOSE', 'f')), - action="store_true" + action="store_true", ) @@ -105,11 +102,10 @@ def format_jq(output, fmt): if fmt == '.': return output raise ImportError( - 'To use `-f jq`, you must install the optional jq dependency.\n' - '`pip install jq`\n', + 'To use `-f jq`, you must install the optional jq dependency.\n' '`pip install jq`\n', 'Note that some platforms may require additional programs to ' 'build jq from source (like `libtool`).\n' - 'See https://pypi.org/project/jq/ for instructions.' + 'See https://pypi.org/project/jq/ for instructions.', ) results = [] for x in jq.jq(fmt).transform(output, multiple_output=True): @@ -127,11 +123,7 @@ def format_json(output, fmt): def format_yaml(output, fmt): output = json.loads(json.dumps(output)) - return yaml.safe_dump( - output, - default_flow_style=False, - allow_unicode=True - ) + return yaml.safe_dump(output, default_flow_style=False, allow_unicode=True) def format_human(output, fmt): @@ -151,10 +143,7 @@ def format_human(output, fmt): column_names.remove(k) table = [column_names] - table.extend([ - [record.get(col, '') for col in column_names] - for record in output - ]) + table.extend([[record.get(col, '') for col in column_names] for record in output]) col_paddings = [] def format_num(v): @@ -184,9 +173,4 @@ def format_human(output, fmt): return '\n'.join(lines) -FORMATTERS = { - 'json': format_json, - 'yaml': format_yaml, - 'jq': format_jq, - 'human': format_human -} +FORMATTERS = {'json': format_json, 'yaml': format_yaml, 'jq': format_jq, 'human': format_human} diff --git a/awxkit/awxkit/cli/options.py b/awxkit/awxkit/cli/options.py index 4d292f3611..d5ad777feb 100644 --- a/awxkit/awxkit/cli/options.py +++ b/awxkit/awxkit/cli/options.py @@ -21,10 +21,7 @@ UNIQUENESS_RULES = { def pk_or_name_list(v2, model_name, value, page=None): - return [ - pk_or_name(v2, model_name, v.strip(), page=page) - for v in value.split(',') - ] + return [pk_or_name(v2, model_name, v.strip(), page=page) for v in value.split(',')] def pk_or_name(v2, model_name, value, page=None): @@ -58,17 +55,9 @@ def pk_or_name(v2, model_name, value, page=None): return int(results.results[0].id) if results.count > 1: raise argparse.ArgumentTypeError( - 'Multiple {0} exist with that {1}. ' - 'To look up an ID, run:\n' - 'awx {0} list --{1} "{2}" -f human'.format( - model_name, identity, value - ) - ) - raise argparse.ArgumentTypeError( - 'Could not find any {0} with that {1}.'.format( - model_name, identity + 'Multiple {0} exist with that {1}. ' 'To look up an ID, run:\n' 'awx {0} list --{1} "{2}" -f human'.format(model_name, identity, value) ) - ) + raise argparse.ArgumentTypeError('Could not find any {0} with that {1}.'.format(model_name, identity)) return value @@ -90,9 +79,7 @@ class ResourceOptionsParser(object): self.page = page self.resource = resource self.parser = parser - self.options = getattr( - self.page.options().json, 'actions', {'GET': {}} - ) + self.options = getattr(self.page.options().json, 'actions', {'GET': {}}) self.get_allowed_options() if self.resource != 'settings': # /api/v2/settings is a special resource that doesn't have @@ -103,9 +90,7 @@ class ResourceOptionsParser(object): self.handle_custom_actions() def get_allowed_options(self): - options = self.page.connection.options( - self.page.endpoint + '1/' - ) + options = self.page.connection.options(self.page.endpoint + '1/') warning = options.headers.get('Warning', '') if '299' in warning and 'deprecated' in warning: self.deprecated = True @@ -121,11 +106,10 @@ class ResourceOptionsParser(object): parser = self.parser.add_parser(method, help='') if method == 'list': parser.add_argument( - '--all', dest='all_pages', action='store_true', - help=( - 'fetch all pages of content from the API when ' - 'returning results (instead of just the first page)' - ) + '--all', + dest='all_pages', + action='store_true', + help=('fetch all pages of content from the API when ' 'returning results (instead of just the first page)'), ) add_output_formatting_arguments(parser, {}) @@ -138,9 +122,7 @@ class ResourceOptionsParser(object): for method in allowed: parser = self.parser.add_parser(method, help='') self.parser.choices[method].add_argument( - 'id', - type=functools.partial(pk_or_name, self.v2, self.resource), - help='the ID (or unique name) of the resource' + 'id', type=functools.partial(pk_or_name, self.v2, self.resource), help='the ID (or unique name) of the resource' ) if method == 'get': add_output_formatting_arguments(parser, {}) @@ -148,10 +130,7 @@ class ResourceOptionsParser(object): def build_query_arguments(self, method, http_method): required_group = None for k, param in self.options.get(http_method, {}).items(): - required = ( - method == 'create' and - param.get('required', False) is True - ) + required = method == 'create' and param.get('required', False) is True help_text = param.get('help_text', '') if method == 'list': @@ -159,10 +138,7 @@ class ResourceOptionsParser(object): # don't allow `awx <resource> list` to filter on `--id` # it's weird, and that's what awx <resource> get is for continue - help_text = 'only list {} with the specified {}'.format( - self.resource, - k - ) + help_text = 'only list {} with the specified {}'.format(self.resource, k) if method == 'list' and param.get('filterable') is False: continue @@ -256,9 +232,8 @@ class ResourceOptionsParser(object): # unlike *other* actual JSON fields in the API, inventory and JT # variables *actually* want json.dumps() strings (ugh) # see: https://github.com/ansible/awx/issues/2371 - if ( - (self.resource in ('job_templates', 'workflow_job_templates') and k == 'extra_vars') or - (self.resource in ('inventory', 'groups', 'hosts') and k == 'variables') + if (self.resource in ('job_templates', 'workflow_job_templates') and k == 'extra_vars') or ( + self.resource in ('inventory', 'groups', 'hosts') and k == 'variables' ): kwargs['type'] = jsonstr @@ -267,15 +242,9 @@ class ResourceOptionsParser(object): required_group = self.parser.choices[method].add_argument_group('required arguments') # put the required group first (before the optional args group) self.parser.choices[method]._action_groups.reverse() - required_group.add_argument( - '--{}'.format(k), - **kwargs - ) + required_group.add_argument('--{}'.format(k), **kwargs) else: - self.parser.choices[method].add_argument( - '--{}'.format(k), - **kwargs - ) + self.parser.choices[method].add_argument('--{}'.format(k), **kwargs) def handle_custom_actions(self): for _, action in CustomAction.registry.items(): diff --git a/awxkit/awxkit/cli/resource.py b/awxkit/awxkit/cli/resource.py index 13419357d2..7aa7c32e0a 100644 --- a/awxkit/awxkit/cli/resource.py +++ b/awxkit/awxkit/cli/resource.py @@ -40,11 +40,9 @@ DEPRECATED_RESOURCES = { 'teams': 'team', 'workflow_job_templates': 'workflow', 'workflow_jobs': 'workflow_job', - 'users': 'user' + 'users': 'user', } -DEPRECATED_RESOURCES_REVERSE = dict( - (v, k) for k, v in DEPRECATED_RESOURCES.items() -) +DEPRECATED_RESOURCES_REVERSE = dict((v, k) for k, v in DEPRECATED_RESOURCES.items()) class CustomCommand(metaclass=CustomRegistryMeta): @@ -81,9 +79,7 @@ class Login(CustomCommand): auth.add_argument('--description', help='description of the generated OAuth2.0 token', metavar='TEXT') auth.add_argument('--conf.client_id', metavar='TEXT') auth.add_argument('--conf.client_secret', metavar='TEXT') - auth.add_argument( - '--conf.scope', choices=['read', 'write'], default='write' - ) + auth.add_argument('--conf.scope', choices=['read', 'write'], default='write') if client.help: self.print_help(parser) raise SystemExit() @@ -99,10 +95,7 @@ class Login(CustomCommand): token = api.Api().get_oauth2_token(**kwargs) except Exception as e: self.print_help(parser) - cprint( - 'Error retrieving an OAuth2.0 token ({}).'.format(e.__class__), - 'red' - ) + cprint('Error retrieving an OAuth2.0 token ({}).'.format(e.__class__), 'red') else: fmt = client.get_config('format') if fmt == 'human': @@ -186,9 +179,7 @@ def parse_resource(client, skip_deprecated=False): # check if the user is running a custom command for command in CustomCommand.__subclasses__(): - client.subparsers[command.name] = subparsers.add_parser( - command.name, help=command.help_text - ) + client.subparsers[command.name] = subparsers.add_parser(command.name, help=command.help_text) if hasattr(client, 'v2'): for k in client.v2.json.keys(): @@ -202,15 +193,11 @@ def parse_resource(client, skip_deprecated=False): if k in DEPRECATED_RESOURCES: kwargs['aliases'] = [DEPRECATED_RESOURCES[k]] - client.subparsers[k] = subparsers.add_parser( - k, help='', **kwargs - ) + client.subparsers[k] = subparsers.add_parser(k, help='', **kwargs) resource = client.parser.parse_known_args()[0].resource if resource in DEPRECATED_RESOURCES.values(): - client.argv[ - client.argv.index(resource) - ] = DEPRECATED_RESOURCES_REVERSE[resource] + client.argv[client.argv.index(resource)] = DEPRECATED_RESOURCES_REVERSE[resource] resource = DEPRECATED_RESOURCES_REVERSE[resource] if resource in CustomCommand.registry: @@ -219,27 +206,14 @@ def parse_resource(client, skip_deprecated=False): response = command.handle(client, parser) if response: _filter = client.get_config('filter') - if ( - resource == 'config' and - client.get_config('format') == 'human' - ): - response = { - 'count': len(response), - 'results': [ - {'key': k, 'value': v} - for k, v in response.items() - ] - } + if resource == 'config' and client.get_config('format') == 'human': + response = {'count': len(response), 'results': [{'key': k, 'value': v} for k, v in response.items()]} _filter = 'key, value' try: connection = client.root.connection except AttributeError: connection = None - formatted = format_response( - Page.from_json(response, connection=connection), - fmt=client.get_config('format'), - filter=_filter - ) + formatted = format_response(Page.from_json(response, connection=connection), fmt=client.get_config('format'), filter=_filter) print(formatted) raise SystemExit() else: diff --git a/awxkit/awxkit/cli/sphinx.py b/awxkit/awxkit/cli/sphinx.py index 850d99e014..970f93700a 100644 --- a/awxkit/awxkit/cli/sphinx.py +++ b/awxkit/awxkit/cli/sphinx.py @@ -8,7 +8,6 @@ from .resource import is_control_resource, CustomCommand class CustomAutoprogramDirective(AutoprogramDirective): - def run(self): nodes = super(CustomAutoprogramDirective, self).run() @@ -23,12 +22,7 @@ class CustomAutoprogramDirective(AutoprogramDirective): nodes[0][0].children = [heading] # add a descriptive top synopsis of the reference guide - nodes[0].children.insert(1, paragraph( - text=( - 'This is an exhaustive guide of every available command in ' - 'the awx CLI tool.' - ) - )) + nodes[0].children.insert(1, paragraph(text=('This is an exhaustive guide of every available command in ' 'the awx CLI tool.'))) disclaimer = ( 'The commands and parameters documented here can (and will) ' 'vary based on a variety of factors, such as the AWX API ' @@ -51,9 +45,7 @@ def render(): # Sphinx document from. for e in ('TOWER_HOST', 'TOWER_USERNAME', 'TOWER_PASSWORD'): if not os.environ.get(e): - raise SystemExit( - 'Please specify a valid {} for a real (running) Tower install.'.format(e) # noqa - ) + raise SystemExit('Please specify a valid {} for a real (running) Tower install.'.format(e)) # noqa cli = CLI() cli.parse_args(['awx', '--help']) cli.connect() diff --git a/awxkit/awxkit/cli/stdout.py b/awxkit/awxkit/cli/stdout.py index 1cf18168e1..5de134ccc4 100644 --- a/awxkit/awxkit/cli/stdout.py +++ b/awxkit/awxkit/cli/stdout.py @@ -9,8 +9,7 @@ from .utils import cprint, color_enabled, STATUS_COLORS from awxkit.utils import to_str -def monitor_workflow(response, session, print_stdout=True, timeout=None, - interval=.25): +def monitor_workflow(response, session, print_stdout=True, timeout=None, interval=0.25): get = response.url.get payload = { 'order_by': 'finished', @@ -18,9 +17,7 @@ def monitor_workflow(response, session, print_stdout=True, timeout=None, } def fetch(seen): - results = response.connection.get( - '/api/v2/unified_jobs', payload - ).json()['results'] + results = response.connection.get('/api/v2/unified_jobs', payload).json()['results'] # erase lines we've previously printed if print_stdout and sys.stdout.isatty(): @@ -61,7 +58,7 @@ def monitor_workflow(response, session, print_stdout=True, timeout=None, # all at the end fetch(seen) - time.sleep(.25) + time.sleep(0.25) json = get().json if json.finished: fetch(seen) @@ -71,7 +68,7 @@ def monitor_workflow(response, session, print_stdout=True, timeout=None, return get().json.status -def monitor(response, session, print_stdout=True, timeout=None, interval=.25): +def monitor(response, session, print_stdout=True, timeout=None, interval=0.25): get = response.url.get payload = {'order_by': 'start_line', 'no_truncate': True} if response.type == 'job': @@ -108,12 +105,9 @@ def monitor(response, session, print_stdout=True, timeout=None, interval=.25): if next_line: payload['start_line__gte'] = next_line - time.sleep(.25) + time.sleep(0.25) json = get().json - if ( - json.event_processing_finished is True or - json.status in ('error', 'canceled') - ): + if json.event_processing_finished is True or json.status in ('error', 'canceled'): fetch(next_line) break if print_stdout: diff --git a/awxkit/awxkit/cli/utils.py b/awxkit/awxkit/cli/utils.py index d064158859..61c2fe8f5e 100644 --- a/awxkit/awxkit/cli/utils.py +++ b/awxkit/awxkit/cli/utils.py @@ -9,8 +9,7 @@ _color = threading.local() _color.enabled = True -__all__ = ['CustomRegistryMeta', 'HelpfulArgumentParser', 'disable_color', - 'color_enabled', 'colored', 'cprint', 'STATUS_COLORS'] +__all__ = ['CustomRegistryMeta', 'HelpfulArgumentParser', 'disable_color', 'color_enabled', 'colored', 'cprint', 'STATUS_COLORS'] STATUS_COLORS = { @@ -25,17 +24,12 @@ STATUS_COLORS = { class CustomRegistryMeta(type): - @property def registry(cls): - return dict( - (command.name, command) - for command in cls.__subclasses__() - ) + return dict((command.name, command) for command in cls.__subclasses__()) class HelpfulArgumentParser(ArgumentParser): - def error(self, message): # pragma: nocover """Prints a usage message incorporating the message to stderr and exits. @@ -67,10 +61,16 @@ COLORS = dict( list( zip( [ - 'grey', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', + 'grey', + 'red', + 'green', + 'yellow', + 'blue', + 'magenta', + 'cyan', 'white', ], - list(range(30, 38)) + list(range(30, 38)), ) ) ) diff --git a/awxkit/awxkit/exceptions.py b/awxkit/awxkit/exceptions.py index 596720b59d..84c37d615e 100644 --- a/awxkit/awxkit/exceptions.py +++ b/awxkit/awxkit/exceptions.py @@ -1,6 +1,4 @@ - class Common(Exception): - def __init__(self, status_string='', message=''): if isinstance(status_string, Exception): self.status_string = '' diff --git a/awxkit/awxkit/scripts/basic_session.py b/awxkit/awxkit/scripts/basic_session.py index 4ede325214..5c58d70504 100755 --- a/awxkit/awxkit/scripts/basic_session.py +++ b/awxkit/awxkit/scripts/basic_session.py @@ -17,35 +17,26 @@ def parse_args(): parser.add_argument( '--base-url', dest='base_url', - default=os.getenv( - 'AWXKIT_BASE_URL', - 'http://127.0.0.1:8013'), - help='URL for AWX. Defaults to env var AWXKIT_BASE_URL or http://127.0.0.1:8013') + default=os.getenv('AWXKIT_BASE_URL', 'http://127.0.0.1:8013'), + help='URL for AWX. Defaults to env var AWXKIT_BASE_URL or http://127.0.0.1:8013', + ) parser.add_argument( '-c', '--credential-file', dest='credential_file', - default=os.getenv( - 'AWXKIT_CREDENTIAL_FILE', - utils.not_provided), + default=os.getenv('AWXKIT_CREDENTIAL_FILE', utils.not_provided), help='Path for yml credential file. If not provided or set by AWXKIT_CREDENTIAL_FILE, set ' - 'AWXKIT_USER and AWXKIT_USER_PASSWORD env vars for awx user credentials.') + 'AWXKIT_USER and AWXKIT_USER_PASSWORD env vars for awx user credentials.', + ) parser.add_argument( '-p', '--project-file', dest='project_file', - default=os.getenv( - 'AWXKIT_PROJECT_FILE'), - help='Path for yml project config file.' - 'If not provided or set by AWXKIT_PROJECT_FILE, projects will not have default SCM_URL') - parser.add_argument('-f', '--file', dest='akit_script', default=False, - help='akit script file to run in interactive session.') - parser.add_argument( - '-x', - '--non-interactive', - action='store_true', - dest='non_interactive', - help='Do not run in interactive mode.') + default=os.getenv('AWXKIT_PROJECT_FILE'), + help='Path for yml project config file.' 'If not provided or set by AWXKIT_PROJECT_FILE, projects will not have default SCM_URL', + ) + parser.add_argument('-f', '--file', dest='akit_script', default=False, help='akit script file to run in interactive session.') + parser.add_argument('-x', '--non-interactive', action='store_true', dest='non_interactive', help='Do not run in interactive mode.') return parser.parse_known_args()[0] @@ -57,19 +48,14 @@ def main(): config.base_url = akit_args.base_url if akit_args.credential_file != utils.not_provided: - config.credentials = utils.load_credentials( - akit_args.credential_file) + config.credentials = utils.load_credentials(akit_args.credential_file) else: - config.credentials = utils.PseudoNamespace({ - 'default': { - 'username': os.getenv('AWXKIT_USER', 'admin'), - 'password': os.getenv('AWXKIT_USER_PASSWORD', 'password') - } - }) + config.credentials = utils.PseudoNamespace( + {'default': {'username': os.getenv('AWXKIT_USER', 'admin'), 'password': os.getenv('AWXKIT_USER_PASSWORD', 'password')}} + ) if akit_args.project_file != utils.not_provided: - config.project_urls = utils.load_projects( - akit_args.project_file) + config.project_urls = utils.load_projects(akit_args.project_file) global root root = api.Api() @@ -106,6 +92,7 @@ def load_interactive(): try: from IPython import start_ipython + basic_session_path = os.path.abspath(__file__) if basic_session_path[-1] == 'c': # start_ipython doesn't work w/ .pyc basic_session_path = basic_session_path[:-1] @@ -115,6 +102,7 @@ def load_interactive(): return start_ipython(argv=sargs) except ImportError: from code import interact + main() interact('', local=dict(globals(), **locals())) diff --git a/awxkit/awxkit/utils/__init__.py b/awxkit/awxkit/utils/__init__.py index d5f4055717..67442bee25 100644 --- a/awxkit/awxkit/utils/__init__.py +++ b/awxkit/awxkit/utils/__init__.py @@ -34,7 +34,8 @@ cloud_types = ( 'rhv', 'satellite6', 'tower', - 'vmware') + 'vmware', +) credential_type_kinds = ('cloud', 'net') not_provided = 'xx__NOT_PROVIDED__xx' @@ -52,7 +53,6 @@ class NoReloadError(Exception): class PseudoNamespace(dict): - def __init__(self, _d=None, **loaded): if not isinstance(_d, dict): _d = {} @@ -79,9 +79,7 @@ class PseudoNamespace(dict): try: return self.__getitem__(attr) except KeyError: - raise AttributeError( - "{!r} has no attribute {!r}".format( - self.__class__.__name__, attr)) + raise AttributeError("{!r} has no attribute {!r}".format(self.__class__.__name__, attr)) def __setattr__(self, attr, value): self.__setitem__(attr, value) @@ -116,11 +114,7 @@ class PseudoNamespace(dict): # PseudoNamespaces if applicable def update(self, iterable=None, **kw): if iterable: - if (hasattr(iterable, - 'keys') and isinstance(iterable.keys, - (types.FunctionType, - types.BuiltinFunctionType, - types.MethodType))): + if hasattr(iterable, 'keys') and isinstance(iterable.keys, (types.FunctionType, types.BuiltinFunctionType, types.MethodType)): for key in iterable: self[key] = iterable[key] else: @@ -161,11 +155,7 @@ def filter_by_class(*item_class_tuples): examined_item = item[0] else: examined_item = item - if is_class_or_instance( - examined_item, - cls) or is_proper_subclass( - examined_item, - cls): + if is_class_or_instance(examined_item, cls) or is_proper_subclass(examined_item, cls): results.append(item) else: updated = (cls, item[1]) if was_tuple else cls @@ -249,7 +239,7 @@ def gen_utf_char(): is_char = False b = 'b' while not is_char: - b = random.randint(32, 0x10ffff) + b = random.randint(32, 0x10FFFF) is_char = chr(b).isprintable() return chr(b) @@ -266,20 +256,12 @@ def random_ipv4(): def random_ipv6(): """Generates a random ipv6 address;; useful for testing.""" - return ':'.join( - '{0:x}'.format( - random.randint( - 0, - 2 ** 16 - - 1)) for i in range(8)) + return ':'.join('{0:x}'.format(random.randint(0, 2 ** 16 - 1)) for i in range(8)) def random_loopback_ip(): """Generates a random loopback ipv4 address;; useful for testing.""" - return "127.{}.{}.{}".format( - random_int(255), - random_int(255), - random_int(255)) + return "127.{}.{}.{}".format(random_int(255), random_int(255), random_int(255)) def random_utf8(*args, **kwargs): @@ -289,8 +271,7 @@ def random_utf8(*args, **kwargs): """ pattern = re.compile('[^\u0000-\uD7FF\uE000-\uFFFF]', re.UNICODE) length = args[0] if len(args) else kwargs.get('length', 10) - scrubbed = pattern.sub('\uFFFD', ''.join( - [gen_utf_char() for _ in range(length)])) + scrubbed = pattern.sub('\uFFFD', ''.join([gen_utf_char() for _ in range(length)])) return scrubbed @@ -374,8 +355,10 @@ def is_proper_subclass(obj, cls): def are_same_endpoint(first, second): """Equivalence check of two urls, stripped of query parameters""" + def strip(url): return url.replace('www.', '').split('?')[0] + return strip(first) == strip(second) @@ -421,10 +404,7 @@ class UTC(tzinfo): return timedelta(0) -def seconds_since_date_string( - date_str, - fmt='%Y-%m-%dT%H:%M:%S.%fZ', - default_tz=UTC()): +def seconds_since_date_string(date_str, fmt='%Y-%m-%dT%H:%M:%S.%fZ', default_tz=UTC()): """Return the number of seconds since the date and time indicated by a date string and its corresponding format string. diff --git a/awxkit/awxkit/utils/toposort.py b/awxkit/awxkit/utils/toposort.py index 4874cd7ec9..58eec59646 100644 --- a/awxkit/awxkit/utils/toposort.py +++ b/awxkit/awxkit/utils/toposort.py @@ -42,18 +42,19 @@ 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 + 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. -""" + 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: @@ -74,9 +75,6 @@ items in the preceeding sets. if not ordered: break yield ordered - data = { - item: (dep - ordered) - for item, dep in data.items() if item not in 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/words.py b/awxkit/awxkit/words.py index 940d71cd0f..eb3822db02 100644 --- a/awxkit/awxkit/words.py +++ b/awxkit/awxkit/words.py @@ -1,193 +1,1529 @@ # list of random English nouns used for resource name utilities -words = ['People', 'History', 'Way', 'Art', 'World', 'Information', 'Map', 'Two', - 'Family', 'Government', 'Health', 'System', 'Computer', 'Meat', 'Year', 'Thanks', - 'Music', 'Person', 'Reading', 'Method', 'Data', 'Food', 'Understanding', 'Theory', - 'Law', 'Bird', 'Literature', 'Problem', 'Software', 'Control', 'Knowledge', 'Power', - 'Ability', 'Economics', 'Love', 'Internet', 'Television', 'Science', 'Library', 'Nature', - 'Fact', 'Product', 'Idea', 'Temperature', 'Investment', 'Area', 'Society', 'Activity', - 'Story', 'Industry', 'Media', 'Thing', 'Oven', 'Community', 'Definition', 'Safety', - 'Quality', 'Development', 'Language', 'Management', 'Player', 'Variety', 'Video', 'Week', - 'Security', 'Country', 'Exam', 'Movie', 'Organization', 'Equipment', 'Physics', 'Analysis', - 'Policy', 'Series', 'Thought', 'Basis', 'Boyfriend', 'Direction', 'Strategy', 'Technology', - 'Army', 'Camera', 'Freedom', 'Paper', 'Environment', 'Child', 'Instance', 'Month', - 'Truth', 'Marketing', 'University', 'Writing', 'Article', 'Department', 'Difference', 'Goal', - 'News', 'Audience', 'Fishing', 'Growth', 'Income', 'Marriage', 'User', 'Combination', - 'Failure', 'Meaning', 'Medicine', 'Philosophy', 'Teacher', 'Communication', 'Night', 'Chemistry', - 'Disease', 'Disk', 'Energy', 'Nation', 'Road', 'Role', 'Soup', 'Advertising', - 'Location', 'Success', 'Addition', 'Apartment', 'Education', 'Math', 'Moment', 'Painting', - 'Politics', 'Attention', 'Decision', 'Event', 'Property', 'Shopping', 'Student', 'Wood', - 'Competition', 'Distribution', 'Entertainment', 'Office', 'Population', 'President', 'Unit', 'Category', - 'Cigarette', 'Context', 'Introduction', 'Opportunity', 'Performance', 'Driver', 'Flight', 'Length', - 'Magazine', 'Newspaper', 'Relationship', 'Teaching', 'Cell', 'Dealer', 'Debate', 'Finding', - 'Lake', 'Member', 'Message', 'Phone', 'Scene', 'Appearance', 'Association', 'Concept', - 'Customer', 'Death', 'Discussion', 'Housing', 'Inflation', 'Insurance', 'Mood', 'Woman', - 'Advice', 'Blood', 'Effort', 'Expression', 'Importance', 'Opinion', 'Payment', 'Reality', - 'Responsibility', 'Situation', 'Skill', 'Statement', 'Wealth', 'Application', 'City', 'County', - 'Depth', 'Estate', 'Foundation', 'Grandmother', 'Heart', 'Perspective', 'Photo', 'Recipe', - 'Studio', 'Topic', 'Collection', 'Depression', 'Imagination', 'Passion', 'Percentage', 'Resource', - 'Setting', 'Ad', 'Agency', 'College', 'Connection', 'Criticism', 'Debt', 'Description', - 'Memory', 'Patience', 'Secretary', 'Solution', 'Administration', 'Aspect', 'Attitude', 'Director', - 'Personality', 'Psychology', 'Recommendation', 'Response', 'Selection', 'Storage', 'Version', 'Alcohol', - 'Argument', 'Complaint', 'Contract', 'Emphasis', 'Highway', 'Loss', 'Membership', 'Possession', - 'Preparation', 'Steak', 'Union', 'Agreement', 'Cancer', 'Currency', 'Employment', 'Engineering', - 'Entry', 'Interaction', 'Limit', 'Mixture', 'Preference', 'Region', 'Republic', 'Seat', - 'Tradition', 'Virus', 'Actor', 'Classroom', 'Delivery', 'Device', 'Difficulty', 'Drama', - 'Election', 'Engine', 'Football', 'Guidance', 'Hotel', 'Match', 'Owner', 'Priority', - 'Protection', 'Suggestion', 'Tension', 'Variation', 'Anxiety', 'Atmosphere', 'Awareness', 'Bread', - 'Climate', 'Comparison', 'Confusion', 'Construction', 'Elevator', 'Emotion', 'Employee', 'Employer', - 'Guest', 'Height', 'Leadership', 'Mall', 'Manager', 'Operation', 'Recording', 'Respect', - 'Sample', 'Transportation', 'Boring', 'Charity', 'Cousin', 'Disaster', 'Editor', 'Efficiency', - 'Excitement', 'Extent', 'Feedback', 'Guitar', 'Homework', 'Leader', 'Mom', 'Outcome', - 'Permission', 'Presentation', 'Promotion', 'Reflection', 'Refrigerator', 'Resolution', 'Revenue', 'Session', - 'Singer', 'Tennis', 'Basket', 'Bonus', 'Cabinet', 'Childhood', 'Church', 'Clothes', - 'Coffee', 'Dinner', 'Drawing', 'Hair', 'Hearing', 'Initiative', 'Judgment', 'Lab', - 'Measurement', 'Mode', 'Mud', 'Orange', 'Poetry', 'Police', 'Possibility', 'Procedure', - 'Queen', 'Ratio', 'Relation', 'Restaurant', 'Satisfaction', 'Sector', 'Signature', 'Significance', - 'Song', 'Tooth', 'Town', 'Vehicle', 'Volume', 'Wife', 'Accident', 'Airport', - 'Appointment', 'Arrival', 'Assumption', 'Baseball', 'Chapter', 'Committee', 'Conversation', 'Database', - 'Enthusiasm', 'Error', 'Explanation', 'Farmer', 'Gate', 'Girl', 'Hall', 'Historian', - 'Hospital', 'Injury', 'Instruction', 'Maintenance', 'Manufacturer', 'Meal', 'Perception', 'Pie', - 'Poem', 'Presence', 'Proposal', 'Reception', 'Replacement', 'Revolution', 'River', 'Son', - 'Speech', 'Tea', 'Village', 'Warning', 'Winner', 'Worker', 'Writer', 'Assistance', - 'Breath', 'Buyer', 'Chest', 'Chocolate', 'Conclusion', 'Contribution', 'Cookie', 'Courage', - 'Dad', 'Desk', 'Drawer', 'Establishment', 'Examination', 'Garbage', 'Grocery', 'Honey', - 'Impression', 'Improvement', 'Independence', 'Insect', 'Inspection', 'Inspector', 'King', 'Ladder', - 'Menu', 'Penalty', 'Piano', 'Potato', 'Profession', 'Professor', 'Quantity', 'Reaction', - 'Requirement', 'Salad', 'Sister', 'Supermarket', 'Tongue', 'Weakness', 'Wedding', 'Affair', - 'Ambition', 'Analyst', 'Apple', 'Assignment', 'Assistant', 'Bathroom', 'Bedroom', 'Beer', - 'Birthday', 'Celebration', 'Championship', 'Cheek', 'Client', 'Consequence', 'Departure', 'Diamond', - 'Dirt', 'Ear', 'Fortune', 'Friendship', 'Snapewife', 'Funeral', 'Gene', 'Girlfriend', 'Hat', - 'Indication', 'Intention', 'Lady', 'Midnight', 'Negotiation', 'Obligation', 'Passenger', 'Pizza', - 'Platform', 'Poet', 'Pollution', 'Recognition', 'Reputation', 'Shirt', 'Sir', 'Speaker', - 'Stranger', 'Surgery', 'Sympathy', 'Tale', 'Throat', 'Trainer', 'Uncle', 'Youth', - 'Time', 'Work', 'Film', 'Water', 'Money', 'Example', 'While', 'Business', - 'Study', 'Game', 'Life', 'Form', 'Air', 'Day', 'Place', 'Number', - 'Part', 'Field', 'Fish', 'Back', 'Process', 'Heat', 'Hand', 'Experience', - 'Job', 'Book', 'End', 'Point', 'Type', 'Home', 'Economy', 'Value', - 'Body', 'Market', 'Guide', 'Interest', 'State', 'Radio', 'Course', 'Company', - 'Price', 'Size', 'Card', 'List', 'Mind', 'Trade', 'Line', 'Care', - 'Group', 'Risk', 'Word', 'Fat', 'Force', 'Key', 'Light', 'Training', - 'Name', 'School', 'Top', 'Amount', 'Level', 'Order', 'Practice', 'Research', - 'Sense', 'Service', 'Piece', 'Web', 'Boss', 'Sport', 'Fun', 'House', - 'Page', 'Term', 'Test', 'Answer', 'Sound', 'Focus', 'Matter', 'Kind', - 'Soil', 'Board', 'Oil', 'Picture', 'Access', 'Garden', 'Range', 'Rate', - 'Reason', 'Future', 'Site', 'Demand', 'Exercise', 'Image', 'Case', 'Cause', - 'Coast', 'Action', 'Age', 'Bad', 'Boat', 'Record', 'Result', 'Section', - 'Building', 'Mouse', 'Cash', 'Class', 'Nothing', 'Period', 'Plan', 'Store', - 'Tax', 'Side', 'Subject', 'Space', 'Rule', 'Stock', 'Weather', 'Chance', - 'Figure', 'Man', 'Model', 'Source', 'Beginning', 'Earth', 'Program', 'Chicken', - 'Design', 'Feature', 'Head', 'Material', 'Purpose', 'Question', 'Rock', 'Salt', - 'Act', 'Birth', 'Car', 'Dog', 'Object', 'Scale', 'Sun', 'Note', - 'Profit', 'Rent', 'Speed', 'Style', 'War', 'Bank', 'Craft', 'Half', - 'Inside', 'Outside', 'Standard', 'Bus', 'Exchange', 'Eye', 'Fire', 'Position', - 'Pressure', 'Stress', 'Advantage', 'Benefit', 'Box', 'Frame', 'Issue', 'Step', - 'Cycle', 'Face', 'Item', 'Metal', 'Paint', 'Review', 'Room', 'Screen', - 'Structure', 'View', 'Account', 'Ball', 'Discipline', 'Medium', 'Share', 'Balance', - 'Bit', 'Black', 'Bottom', 'Choice', 'Gift', 'Impact', 'Machine', 'Shape', - 'Tool', 'Wind', 'Address', 'Average', 'Career', 'Culture', 'Morning', 'Pot', - 'Sign', 'Table', 'Task', 'Condition', 'Contact', 'Credit', 'Egg', 'Hope', - 'Ice', 'Network', 'North', 'Square', 'Attempt', 'Date', 'Effect', 'Link', - 'Post', 'Star', 'Voice', 'Capital', 'Challenge', 'Friend', 'Self', 'Shot', - 'Brush', 'Couple', 'Exit', 'Front', 'Function', 'Lack', 'Living', 'Plant', - 'Plastic', 'Spot', 'Summer', 'Taste', 'Theme', 'Track', 'Wing', 'Brain', - 'Button', 'Click', 'Desire', 'Foot', 'Gas', 'Influence', 'Notice', 'Rain', - 'Wall', 'Base', 'Damage', 'Distance', 'Feeling', 'Pair', 'Savings', 'Staff', - 'Sugar', 'Target', 'Text', 'Animal', 'Author', 'Budget', 'Discount', 'File', - 'Ground', 'Lesson', 'Minute', 'Officer', 'Phase', 'Reference', 'Register', 'Sky', - 'Stage', 'Stick', 'Title', 'Trouble', 'Bowl', 'Bridge', 'Campaign', 'Character', - 'Club', 'Edge', 'Evidence', 'Fan', 'Letter', 'Lock', 'Maximum', 'Novel', - 'Option', 'Pack', 'Park', 'Plenty', 'Quarter', 'Skin', 'Sort', 'Weight', - 'Baby', 'Background', 'Carry', 'Dish', 'Factor', 'Fruit', 'Glass', 'Joint', - 'Master', 'Muscle', 'Red', 'Strength', 'Traffic', 'Trip', 'Vegetable', 'Appeal', - 'Chart', 'Gear', 'Ideal', 'Kitchen', 'Land', 'Log', 'Mother', 'Net', - 'Party', 'Principle', 'Relative', 'Sale', 'Season', 'Signal', 'Spirit', 'Street', - 'Tree', 'Wave', 'Belt', 'Bench', 'Commission', 'Copy', 'Drop', 'Minimum', - 'Path', 'Progress', 'Project', 'Sea', 'South', 'Status', 'Stuff', 'Ticket', - 'Tour', 'Angle', 'Blue', 'Breakfast', 'Confidence', 'Daughter', 'Degree', 'Doctor', - 'Dot', 'Dream', 'Duty', 'Essay', 'Father', 'Fee', 'Finance', 'Hour', - 'Juice', 'Luck', 'Milk', 'Mouth', 'Peace', 'Pipe', 'Stable', 'Storm', - 'Substance', 'Team', 'Trick', 'Afternoon', 'Bat', 'Beach', 'Blank', 'Catch', - 'Chain', 'Consideration', 'Cream', 'Crew', 'Detail', 'Gold', 'Interview', 'Kid', - 'Mark', 'Mission', 'Pain', 'Pleasure', 'Score', 'Screw', 'Gratitude', 'Shop', - 'Shower', 'Suit', 'Tone', 'Window', 'Agent', 'Band', 'Bath', 'Block', - 'Bone', 'Calendar', 'Candidate', 'Cap', 'Coat', 'Contest', 'Corner', 'Court', - 'Cup', 'District', 'Door', 'East', 'Finger', 'Garage', 'Guarantee', 'Hole', - 'Hook', 'Implement', 'Layer', 'Lecture', 'Lie', 'Manner', 'Meeting', 'Nose', - 'Parking', 'Partner', 'Profile', 'Rice', 'Routine', 'Schedule', 'Swimming', 'Telephone', - 'Tip', 'Winter', 'Airline', 'Bag', 'Battle', 'Bed', 'Bill', 'Bother', - 'Cake', 'Code', 'Curve', 'Designer', 'Dimension', 'Dress', 'Ease', 'Emergency', - 'Evening', 'Extension', 'Farm', 'Fight', 'Gap', 'Grade', 'Holiday', 'Horror', - 'Horse', 'Host', 'Husband', 'Loan', 'Mistake', 'Mountain', 'Nail', 'Noise', - 'Occasion', 'Package', 'Patient', 'Pause', 'Phrase', 'Proof', 'Race', 'Relief', - 'Sand', 'Sentence', 'Shoulder', 'Smoke', 'Stomach', 'String', 'Tourist', 'Towel', - 'Vacation', 'West', 'Wheel', 'Wine', 'Arm', 'Aside', 'Associate', 'Bet', - 'Blow', 'Border', 'Branch', 'Breast', 'Brother', 'Buddy', 'Bunch', 'Chip', - 'Coach', 'Cross', 'Document', 'Draft', 'Dust', 'Expert', 'Floor', 'God', - 'Golf', 'Habit', 'Iron', 'Judge', 'Knife', 'Landscape', 'League', 'Mail', - 'Mess', 'Native', 'Opening', 'Parent', 'Pattern', 'Pin', 'Pool', 'Pound', - 'Request', 'Salary', 'Shame', 'Shelter', 'Shoe', 'Silver', 'Tackle', 'Tank', - 'Trust', 'Assist', 'Bake', 'Bar', 'Bell', 'Bike', 'Blame', 'Boy', - 'Brick', 'Chair', 'Closet', 'Clue', 'Collar', 'Comment', 'Conference', 'Devil', - 'Diet', 'Fear', 'Fuel', 'Glove', 'Jacket', 'Lunch', 'Monitor', 'Mortgage', - 'Nurse', 'Pace', 'Panic', 'Peak', 'Plane', 'Reward', 'Row', 'Sandwich', - 'Shock', 'Spite', 'Spray', 'Surprise', 'Till', 'Transition', 'Weekend', 'Welcome', - 'Yard', 'Alarm', 'Bend', 'Bicycle', 'Bite', 'Blind', 'Bottle', 'Cable', - 'Candle', 'Clerk', 'Cloud', 'Concert', 'Counter', 'Flower', 'Grandfather', 'Harm', - 'Knee', 'Lawyer', 'Leather', 'Load', 'Mirror', 'Neck', 'Pension', 'Plate', - 'Purple', 'Ruin', 'Ship', 'Skirt', 'Slice', 'Snow', 'Specialist', 'Stroke', - 'Switch', 'Trash', 'Tune', 'Zone', 'Anger', 'Award', 'Bid', 'Bitter', - 'Boot', 'Bug', 'Camp', 'Candy', 'Carpet', 'Cat', 'Champion', 'Channel', - 'Clock', 'Comfort', 'Cow', 'Crack', 'Engineer', 'Entrance', 'Fault', 'Grass', - 'Guy', 'Hell', 'Highlight', 'Incident', 'Island', 'Joke', 'Jury', 'Leg', - 'Lip', 'Mate', 'Motor', 'Nerve', 'Passage', 'Pen', 'Pride', 'Priest', - 'Prize', 'Promise', 'Resident', 'Resort', 'Ring', 'Roof', 'Rope', 'Sail', - 'Scheme', 'Script', 'Sock', 'Station', 'Toe', 'Tower', 'Truck', 'Witness', - 'Asparagus', 'You', 'It', 'Can', 'Will', 'If', 'One', 'Many', - 'Most', 'Other', 'Use', 'Make', 'Good', 'Look', 'Help', 'Go', - 'Great', 'Being', 'Few', 'Might', 'Still', 'Public', 'Read', 'Keep', - 'Start', 'Give', 'Human', 'Local', 'General', 'She', 'Specific', 'Long', - 'Play', 'Feel', 'High', 'Tonight', 'Put', 'Common', 'Set', 'Change', - 'Simple', 'Past', 'Big', 'Possible', 'Particular', 'Today', 'Major', 'Personal', - 'Current', 'National', 'Cut', 'Natural', 'Physical', 'Show', 'Try', 'Check', - 'Second', 'Call', 'Move', 'Pay', 'Let', 'Increase', 'Single', 'Individual', - 'Turn', 'Ask', 'Buy', 'Guard', 'Hold', 'Main', 'Offer', 'Potential', - 'Professional', 'International', 'Travel', 'Cook', 'Alternative', 'Following', 'Special', 'Working', - 'Whole', 'Dance', 'Excuse', 'Cold', 'Commercial', 'Low', 'Purchase', 'Deal', - 'Primary', 'Worth', 'Fall', 'Necessary', 'Positive', 'Produce', 'Search', 'Present', - 'Spend', 'Talk', 'Creative', 'Tell', 'Cost', 'Drive', 'Green', 'Support', - 'Glad', 'Remove', 'Return', 'Run', 'Complex', 'Due', 'Effective', 'Middle', - 'Regular', 'Reserve', 'Independent', 'Leave', 'Original', 'Reach', 'Rest', 'Serve', - 'Watch', 'Beautiful', 'Charge', 'Active', 'Break', 'Negative', 'Safe', 'Stay', - 'Visit', 'Visual', 'Affect', 'Cover', 'Report', 'Rise', 'Walk', 'White', - 'Beyond', 'Junior', 'Pick', 'Unique', 'Anything', 'Classic', 'Final', 'Lift', - 'Mix', 'Private', 'Stop', 'Teach', 'Western', 'Concern', 'Familiar', 'Fly', - 'Official', 'Broad', 'Comfortable', 'Gain', 'Maybe', 'Rich', 'Save', 'Stand', - 'Young', 'Heavy', 'Hello', 'Lead', 'Listen', 'Valuable', 'Worry', 'Handle', - 'Leading', 'Meet', 'Release', 'Sell', 'Finish', 'Normal', 'Press', 'Ride', - 'Secret', 'Spread', 'Spring', 'Tough', 'Wait', 'Brown', 'Deep', 'Display', - 'Flow', 'Hit', 'Objective', 'Shoot', 'Touch', 'Cancel', 'Chemical', 'Cry', - 'Dump', 'Extreme', 'Push', 'Conflict', 'Eat', 'Fill', 'Formal', 'Jump', - 'Kick', 'Opposite', 'Pass', 'Pitch', 'Remote', 'Total', 'Treat', 'Vast', - 'Abuse', 'Beat', 'Burn', 'Deposit', 'Print', 'Raise', 'Sleep', 'Somewhere', - 'Advance', 'Anywhere', 'Consist', 'Dark', 'Double', 'Draw', 'Equal', 'Fix', - 'Hire', 'Internal', 'Join', 'Kill', 'Sensitive', 'Tap', 'Win', 'Attack', - 'Claim', 'Constant', 'Drag', 'Drink', 'Guess', 'Minor', 'Pull', 'Raw', - 'Soft', 'Solid', 'Wear', 'Weird', 'Wonder', 'Annual', 'Count', 'Dead', - 'Doubt', 'Feed', 'Forever', 'Impress', 'Nobody', 'Repeat', 'Round', 'Sing', - 'Slide', 'Strip', 'Whereas', 'Wish', 'Combine', 'Command', 'Dig', 'Divide', - 'Equivalent', 'Hang', 'Hunt', 'Initial', 'March', 'Mention', 'Spiritual', 'Survey', - 'Tie', 'Adult', 'Brief', 'Crazy', 'Escape', 'Gather', 'Hate', 'Prior', - 'Repair', 'Rough', 'Sad', 'Scratch', 'Sick', 'Strike', 'Employ', 'External', - 'Hurt', 'Illegal', 'Laugh', 'Lay', 'Mobile', 'Nasty', 'Ordinary', 'Respond', - 'Royal', 'Senior', 'Split', 'Strain', 'Struggle', 'Swim', 'Train', 'Upper', - 'Wash', 'Yellow', 'Convert', 'Crash', 'Dependent', 'Fold', 'Funny', 'Grab', - 'Hide', 'Miss', 'Permit', 'Quote', 'Recover', 'Resolve', 'Roll', 'Sink', - 'Slip', 'Spare', 'Suspect', 'Sweet', 'Swing', 'Twist', 'Upstairs', 'Usual', - 'Abroad', 'Brave', 'Calm', 'Concentrate', 'Estimate', 'Grand', 'Male', 'Mine', - 'Prompt', 'Quiet', 'Refuse', 'Regret', 'Reveal', 'Rush', 'Shake', 'Shift', - 'Shine', 'Steal', 'Suck', 'Surround', 'Anybody', 'Bear', 'Brilliant', 'Dare', - 'Dear', 'Delay', 'Drunk', 'Female', 'Hurry', 'Inevitable', 'Invite', 'Kiss', - 'Neat', 'Pop', 'Punch', 'Quit', 'Reply', 'Representative', 'Resist', 'Rip', - 'Rub', 'Silly', 'Smile', 'Spell', 'Stretch', 'Stupid', 'Tear', 'Temporary', - 'Tomorrow', 'Wake', 'Wrap', 'Yesterday'] +words = [ + 'People', + 'History', + 'Way', + 'Art', + 'World', + 'Information', + 'Map', + 'Two', + 'Family', + 'Government', + 'Health', + 'System', + 'Computer', + 'Meat', + 'Year', + 'Thanks', + 'Music', + 'Person', + 'Reading', + 'Method', + 'Data', + 'Food', + 'Understanding', + 'Theory', + 'Law', + 'Bird', + 'Literature', + 'Problem', + 'Software', + 'Control', + 'Knowledge', + 'Power', + 'Ability', + 'Economics', + 'Love', + 'Internet', + 'Television', + 'Science', + 'Library', + 'Nature', + 'Fact', + 'Product', + 'Idea', + 'Temperature', + 'Investment', + 'Area', + 'Society', + 'Activity', + 'Story', + 'Industry', + 'Media', + 'Thing', + 'Oven', + 'Community', + 'Definition', + 'Safety', + 'Quality', + 'Development', + 'Language', + 'Management', + 'Player', + 'Variety', + 'Video', + 'Week', + 'Security', + 'Country', + 'Exam', + 'Movie', + 'Organization', + 'Equipment', + 'Physics', + 'Analysis', + 'Policy', + 'Series', + 'Thought', + 'Basis', + 'Boyfriend', + 'Direction', + 'Strategy', + 'Technology', + 'Army', + 'Camera', + 'Freedom', + 'Paper', + 'Environment', + 'Child', + 'Instance', + 'Month', + 'Truth', + 'Marketing', + 'University', + 'Writing', + 'Article', + 'Department', + 'Difference', + 'Goal', + 'News', + 'Audience', + 'Fishing', + 'Growth', + 'Income', + 'Marriage', + 'User', + 'Combination', + 'Failure', + 'Meaning', + 'Medicine', + 'Philosophy', + 'Teacher', + 'Communication', + 'Night', + 'Chemistry', + 'Disease', + 'Disk', + 'Energy', + 'Nation', + 'Road', + 'Role', + 'Soup', + 'Advertising', + 'Location', + 'Success', + 'Addition', + 'Apartment', + 'Education', + 'Math', + 'Moment', + 'Painting', + 'Politics', + 'Attention', + 'Decision', + 'Event', + 'Property', + 'Shopping', + 'Student', + 'Wood', + 'Competition', + 'Distribution', + 'Entertainment', + 'Office', + 'Population', + 'President', + 'Unit', + 'Category', + 'Cigarette', + 'Context', + 'Introduction', + 'Opportunity', + 'Performance', + 'Driver', + 'Flight', + 'Length', + 'Magazine', + 'Newspaper', + 'Relationship', + 'Teaching', + 'Cell', + 'Dealer', + 'Debate', + 'Finding', + 'Lake', + 'Member', + 'Message', + 'Phone', + 'Scene', + 'Appearance', + 'Association', + 'Concept', + 'Customer', + 'Death', + 'Discussion', + 'Housing', + 'Inflation', + 'Insurance', + 'Mood', + 'Woman', + 'Advice', + 'Blood', + 'Effort', + 'Expression', + 'Importance', + 'Opinion', + 'Payment', + 'Reality', + 'Responsibility', + 'Situation', + 'Skill', + 'Statement', + 'Wealth', + 'Application', + 'City', + 'County', + 'Depth', + 'Estate', + 'Foundation', + 'Grandmother', + 'Heart', + 'Perspective', + 'Photo', + 'Recipe', + 'Studio', + 'Topic', + 'Collection', + 'Depression', + 'Imagination', + 'Passion', + 'Percentage', + 'Resource', + 'Setting', + 'Ad', + 'Agency', + 'College', + 'Connection', + 'Criticism', + 'Debt', + 'Description', + 'Memory', + 'Patience', + 'Secretary', + 'Solution', + 'Administration', + 'Aspect', + 'Attitude', + 'Director', + 'Personality', + 'Psychology', + 'Recommendation', + 'Response', + 'Selection', + 'Storage', + 'Version', + 'Alcohol', + 'Argument', + 'Complaint', + 'Contract', + 'Emphasis', + 'Highway', + 'Loss', + 'Membership', + 'Possession', + 'Preparation', + 'Steak', + 'Union', + 'Agreement', + 'Cancer', + 'Currency', + 'Employment', + 'Engineering', + 'Entry', + 'Interaction', + 'Limit', + 'Mixture', + 'Preference', + 'Region', + 'Republic', + 'Seat', + 'Tradition', + 'Virus', + 'Actor', + 'Classroom', + 'Delivery', + 'Device', + 'Difficulty', + 'Drama', + 'Election', + 'Engine', + 'Football', + 'Guidance', + 'Hotel', + 'Match', + 'Owner', + 'Priority', + 'Protection', + 'Suggestion', + 'Tension', + 'Variation', + 'Anxiety', + 'Atmosphere', + 'Awareness', + 'Bread', + 'Climate', + 'Comparison', + 'Confusion', + 'Construction', + 'Elevator', + 'Emotion', + 'Employee', + 'Employer', + 'Guest', + 'Height', + 'Leadership', + 'Mall', + 'Manager', + 'Operation', + 'Recording', + 'Respect', + 'Sample', + 'Transportation', + 'Boring', + 'Charity', + 'Cousin', + 'Disaster', + 'Editor', + 'Efficiency', + 'Excitement', + 'Extent', + 'Feedback', + 'Guitar', + 'Homework', + 'Leader', + 'Mom', + 'Outcome', + 'Permission', + 'Presentation', + 'Promotion', + 'Reflection', + 'Refrigerator', + 'Resolution', + 'Revenue', + 'Session', + 'Singer', + 'Tennis', + 'Basket', + 'Bonus', + 'Cabinet', + 'Childhood', + 'Church', + 'Clothes', + 'Coffee', + 'Dinner', + 'Drawing', + 'Hair', + 'Hearing', + 'Initiative', + 'Judgment', + 'Lab', + 'Measurement', + 'Mode', + 'Mud', + 'Orange', + 'Poetry', + 'Police', + 'Possibility', + 'Procedure', + 'Queen', + 'Ratio', + 'Relation', + 'Restaurant', + 'Satisfaction', + 'Sector', + 'Signature', + 'Significance', + 'Song', + 'Tooth', + 'Town', + 'Vehicle', + 'Volume', + 'Wife', + 'Accident', + 'Airport', + 'Appointment', + 'Arrival', + 'Assumption', + 'Baseball', + 'Chapter', + 'Committee', + 'Conversation', + 'Database', + 'Enthusiasm', + 'Error', + 'Explanation', + 'Farmer', + 'Gate', + 'Girl', + 'Hall', + 'Historian', + 'Hospital', + 'Injury', + 'Instruction', + 'Maintenance', + 'Manufacturer', + 'Meal', + 'Perception', + 'Pie', + 'Poem', + 'Presence', + 'Proposal', + 'Reception', + 'Replacement', + 'Revolution', + 'River', + 'Son', + 'Speech', + 'Tea', + 'Village', + 'Warning', + 'Winner', + 'Worker', + 'Writer', + 'Assistance', + 'Breath', + 'Buyer', + 'Chest', + 'Chocolate', + 'Conclusion', + 'Contribution', + 'Cookie', + 'Courage', + 'Dad', + 'Desk', + 'Drawer', + 'Establishment', + 'Examination', + 'Garbage', + 'Grocery', + 'Honey', + 'Impression', + 'Improvement', + 'Independence', + 'Insect', + 'Inspection', + 'Inspector', + 'King', + 'Ladder', + 'Menu', + 'Penalty', + 'Piano', + 'Potato', + 'Profession', + 'Professor', + 'Quantity', + 'Reaction', + 'Requirement', + 'Salad', + 'Sister', + 'Supermarket', + 'Tongue', + 'Weakness', + 'Wedding', + 'Affair', + 'Ambition', + 'Analyst', + 'Apple', + 'Assignment', + 'Assistant', + 'Bathroom', + 'Bedroom', + 'Beer', + 'Birthday', + 'Celebration', + 'Championship', + 'Cheek', + 'Client', + 'Consequence', + 'Departure', + 'Diamond', + 'Dirt', + 'Ear', + 'Fortune', + 'Friendship', + 'Snapewife', + 'Funeral', + 'Gene', + 'Girlfriend', + 'Hat', + 'Indication', + 'Intention', + 'Lady', + 'Midnight', + 'Negotiation', + 'Obligation', + 'Passenger', + 'Pizza', + 'Platform', + 'Poet', + 'Pollution', + 'Recognition', + 'Reputation', + 'Shirt', + 'Sir', + 'Speaker', + 'Stranger', + 'Surgery', + 'Sympathy', + 'Tale', + 'Throat', + 'Trainer', + 'Uncle', + 'Youth', + 'Time', + 'Work', + 'Film', + 'Water', + 'Money', + 'Example', + 'While', + 'Business', + 'Study', + 'Game', + 'Life', + 'Form', + 'Air', + 'Day', + 'Place', + 'Number', + 'Part', + 'Field', + 'Fish', + 'Back', + 'Process', + 'Heat', + 'Hand', + 'Experience', + 'Job', + 'Book', + 'End', + 'Point', + 'Type', + 'Home', + 'Economy', + 'Value', + 'Body', + 'Market', + 'Guide', + 'Interest', + 'State', + 'Radio', + 'Course', + 'Company', + 'Price', + 'Size', + 'Card', + 'List', + 'Mind', + 'Trade', + 'Line', + 'Care', + 'Group', + 'Risk', + 'Word', + 'Fat', + 'Force', + 'Key', + 'Light', + 'Training', + 'Name', + 'School', + 'Top', + 'Amount', + 'Level', + 'Order', + 'Practice', + 'Research', + 'Sense', + 'Service', + 'Piece', + 'Web', + 'Boss', + 'Sport', + 'Fun', + 'House', + 'Page', + 'Term', + 'Test', + 'Answer', + 'Sound', + 'Focus', + 'Matter', + 'Kind', + 'Soil', + 'Board', + 'Oil', + 'Picture', + 'Access', + 'Garden', + 'Range', + 'Rate', + 'Reason', + 'Future', + 'Site', + 'Demand', + 'Exercise', + 'Image', + 'Case', + 'Cause', + 'Coast', + 'Action', + 'Age', + 'Bad', + 'Boat', + 'Record', + 'Result', + 'Section', + 'Building', + 'Mouse', + 'Cash', + 'Class', + 'Nothing', + 'Period', + 'Plan', + 'Store', + 'Tax', + 'Side', + 'Subject', + 'Space', + 'Rule', + 'Stock', + 'Weather', + 'Chance', + 'Figure', + 'Man', + 'Model', + 'Source', + 'Beginning', + 'Earth', + 'Program', + 'Chicken', + 'Design', + 'Feature', + 'Head', + 'Material', + 'Purpose', + 'Question', + 'Rock', + 'Salt', + 'Act', + 'Birth', + 'Car', + 'Dog', + 'Object', + 'Scale', + 'Sun', + 'Note', + 'Profit', + 'Rent', + 'Speed', + 'Style', + 'War', + 'Bank', + 'Craft', + 'Half', + 'Inside', + 'Outside', + 'Standard', + 'Bus', + 'Exchange', + 'Eye', + 'Fire', + 'Position', + 'Pressure', + 'Stress', + 'Advantage', + 'Benefit', + 'Box', + 'Frame', + 'Issue', + 'Step', + 'Cycle', + 'Face', + 'Item', + 'Metal', + 'Paint', + 'Review', + 'Room', + 'Screen', + 'Structure', + 'View', + 'Account', + 'Ball', + 'Discipline', + 'Medium', + 'Share', + 'Balance', + 'Bit', + 'Black', + 'Bottom', + 'Choice', + 'Gift', + 'Impact', + 'Machine', + 'Shape', + 'Tool', + 'Wind', + 'Address', + 'Average', + 'Career', + 'Culture', + 'Morning', + 'Pot', + 'Sign', + 'Table', + 'Task', + 'Condition', + 'Contact', + 'Credit', + 'Egg', + 'Hope', + 'Ice', + 'Network', + 'North', + 'Square', + 'Attempt', + 'Date', + 'Effect', + 'Link', + 'Post', + 'Star', + 'Voice', + 'Capital', + 'Challenge', + 'Friend', + 'Self', + 'Shot', + 'Brush', + 'Couple', + 'Exit', + 'Front', + 'Function', + 'Lack', + 'Living', + 'Plant', + 'Plastic', + 'Spot', + 'Summer', + 'Taste', + 'Theme', + 'Track', + 'Wing', + 'Brain', + 'Button', + 'Click', + 'Desire', + 'Foot', + 'Gas', + 'Influence', + 'Notice', + 'Rain', + 'Wall', + 'Base', + 'Damage', + 'Distance', + 'Feeling', + 'Pair', + 'Savings', + 'Staff', + 'Sugar', + 'Target', + 'Text', + 'Animal', + 'Author', + 'Budget', + 'Discount', + 'File', + 'Ground', + 'Lesson', + 'Minute', + 'Officer', + 'Phase', + 'Reference', + 'Register', + 'Sky', + 'Stage', + 'Stick', + 'Title', + 'Trouble', + 'Bowl', + 'Bridge', + 'Campaign', + 'Character', + 'Club', + 'Edge', + 'Evidence', + 'Fan', + 'Letter', + 'Lock', + 'Maximum', + 'Novel', + 'Option', + 'Pack', + 'Park', + 'Plenty', + 'Quarter', + 'Skin', + 'Sort', + 'Weight', + 'Baby', + 'Background', + 'Carry', + 'Dish', + 'Factor', + 'Fruit', + 'Glass', + 'Joint', + 'Master', + 'Muscle', + 'Red', + 'Strength', + 'Traffic', + 'Trip', + 'Vegetable', + 'Appeal', + 'Chart', + 'Gear', + 'Ideal', + 'Kitchen', + 'Land', + 'Log', + 'Mother', + 'Net', + 'Party', + 'Principle', + 'Relative', + 'Sale', + 'Season', + 'Signal', + 'Spirit', + 'Street', + 'Tree', + 'Wave', + 'Belt', + 'Bench', + 'Commission', + 'Copy', + 'Drop', + 'Minimum', + 'Path', + 'Progress', + 'Project', + 'Sea', + 'South', + 'Status', + 'Stuff', + 'Ticket', + 'Tour', + 'Angle', + 'Blue', + 'Breakfast', + 'Confidence', + 'Daughter', + 'Degree', + 'Doctor', + 'Dot', + 'Dream', + 'Duty', + 'Essay', + 'Father', + 'Fee', + 'Finance', + 'Hour', + 'Juice', + 'Luck', + 'Milk', + 'Mouth', + 'Peace', + 'Pipe', + 'Stable', + 'Storm', + 'Substance', + 'Team', + 'Trick', + 'Afternoon', + 'Bat', + 'Beach', + 'Blank', + 'Catch', + 'Chain', + 'Consideration', + 'Cream', + 'Crew', + 'Detail', + 'Gold', + 'Interview', + 'Kid', + 'Mark', + 'Mission', + 'Pain', + 'Pleasure', + 'Score', + 'Screw', + 'Gratitude', + 'Shop', + 'Shower', + 'Suit', + 'Tone', + 'Window', + 'Agent', + 'Band', + 'Bath', + 'Block', + 'Bone', + 'Calendar', + 'Candidate', + 'Cap', + 'Coat', + 'Contest', + 'Corner', + 'Court', + 'Cup', + 'District', + 'Door', + 'East', + 'Finger', + 'Garage', + 'Guarantee', + 'Hole', + 'Hook', + 'Implement', + 'Layer', + 'Lecture', + 'Lie', + 'Manner', + 'Meeting', + 'Nose', + 'Parking', + 'Partner', + 'Profile', + 'Rice', + 'Routine', + 'Schedule', + 'Swimming', + 'Telephone', + 'Tip', + 'Winter', + 'Airline', + 'Bag', + 'Battle', + 'Bed', + 'Bill', + 'Bother', + 'Cake', + 'Code', + 'Curve', + 'Designer', + 'Dimension', + 'Dress', + 'Ease', + 'Emergency', + 'Evening', + 'Extension', + 'Farm', + 'Fight', + 'Gap', + 'Grade', + 'Holiday', + 'Horror', + 'Horse', + 'Host', + 'Husband', + 'Loan', + 'Mistake', + 'Mountain', + 'Nail', + 'Noise', + 'Occasion', + 'Package', + 'Patient', + 'Pause', + 'Phrase', + 'Proof', + 'Race', + 'Relief', + 'Sand', + 'Sentence', + 'Shoulder', + 'Smoke', + 'Stomach', + 'String', + 'Tourist', + 'Towel', + 'Vacation', + 'West', + 'Wheel', + 'Wine', + 'Arm', + 'Aside', + 'Associate', + 'Bet', + 'Blow', + 'Border', + 'Branch', + 'Breast', + 'Brother', + 'Buddy', + 'Bunch', + 'Chip', + 'Coach', + 'Cross', + 'Document', + 'Draft', + 'Dust', + 'Expert', + 'Floor', + 'God', + 'Golf', + 'Habit', + 'Iron', + 'Judge', + 'Knife', + 'Landscape', + 'League', + 'Mail', + 'Mess', + 'Native', + 'Opening', + 'Parent', + 'Pattern', + 'Pin', + 'Pool', + 'Pound', + 'Request', + 'Salary', + 'Shame', + 'Shelter', + 'Shoe', + 'Silver', + 'Tackle', + 'Tank', + 'Trust', + 'Assist', + 'Bake', + 'Bar', + 'Bell', + 'Bike', + 'Blame', + 'Boy', + 'Brick', + 'Chair', + 'Closet', + 'Clue', + 'Collar', + 'Comment', + 'Conference', + 'Devil', + 'Diet', + 'Fear', + 'Fuel', + 'Glove', + 'Jacket', + 'Lunch', + 'Monitor', + 'Mortgage', + 'Nurse', + 'Pace', + 'Panic', + 'Peak', + 'Plane', + 'Reward', + 'Row', + 'Sandwich', + 'Shock', + 'Spite', + 'Spray', + 'Surprise', + 'Till', + 'Transition', + 'Weekend', + 'Welcome', + 'Yard', + 'Alarm', + 'Bend', + 'Bicycle', + 'Bite', + 'Blind', + 'Bottle', + 'Cable', + 'Candle', + 'Clerk', + 'Cloud', + 'Concert', + 'Counter', + 'Flower', + 'Grandfather', + 'Harm', + 'Knee', + 'Lawyer', + 'Leather', + 'Load', + 'Mirror', + 'Neck', + 'Pension', + 'Plate', + 'Purple', + 'Ruin', + 'Ship', + 'Skirt', + 'Slice', + 'Snow', + 'Specialist', + 'Stroke', + 'Switch', + 'Trash', + 'Tune', + 'Zone', + 'Anger', + 'Award', + 'Bid', + 'Bitter', + 'Boot', + 'Bug', + 'Camp', + 'Candy', + 'Carpet', + 'Cat', + 'Champion', + 'Channel', + 'Clock', + 'Comfort', + 'Cow', + 'Crack', + 'Engineer', + 'Entrance', + 'Fault', + 'Grass', + 'Guy', + 'Hell', + 'Highlight', + 'Incident', + 'Island', + 'Joke', + 'Jury', + 'Leg', + 'Lip', + 'Mate', + 'Motor', + 'Nerve', + 'Passage', + 'Pen', + 'Pride', + 'Priest', + 'Prize', + 'Promise', + 'Resident', + 'Resort', + 'Ring', + 'Roof', + 'Rope', + 'Sail', + 'Scheme', + 'Script', + 'Sock', + 'Station', + 'Toe', + 'Tower', + 'Truck', + 'Witness', + 'Asparagus', + 'You', + 'It', + 'Can', + 'Will', + 'If', + 'One', + 'Many', + 'Most', + 'Other', + 'Use', + 'Make', + 'Good', + 'Look', + 'Help', + 'Go', + 'Great', + 'Being', + 'Few', + 'Might', + 'Still', + 'Public', + 'Read', + 'Keep', + 'Start', + 'Give', + 'Human', + 'Local', + 'General', + 'She', + 'Specific', + 'Long', + 'Play', + 'Feel', + 'High', + 'Tonight', + 'Put', + 'Common', + 'Set', + 'Change', + 'Simple', + 'Past', + 'Big', + 'Possible', + 'Particular', + 'Today', + 'Major', + 'Personal', + 'Current', + 'National', + 'Cut', + 'Natural', + 'Physical', + 'Show', + 'Try', + 'Check', + 'Second', + 'Call', + 'Move', + 'Pay', + 'Let', + 'Increase', + 'Single', + 'Individual', + 'Turn', + 'Ask', + 'Buy', + 'Guard', + 'Hold', + 'Main', + 'Offer', + 'Potential', + 'Professional', + 'International', + 'Travel', + 'Cook', + 'Alternative', + 'Following', + 'Special', + 'Working', + 'Whole', + 'Dance', + 'Excuse', + 'Cold', + 'Commercial', + 'Low', + 'Purchase', + 'Deal', + 'Primary', + 'Worth', + 'Fall', + 'Necessary', + 'Positive', + 'Produce', + 'Search', + 'Present', + 'Spend', + 'Talk', + 'Creative', + 'Tell', + 'Cost', + 'Drive', + 'Green', + 'Support', + 'Glad', + 'Remove', + 'Return', + 'Run', + 'Complex', + 'Due', + 'Effective', + 'Middle', + 'Regular', + 'Reserve', + 'Independent', + 'Leave', + 'Original', + 'Reach', + 'Rest', + 'Serve', + 'Watch', + 'Beautiful', + 'Charge', + 'Active', + 'Break', + 'Negative', + 'Safe', + 'Stay', + 'Visit', + 'Visual', + 'Affect', + 'Cover', + 'Report', + 'Rise', + 'Walk', + 'White', + 'Beyond', + 'Junior', + 'Pick', + 'Unique', + 'Anything', + 'Classic', + 'Final', + 'Lift', + 'Mix', + 'Private', + 'Stop', + 'Teach', + 'Western', + 'Concern', + 'Familiar', + 'Fly', + 'Official', + 'Broad', + 'Comfortable', + 'Gain', + 'Maybe', + 'Rich', + 'Save', + 'Stand', + 'Young', + 'Heavy', + 'Hello', + 'Lead', + 'Listen', + 'Valuable', + 'Worry', + 'Handle', + 'Leading', + 'Meet', + 'Release', + 'Sell', + 'Finish', + 'Normal', + 'Press', + 'Ride', + 'Secret', + 'Spread', + 'Spring', + 'Tough', + 'Wait', + 'Brown', + 'Deep', + 'Display', + 'Flow', + 'Hit', + 'Objective', + 'Shoot', + 'Touch', + 'Cancel', + 'Chemical', + 'Cry', + 'Dump', + 'Extreme', + 'Push', + 'Conflict', + 'Eat', + 'Fill', + 'Formal', + 'Jump', + 'Kick', + 'Opposite', + 'Pass', + 'Pitch', + 'Remote', + 'Total', + 'Treat', + 'Vast', + 'Abuse', + 'Beat', + 'Burn', + 'Deposit', + 'Print', + 'Raise', + 'Sleep', + 'Somewhere', + 'Advance', + 'Anywhere', + 'Consist', + 'Dark', + 'Double', + 'Draw', + 'Equal', + 'Fix', + 'Hire', + 'Internal', + 'Join', + 'Kill', + 'Sensitive', + 'Tap', + 'Win', + 'Attack', + 'Claim', + 'Constant', + 'Drag', + 'Drink', + 'Guess', + 'Minor', + 'Pull', + 'Raw', + 'Soft', + 'Solid', + 'Wear', + 'Weird', + 'Wonder', + 'Annual', + 'Count', + 'Dead', + 'Doubt', + 'Feed', + 'Forever', + 'Impress', + 'Nobody', + 'Repeat', + 'Round', + 'Sing', + 'Slide', + 'Strip', + 'Whereas', + 'Wish', + 'Combine', + 'Command', + 'Dig', + 'Divide', + 'Equivalent', + 'Hang', + 'Hunt', + 'Initial', + 'March', + 'Mention', + 'Spiritual', + 'Survey', + 'Tie', + 'Adult', + 'Brief', + 'Crazy', + 'Escape', + 'Gather', + 'Hate', + 'Prior', + 'Repair', + 'Rough', + 'Sad', + 'Scratch', + 'Sick', + 'Strike', + 'Employ', + 'External', + 'Hurt', + 'Illegal', + 'Laugh', + 'Lay', + 'Mobile', + 'Nasty', + 'Ordinary', + 'Respond', + 'Royal', + 'Senior', + 'Split', + 'Strain', + 'Struggle', + 'Swim', + 'Train', + 'Upper', + 'Wash', + 'Yellow', + 'Convert', + 'Crash', + 'Dependent', + 'Fold', + 'Funny', + 'Grab', + 'Hide', + 'Miss', + 'Permit', + 'Quote', + 'Recover', + 'Resolve', + 'Roll', + 'Sink', + 'Slip', + 'Spare', + 'Suspect', + 'Sweet', + 'Swing', + 'Twist', + 'Upstairs', + 'Usual', + 'Abroad', + 'Brave', + 'Calm', + 'Concentrate', + 'Estimate', + 'Grand', + 'Male', + 'Mine', + 'Prompt', + 'Quiet', + 'Refuse', + 'Regret', + 'Reveal', + 'Rush', + 'Shake', + 'Shift', + 'Shine', + 'Steal', + 'Suck', + 'Surround', + 'Anybody', + 'Bear', + 'Brilliant', + 'Dare', + 'Dear', + 'Delay', + 'Drunk', + 'Female', + 'Hurry', + 'Inevitable', + 'Invite', + 'Kiss', + 'Neat', + 'Pop', + 'Punch', + 'Quit', + 'Reply', + 'Representative', + 'Resist', + 'Rip', + 'Rub', + 'Silly', + 'Smile', + 'Spell', + 'Stretch', + 'Stupid', + 'Tear', + 'Temporary', + 'Tomorrow', + 'Wake', + 'Wrap', + 'Yesterday', +] diff --git a/awxkit/awxkit/ws.py b/awxkit/awxkit/ws.py index ee39a5e990..41380d406e 100644 --- a/awxkit/awxkit/ws.py +++ b/awxkit/awxkit/ws.py @@ -84,12 +84,9 @@ class WSClient(object): auth_cookie = '' pref = 'wss://' if self._use_ssl else 'ws://' url = '{0}{1.hostname}:{1.port}/websocket/'.format(pref, self) - self.ws = websocket.WebSocketApp(url, - on_open=self._on_open, - on_message=self._on_message, - on_error=self._on_error, - on_close=self._on_close, - cookie=auth_cookie) + self.ws = websocket.WebSocketApp( + url, on_open=self._on_open, on_message=self._on_message, on_error=self._on_error, on_close=self._on_close, cookie=auth_cookie + ) self._message_cache = [] self._should_subscribe_to_pending_job = False self._pending_unsubscribe = threading.Event() @@ -199,12 +196,8 @@ class WSClient(object): message = json.loads(message) log.debug('received message: {}'.format(message)) - if all([message.get('group_name') == 'jobs', - message.get('status') == 'pending', - message.get('unified_job_id'), - self._should_subscribe_to_pending_job]): - if bool(message.get('project_id')) == ( - self._should_subscribe_to_pending_job['events'] == 'project_update_events'): + if all([message.get('group_name') == 'jobs', message.get('status') == 'pending', message.get('unified_job_id'), self._should_subscribe_to_pending_job]): + if bool(message.get('project_id')) == (self._should_subscribe_to_pending_job['events'] == 'project_update_events'): self._update_subscription(message['unified_job_id']) ret = self._recv_queue.put(message) diff --git a/awxkit/awxkit/yaml_file.py b/awxkit/awxkit/yaml_file.py index 750ee3fbb1..49924cef93 100644 --- a/awxkit/awxkit/yaml_file.py +++ b/awxkit/awxkit/yaml_file.py @@ -12,7 +12,6 @@ file_path_cache = {} class Loader(yaml.SafeLoader): - def __init__(self, stream): self._root = os.path.split(stream.name)[0] super(Loader, self).__init__(stream) @@ -82,6 +81,7 @@ def load_file(filename): random_thing: "{random_string:24}" """ from py.path import local + if filename is None: this_file = os.path.abspath(__file__) path = local(this_file).new(basename='../data.yaml') diff --git a/awxkit/setup.py b/awxkit/setup.py index b446ba0a90..23f4e161cb 100644 --- a/awxkit/setup.py +++ b/awxkit/setup.py @@ -68,11 +68,7 @@ setup( 'requests', ], python_requires=">=3.6", - extras_require={ - 'formatting': ['jq'], - 'websockets': ['websocket-client==0.57.0'], - 'crypto': ['cryptography'] - }, + extras_require={'formatting': ['jq'], 'websockets': ['websocket-client==0.57.0'], 'crypto': ['cryptography']}, license='Apache 2.0', classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -87,10 +83,5 @@ setup( 'Topic :: System :: Software Distribution', 'Topic :: System :: Systems Administration', ], - entry_points={ - 'console_scripts': [ - 'akit=awxkit.scripts.basic_session:load_interactive', - 'awx=awxkit.cli:run' - ] - } + entry_points={'console_scripts': ['akit=awxkit.scripts.basic_session:load_interactive', 'awx=awxkit.cli:run']}, ) diff --git a/awxkit/test/cli/test_client.py b/awxkit/test/cli/test_client.py index e792b6c267..9a63e37c55 100644 --- a/awxkit/test/cli/test_client.py +++ b/awxkit/test/cli/test_client.py @@ -7,7 +7,6 @@ from awxkit.cli import run, CLI class MockedCLI(CLI): - def fetch_version_root(self): pass @@ -17,9 +16,7 @@ class MockedCLI(CLI): @property def json(self): - return { - 'users': None - } + return {'users': None} @pytest.mark.parametrize('help_param', ['-h', '--help']) @@ -29,10 +26,7 @@ def test_help(capfd, help_param): out, err = capfd.readouterr() assert "usage:" in out - for snippet in ( - '--conf.host https://example.awx.org]', - '-v, --verbose' - ): + for snippet in ('--conf.host https://example.awx.org]', '-v, --verbose'): assert snippet in out @@ -59,8 +53,5 @@ def test_list_resources(capfd, resource): _, out = capfd.readouterr() assert "usage:" in out - for snippet in ( - '--conf.host https://example.awx.org]', - '-v, --verbose' - ): + for snippet in ('--conf.host https://example.awx.org]', '-v, --verbose'): assert snippet in out diff --git a/awxkit/test/cli/test_config.py b/awxkit/test/cli/test_config.py index 3154fdb081..61b6b4c54d 100644 --- a/awxkit/test/cli/test_config.py +++ b/awxkit/test/cli/test_config.py @@ -4,16 +4,15 @@ from requests.exceptions import ConnectionError from awxkit.cli import CLI from awxkit import config + def test_host_from_environment(): cli = CLI() - cli.parse_args( - ['awx'], - env={'TOWER_HOST': 'https://xyz.local'} - ) + cli.parse_args(['awx'], env={'TOWER_HOST': 'https://xyz.local'}) with pytest.raises(ConnectionError): cli.connect() assert config.base_url == 'https://xyz.local' + def test_host_from_argv(): cli = CLI() cli.parse_args(['awx', '--conf.host', 'https://xyz.local']) @@ -21,43 +20,30 @@ def test_host_from_argv(): cli.connect() assert config.base_url == 'https://xyz.local' + def test_username_and_password_from_environment(): cli = CLI() - cli.parse_args( - ['awx'], - env={ - 'TOWER_USERNAME': 'mary', - 'TOWER_PASSWORD': 'secret' - } - ) + cli.parse_args(['awx'], env={'TOWER_USERNAME': 'mary', 'TOWER_PASSWORD': 'secret'}) with pytest.raises(ConnectionError): cli.connect() assert config.credentials.default.username == 'mary' assert config.credentials.default.password == 'secret' + def test_username_and_password_argv(): cli = CLI() - cli.parse_args([ - 'awx', '--conf.username', 'mary', '--conf.password', 'secret' - ]) + cli.parse_args(['awx', '--conf.username', 'mary', '--conf.password', 'secret']) with pytest.raises(ConnectionError): cli.connect() assert config.credentials.default.username == 'mary' assert config.credentials.default.password == 'secret' + def test_config_precedence(): cli = CLI() - cli.parse_args( - [ - 'awx', '--conf.username', 'mary', '--conf.password', 'secret' - ], - env={ - 'TOWER_USERNAME': 'IGNORE', - 'TOWER_PASSWORD': 'IGNORE' - } - ) + cli.parse_args(['awx', '--conf.username', 'mary', '--conf.password', 'secret'], env={'TOWER_USERNAME': 'IGNORE', 'TOWER_PASSWORD': 'IGNORE'}) with pytest.raises(ConnectionError): cli.connect() diff --git a/awxkit/test/cli/test_format.py b/awxkit/test/cli/test_format.py index 7327f91518..adbe0ef463 100644 --- a/awxkit/test/cli/test_format.py +++ b/awxkit/test/cli/test_format.py @@ -11,19 +11,17 @@ from awxkit.cli.resource import Import def test_json_empty_list(): - page = Page.from_json({ - 'results': [] - }) + page = Page.from_json({'results': []}) formatted = format_response(page) assert json.loads(formatted) == {'results': []} + def test_yaml_empty_list(): - page = Page.from_json({ - 'results': [] - }) + page = Page.from_json({'results': []}) formatted = format_response(page, fmt='yaml') assert yaml.safe_load(formatted) == {'results': []} + def test_json_list(): users = { 'results': [ @@ -36,6 +34,7 @@ def test_json_list(): formatted = format_response(page) assert json.loads(formatted) == users + def test_yaml_list(): users = { 'results': [ diff --git a/awxkit/test/cli/test_options.py b/awxkit/test/cli/test_options.py index 83bb4ac36a..fc2e53f957 100644 --- a/awxkit/test/cli/test_options.py +++ b/awxkit/test/cli/test_options.py @@ -11,13 +11,11 @@ from awxkit.cli.options import ResourceOptionsParser class ResourceOptionsParser(ResourceOptionsParser): - def get_allowed_options(self): self.allowed_options = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] class OptionsPage(Page): - def options(self): return self @@ -33,30 +31,31 @@ class OptionsPage(Page): class TestOptions(unittest.TestCase): - def setUp(self): _parser = argparse.ArgumentParser() self.parser = _parser.add_subparsers(help='action') def test_list(self): - page = OptionsPage.from_json({ - 'actions': { - 'GET': {}, - 'POST': {}, + page = OptionsPage.from_json( + { + 'actions': { + 'GET': {}, + 'POST': {}, + } } - }) + ) ResourceOptionsParser(None, page, 'users', self.parser) assert 'list' in self.parser.choices def test_list_filtering(self): - page = OptionsPage.from_json({ - 'actions': { - 'GET': {}, - 'POST': { - 'first_name': {'type': 'string'} - }, + page = OptionsPage.from_json( + { + 'actions': { + 'GET': {}, + 'POST': {'first_name': {'type': 'string'}}, + } } - }) + ) options = ResourceOptionsParser(None, page, 'users', self.parser) options.build_query_arguments('list', 'POST') assert 'list' in self.parser.choices @@ -66,14 +65,14 @@ class TestOptions(unittest.TestCase): assert '--first_name TEXT' in out.getvalue() def test_list_not_filterable(self): - page = OptionsPage.from_json({ - 'actions': { - 'GET': {}, - 'POST': { - 'middle_name': {'type': 'string', 'filterable': False} - }, + page = OptionsPage.from_json( + { + 'actions': { + 'GET': {}, + 'POST': {'middle_name': {'type': 'string', 'filterable': False}}, + } } - }) + ) options = ResourceOptionsParser(None, page, 'users', self.parser) options.build_query_arguments('list', 'POST') assert 'list' in self.parser.choices @@ -83,16 +82,18 @@ class TestOptions(unittest.TestCase): assert '--middle_name' not in out.getvalue() def test_creation_optional_argument(self): - page = OptionsPage.from_json({ - 'actions': { - 'POST': { - 'first_name': { - 'type': 'string', - 'help_text': 'Please specify your first name', - } - }, + page = OptionsPage.from_json( + { + 'actions': { + 'POST': { + 'first_name': { + 'type': 'string', + 'help_text': 'Please specify your first name', + } + }, + } } - }) + ) options = ResourceOptionsParser(None, page, 'users', self.parser) options.build_query_arguments('create', 'POST') assert 'create' in self.parser.choices @@ -102,17 +103,13 @@ class TestOptions(unittest.TestCase): assert '--first_name TEXT Please specify your first name' in out.getvalue() def test_creation_required_argument(self): - page = OptionsPage.from_json({ - 'actions': { - 'POST': { - 'username': { - 'type': 'string', - 'help_text': 'Please specify a username', - 'required': True - } - }, + page = OptionsPage.from_json( + { + 'actions': { + 'POST': {'username': {'type': 'string', 'help_text': 'Please specify a username', 'required': True}}, + } } - }) + ) options = ResourceOptionsParser(None, page, 'users', self.parser) options.build_query_arguments('create', 'POST') assert 'create' in self.parser.choices @@ -122,13 +119,13 @@ class TestOptions(unittest.TestCase): assert '--username TEXT Please specify a username' def test_integer_argument(self): - page = OptionsPage.from_json({ - 'actions': { - 'POST': { - 'max_hosts': {'type': 'integer'} - }, + page = OptionsPage.from_json( + { + 'actions': { + 'POST': {'max_hosts': {'type': 'integer'}}, + } } - }) + ) options = ResourceOptionsParser(None, page, 'organizations', self.parser) options.build_query_arguments('create', 'POST') assert 'create' in self.parser.choices @@ -138,13 +135,13 @@ class TestOptions(unittest.TestCase): assert '--max_hosts INTEGER' in out.getvalue() def test_boolean_argument(self): - page = OptionsPage.from_json({ - 'actions': { - 'POST': { - 'diff_mode': {'type': 'boolean'} - }, + page = OptionsPage.from_json( + { + 'actions': { + 'POST': {'diff_mode': {'type': 'boolean'}}, + } } - }) + ) options = ResourceOptionsParser(None, page, 'users', self.parser) options.build_query_arguments('create', 'POST') assert 'create' in self.parser.choices @@ -154,23 +151,25 @@ class TestOptions(unittest.TestCase): assert '--diff_mode BOOLEAN' in out.getvalue() def test_choices(self): - page = OptionsPage.from_json({ - 'actions': { - 'POST': { - 'verbosity': { - 'type': 'integer', - 'choices': [ - (0, '0 (Normal)'), - (1, '1 (Verbose)'), - (2, '2 (More Verbose)'), - (3, '3 (Debug)'), - (4, '4 (Connection Debug)'), - (5, '5 (WinRM Debug)'), - ] - } - }, + page = OptionsPage.from_json( + { + 'actions': { + 'POST': { + 'verbosity': { + 'type': 'integer', + 'choices': [ + (0, '0 (Normal)'), + (1, '1 (Verbose)'), + (2, '2 (More Verbose)'), + (3, '3 (Debug)'), + (4, '4 (Connection Debug)'), + (5, '5 (WinRM Debug)'), + ], + } + }, + } } - }) + ) options = ResourceOptionsParser(None, page, 'users', self.parser) options.build_query_arguments('create', 'POST') assert 'create' in self.parser.choices @@ -181,9 +180,7 @@ class TestOptions(unittest.TestCase): def test_actions_with_primary_key(self): for method in ('get', 'modify', 'delete'): - page = OptionsPage.from_json({ - 'actions': {'GET': {}, 'POST': {}} - }) + page = OptionsPage.from_json({'actions': {'GET': {}, 'POST': {}}}) ResourceOptionsParser(None, page, 'jobs', self.parser) assert method in self.parser.choices @@ -193,19 +190,20 @@ class TestOptions(unittest.TestCase): class TestSettingsOptions(unittest.TestCase): - def setUp(self): _parser = argparse.ArgumentParser() self.parser = _parser.add_subparsers(help='action') def test_list(self): - page = OptionsPage.from_json({ - 'actions': { - 'GET': {}, - 'POST': {}, - 'PUT': {}, + page = OptionsPage.from_json( + { + 'actions': { + 'GET': {}, + 'POST': {}, + 'PUT': {}, + } } - }) + ) page.endpoint = '/settings/all/' ResourceOptionsParser(None, page, 'settings', self.parser) assert 'list' in self.parser.choices diff --git a/awxkit/test/test_credentials.py b/awxkit/test/test_credentials.py index 714550119e..6adc5e9332 100644 --- a/awxkit/test/test_credentials.py +++ b/awxkit/test/test_credentials.py @@ -14,32 +14,39 @@ def set_config_cred_to_desired(config, location): config_ref = config_ref[_location] setattr(config_ref, split[-1], 'desired') -class MockCredentialType(object): +class MockCredentialType(object): def __init__(self, name, kind, managed_by_tower=True): self.name = name self.kind = kind self.managed_by_tower = managed_by_tower -@pytest.mark.parametrize('field, kind, config_cred, desired_field, desired_value', - [('field', 'ssh', PseudoNamespace(field=123), 'field', 123), - ('subscription', 'azure', PseudoNamespace(subscription_id=123), 'subscription', 123), - ('project_id', 'gce', PseudoNamespace(project=123), 'project', 123), - ('authorize_password', 'net', PseudoNamespace(authorize=123), 'authorize_password', 123)]) + +@pytest.mark.parametrize( + 'field, kind, config_cred, desired_field, desired_value', + [ + ('field', 'ssh', PseudoNamespace(field=123), 'field', 123), + ('subscription', 'azure', PseudoNamespace(subscription_id=123), 'subscription', 123), + ('project_id', 'gce', PseudoNamespace(project=123), 'project', 123), + ('authorize_password', 'net', PseudoNamespace(authorize=123), 'authorize_password', 123), + ], +) def test_get_payload_field_and_value_from_config_cred(field, kind, config_cred, desired_field, desired_value): - ret_field, ret_val = credentials.get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, {}, - config_cred) + ret_field, ret_val = credentials.get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, {}, config_cred) assert ret_field == desired_field assert ret_val == desired_value -@pytest.mark.parametrize('field, kind, kwargs, desired_field, desired_value', - [('field', 'ssh', dict(field=123), 'field', 123), - ('subscription', 'azure', dict(subscription=123), 'subscription', 123), - ('project_id', 'gce', dict(project_id=123), 'project', 123), - ('authorize_password', 'net', dict(authorize_password=123), 'authorize_password', 123)]) +@pytest.mark.parametrize( + 'field, kind, kwargs, desired_field, desired_value', + [ + ('field', 'ssh', dict(field=123), 'field', 123), + ('subscription', 'azure', dict(subscription=123), 'subscription', 123), + ('project_id', 'gce', dict(project_id=123), 'project', 123), + ('authorize_password', 'net', dict(authorize_password=123), 'authorize_password', 123), + ], +) def test_get_payload_field_and_value_from_kwarg(field, kind, kwargs, desired_field, desired_value): - ret_field, ret_val = credentials.get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, kwargs, - PseudoNamespace()) + ret_field, ret_val = credentials.get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, kwargs, PseudoNamespace()) assert ret_field == desired_field assert ret_val == desired_value diff --git a/awxkit/test/test_dependency_resolver.py b/awxkit/test/test_dependency_resolver.py index 4a7e85e3c4..b713a73adc 100644 --- a/awxkit/test/test_dependency_resolver.py +++ b/awxkit/test/test_dependency_resolver.py @@ -21,7 +21,6 @@ class MockHasCreate(has_create.HasCreate): class A(MockHasCreate): - def create(self, **kw): return self @@ -87,13 +86,12 @@ class H(MockHasCreate): optional_dependencies = [E, A] - def create(self, a=None, e=None, **kw): + def create(self, a=None, e=None, **kw): self.create_and_update_dependencies(*filter_by_class((a, A), (e, E))) return self class MultipleWordClassName(MockHasCreate): - def create(self, **kw): return self @@ -102,7 +100,7 @@ class AnotherMultipleWordClassName(MockHasCreate): optional_dependencies = [MultipleWordClassName] - def create(self, multiple_word_class_name=None, **kw): + def create(self, multiple_word_class_name=None, **kw): self.create_and_update_dependencies(*filter_by_class((multiple_word_class_name, MultipleWordClassName))) return self @@ -183,19 +181,17 @@ def test_optional_dependency_graph_with_additional(): def test_creation_order(): """confirms that `has_create.creation_order()` returns a valid creation order in the desired list of sets format""" - dependency_graph = dict(eight=set(['seven', 'six']), - seven=set(['five']), - six=set(), - five=set(['two', 'one']), - four=set(['one']), - three=set(['two']), - two=set(['one']), - one=set()) - desired = [set(['one', 'six']), - set(['two', 'four']), - set(['three', 'five']), - set(['seven']), - set(['eight'])] + dependency_graph = dict( + eight=set(['seven', 'six']), + seven=set(['five']), + six=set(), + five=set(['two', 'one']), + four=set(['one']), + three=set(['two']), + two=set(['one']), + one=set(), + ) + desired = [set(['one', 'six']), set(['two', 'four']), set(['three', 'five']), set(['seven']), set(['eight'])] assert has_create.creation_order(dependency_graph) == desired @@ -203,14 +199,16 @@ def test_creation_order_with_loop(): """confirms that `has_create.creation_order()` raises toposort.CircularDependencyError when evaluating a cyclic dependency graph """ - dependency_graph = dict(eight=set(['seven', 'six']), - seven=set(['five']), - six=set(), - five=set(['two', 'one']), - four=set(['one']), - three=set(['two']), - two=set(['one']), - one=set(['eight'])) + dependency_graph = dict( + eight=set(['seven', 'six']), + seven=set(['five']), + six=set(), + five=set(['two', 'one']), + four=set(['one']), + three=set(['two']), + two=set(['one']), + one=set(['eight']), + ) with pytest.raises(CircularDependencyError): assert has_create.creation_order(dependency_graph) @@ -239,9 +237,11 @@ class Five(MockHasCreate): class IsntAHasCreate(object): pass + class Six(MockHasCreate, IsntAHasCreate): dependencies = [Two] + class Seven(MockHasCreate): dependencies = [IsntAHasCreate] @@ -265,8 +265,7 @@ def test_separate_async_optionals_three_exist(): the class that has shared item as a dependency occurs first in a separate creation group """ order = has_create.creation_order(has_create.optional_dependency_graph(Five, Four, Three)) - assert has_create.separate_async_optionals(order) == [set([One]), set([Two]), set([Three]), - set([Five]), set([Four])] + assert has_create.separate_async_optionals(order) == [set([One]), set([Two]), set([Three]), set([Five]), set([Four])] def test_separate_async_optionals_not_has_create(): @@ -345,8 +344,7 @@ def test_dependency_resolution_complete(): for item in (h, a, e, d, c, b): if item._dependency_store: - assert all(item._dependency_store.values() - ), "{0} missing dependency: {0._dependency_store}".format(item) + assert all(item._dependency_store.values()), "{0} missing dependency: {0._dependency_store}".format(item) assert a == b._dependency_store[A], "Duplicate dependency detected" assert a == c._dependency_store[A], "Duplicate dependency detected" @@ -468,7 +466,6 @@ def test_teardown_ds_cleared(): class OneWithArgs(MockHasCreate): - def create(self, **kw): self.kw = kw return self @@ -492,18 +489,17 @@ class ThreeWithArgs(MockHasCreate): optional_dependencies = [TwoWithArgs] def create(self, one_with_args=OneWithArgs, two_with_args=None, **kw): - self.create_and_update_dependencies(*filter_by_class((one_with_args, OneWithArgs), - (two_with_args, TwoWithArgs))) + self.create_and_update_dependencies(*filter_by_class((one_with_args, OneWithArgs), (two_with_args, TwoWithArgs))) self.kw = kw return self + class FourWithArgs(MockHasCreate): dependencies = [TwoWithArgs, ThreeWithArgs] def create(self, two_with_args=TwoWithArgs, three_with_args=ThreeWithArgs, **kw): - self.create_and_update_dependencies(*filter_by_class((two_with_args, TwoWithArgs), - (three_with_args, ThreeWithArgs))) + self.create_and_update_dependencies(*filter_by_class((two_with_args, TwoWithArgs), (three_with_args, ThreeWithArgs))) self.kw = kw return self @@ -536,10 +532,9 @@ def test_no_tuple_for_class_arg_causes_shared_dependencies_nested_staggering(): def test_tuple_for_class_arg_causes_unshared_dependencies_when_downstream(): """Confirms that provided arg-tuple for dependency type is applied instead of chained dependency""" - three_wa = ThreeWithArgs().create(two_with_args=(TwoWithArgs, dict(one_with_args=False, - make_one_with_args=True, - two_with_args_kw_arg=234)), - three_with_args_kw_arg=345) + three_wa = ThreeWithArgs().create( + two_with_args=(TwoWithArgs, dict(one_with_args=False, make_one_with_args=True, two_with_args_kw_arg=234)), three_with_args_kw_arg=345 + ) assert isinstance(three_wa.ds.one_with_args, OneWithArgs) assert isinstance(three_wa.ds.two_with_args, TwoWithArgs) assert isinstance(three_wa.ds.two_with_args.ds.one_with_args, OneWithArgs) @@ -552,13 +547,12 @@ def test_tuple_for_class_arg_causes_unshared_dependencies_when_downstream(): def test_tuples_for_class_arg_cause_unshared_dependencies_when_downstream(): """Confirms that provided arg-tuple for dependency type is applied instead of chained dependency""" - four_wa = FourWithArgs().create(two_with_args=(TwoWithArgs, dict(one_with_args=False, - make_one_with_args=True, - two_with_args_kw_arg=456)), - # No shared dependencies with four_wa.ds.two_with_args - three_with_args=(ThreeWithArgs, dict(one_with_args=(OneWithArgs, {}), - two_with_args=False)), - four_with_args_kw=567) + four_wa = FourWithArgs().create( + two_with_args=(TwoWithArgs, dict(one_with_args=False, make_one_with_args=True, two_with_args_kw_arg=456)), + # No shared dependencies with four_wa.ds.two_with_args + three_with_args=(ThreeWithArgs, dict(one_with_args=(OneWithArgs, {}), two_with_args=False)), + four_with_args_kw=567, + ) assert isinstance(four_wa.ds.two_with_args, TwoWithArgs) assert isinstance(four_wa.ds.three_with_args, ThreeWithArgs) assert isinstance(four_wa.ds.two_with_args.ds.one_with_args, OneWithArgs) @@ -575,25 +569,21 @@ class NotHasCreate(object): class MixinUserA(MockHasCreate, NotHasCreate): - def create(self, **kw): return self class MixinUserB(MockHasCreate, NotHasCreate): - def create(self, **kw): return self class MixinUserC(MixinUserB): - def create(self, **kw): return self class MixinUserD(MixinUserC): - def create(self, **kw): return self @@ -646,17 +636,12 @@ class DynamicallyDeclaresNotHasCreateDependency(MockHasCreate): dependencies = [NotHasCreate] def create(self, not_has_create=MixinUserA): - dynamic_dependency = dict(mixinusera=MixinUserA, - mixinuserb=MixinUserB, - mixinuserc=MixinUserC) + dynamic_dependency = dict(mixinusera=MixinUserA, mixinuserb=MixinUserB, mixinuserc=MixinUserC) self.create_and_update_dependencies(dynamic_dependency[not_has_create]) return self -@pytest.mark.parametrize('dependency,dependency_class', - [('mixinusera', MixinUserA), - ('mixinuserb', MixinUserB), - ('mixinuserc', MixinUserC)]) +@pytest.mark.parametrize('dependency,dependency_class', [('mixinusera', MixinUserA), ('mixinuserb', MixinUserB), ('mixinuserc', MixinUserC)]) def test_subclass_or_parent_dynamic_not_has_create_dependency_declaration(dependency, dependency_class): """Confirms that dependencies that dynamically declare dependencies subclassed from not HasCreate are properly linked @@ -670,17 +655,12 @@ class DynamicallyDeclaresHasCreateDependency(MockHasCreate): dependencies = [MixinUserB] def create(self, mixin_user_b=MixinUserB): - dynamic_dependency = dict(mixinuserb=MixinUserB, - mixinuserc=MixinUserC, - mixinuserd=MixinUserD) + dynamic_dependency = dict(mixinuserb=MixinUserB, mixinuserc=MixinUserC, mixinuserd=MixinUserD) self.create_and_update_dependencies(dynamic_dependency[mixin_user_b]) return self -@pytest.mark.parametrize('dependency,dependency_class', - [('mixinuserb', MixinUserB), - ('mixinuserc', MixinUserC), - ('mixinuserd', MixinUserD)]) +@pytest.mark.parametrize('dependency,dependency_class', [('mixinuserb', MixinUserB), ('mixinuserc', MixinUserC), ('mixinuserd', MixinUserD)]) def test_subclass_or_parent_dynamic_has_create_dependency_declaration(dependency, dependency_class): """Confirms that dependencies that dynamically declare dependencies subclassed from not HasCreate are properly linked diff --git a/awxkit/test/test_registry.py b/awxkit/test/test_registry.py index bfc3ca09ff..b0f0f2527f 100644 --- a/awxkit/test/test_registry.py +++ b/awxkit/test/test_registry.py @@ -169,8 +169,7 @@ def test_wildcard_and_specific_method_registration_acts_as_default(reg): def test_multiple_method_registrations_disallowed_for_single_path_single_registration(reg, method): with pytest.raises(TypeError) as e: reg.register((('some_path', method), ('some_path', method)), One) - assert str(e.value) == ('"{0.pattern}" already has registered method "{1}"' - .format(reg.url_pattern('some_path'), method)) + assert str(e.value) == ('"{0.pattern}" already has registered method "{1}"'.format(reg.url_pattern('some_path'), method)) @pytest.mark.parametrize('method', ('method', '.*')) @@ -178,8 +177,7 @@ def test_multiple_method_registrations_disallowed_for_single_path_multiple_regis reg.register('some_path', method, One) with pytest.raises(TypeError) as e: reg.register('some_path', method, One) - assert str(e.value) == ('"{0.pattern}" already has registered method "{1}"' - .format(reg.url_pattern('some_path'), method)) + assert str(e.value) == ('"{0.pattern}" already has registered method "{1}"'.format(reg.url_pattern('some_path'), method)) def test_paths_can_be_patterns(reg): @@ -188,10 +186,9 @@ def test_paths_can_be_patterns(reg): def test_mixed_form_single_registration(reg): - reg.register([('some_path_one', 'method_one'), - 'some_path_two', - ('some_path_three', ('method_two', 'method_three')), - 'some_path_four', 'some_path_five'], One) + reg.register( + [('some_path_one', 'method_one'), 'some_path_two', ('some_path_three', ('method_two', 'method_three')), 'some_path_four', 'some_path_five'], One + ) assert reg.get('some_path_one', 'method_one') is One assert reg.get('some_path_one') is None assert reg.get('some_path_one', 'nonexistent') is None @@ -209,10 +206,9 @@ def test_mixed_form_single_registration(reg): def test_mixed_form_single_registration_with_methodless_default(reg): reg.setdefault(One) - reg.register([('some_path_one', 'method_one'), - 'some_path_two', - ('some_path_three', ('method_two', 'method_three')), - 'some_path_four', 'some_path_five'], Two) + reg.register( + [('some_path_one', 'method_one'), 'some_path_two', ('some_path_three', ('method_two', 'method_three')), 'some_path_four', 'some_path_five'], Two + ) assert reg.get('some_path_one', 'method_one') is Two assert reg.get('some_path_one') is One assert reg.get('some_path_one', 'nonexistent') is One @@ -230,10 +226,9 @@ def test_mixed_form_single_registration_with_methodless_default(reg): def test_mixed_form_single_registration_with_method_default(reg): reg.setdefault('existent', One) - reg.register([('some_path_one', 'method_one'), - 'some_path_two', - ('some_path_three', ('method_two', 'method_three')), - 'some_path_four', 'some_path_five'], Two) + reg.register( + [('some_path_one', 'method_one'), 'some_path_two', ('some_path_three', ('method_two', 'method_three')), 'some_path_four', 'some_path_five'], Two + ) assert reg.get('some_path_one', 'method_one') is Two assert reg.get('some_path_one') is None assert reg.get('some_path_one', 'existent') is One diff --git a/awxkit/test/test_utils.py b/awxkit/test/test_utils.py index 5f497d2e93..1a0a5412f2 100644 --- a/awxkit/test/test_utils.py +++ b/awxkit/test/test_utils.py @@ -9,60 +9,68 @@ from awxkit import utils from awxkit import exceptions as exc -@pytest.mark.parametrize('inp, out', - [[True, True], - [False, False], - [1, True], - [0, False], - [1.0, True], - [0.0, False], - ['TrUe', True], - ['FalSe', False], - ['yEs', True], - ['No', False], - ['oN', True], - ['oFf', False], - ['asdf', True], - ['0', False], - ['', False], - [{1: 1}, True], - [{}, False], - [(0,), True], - [(), False], - [[1], True], - [[], False]]) +@pytest.mark.parametrize( + 'inp, out', + [ + [True, True], + [False, False], + [1, True], + [0, False], + [1.0, True], + [0.0, False], + ['TrUe', True], + ['FalSe', False], + ['yEs', True], + ['No', False], + ['oN', True], + ['oFf', False], + ['asdf', True], + ['0', False], + ['', False], + [{1: 1}, True], + [{}, False], + [(0,), True], + [(), False], + [[1], True], + [[], False], + ], +) def test_to_bool(inp, out): assert utils.to_bool(inp) == out -@pytest.mark.parametrize('inp, out', - [["{}", {}], - ["{'null': null}", {"null": None}], - ["{'bool': true}", {"bool": True}], - ["{'bool': false}", {"bool": False}], - ["{'int': 0}", {"int": 0}], - ["{'float': 1.0}", {"float": 1.0}], - ["{'str': 'abc'}", {"str": "abc"}], - ["{'obj': {}}", {"obj": {}}], - ["{'list': []}", {"list": []}], - ["---", None], - ["---\n'null': null", {'null': None}], - ["---\n'bool': true", {'bool': True}], - ["---\n'bool': false", {'bool': False}], - ["---\n'int': 0", {'int': 0}], - ["---\n'float': 1.0", {'float': 1.0}], - ["---\n'string': 'abc'", {'string': 'abc'}], - ["---\n'obj': {}", {'obj': {}}], - ["---\n'list': []", {'list': []}], - ["", None], - ["'null': null", {'null': None}], - ["'bool': true", {'bool': True}], - ["'bool': false", {'bool': False}], - ["'int': 0", {'int': 0}], - ["'float': 1.0", {'float': 1.0}], - ["'string': 'abc'", {'string': 'abc'}], - ["'obj': {}", {'obj': {}}], - ["'list': []", {'list': []}]]) +@pytest.mark.parametrize( + 'inp, out', + [ + ["{}", {}], + ["{'null': null}", {"null": None}], + ["{'bool': true}", {"bool": True}], + ["{'bool': false}", {"bool": False}], + ["{'int': 0}", {"int": 0}], + ["{'float': 1.0}", {"float": 1.0}], + ["{'str': 'abc'}", {"str": "abc"}], + ["{'obj': {}}", {"obj": {}}], + ["{'list': []}", {"list": []}], + ["---", None], + ["---\n'null': null", {'null': None}], + ["---\n'bool': true", {'bool': True}], + ["---\n'bool': false", {'bool': False}], + ["---\n'int': 0", {'int': 0}], + ["---\n'float': 1.0", {'float': 1.0}], + ["---\n'string': 'abc'", {'string': 'abc'}], + ["---\n'obj': {}", {'obj': {}}], + ["---\n'list': []", {'list': []}], + ["", None], + ["'null': null", {'null': None}], + ["'bool': true", {'bool': True}], + ["'bool': false", {'bool': False}], + ["'int': 0", {'int': 0}], + ["'float': 1.0", {'float': 1.0}], + ["'string': 'abc'", {'string': 'abc'}], + ["'obj': {}", {'obj': {}}], + ["'list': []", {'list': []}], + ], +) def test_load_valid_json_or_yaml(inp, out): assert utils.load_json_or_yaml(inp) == out @@ -74,19 +82,13 @@ def test_load_invalid_json_or_yaml(inp): @pytest.mark.parametrize('non_ascii', [True, False]) -@pytest.mark.skipif( - sys.version_info < (3, 6), - reason='this is only intended to be used in py3, not the CLI' -) +@pytest.mark.skipif(sys.version_info < (3, 6), reason='this is only intended to be used in py3, not the CLI') def test_random_titles_are_unicode(non_ascii): assert isinstance(utils.random_title(non_ascii=non_ascii), str) @pytest.mark.parametrize('non_ascii', [True, False]) -@pytest.mark.skipif( - sys.version_info < (3, 6), - reason='this is only intended to be used in py3, not the CLI' -) +@pytest.mark.skipif(sys.version_info < (3, 6), reason='this is only intended to be used in py3, not the CLI') def test_random_titles_generates_correct_characters(non_ascii): title = utils.random_title(non_ascii=non_ascii) if non_ascii: @@ -98,34 +100,39 @@ def test_random_titles_generates_correct_characters(non_ascii): title.encode('utf-8') -@pytest.mark.parametrize('inp, out', - [['ClassNameShouldChange', 'class_name_should_change'], - ['classnameshouldntchange', 'classnameshouldntchange'], - ['Classspacingshouldntchange', 'classspacingshouldntchange'], - ['Class1Name2Should3Change', 'class_1_name_2_should_3_change'], - ['Class123name234should345change456', 'class_123_name_234_should_345_change_456']]) +@pytest.mark.parametrize( + 'inp, out', + [ + ['ClassNameShouldChange', 'class_name_should_change'], + ['classnameshouldntchange', 'classnameshouldntchange'], + ['Classspacingshouldntchange', 'classspacingshouldntchange'], + ['Class1Name2Should3Change', 'class_1_name_2_should_3_change'], + ['Class123name234should345change456', 'class_123_name_234_should_345_change_456'], + ], +) def test_class_name_to_kw_arg(inp, out): assert utils.class_name_to_kw_arg(inp) == out -@pytest.mark.parametrize('first, second, expected', - [['/api/v2/resources/', '/api/v2/resources/', True], - ['/api/v2/resources/', '/api/v2/resources/?test=ignored', True], - ['/api/v2/resources/?one=ignored', '/api/v2/resources/?two=ignored', True], - ['http://one.com', 'http://one.com', True], - ['http://one.com', 'http://www.one.com', True], - ['http://one.com', 'http://one.com?test=ignored', True], - ['http://one.com', 'http://www.one.com?test=ignored', True], - ['http://one.com', 'https://one.com', False], - ['http://one.com', 'https://one.com?test=ignored', False]]) +@pytest.mark.parametrize( + 'first, second, expected', + [ + ['/api/v2/resources/', '/api/v2/resources/', True], + ['/api/v2/resources/', '/api/v2/resources/?test=ignored', True], + ['/api/v2/resources/?one=ignored', '/api/v2/resources/?two=ignored', True], + ['http://one.com', 'http://one.com', True], + ['http://one.com', 'http://www.one.com', True], + ['http://one.com', 'http://one.com?test=ignored', True], + ['http://one.com', 'http://www.one.com?test=ignored', True], + ['http://one.com', 'https://one.com', False], + ['http://one.com', 'https://one.com?test=ignored', False], + ], +) def test_are_same_endpoint(first, second, expected): assert utils.are_same_endpoint(first, second) == expected -@pytest.mark.parametrize('endpoint, expected', - [['/api/v2/resources/', 'v2'], - ['/api/v2000/resources/', 'v2000'], - ['/api/', 'common']]) +@pytest.mark.parametrize('endpoint, expected', [['/api/v2/resources/', 'v2'], ['/api/v2000/resources/', 'v2000'], ['/api/', 'common']]) def test_version_from_endpoint(endpoint, expected): assert utils.version_from_endpoint(endpoint) == expected @@ -133,42 +140,51 @@ def test_version_from_endpoint(endpoint, expected): class OneClass: pass + class TwoClass: pass + class ThreeClass: pass + class FourClass(ThreeClass): pass + def test_filter_by_class_with_subclass_class(): filtered = utils.filter_by_class((OneClass, OneClass), (FourClass, ThreeClass)) assert filtered == [OneClass, FourClass] + def test_filter_by_class_with_subclass_instance(): one = OneClass() four = FourClass() filtered = utils.filter_by_class((one, OneClass), (four, ThreeClass)) assert filtered == [one, four] + def test_filter_by_class_no_arg_tuples(): three = ThreeClass() filtered = utils.filter_by_class((True, OneClass), (False, TwoClass), (three, ThreeClass)) assert filtered == [OneClass, None, three] + def test_filter_by_class_with_arg_tuples_containing_class(): one = OneClass() three = (ThreeClass, dict(one=1, two=2)) filtered = utils.filter_by_class((one, OneClass), (False, TwoClass), (three, ThreeClass)) assert filtered == [one, None, three] + def test_filter_by_class_with_arg_tuples_containing_subclass(): one = OneClass() three = (FourClass, dict(one=1, two=2)) filtered = utils.filter_by_class((one, OneClass), (False, TwoClass), (three, ThreeClass)) assert filtered == [one, None, three] + @pytest.mark.parametrize('truthy', (True, 123, 'yes')) def test_filter_by_class_with_arg_tuples_containing_truthy(truthy): one = OneClass() @@ -177,18 +193,20 @@ def test_filter_by_class_with_arg_tuples_containing_truthy(truthy): assert filtered == [one, None, (ThreeClass, dict(one=1, two=2))] -@pytest.mark.parametrize('date_string,now,expected', [ - ('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 2, 750000), 1.25), - ('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 1, 500000), 0.00), - ('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 0, 500000), -1.00), -]) +@pytest.mark.parametrize( + 'date_string,now,expected', + [ + ('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 2, 750000), 1.25), + ('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 1, 500000), 0.00), + ('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 0, 500000), -1.00), + ], +) def test_seconds_since_date_string(date_string, now, expected): with mock.patch('awxkit.utils.utcnow', return_value=now): assert utils.seconds_since_date_string(date_string) == expected class RecordingCallback(object): - def __init__(self, value=True): self.call_count = 0 self.value = value @@ -225,7 +243,6 @@ def test_suppress(): class TestPollUntil(object): - @pytest.mark.parametrize('timeout', [0, 0.0, -0.5, -1, -9999999]) def test_callback_called_once_for_non_positive_timeout(self, timeout): with mock.patch('awxkit.utils.logged_sleep') as sleep: @@ -246,7 +263,6 @@ class TestPollUntil(object): class TestPseudoNamespace(object): - def test_set_item_check_item(self): pn = utils.PseudoNamespace() pn['key'] = 'value' @@ -319,10 +335,7 @@ class TestPseudoNamespace(object): assert pn == dict(one=[dict(two=2), dict(three=3)]) def test_instantiation_via_nested_dict_with_lists(self): - pn = utils.PseudoNamespace(dict(one=[dict(two=2), - dict(three=dict(four=4, - five=[dict(six=6), - dict(seven=7)]))])) + pn = utils.PseudoNamespace(dict(one=[dict(two=2), dict(three=dict(four=4, five=[dict(six=6), dict(seven=7)]))])) assert pn.one[1].three.five[1].seven == 7 def test_instantiation_via_nested_dict_with_tuple(self): @@ -332,10 +345,7 @@ class TestPseudoNamespace(object): assert pn == dict(one=(dict(two=2), dict(three=3))) def test_instantiation_via_nested_dict_with_tuples(self): - pn = utils.PseudoNamespace(dict(one=(dict(two=2), - dict(three=dict(four=4, - five=(dict(six=6), - dict(seven=7))))))) + pn = utils.PseudoNamespace(dict(one=(dict(two=2), dict(three=dict(four=4, five=(dict(six=6), dict(seven=7))))))) assert pn.one[1].three.five[1].seven == 7 def test_update_with_nested_dict(self): @@ -348,23 +358,16 @@ class TestPseudoNamespace(object): def test_update_with_nested_dict_with_lists(self): pn = utils.PseudoNamespace() - pn.update(dict(one=[dict(two=2), - dict(three=dict(four=4, - five=[dict(six=6), - dict(seven=7)]))])) + pn.update(dict(one=[dict(two=2), dict(three=dict(four=4, five=[dict(six=6), dict(seven=7)]))])) assert pn.one[1].three.five[1].seven == 7 def test_update_with_nested_dict_with_tuples(self): pn = utils.PseudoNamespace() - pn.update(dict(one=(dict(two=2), - dict(three=dict(four=4, - five=(dict(six=6), - dict(seven=7))))))) + pn.update(dict(one=(dict(two=2), dict(three=dict(four=4, five=(dict(six=6), dict(seven=7))))))) assert pn.one[1].three.five[1].seven == 7 class TestUpdatePayload(object): - def test_empty_payload(self): fields = ('one', 'two', 'three', 'four') kwargs = dict(two=2, four=4) diff --git a/awxkit/test/test_ws.py b/awxkit/test/test_ws.py index afc6b42fc5..8e89cdc6f2 100644 --- a/awxkit/test/test_ws.py +++ b/awxkit/test/test_ws.py @@ -8,6 +8,7 @@ from awxkit.ws import WSClient ParseResult = namedtuple("ParseResult", ["port", "hostname", "secure"]) + def test_explicit_hostname(): client = WSClient("token", "some-hostname", 556, False) assert client.port == 556 @@ -16,12 +17,15 @@ def test_explicit_hostname(): assert client.token == "token" -@pytest.mark.parametrize('url, result', - [['https://somename:123', ParseResult(123, "somename", True)], - ['http://othername:456', ParseResult(456, "othername", False)], - ['http://othername', ParseResult(80, "othername", False)], - ['https://othername', ParseResult(443, "othername", True)], -]) +@pytest.mark.parametrize( + 'url, result', + [ + ['https://somename:123', ParseResult(123, "somename", True)], + ['http://othername:456', ParseResult(456, "othername", False)], + ['http://othername', ParseResult(80, "othername", False)], + ['https://othername', ParseResult(443, "othername", True)], + ], +) def test_urlparsing(url, result): with patch("awxkit.ws.config") as mock_config: mock_config.base_url = url |