From 44893249fcd1bf6efe34c155d202fc9befa6a891 Mon Sep 17 00:00:00 2001 From: Jason Stiles <99777116+stilesj-uchicago@users.noreply.github.com> Date: Thu, 13 Apr 2023 16:23:48 -0400 Subject: [PATCH] REQ-456 Retire python3.5 to support psqlgraph (#401) * REQ-456 Retire python3.5 to support psqlgraph --- .travis.yml | 1 - bin/update_related_case_caches.py | 4 +-- dev-requirements.txt | 25 +++++++++-------- gdcdatamodel/gdc_postgres_admin.py | 6 ++--- gdcdatamodel/models/__init__.py | 18 ++++++------- gdcdatamodel/models/caching.py | 4 +-- gdcdatamodel/models/indexes.py | 6 ++--- gdcdatamodel/models/versioning.py | 4 +-- gdcdatamodel/validators/graph_validators.py | 14 +++++----- gdcdatamodel/validators/json_validators.py | 2 +- migrations/update_case_cache_append_only.py | 2 +- migrations/update_legacy_states.py | 2 +- requirements.txt | 30 +++++++++++++++------ setup.py | 6 ++--- test/helpers.py | 6 ++--- test/test_admin_script.py | 10 +++---- test/test_datamodel.py | 4 +-- test/test_dictionary_loadiing.py | 4 +-- test/test_gdc_postgres_admin.py | 2 +- test/test_validators.py | 4 +-- test/unit/test_tagging.py | 4 +-- tox.ini | 2 +- 22 files changed, 86 insertions(+), 74 deletions(-) diff --git a/.travis.yml b/.travis.yml index a6ff0dea..b2665703 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: python dist: focal python: - - 3.5 - 3.6.8 - 3.7 - 3.8 diff --git a/bin/update_related_case_caches.py b/bin/update_related_case_caches.py index 52ea94c6..055cdedb 100644 --- a/bin/update_related_case_caches.py +++ b/bin/update_related_case_caches.py @@ -57,7 +57,7 @@ def update_project_related_case_cache(project): """ - logger.info("Project: {}".format(project.code)) + logger.info(f"Project: {project.code}") for case in project.cases: recursive_update_related_case_caches(case, case) @@ -83,7 +83,7 @@ def main(): ) args = parser.parse_args() - prompt = "Password for {}:".format(args.user) + prompt = f"Password for {args.user}:" password = args.password or getpass.getpass(prompt) g = PsqlGraphDriver(args.host, args.user, password, args.database) diff --git a/dev-requirements.txt b/dev-requirements.txt index 5569fd4b..edddbec7 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.6 # To update, run: # # pip-compile dev-requirements.in @@ -55,6 +55,11 @@ ipykernel==5.5.6 # jupyter-console # notebook # qtconsole +ipython==7.9.0 + # via + # ipykernel + # ipywidgets + # jupyter-console ipython-genutils==0.2.0 # via # ipykernel @@ -63,11 +68,6 @@ ipython-genutils==0.2.0 # notebook # qtconsole # traitlets -ipython==7.9.0 - # via - # ipykernel - # ipywidgets - # jupyter-console ipywidgets==7.7.2 # via jupyter jedi==0.17.2 @@ -80,6 +80,8 @@ jsonschema==3.2.0 # via # -c requirements.txt # nbformat +jupyter==1.0.0 + # via -r dev-requirements.in jupyter-client==4.4.0 # via # -r dev-requirements.in @@ -99,8 +101,8 @@ jupyter-core==4.6.3 # nbformat # notebook # qtconsole -jupyter==1.0.0 - # via -r dev-requirements.in +jupyterlab-widgets==1.1.4 + # via ipywidgets markupsafe==1.1.1 # via jinja2 mistune==0.8.4 @@ -130,8 +132,6 @@ pandocfilters==1.5.0 # via nbconvert parso==0.7.1 # via jedi -pathlib2==2.3.7.post1 - # via pytest pexpect==4.8.0 # via ipython pickleshare==0.7.5 @@ -161,12 +161,12 @@ pyrsistent==0.16.1 # via # -c requirements.txt # jsonschema -pytest-cov==2.8.1 - # via -r dev-requirements.in pytest==4.6.10 # via # -r dev-requirements.in # pytest-cov +pytest-cov==2.8.1 + # via -r dev-requirements.in pyyaml==5.3.1 # via # -c requirements.txt @@ -186,7 +186,6 @@ six==1.15.0 # bleach # cfgv # jsonschema - # pathlib2 # pre-commit # prompt-toolkit # pytest diff --git a/gdcdatamodel/gdc_postgres_admin.py b/gdcdatamodel/gdc_postgres_admin.py index 71216d4c..ed18faa8 100644 --- a/gdcdatamodel/gdc_postgres_admin.py +++ b/gdcdatamodel/gdc_postgres_admin.py @@ -24,7 +24,7 @@ logger.setLevel(logging.INFO) name_root = "table_creator_" -app_name = "{}{}".format(name_root, random.randint(1000, 9999)) +app_name = f"{name_root}{random.randint(1000, 9999)}" GRANT_READ_PRIVS_SQL = """ @@ -106,7 +106,7 @@ def create_graph_tables(engine, timeout, namespace=None): trans = connection.begin() logger.info("Setting lock_timeout to %d", timeout) - timeout_str = "{}s".format(int(timeout + 1)) + timeout_str = f"{int(timeout + 1)}s" connection.execute("SET LOCAL lock_timeout = %s;", timeout_str) orm_base = ext.get_orm_base(namespace) if namespace else ORMBase @@ -137,7 +137,7 @@ def create_tables(engine, delay, retries, namespace=None): raise RuntimeError("Max retries exceeded") logger.info( - "Trying again in {} seconds ({} retries remaining)".format(delay, retries) + f"Trying again in {delay} seconds ({retries} retries remaining)" ) time.sleep(delay) diff --git a/gdcdatamodel/models/__init__.py b/gdcdatamodel/models/__init__.py index cb0fd79f..b6126597 100644 --- a/gdcdatamodel/models/__init__.py +++ b/gdcdatamodel/models/__init__.py @@ -69,7 +69,7 @@ def remove_spaces(s): def get_cls_package(package_namespace=None): cls_package = "gdcdatamodel.models" if package_namespace: - cls_package = "{}.{}".format(cls_package, package_namespace) + cls_package = f"{cls_package}.{package_namespace}" return cls_package @@ -198,7 +198,7 @@ def _versions(self): session = self.get_session() if not session: raise RuntimeError( - "{} not bound to a session. Try .get_versions(session).".format(self) + f"{self} not bound to a session. Try .get_versions(session)." ) return self.get_versions(session) @@ -509,7 +509,7 @@ def generate_edge_tablename(src_label, label, dst_label): # truncate the each part of the name if len(tablename) > 40: oldname = tablename - logger.debug("Edge tablename {} too long, shortening".format(oldname)) + logger.debug(f"Edge tablename {oldname} too long, shortening") tablename = "edge_{}_{}".format( hashlib.md5(py3_to_bytes(tablename)).hexdigest()[:8], "{}{}{}".format( @@ -518,7 +518,7 @@ def generate_edge_tablename(src_label, label, dst_label): "".join([a[:2] for a in dst_label.split("_")])[:10], ), ) - logger.debug("Shortening {} -> {}".format(oldname, tablename)) + logger.debug(f"Shortening {oldname} -> {tablename}") return tablename @@ -648,7 +648,7 @@ def load_nodes(dictionary, node_cls=None, package_namespace=None): cls = NodeFactory(_id, subschema, node_cls, package_namespace) register_class(cls, package_namespace) except Exception: - print("Unable to load {}".format(name)) + print(f"Unable to load {name}") raise @@ -684,7 +684,7 @@ def parse_edge( edge_name = "".join(map(get_class_name_from_id, [src_label, edge_label, dst_label])) if edge_cls.is_subclass_loaded(name): - return "_{}_out".format(edge_name) + return f"_{edge_name}_out" edge = EdgeFactory( edge_name, @@ -698,7 +698,7 @@ def parse_edge( package_namespace=package_namespace, ) - return "_{}_out".format(edge.__name__) + return f"_{edge.__name__}_out" def load_edges(dictionary, node_cls=Node, edge_cls=Edge, package_namespace=None): @@ -712,7 +712,7 @@ def load_edges(dictionary, node_cls=Node, edge_cls=Edge, package_namespace=None) src_cls = node_cls.get_subclass(src_label) if not src_cls: - raise RuntimeError("No source class labeled {}".format(src_label)) + raise RuntimeError(f"No source class labeled {src_label}") for name, link in get_links(subschema).items(): edge_label = link["label"] @@ -747,7 +747,7 @@ def load_edges(dictionary, node_cls=Node, edge_cls=Edge, package_namespace=None) "required": False, "target_type": "case", "label": "relates_to", - "backref": "_related_{}".format(src_cls.label), + "backref": f"_related_{src_cls.label}", } parse_edge( diff --git a/gdcdatamodel/models/caching.py b/gdcdatamodel/models/caching.py index 92b13e84..120e4af3 100644 --- a/gdcdatamodel/models/caching.py +++ b/gdcdatamodel/models/caching.py @@ -53,7 +53,7 @@ def get_related_case_edge_cls_name(node): """ - return "{}RelatesToCase".format(node.__class__.__name__) + return f"{node.__class__.__name__}RelatesToCase" def get_edge_src(edge): @@ -219,7 +219,7 @@ def update_cache_edges(node, session, correct_cases): # Get information about the existing edges edge_name = get_related_case_edge_cls_name(node) - existing_edges = getattr(node, "_{}_out".format(edge_name)) + existing_edges = getattr(node, f"_{edge_name}_out") # Remove edges that should no longer exist cases_disconnected = [ diff --git a/gdcdatamodel/models/indexes.py b/gdcdatamodel/models/indexes.py index 9b633445..410c692f 100644 --- a/gdcdatamodel/models/indexes.py +++ b/gdcdatamodel/models/indexes.py @@ -32,20 +32,20 @@ def index_name(cls, description): """ - name = "index_{}_{}".format(cls.__tablename__, description) + name = f"index_{cls.__tablename__}_{description}" # If the name is too long, prepend it with the first 8 hex of it's hash # truncate the each part of the name if len(name) > 40: oldname = index_name - logger.debug("Edge tablename {} too long, shortening".format(oldname)) + logger.debug(f"Edge tablename {oldname} too long, shortening") name = "index_{}_{}_{}".format( hashlib.md5(py3_to_bytes(cls.__tablename__)).hexdigest()[:8], "".join([a[:4] for a in cls.get_label().split("_")])[:20], "_".join([a[:8] for a in description.split("_")])[:25], ) - logger.debug("Shortening {} -> {}".format(oldname, index_name)) + logger.debug(f"Shortening {oldname} -> {index_name}") return name diff --git a/gdcdatamodel/models/versioning.py b/gdcdatamodel/models/versioning.py index 82f5300f..268fc2b7 100644 --- a/gdcdatamodel/models/versioning.py +++ b/gdcdatamodel/models/versioning.py @@ -7,7 +7,7 @@ UUID_NAMESPACE_SEED = os.getenv( "UUID_NAMESPACE_SEED", "86bb916a-24c5-48e4-8a46-5ea73a379d47" ) -UUID_NAMESPACE = uuid.UUID("urn:uuid:{}".format(UUID_NAMESPACE_SEED), version=4) +UUID_NAMESPACE = uuid.UUID(f"urn:uuid:{UUID_NAMESPACE_SEED}", version=4) class TagKeys: @@ -106,7 +106,7 @@ def is_taggable(self, node): def __generate_hash(seed, label): namespace = UUID_NAMESPACE - name = "{}-{}".format(seed, label) + name = f"{seed}-{label}" return str(uuid.uuid5(namespace, name)) diff --git a/gdcdatamodel/validators/graph_validators.py b/gdcdatamodel/validators/graph_validators.py index 093e220e..cba11930 100644 --- a/gdcdatamodel/validators/graph_validators.py +++ b/gdcdatamodel/validators/graph_validators.py @@ -1,7 +1,7 @@ from gdcdictionary import gdcdictionary -class GDCGraphValidator(object): +class GDCGraphValidator: """ Validator that validates entities' relationship with existing nodes in database. @@ -28,7 +28,7 @@ def record_errors(self, graph, entities): self.optional_validators[validator_name].validate() -class GDCLinksValidator(object): +class GDCLinksValidator: def validate(self, entities, graph=None): for entity in entities: for link in gdcdictionary.schema[entity.node.label]["links"]: @@ -57,7 +57,7 @@ def validate_edge_group(self, schema, entity): if schema.get("required") is True and len(submitted_links) == 0: names = ", ".join(schema_links[:-2] + [" or ".join(schema_links[-2:])]) entity.record_error( - "Entity is missing a required link to {}".format(names), + f"Entity is missing a required link to {names}", keys=schema_links, ) @@ -70,7 +70,7 @@ def validate_edge_group(self, schema, entity): keys=schema_links, ) for edge in entity.node.edges_out: - entity.record_error("{}".format(edge.dst.submitter_id)) + entity.record_error(f"{edge.dst.submitter_id}") result = {"length": num_of_edges, "name": ", ".join(schema_links)} @@ -86,7 +86,7 @@ def validate_edge(self, link_sub_schema, entity): if multi in ["many_to_one", "one_to_one"]: if len(targets) > 1: entity.record_error( - "'{}' link has to be {}".format(association, multi), + f"'{association}' link has to be {multi}", keys=[association], ) @@ -108,13 +108,13 @@ def validate_edge(self, link_sub_schema, entity): else: if link_sub_schema.get("required") is True: entity.record_error( - "Entity is missing required link to {}".format(association), + f"Entity is missing required link to {association}", keys=[association], ) return result -class GDCUniqueKeysValidator(object): +class GDCUniqueKeysValidator: def validate(self, entities, graph=None): for entity in entities: schema = gdcdictionary.schema[entity.node.label] diff --git a/gdcdatamodel/validators/json_validators.py b/gdcdatamodel/validators/json_validators.py index 65d6ac85..1ae896b4 100644 --- a/gdcdatamodel/validators/json_validators.py +++ b/gdcdatamodel/validators/json_validators.py @@ -22,7 +22,7 @@ def get_keys(error_msg): return [] -class GDCJSONValidator(object): +class GDCJSONValidator: def __init__(self): self.schemas = gdcdictionary diff --git a/migrations/update_case_cache_append_only.py b/migrations/update_case_cache_append_only.py index 0ca4c508..052e10df 100644 --- a/migrations/update_case_cache_append_only.py +++ b/migrations/update_case_cache_append_only.py @@ -161,7 +161,7 @@ def seed_level_1(graph, cls): cls_to_case_edge_table=case_edge.__tablename__, ) - print("Seeding {} through {}".format(cls.get_label(), case_edge.__name__)) + print(f"Seeding {cls.get_label()} through {case_edge.__name__}") graph.current_session().execute(statement) diff --git a/migrations/update_legacy_states.py b/migrations/update_legacy_states.py index 4d280524..0e142a7d 100644 --- a/migrations/update_legacy_states.py +++ b/migrations/update_legacy_states.py @@ -107,7 +107,7 @@ def print_cls_query_summary(graph): "%s: %d" % ( "legacy_stateless_nodes".ljust(40), - sum([query.count() for query in cls_queries.itervalues()]), + sum(query.count() for query in cls_queries.itervalues()), ) ) diff --git a/requirements.txt b/requirements.txt index e1155fe9..5b305c66 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,22 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.6 # To update, run: # # pip-compile setup.py # attrs==22.1.0 - # via jsonschema + # via + # gdcdictionary + # jsonschema +cffi==1.15.1 + # via cryptography +cryptography==3.2.1 + # via gdc-ng-models decorator==4.4.2 # via gdcdatamodel (setup.py) -git+https://github.com/NCI-GDC/gdc-ng-models.git@1.5.2#egg=gdc-ng-models +gdc-ng-models @ git+https://github.com/NCI-GDC/gdc-ng-models.git@1.6.2 # via gdcdatamodel (setup.py) -git+https://github.com/NCI-GDC/gdcdictionary.git@2.6.3#egg=gdcdictionary +gdcdictionary @ git+https://github.com/NCI-GDC/gdcdictionary.git@2.6.5 # via gdcdatamodel (setup.py) graphviz==0.14.2 # via gdcdatamodel (setup.py) @@ -20,27 +26,35 @@ jsonschema==3.2.0 # via # gdcdatamodel (setup.py) # gdcdictionary -git+https://github.com/NCI-GDC/psqlgraph.git@3.4.0#egg=psqlgraph +psqlgraph @ git+https://github.com/NCI-GDC/psqlgraph.git@4.0.0 # via gdcdatamodel (setup.py) psycopg2==2.8.6 # via psqlgraph +psycopg2-binary==2.8.6 + # via gdc-ng-models +pycparser==2.21 + # via cffi pyrsistent==0.16.1 # via # gdcdatamodel (setup.py) # jsonschema pytz==2020.5 - # via gdcdatamodel (setup.py) + # via + # gdc-ng-models + # gdcdatamodel (setup.py) pyyaml==5.3.1 # via gdcdictionary rstr==2.2.6 # via psqlgraph six==1.15.0 # via + # cryptography # jsonschema - # psqlgraph # pyrsistent sqlalchemy==1.3.24 - # via psqlgraph + # via + # gdc-ng-models + # psqlgraph xlocal==0.5 # via psqlgraph zipp==1.2.0 diff --git a/setup.py b/setup.py index 8899e8d5..96b5546d 100644 --- a/setup.py +++ b/setup.py @@ -14,9 +14,9 @@ "jsonschema~=3.2", "pyrsistent<0.17.0", "decorator<=5.0.0", - "gdcdictionary @ git+https://github.com/NCI-GDC/gdcdictionary.git@2.6.3#egg=gdcdictionary", - "gdc-ng-models @ git+https://github.com/NCI-GDC/gdc-ng-models.git@1.5.2#egg=gdc-ng-models", - "psqlgraph @ git+https://github.com/NCI-GDC/psqlgraph.git@3.4.0#egg=psqlgraph", + "gdcdictionary @ git+https://github.com/NCI-GDC/gdcdictionary.git@2.6.5#egg=gdcdictionary", + "gdc-ng-models @ git+https://github.com/NCI-GDC/gdc-ng-models.git@1.6.2#egg=gdc-ng-models", + "psqlgraph @ git+https://github.com/NCI-GDC/psqlgraph.git@4.0.0#egg=psqlgraph", ], package_data={ "gdcdatamodel": [ diff --git a/test/helpers.py b/test/helpers.py index 0b8266b1..7af0b770 100644 --- a/test/helpers.py +++ b/test/helpers.py @@ -16,10 +16,10 @@ def truncate(engine, namespace=None): conn = engine.connect() for table in abstract_node.get_subclass_table_names(): if table != abstract_node.__tablename__: - conn.execute("delete from {}".format(table)) + conn.execute(f"delete from {table}") for table in abstract_edge.get_subclass_table_names(): if table != abstract_edge.__tablename__: - conn.execute("delete from {}".format(table)) + conn.execute(f"delete from {table}") if not namespace: # add ng models only to main graph model @@ -63,4 +63,4 @@ def truncate_ng_tables(conn): for meta in ng_models_metadata: for table in meta.tables: - conn.execute("DELETE FROM {}".format(table)) + conn.execute(f"DELETE FROM {table}") diff --git a/test/test_admin_script.py b/test/test_admin_script.py index 54b82383..78219491 100644 --- a/test/test_admin_script.py +++ b/test/test_admin_script.py @@ -77,9 +77,9 @@ def add_test_database_user(db_config): try: g.engine.execute( - "CREATE USER {} WITH PASSWORD '{}'".format(dummy_user, dummy_pwd) + f"CREATE USER {dummy_user} WITH PASSWORD '{dummy_pwd}'" ) - g.engine.execute("GRANT USAGE ON SCHEMA public TO {}".format(dummy_user)) + g.engine.execute(f"GRANT USAGE ON SCHEMA public TO {dummy_user}") yield dummy_user, dummy_pwd finally: g.engine.execute("DROP OWNED BY {0}; DROP USER {0}".format(dummy_user)) @@ -161,7 +161,7 @@ def test_grant_permissions( # verify user does not have permission invalid_permission_fn(g) - run_admin_command(["graph-grant", "--{}={}".format(permission, dummy_user)]) + run_admin_command(["graph-grant", f"--{permission}={dummy_user}"]) # verify user now has permission valid_permission_fn(g) @@ -200,12 +200,12 @@ def test_revoke_permissions( ) # grant user permissions - run_admin_command(["graph-grant", "--{}={}".format(permission, dummy_user)]) + run_admin_command(["graph-grant", f"--{permission}={dummy_user}"]) # verify user has permission valid_permission_fn(g) # revoke permission - run_admin_command(["graph-revoke", "--{}={}".format(permission, dummy_user)]) + run_admin_command(["graph-revoke", f"--{permission}={dummy_user}"]) # verify user no longer has permission invalid_permission_fn(g) diff --git a/test/test_datamodel.py b/test/test_datamodel.py index e638b38f..47972a6b 100644 --- a/test/test_datamodel.py +++ b/test/test_datamodel.py @@ -30,10 +30,10 @@ def _clear_tables(cls): conn.execute("commit") for table in Node().get_subclass_table_names(): if table != Node.__tablename__: - conn.execute("delete from {}".format(table)) + conn.execute(f"delete from {table}") for table in Edge.get_subclass_table_names(): if table != Edge.__tablename__: - conn.execute("delete from {}".format(table)) + conn.execute(f"delete from {table}") conn.execute("delete from versioned_nodes") conn.execute("delete from _voided_nodes") conn.execute("delete from _voided_edges") diff --git a/test/test_dictionary_loadiing.py b/test/test_dictionary_loadiing.py index b7673a01..c0848ac4 100644 --- a/test/test_dictionary_loadiing.py +++ b/test/test_dictionary_loadiing.py @@ -42,7 +42,7 @@ def test_loading_same_dictionary(): def test_case_cache_related_edge_resolution(): def_ns = models.caching.get_related_case_edge_cls(models.AlignedReads()) - def_class_name = "{}.{}".format(def_ns.__module__, def_ns.__name__) + def_class_name = f"{def_ns.__module__}.{def_ns.__name__}" assert "gdcdatamodel.models.AlignedReadsRelatesToCase" == def_class_name @@ -50,5 +50,5 @@ def test_case_cache_related_edge_resolution(): from gdcdatamodel.models import gdc # noqa ns = models.caching.get_related_case_edge_cls(gdc.AlignedReads()) - class_name = "{}.{}".format(ns.__module__, ns.__name__) + class_name = f"{ns.__module__}.{ns.__name__}" assert "gdcdatamodel.models.gdc.AlignedReadsRelatesToCase" == class_name diff --git a/test/test_gdc_postgres_admin.py b/test/test_gdc_postgres_admin.py index e7899533..ca8b8d0d 100644 --- a/test/test_gdc_postgres_admin.py +++ b/test/test_gdc_postgres_admin.py @@ -54,7 +54,7 @@ def tearDownClass(cls): def drop_all_tables(cls): for scls in Node.get_subclasses(): try: - cls.engine.execute("DROP TABLE {} CASCADE".format(scls.__tablename__)) + cls.engine.execute(f"DROP TABLE {scls.__tablename__} CASCADE") except Exception as e: cls.logger.warning(e) diff --git a/test/test_validators.py b/test/test_validators.py index fd760138..35bd3e82 100644 --- a/test/test_validators.py +++ b/test/test_validators.py @@ -6,7 +6,7 @@ from gdcdatamodel.validators import GDCGraphValidator, GDCJSONValidator -class MockSubmissionEntity(object): +class MockSubmissionEntity: def __init__(self): self.errors = [] self.node = None @@ -18,7 +18,7 @@ def record_error(self, message, **kwargs): class TestValidators(BaseTestCase): def setUp(self): - super(TestValidators, self).setUp() + super().setUp() self.graph_validator = GDCGraphValidator() self.json_validator = GDCJSONValidator() diff --git a/test/unit/test_tagging.py b/test/unit/test_tagging.py index c59e0e1a..f1d87f43 100644 --- a/test/unit/test_tagging.py +++ b/test/unit/test_tagging.py @@ -19,11 +19,11 @@ def test_compute_tag(sample_data): """Tests version tags are computed correctly per node""" for node in sample_data: - print("\n..........{}...........".format(node)) + print(f"\n..........{node}...........") v_tag = v.compute_tag(node) assert ( v_tag == EXPECTED_TAGS[node.node_id] - ), "invalid tag computed for {}".format(node.node_id) + ), f"invalid tag computed for {node.node_id}" def test_multi_parent(sample_data): diff --git a/tox.ini b/tox.ini index e1006763..3bab1b1c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,py38 +envlist = py36,py37,py38 [pytest] testpaths =