From 2fcdf6f3d3c35cf3cbede8a274dc13971b15f55a Mon Sep 17 00:00:00 2001 From: Alex Ramos Date: Tue, 24 Apr 2018 07:07:45 -0700 Subject: [PATCH 1/3] Add cache for ads Caches the absolute paths of the ads.yml files associated with a project. The cache is never used when: - The list command is invoked - A command is invoked without a subcommand AND no services are defined in the adsroot.yml. The cache will miss when: - A removed service is still in the cache. - The project was switched. - The services defined in adsroot.yml are not all present in the cache. Testing Notes: - Ran all existing tests and passed. - Added unit tests and modified existing integration test to handle caching. - Performed local testing removing and adding services. Verified worked as ads without caching. --- ads/__init__.py | 2 +- ads/ads.py | 191 +++++++++++++++++++++++++++++------ tests/bash/util/Framework.sh | 12 ++- tests/unit/test_cache.py | 101 ++++++++++++++++++ 4 files changed, 271 insertions(+), 35 deletions(-) create mode 100644 tests/unit/test_cache.py diff --git a/ads/__init__.py b/ads/__init__.py index e3bc2b0..2a0058b 100644 --- a/ads/__init__.py +++ b/ads/__init__.py @@ -1 +1 @@ -from ads import Ads, Project, Service, ServiceSet, Profile, BadSelectorException +from ads import Ads, Project, Service, ServiceSet, Profile, BadSelectorException, Cache, _load_spec_file diff --git a/ads/ads.py b/ads/ads.py index c8fded3..e0fa98c 100644 --- a/ads/ads.py +++ b/ads/ads.py @@ -5,7 +5,7 @@ class colors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKGREEN = '\033[92m' - WARNING = '\033[93m' + WARNING = '\033[33m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' @@ -22,6 +22,11 @@ def info(msg): sys.stdout.flush() +def warning(msg): + print(colors.WARNING + "!! " + msg + colors.ENDC) + sys.stdout.flush() + + def error(msg): sys.stderr.write(colors.FAIL + "!!! " + msg + "\n" + colors.ENDC) sys.stderr.flush() @@ -158,7 +163,11 @@ def _expect(expected_type, actual, origin_file): def _load_spec_file(path): - result = yaml.safe_load(file(path, "r").read()) or {} + try: + result = yaml.safe_load(file(path, "r").read()) or {} + except IOError: + result = {} + _expect(dict, result, path) return result @@ -173,7 +182,7 @@ def _abs_to_cwd_rel(abspath): class Service: @classmethod - def load(cls, svc_yml, name): + def load(cls, name, svc_yml): spec = _load_spec_file(svc_yml) return Service(name, os.path.dirname(svc_yml), @@ -312,6 +321,80 @@ def __init__(self, name, selector_set): self.selectors = selector_set +############################################## +# Cache +############################################## + +ADS_ROOT = "adsroot" + +class Cache: + @classmethod + def get_cache_path(cls, dir_): + adscache = ".ads_cache.yml" + cache_home = os.getenv("ADS_CACHE_HOME") + if cache_home: + return "%s/%s" % (cache_home, adscache) + + return "%s/%s" % (dir_, adscache) + + @classmethod + def load_from_cache(cls, cachefile, project_file, profile_dir): + map_ = {} + if os.path.isfile(cachefile): + cache_spec = _load_spec_file(cachefile) + + if cache_spec.get(ADS_ROOT) == project_file: + del cache_spec[ADS_ROOT] + map_ = cache_spec + + return map_ + + def __init__(self, project_file, profile_dir): + self.cachefile = Cache.get_cache_path(profile_dir) + self.cache_map = Cache.load_from_cache(self.cachefile, project_file, profile_dir) + + def get(self, key): + if isinstance(key, Service): + return self.cache_map.get(str(key)) + + return self.cache_map.get(key) + + def valid_groups(self, service_sets): + selectors = set() + for service_set in service_sets: + for selector in service_set.selectors: + selectors.add(selector) + + groups = set(group.name for group in service_sets) + services = selectors.difference(groups) + + return self.yamls_exist(list(services)) + + def yamls_exist(self, services): + not_cached = [] + for service in services: + yaml = self.get(service) + + if not yaml or not os.path.isfile(yaml): + not_cached.append(service) + + if not_cached: + warning("The following service(s) are not cached: " + + "%s. This could be due to addition/removal of a service. " % (not_cached) + + "If removed, deletion from adsroot.yml is required.") + return False + + return True + + def write_to_cache(self, project_file, svc_to_yml): + self.cache_map = dict(svc_to_yml) + svc_to_yml[ADS_ROOT] = project_file + with open(self.cachefile, 'w') as outfile: + yaml.safe_dump(svc_to_yml, outfile, default_flow_style=False) + + del svc_to_yml[ADS_ROOT] + + ############################################## # Project ############################################## @@ -345,49 +428,61 @@ def in_nested_project_dir(path_str): return False # BEWARE: O(n*m) algorithm! - return [ + service_yamls = [ os.path.join(project_root, p) for p in find_output if os.path.basename(p) == "ads.yml" and not in_nested_project_dir(p) ] + return _services_to_adsfiles(service_yamls) + -def _adsfiles_to_service_names(adsfiles): +def _services_to_adsfiles(adsfiles): svc_name_to_file = {} - file_to_svc_name = {} for f in adsfiles: basename = os.path.basename(os.path.dirname(f)) if basename in svc_name_to_file: - raise Exception("not yet implemented") - svc_name_to_file[basename] = f - file_to_svc_name[f] = basename - return file_to_svc_name + warning("Duplicate service: %s! Using %s." % + (basename, svc_name_to_file[basename])) + else: + svc_name_to_file[basename] = f + + return svc_name_to_file class Project: @classmethod - def load_from_dir(cls, root_dir): + def load_from_dir(cls, root_dir, profile_dir, check_cache): project_yml = _find_project_yml(os.path.abspath(root_dir)) if not project_yml: return None - service_ymls = _find_service_ymls(os.path.dirname(project_yml)) - return Project.load_from_files(project_yml, service_ymls) + return Project.load_from_files(project_yml, profile_dir, check_cache) @classmethod - def load_from_files(cls, project_yml, svc_ymls): + def load_from_files(cls, project_yml, profile_dir, check_cache): spec = _load_spec_file(project_yml) home = os.path.dirname(project_yml) name = spec.get("name") or os.path.basename(home) - services = [ - Service.load(svc_file, svc_name) - for (svc_file, svc_name) - in _adsfiles_to_service_names(svc_ymls).items() - ] + service_sets = ServiceSet.load_multiple( spec.get("groups"), project_yml) default_selector = ServiceSet.load_default( spec.get("default"), project_yml) or "all" + + cache = Cache(project_yml, profile_dir) + if check_cache and cache.cache_map and cache.valid_groups(service_sets): + ymls_by_service = cache.cache_map + else: + ymls_by_service = _find_service_ymls(os.path.dirname(project_yml)) + cache.write_to_cache(project_yml, ymls_by_service) + + services = [ + Service.load(svc_name, svc_file) + for (svc_name, svc_file) + in ymls_by_service.items() + ] + return Project(name, home, services, service_sets, default_selector) def __init__(self, @@ -428,8 +523,8 @@ def __init__(self, service_sets=None, default_selector=None): class Ads: @staticmethod - def load_from_fs(root_dir, profile_dir): - project = Project.load_from_dir(root_dir) + def load_from_fs(root_dir, profile_dir, check_cache): + project = Project.load_from_dir(root_dir, profile_dir, check_cache) if not project: return None @@ -437,11 +532,23 @@ def load_from_fs(root_dir, profile_dir): return Ads(project, profile) @staticmethod - def load_from_env(): + def load_from_env(use_cache): profile_home = os.getenv("ADS_PROFILE_HOME") if not profile_home or len(profile_home) == 0: profile_home = os.path.expanduser("~") - return Ads.load_from_fs(os.curdir, profile_home) + + if use_cache == ALWAYS: + check_cache = True + elif use_cache == NEVER: + check_cache = False + elif use_cache == WITH_PROFILE: + check_cache = os.path.isfile( + os.path.join(profile_home, + ".ads_profile.yml")) + else: + check_cache = False + + return Ads.load_from_fs(os.curdir, profile_home, check_cache) def __init__(self, project, profile=Profile()): self.project = project @@ -509,6 +616,10 @@ def error(self, message): # AdsCommand ############################################## +ALWAYS = "always" +WITH_PROFILE = "with profile" +NEVER = "never" + class AdsCommandException(Exception): def __init__(self, exit_code, msg=None): self.exit_code = exit_code @@ -545,8 +656,8 @@ def __init__(self): super(SomeDown, self).__init__(23) -def _load_or_die(): - ads = Ads.load_from_env() +def _load_or_die(use_cache): + ads = Ads.load_from_env(use_cache) if not ads: raise UsageError( "ads must be run from within an ads project. " @@ -732,7 +843,7 @@ def _collect_logs_nonempty(services, log_type): def list_func(args): parser = MyArgParser(prog=cmd_list.name, description=cmd_list.description) parser.parse_args(args) - ads = _load_or_die() + ads = _load_or_die(use_cache=NEVER) ads.list() @@ -741,7 +852,9 @@ def up(args): _add_verbose_arg(parser) _add_services_arg(parser) parsed_args = parser.parse_args(args) - ads = _load_or_die() + ads = _load_or_die(use_cache=ALWAYS + if len(parsed_args.service) > 0 + else WITH_PROFILE) services = _resolve_selectors(ads, parsed_args.service, True) if len(services) > 1: info("Starting " + str(services)) @@ -754,7 +867,9 @@ def down(args): _add_verbose_arg(parser) _add_services_arg(parser) parsed_args = parser.parse_args(args) - ads = _load_or_die() + ads = _load_or_die(use_cache=ALWAYS + if len(parsed_args.service) > 0 + else WITH_PROFILE) services = _resolve_selectors(ads, parsed_args.service, True) if not all(map(lambda sp: _down(sp, parsed_args.verbose), services)): raise StopFailed("One or more services failed to stop") @@ -766,7 +881,9 @@ def bounce(args): _add_verbose_arg(parser) _add_services_arg(parser) parsed_args = parser.parse_args(args) - ads = _load_or_die() + ads = _load_or_die(use_cache=ALWAYS + if len(parsed_args.service) > 0 + else WITH_PROFILE) services = _resolve_selectors(ads, parsed_args.service, True) all_stopped = all( map(lambda sp: _down(sp, parsed_args.verbose), services)) @@ -784,7 +901,9 @@ def status(args): _add_verbose_arg(parser) _add_services_arg(parser) parsed_args = parser.parse_args(args) - ads = _load_or_die() + ads = _load_or_die(use_cache=ALWAYS + if len(parsed_args.service) > 0 + else WITH_PROFILE) services = _resolve_selectors(ads, parsed_args.service, False) if not all(map(lambda sp: _status(sp, parsed_args.verbose), services)): raise SomeDown() @@ -825,7 +944,9 @@ def logs(args): # Default log_type = "general" - ads = _load_or_die() + ads = _load_or_die(use_cache=ALWAYS + if len(parsed_args.service) > 0 + else WITH_PROFILE) services = _resolve_selectors(ads, parsed_args.service, False) resolved_log_paths = _collect_logs_nonempty(services, log_type) @@ -844,7 +965,9 @@ def home(args): parser = MyArgParser(prog=cmd_home.name, description=cmd_home.description) _add_services_arg(parser) parsed_args = parser.parse_args(args) - ads = _load_or_die() + ads = _load_or_die(use_cache=ALWAYS + if len(parsed_args.service) > 0 + else WITH_PROFILE) services = _resolve_selectors(ads, parsed_args.service, True) print("\n".join(_collect_rel_homes(services))) @@ -853,7 +976,9 @@ def edit(args): parser = MyArgParser(prog=cmd_edit.name, description=cmd_edit.description) _add_services_arg(parser) parsed_args = parser.parse_args(args) - ads = _load_or_die() + ads = _load_or_die(use_cache=ALWAYS + if len(parsed_args.service) > 0 + else WITH_PROFILE) services = _resolve_selectors(ads, parsed_args.service, True) homes = _collect_rel_homes(services) ymls = [os.path.join(home, "ads.yml") for home in homes] diff --git a/tests/bash/util/Framework.sh b/tests/bash/util/Framework.sh index 563a7db..650604d 100644 --- a/tests/bash/util/Framework.sh +++ b/tests/bash/util/Framework.sh @@ -20,6 +20,10 @@ copy_project_to_tmp_and_go() { if [[ "$(ls "$project_dir")" ]]; then cp -R "$project_dir"/* "$project_tmp" fi + project_cache="$project_tmp/../.ads_cache.yml" + if [ -f $project_cache ]; then + rm "$project_cache" + fi cd "$project_tmp" } @@ -35,6 +39,12 @@ set_ads_profile() { cat > "$test_tmp/.ads_profile.yml" } +# Temporarily sets your ads cache to the value of stdin +set_ads_cache() { + export ADS_CACHE_HOME="$test_tmp" + cat > "$test_tmp/.ads_cache.yml" +} + ############################################################################### # setup and teardown ############################################################################### @@ -57,4 +67,4 @@ teardown() { # main() ############################################################################### -run_tests \ No newline at end of file +run_tests diff --git a/tests/unit/test_cache.py b/tests/unit/test_cache.py new file mode 100644 index 0000000..2b3a761 --- /dev/null +++ b/tests/unit/test_cache.py @@ -0,0 +1,101 @@ +import sys +import os +import unittest +from mock import patch, mock_open +from ads import Service, ServiceSet, Cache, _load_spec_file + +class MockDevice(): + """MockDevice to suppress stdout""" + + @classmethod + def flush(s): pass + + def write(self, s): pass + +class TestCache(unittest.TestCase): + + def setUp(self): + self.cachefile = ".ads_cache.yml" + self.project_file = "/adsroot/adsroot.yml" + self.profile_dir = "/profile" + self.service_sets = [ServiceSet("service1", ["service1"]), + ServiceSet("service1", ["service2"])] + self.map = {"adsroot": self.project_file, + "service1": "/adsroot/service1/ads.yml", + "service2": "/adsroot/service2/ads.yml"} + self.read_data = "adsroot: /adsroot/adsroot.yml\n"\ + + "service1: /adsroot/service1/ads.yml\n"\ + + "service2: /adsroot/service2/ads.yml\n" + + def test_get_cache_path(self): + default_home = "/default" + self.assertEqual( + Cache.get_cache_path(default_home), + "%s/.ads_cache.yml" % (default_home) + ) + + def test_get_cache_path_with_env_var(self): + default_home = "/default" + cache_home = "/ads_cache_home" + os.environ["ADS_CACHE_HOME"] = cache_home + self.assertEqual( + Cache.get_cache_path(default_home), + "%s/.ads_cache.yml" % (cache_home) + ) + + def test_load_from_cache_no_cachefile(self): + with patch('os.path.isfile', return_value=False): + self.assertEqual( + Cache.load_from_cache(self.cachefile, + self.project_file, + self.profile_dir), {} + ) + + def test_load_from_cache_cachefile(self): + with patch("os.path.isfile", return_value=True): + with patch("ads.ads.open", mock_open(read_data=self.read_data), create=True): + with patch("ads.ads._load_spec_file", return_value=self.map): + self.assertEqual( + Cache.load_from_cache(self.cachefile, + self.project_file, + self.profile_dir), self.map + ) + + def test_get_isinstance_of_service(self): + with patch("os.path.isfile", return_value=True): + with patch("ads.ads.open", mock_open(read_data=self.read_data), create=True): + with patch("ads.ads._load_spec_file", return_value=self.map): + cache = Cache(self.project_file, self.profile_dir) + value = cache.get(Service("service2", self.map.get("service2"))) + self.assertEqual(value, "/adsroot/service2/ads.yml") + + def test_get_is_string(self): + with patch("os.path.isfile", return_value=True): + with patch("ads.ads.open", mock_open(read_data=self.read_data), create=True): + with patch("ads.ads._load_spec_file", return_value=self.map): + cache = Cache(self.project_file, self.profile_dir) + value = cache.get("service2") + self.assertEqual(value, "/adsroot/service2/ads.yml") + + def test_valid_groups_true(self): + with patch("os.path.isfile", return_value=True): + with patch("ads.ads.open", mock_open(read_data=self.read_data), create=True): + with patch("ads.ads._load_spec_file", return_value=self.map): + cache = Cache(self.project_file, self.profile_dir) + self.assertEqual(cache.valid_groups(self.service_sets), True) + + def test_write_to_cache(self): + with patch("os.path.isfile", return_value=False): + with patch("ads.ads._load_spec_file", return_value={}): + cache = Cache(self.project_file, self.profile_dir) + m_open = mock_open(read_data=self.read_data) + with patch("ads.ads.open", m_open, create=True): + cache.write_to_cache(self.project_file, + {"random-service": "/random-service/ads.yml"} + ) + self.assertEqual(cache.get("random-service"), "/random-service/ads.yml") + file_desc = m_open() + self.assertTrue(file_desc.write.called) + +if __name__ == '__main__': + unittest.main() From 5a1b75edb7cc050fb9b94e87283d869873959eec Mon Sep 17 00:00:00 2001 From: Alex Ramos Date: Tue, 2 Feb 2021 20:20:20 -0800 Subject: [PATCH 2/3] Convert for python3 --- ads/__init__.py | 2 +- ads/ads.py | 58 ++++++++++++++++++------------------- ads/terminal.py | 14 ++++----- tests/unit/test_selector.py | 10 +++---- 4 files changed, 41 insertions(+), 43 deletions(-) diff --git a/ads/__init__.py b/ads/__init__.py index 2a0058b..2413d96 100644 --- a/ads/__init__.py +++ b/ads/__init__.py @@ -1 +1 @@ -from ads import Ads, Project, Service, ServiceSet, Profile, BadSelectorException, Cache, _load_spec_file +from ads.ads import Ads, Project, Service, ServiceSet, Profile, BadSelectorException, Cache, _load_spec_file diff --git a/ads/ads.py b/ads/ads.py index e0fa98c..2dbbe89 100644 --- a/ads/ads.py +++ b/ads/ads.py @@ -1,5 +1,5 @@ import sys - +from functools import reduce class colors: HEADER = '\033[95m' @@ -13,17 +13,17 @@ class colors: def debug(msg): - print(colors.OKBLUE + msg + colors.ENDC) + print((colors.OKBLUE + msg + colors.ENDC)) sys.stdout.flush() def info(msg): - print(colors.OKGREEN + "--- " + msg + colors.ENDC) + print((colors.OKGREEN + "--- " + msg + colors.ENDC)) sys.stdout.flush() def warning(msg): - print(colors.WARNING + "!! " + msg + colors.ENDC) + print((colors.WARNING + "!! " + msg + colors.ENDC)) sys.stdout.flush() @@ -70,16 +70,16 @@ def pretty_print(self): all_keys = [ k for (heading, listing_dict, _) in self.sections - for k in listing_dict.keys()] + for k in list(listing_dict.keys())] if len(all_keys) == 0: return - column_width = max(map(len, all_keys)) + 1 + column_width = max(list(map(len, all_keys))) + 1 for (heading, listing_dict, empty_msg) in self.sections: empty = len(listing_dict) == 0 - print("\n" + heading + (empty and "\n " + empty_msg or "")) + print(("\n" + heading + (empty and "\n " + empty_msg or ""))) if not empty: - for (k, v) in listing_dict.items(): - print(("%" + str(column_width) + "s: %s") % (k, v)) + for (k, v) in list(listing_dict.items()): + print((("%" + str(column_width) + "s: %s") % (k, v))) ############################################## @@ -259,18 +259,18 @@ def _resolve(selector, project, service_sets_by_name, selector_stack): (stack_as_list[0], " -> ".join(stack_as_list))) if selector == "all": - return frozenset(project.services_by_name.keys()) + return frozenset(list(project.services_by_name.keys())) if selector in project.services_by_name: return frozenset([project.services_by_name[selector].name]) if selector in service_sets_by_name: selector_stack[selector] = True - sub_results = map(lambda s: _resolve(s, + sub_results = [_resolve(s, project, service_sets_by_name, - selector_stack), - service_sets_by_name[selector].selectors) + selector_stack) for s in service_sets_by_name[selector].selectors] + print(selector) selector_stack.popitem(True) return frozenset(reduce(frozenset.__or__, sub_results)) @@ -296,7 +296,7 @@ def load_multiple(cls, spec, origin_file): _expect(dict, spec, origin_file) return [ServiceSet.load(name, value, origin_file) for (name, value) - in spec.items()] + in list(spec.items())] @classmethod def load_default(cls, spec, origin_file): @@ -480,7 +480,7 @@ def load_from_files(cls, project_yml, profile_dir, check_cache): services = [ Service.load(svc_name, svc_file) for (svc_name, svc_file) - in ymls_by_service.items() + in list(ymls_by_service.items()) ] return Project(name, home, services, service_sets, default_selector) @@ -581,7 +581,7 @@ def list(self): (Treelisting() .with_section( "All services in current project (%s):" % self.project.name, - Service.as_printable_dict(self.project.services_by_name.values()), + Service.as_printable_dict(list(self.project.services_by_name.values())), "None (create ads.yml files in this dir tree)") .with_section( "Groups defined in current project:", @@ -817,9 +817,7 @@ def _resolve_selectors(ads, selectors, fail_if_empty): except BadSelectorException as e: raise NotFound(str(e)) - services = map( - lambda name: ads.project.services_by_name[name], - sorted(service_names)) + services = [ads.project.services_by_name[name] for name in sorted(service_names)] if fail_if_empty and len(services) == 0: raise NotFound("No services found that match '%s'" % @@ -858,7 +856,7 @@ def up(args): services = _resolve_selectors(ads, parsed_args.service, True) if len(services) > 1: info("Starting " + str(services)) - if not all(map(lambda sp: _up(sp, parsed_args.verbose), services)): + if not all([_up(sp, parsed_args.verbose) for sp in services]): raise StartFailed("One or more services failed to start") @@ -871,7 +869,7 @@ def down(args): if len(parsed_args.service) > 0 else WITH_PROFILE) services = _resolve_selectors(ads, parsed_args.service, True) - if not all(map(lambda sp: _down(sp, parsed_args.verbose), services)): + if not all([_down(sp, parsed_args.verbose) for sp in services]): raise StopFailed("One or more services failed to stop") @@ -886,9 +884,9 @@ def bounce(args): else WITH_PROFILE) services = _resolve_selectors(ads, parsed_args.service, True) all_stopped = all( - map(lambda sp: _down(sp, parsed_args.verbose), services)) + [_down(sp, parsed_args.verbose) for sp in services]) all_started = all( - map(lambda sp: _up(sp, parsed_args.verbose), services)) + [_up(sp, parsed_args.verbose) for sp in services]) if not all_stopped: raise StopFailed("One or more services failed to stop") if not all_started: @@ -905,7 +903,7 @@ def status(args): if len(parsed_args.service) > 0 else WITH_PROFILE) services = _resolve_selectors(ads, parsed_args.service, False) - if not all(map(lambda sp: _status(sp, parsed_args.verbose), services)): + if not all([_status(sp, parsed_args.verbose) for sp in services]): raise SomeDown() @@ -951,7 +949,7 @@ def logs(args): resolved_log_paths = _collect_logs_nonempty(services, log_type) if parsed_args.list: - print("\n".join(resolved_log_paths)) + print(("\n".join(resolved_log_paths))) elif parsed_args.cat: if not _cat(resolved_log_paths): raise InternalError("cat command failed") @@ -969,7 +967,7 @@ def home(args): if len(parsed_args.service) > 0 else WITH_PROFILE) services = _resolve_selectors(ads, parsed_args.service, True) - print("\n".join(_collect_rel_homes(services))) + print(("\n".join(_collect_rel_homes(services)))) def edit(args): @@ -1057,8 +1055,8 @@ def create_main_arg_parser(): %s See 'ads help ' to read about a specific subcommand. -""" % (format_help_for_cmds(filter(lambda cmd: cmd.is_common, all_cmds)), - format_help_for_cmds(filter(lambda cmd: not cmd.is_common, all_cmds))) +""" % (format_help_for_cmds([cmd for cmd in all_cmds if cmd.is_common]), + format_help_for_cmds([cmd for cmd in all_cmds if not cmd.is_common])) usage = "ads [-h] [args] [service [service ...]]" parser = MyArgParser( prog="ads", @@ -1069,7 +1067,7 @@ def create_main_arg_parser(): parser.add_argument( "command", metavar="", - choices=cmds_by_alias.keys(), + choices=list(cmds_by_alias.keys()), help="Do something to a service") return parser @@ -1083,7 +1081,7 @@ def help(args): "command", metavar="", nargs="?", - choices=cmds_by_alias.keys(), + choices=list(cmds_by_alias.keys()), help="command to learn about") parsed_args = parser.parse_args(args) if parsed_args.command: diff --git a/ads/terminal.py b/ads/terminal.py index 0294d6f..de2848a 100644 --- a/ads/terminal.py +++ b/ads/terminal.py @@ -13,12 +13,12 @@ class colors: def debug(msg): - print(colors.OKBLUE + msg + colors.ENDC) + print((colors.OKBLUE + msg + colors.ENDC)) sys.stdout.flush() def info(msg): - print(colors.OKGREEN + "--- " + msg + colors.ENDC) + print((colors.OKGREEN + "--- " + msg + colors.ENDC)) sys.stdout.flush() @@ -47,13 +47,13 @@ def pretty_print(self): all_keys = [ k for (heading, listing_dict, _) in self.sections - for k in listing_dict.keys()] + for k in list(listing_dict.keys())] if len(all_keys) == 0: return - column_width = max(map(len, all_keys)) + 1 + column_width = max(list(map(len, all_keys))) + 1 for (heading, listing_dict, empty_msg) in self.sections: empty = len(listing_dict) == 0 - print("\n" + heading + (empty and "\n " + empty_msg or "")) + print(("\n" + heading + (empty and "\n " + empty_msg or ""))) if not empty: - for (k, v) in listing_dict.items(): - print(("%" + str(column_width) + "s: %s") % (k, v)) + for (k, v) in list(listing_dict.items()): + print((("%" + str(column_width) + "s: %s") % (k, v))) diff --git a/tests/unit/test_selector.py b/tests/unit/test_selector.py index 59c49bd..e708637 100644 --- a/tests/unit/test_selector.py +++ b/tests/unit/test_selector.py @@ -56,13 +56,13 @@ def test_recursive_selectors_across_project_and_profile(self): def test_selector_resolves_to_nonexistent_service(self): ads = Ads(Project("test", "/test", [], [ServiceSet("ab", frozenset(["a", "b"]))])) - self.assertRaisesRegexp( - BadSelectorException, "No service .* ab -> a", ads.resolve, "ab") + self.assertRaisesRegex( + BadSelectorException, "No service .* ab -> .*", ads.resolve, "ab") pass def test_unknown_selector(self): ads = Ads(Project("test", "/test")) - self.assertRaisesRegexp( + self.assertRaisesRegex( BadSelectorException, "No service", ads.resolve, "ab") pass @@ -70,9 +70,9 @@ def test_circular_selectors(self): ads = Ads(Project("test", "/test", some_services, [ServiceSet("foo", frozenset(["bar"]))]), Profile([ServiceSet("bar", frozenset(["foo"]))])) - self.assertRaisesRegexp( + self.assertRaisesRegex( BadSelectorException, "foo -> bar -> foo", ads.resolve, "foo") - self.assertRaisesRegexp( + self.assertRaisesRegex( BadSelectorException, "bar -> foo -> bar", ads.resolve, "bar") pass From 00095acf1a0598059d4c745fe6efb0a136a7cd39 Mon Sep 17 00:00:00 2001 From: Alex Ramos Date: Tue, 2 Feb 2021 20:22:43 -0800 Subject: [PATCH 3/3] Update import --- ads/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ads/__main__.py b/ads/__main__.py index 2c22ca0..f0fd044 100644 --- a/ads/__main__.py +++ b/ads/__main__.py @@ -5,7 +5,7 @@ # # @author adamcath -from ads import main +from ads.ads import main if __name__ == "__main__": main()