From 1d66eddae59b28bbe6ffc8486fa819546fda603c Mon Sep 17 00:00:00 2001 From: Stephen Knox Date: Fri, 6 Mar 2026 13:05:18 +0000 Subject: [PATCH 01/11] Fix the get_all_resource_attributes function and add a get_all_attributes_in_network function --- hydra_base/lib/attributes.py | 94 ++++++++++++++++++++++-------------- hydra_base/lib/network.py | 37 ++++++++++++++ 2 files changed, 96 insertions(+), 35 deletions(-) diff --git a/hydra_base/lib/attributes.py b/hydra_base/lib/attributes.py index 62ae63a0..4c9f0765 100644 --- a/hydra_base/lib/attributes.py +++ b/hydra_base/lib/attributes.py @@ -134,20 +134,18 @@ def get_attributes_by_id(attr_ids, **kwargs): def get_template_attributes(template_id, **kwargs): """ - Get a specific attribute by its ID. + Get all attributes linked to a template via template types. """ + import hydra_base.lib.template as templatelib + template = templatelib.get_template(template_id, **kwargs) + attr_id_map = {} - try: - attrs_i = db.DBSession.query(Attr).filter( - TemplateType.template_id == template_id).filter( - TypeAttr.type_id == TemplateType.id).filter( - Attr.id == TypeAttr.id).all() - - log.debug(attrs_i) - return attrs_i - except NoResultFound: - return None - + for tt in template.templatetypes: + for ta in tt.typeattrs: + attr_id_map[ta.attr_id] = ta.attr + attrs = list(attr_id_map.values()) + log.info("Attributes linked to template %s: %s", template_id, len(attrs)) + return [JSONObject(a) for a in attrs] def get_attribute_by_name_and_dimension(name, dimension_id=None, network_id=None, project_id=None, **kwargs): """ @@ -1158,6 +1156,7 @@ def get_all_network_attributes(network_id, template_id=None, **kwargs): args: network_id (int): The ID of the network containing the attributes + ref_key (str): An optional reference key to filter attributes template_id (int): A filter which will cause the function to return attributes associated to that template @@ -1244,11 +1243,12 @@ def get_all_resource_attributes(ref_key, network_id, template_id=None, **kwargs) """ Get all the resource attributes for a given resource type in the network. That includes all the resource attributes for a given type within the network. - For example, if the ref_key is 'NODE', then it will return all the attirbutes + For example, if the ref_key is 'NODE', then it will return all the attributes of all nodes in the network. This function allows a front end to pre-load an entire network's resource attribute information to reduce on function calls. - If type_id is specified, only + If template_id is specified, only return the resource attributes within the type. + NOTE: This uses discrete queries per resource type for performance reasons. """ user_id = kwargs.get('user_id') @@ -1256,24 +1256,41 @@ def get_all_resource_attributes(ref_key, network_id, template_id=None, **kwargs) net = _get_network(network_id) net.check_read_permission(user_id, do_raise=True) - resource_attr_qry = db.DBSession.query(ResourceAttr).\ - outerjoin(Node, Node.id == ResourceAttr.node_id).\ - outerjoin(Link, Link.id == ResourceAttr.link_id).\ - outerjoin(ResourceGroup, ResourceGroup.id == ResourceAttr.group_id).filter( - ResourceAttr.ref_key == ref_key, - or_( - and_(ResourceAttr.node_id != None, - ResourceAttr.node_id == Node.id, - Node.network_id == network_id), - - and_(ResourceAttr.link_id != None, - ResourceAttr.link_id == Link.id, - Link.network_id == network_id), - - and_(ResourceAttr.group_id != None, - ResourceAttr.group_id == ResourceGroup.id, - ResourceGroup.network_id == network_id) - )) + resource_attrs = [] + + if ref_key.upper() == 'NODE': + results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ + join(Node, Node.id == ResourceAttr.node_id).\ + join(Attr, Attr.id == ResourceAttr.attr_id).\ + filter( + ResourceAttr.node_id != None, + Node.network_id == network_id).all() + resource_attrs = results + + elif ref_key.upper() == 'LINK': + results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ + join(Link, Link.id == ResourceAttr.link_id).\ + join(Attr, Attr.id == ResourceAttr.attr_id).\ + filter( + ResourceAttr.link_id != None, + Link.network_id == network_id).all() + resource_attrs = results + + elif ref_key.upper() == 'GROUP': + results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ + join(ResourceGroup, ResourceGroup.id == ResourceAttr.group_id).\ + join(Attr, Attr.id == ResourceAttr.attr_id).\ + filter( + ResourceAttr.group_id != None, + ResourceGroup.network_id == network_id).all() + resource_attrs = results + + elif ref_key.upper() == 'NETWORK': + results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ + join(Attr, Attr.id == ResourceAttr.attr_id).\ + filter( + ResourceAttr.network_id == network_id).all() + resource_attrs = results if template_id is not None: attr_ids = [] @@ -1284,11 +1301,18 @@ def get_all_resource_attributes(ref_key, network_id, template_id=None, **kwargs) for r in rs: attr_ids.append(r.attr_id) - resource_attr_qry = resource_attr_qry.filter(ResourceAttr.attr_id.in_(attr_ids)) + resource_attrs = [ra for ra in resource_attrs if ra[0].attr_id in attr_ids] - resource_attrs = resource_attr_qry.all() + # Convert results to JSONObjects with attribute data included + result_objects = [] + for ra in resource_attrs: + ra_obj = JSONObject(ra[0]) # ResourceAttr object + ra_obj.attr_id = ra[2] # Attr.id + ra_obj.name = ra[1] # Attr.name + ra_obj.description = ra[3] # Attr.description + result_objects.append(ra_obj) - return resource_attrs + return result_objects def get_resource_attributes(ref_key, ref_id, type_id=None, **kwargs): """ diff --git a/hydra_base/lib/network.py b/hydra_base/lib/network.py index 10bb8f13..53c01566 100644 --- a/hydra_base/lib/network.py +++ b/hydra_base/lib/network.py @@ -2661,6 +2661,43 @@ def get_all_resource_attributes_in_network(attr_id, network_id, include_resource return json_ra +def get_all_attributes_in_network(network_id, **kwargs): + """ + Find every attribute def (not resource attribute) + Args: + network_id (int): The ID of the network to search + Returns: + List of JSONObjects + Raises: + HydraError if the network_id does not exist + """ + + user_id = kwargs.get('user_id') + + #check the user can read the network + net = db.DBSession.query(Network).filter(Network.id == network_id).one() + net.check_read_permission(user_id) + + network_attr_ids = db.DBSession.query(ResourceAttr.attr_id.label('attr_id')).filter( + ResourceAttr.network_id == network_id + ) + node_attr_ids = db.DBSession.query(ResourceAttr.attr_id.label('attr_id')).join( + Node, ResourceAttr.node_id == Node.id + ).filter(Node.network_id == network_id) + link_attr_ids = db.DBSession.query(ResourceAttr.attr_id.label('attr_id')).join( + Link, ResourceAttr.link_id == Link.id + ).filter(Link.network_id == network_id) + group_attr_ids = db.DBSession.query(ResourceAttr.attr_id.label('attr_id')).join( + ResourceGroup, ResourceAttr.group_id == ResourceGroup.id + ).filter(ResourceGroup.network_id == network_id) + + all_attr_ids = network_attr_ids.union(node_attr_ids, link_attr_ids, group_attr_ids).subquery() + + attrs = db.DBSession.query(Attr).join(all_attr_ids, Attr.id == all_attr_ids.c.attr_id).all() + + return [JSONObject(a) for a in attrs] + + def get_all_resource_data( scenario_id, include_metadata=False, From 033df56d521a01354d8615815b49c0b8a35c9b4d Mon Sep 17 00:00:00 2001 From: Stephen Knox Date: Fri, 6 Mar 2026 13:05:34 +0000 Subject: [PATCH 02/11] Minor refactor for clarity --- hydra_base/lib/template/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/hydra_base/lib/template/__init__.py b/hydra_base/lib/template/__init__.py index 57756468..4795633d 100644 --- a/hydra_base/lib/template/__init__.py +++ b/hydra_base/lib/template/__init__.py @@ -617,19 +617,19 @@ def update_template(template, auto_delete=False, **kwargs): should be deleted automatically. This flag is also used when updating the typeattrs of type. Defaults to False. """ - tmpl = db.DBSession.query(Template).filter(Template.id == template.id).one() - tmpl.name = template.name + tmpl_i = db.DBSession.query(Template).filter(Template.id == template.id).one() + tmpl_i.name = template.name if template.status is not None: - tmpl.status = template.status + tmpl_i.status = template.status if template.description: - tmpl.description = template.description + tmpl_i.description = template.description - template_types = tmpl.get_types() + template_types = tmpl_i.get_types() if template.layout: - tmpl.layout = get_json_as_string(template.layout) + tmpl_i.layout = get_json_as_string(template.layout) type_dict = dict([(t.id, t) for t in template_types]) @@ -640,7 +640,7 @@ def update_template(template, auto_delete=False, **kwargs): types = template.types if template.types is not None else template.templatetypes for templatetype in types: - if templatetype.id is not None and templatetype.template_id != tmpl.id: + if templatetype.id is not None and templatetype.template_id != tmpl_i.id: log.debug("Type %s is a part of a parent template. Ignoring.", templatetype.id) req_templatetype_ids.append(type_i.id) continue @@ -662,9 +662,9 @@ def update_template(template, auto_delete=False, **kwargs): db.DBSession.flush() - updated_templatetypes = tmpl.get_types() + updated_templatetypes = tmpl_i.get_types() - tmpl_j = JSONObject(tmpl) + tmpl_j = JSONObject(tmpl_i) tmpl_j.templatetypes = updated_templatetypes From 10e1afde8089b970d5c33fc589e64e023f2fd731 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:43:46 +0000 Subject: [PATCH 03/11] Initial plan From cbca054a298783e2aa75341677d813e38b5785ec Mon Sep 17 00:00:00 2001 From: knoxsp Date: Mon, 9 Mar 2026 11:43:49 +0000 Subject: [PATCH 04/11] Update hydra_base/lib/attributes.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- hydra_base/lib/attributes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hydra_base/lib/attributes.py b/hydra_base/lib/attributes.py index 4c9f0765..682e4873 100644 --- a/hydra_base/lib/attributes.py +++ b/hydra_base/lib/attributes.py @@ -1156,7 +1156,6 @@ def get_all_network_attributes(network_id, template_id=None, **kwargs): args: network_id (int): The ID of the network containing the attributes - ref_key (str): An optional reference key to filter attributes template_id (int): A filter which will cause the function to return attributes associated to that template From 040a190ebf7c4861654368b4ef692aa3f5092f0c Mon Sep 17 00:00:00 2001 From: knoxsp Date: Mon, 9 Mar 2026 11:45:43 +0000 Subject: [PATCH 05/11] Update hydra_base/lib/attributes.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- hydra_base/lib/attributes.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/hydra_base/lib/attributes.py b/hydra_base/lib/attributes.py index 682e4873..ea3b4cc3 100644 --- a/hydra_base/lib/attributes.py +++ b/hydra_base/lib/attributes.py @@ -1257,38 +1257,44 @@ def get_all_resource_attributes(ref_key, network_id, template_id=None, **kwargs) resource_attrs = [] - if ref_key.upper() == 'NODE': + ref_key_norm = ref_key.upper() + + if ref_key_norm == 'NODE': results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ join(Node, Node.id == ResourceAttr.node_id).\ join(Attr, Attr.id == ResourceAttr.attr_id).\ filter( ResourceAttr.node_id != None, - Node.network_id == network_id).all() + Node.network_id == network_id, + ResourceAttr.ref_key == ref_key_norm).all() resource_attrs = results - elif ref_key.upper() == 'LINK': + elif ref_key_norm == 'LINK': results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ join(Link, Link.id == ResourceAttr.link_id).\ join(Attr, Attr.id == ResourceAttr.attr_id).\ filter( ResourceAttr.link_id != None, - Link.network_id == network_id).all() + Link.network_id == network_id, + ResourceAttr.ref_key == ref_key_norm).all() resource_attrs = results - elif ref_key.upper() == 'GROUP': + elif ref_key_norm == 'GROUP': results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ join(ResourceGroup, ResourceGroup.id == ResourceAttr.group_id).\ join(Attr, Attr.id == ResourceAttr.attr_id).\ filter( ResourceAttr.group_id != None, - ResourceGroup.network_id == network_id).all() + ResourceGroup.network_id == network_id, + ResourceAttr.ref_key == ref_key_norm).all() resource_attrs = results - elif ref_key.upper() == 'NETWORK': + elif ref_key_norm == 'NETWORK': results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ join(Attr, Attr.id == ResourceAttr.attr_id).\ filter( - ResourceAttr.network_id == network_id).all() + ResourceAttr.network_id == network_id, + ResourceAttr.ref_key == ref_key_norm).all() resource_attrs = results if template_id is not None: From f38e585055271b45872e50557c7765c59fb90890 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:46:34 +0000 Subject: [PATCH 06/11] Initial plan From 2d78ac5421817850122437c340e930af98ddedaa Mon Sep 17 00:00:00 2001 From: knoxsp Date: Mon, 9 Mar 2026 11:46:41 +0000 Subject: [PATCH 07/11] Update hydra_base/lib/template/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- hydra_base/lib/template/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hydra_base/lib/template/__init__.py b/hydra_base/lib/template/__init__.py index 4795633d..20f66d7a 100644 --- a/hydra_base/lib/template/__init__.py +++ b/hydra_base/lib/template/__init__.py @@ -642,7 +642,7 @@ def update_template(template, auto_delete=False, **kwargs): if templatetype.id is not None and templatetype.template_id != tmpl_i.id: log.debug("Type %s is a part of a parent template. Ignoring.", templatetype.id) - req_templatetype_ids.append(type_i.id) + req_templatetype_ids.append(templatetype.id) continue if templatetype.id is not None: From d552605cb59888411458df56f6e9cbb3cf793774 Mon Sep 17 00:00:00 2001 From: knoxsp Date: Mon, 9 Mar 2026 11:47:09 +0000 Subject: [PATCH 08/11] Update hydra_base/lib/network.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- hydra_base/lib/network.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hydra_base/lib/network.py b/hydra_base/lib/network.py index 53c01566..d978d15c 100644 --- a/hydra_base/lib/network.py +++ b/hydra_base/lib/network.py @@ -2675,7 +2675,10 @@ def get_all_attributes_in_network(network_id, **kwargs): user_id = kwargs.get('user_id') #check the user can read the network - net = db.DBSession.query(Network).filter(Network.id == network_id).one() + try: + net = db.DBSession.query(Network).filter(Network.id == network_id).one() + except NoResultFound: + raise HydraError("Network %s not found" % (network_id,)) net.check_read_permission(user_id) network_attr_ids = db.DBSession.query(ResourceAttr.attr_id.label('attr_id')).filter( From e7a2d5714f0f69d5d801c7d53c92b68b5669fed6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:49:50 +0000 Subject: [PATCH 09/11] Push template_id filter into SQL query in get_all_resource_attributes Co-authored-by: knoxsp <9108420+knoxsp@users.noreply.github.com> --- hydra_base/lib/attributes.py | 55 +++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/hydra_base/lib/attributes.py b/hydra_base/lib/attributes.py index ea3b4cc3..5b49c460 100644 --- a/hydra_base/lib/attributes.py +++ b/hydra_base/lib/attributes.py @@ -1259,54 +1259,63 @@ def get_all_resource_attributes(ref_key, network_id, template_id=None, **kwargs) ref_key_norm = ref_key.upper() + # If a template_id is provided, retrieve the relevant attr_ids up front + # so the filter can be applied at the SQL level in each query branch below. + attr_ids = None + if template_id is not None: + attr_ids = db.DBSession.query(TypeAttr.attr_id).join( + TemplateType, + TemplateType.id == TypeAttr.type_id).filter( + TemplateType.template_id == template_id).all() + attr_ids = [r.attr_id for r in attr_ids] + if not attr_ids: + return [] + if ref_key_norm == 'NODE': - results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ + qry = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ join(Node, Node.id == ResourceAttr.node_id).\ join(Attr, Attr.id == ResourceAttr.attr_id).\ filter( ResourceAttr.node_id != None, Node.network_id == network_id, - ResourceAttr.ref_key == ref_key_norm).all() - resource_attrs = results + ResourceAttr.ref_key == ref_key_norm) + if attr_ids is not None: + qry = qry.filter(ResourceAttr.attr_id.in_(attr_ids)) + resource_attrs = qry.all() elif ref_key_norm == 'LINK': - results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ + qry = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ join(Link, Link.id == ResourceAttr.link_id).\ join(Attr, Attr.id == ResourceAttr.attr_id).\ filter( ResourceAttr.link_id != None, Link.network_id == network_id, - ResourceAttr.ref_key == ref_key_norm).all() - resource_attrs = results + ResourceAttr.ref_key == ref_key_norm) + if attr_ids is not None: + qry = qry.filter(ResourceAttr.attr_id.in_(attr_ids)) + resource_attrs = qry.all() elif ref_key_norm == 'GROUP': - results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ + qry = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ join(ResourceGroup, ResourceGroup.id == ResourceAttr.group_id).\ join(Attr, Attr.id == ResourceAttr.attr_id).\ filter( ResourceAttr.group_id != None, ResourceGroup.network_id == network_id, - ResourceAttr.ref_key == ref_key_norm).all() - resource_attrs = results + ResourceAttr.ref_key == ref_key_norm) + if attr_ids is not None: + qry = qry.filter(ResourceAttr.attr_id.in_(attr_ids)) + resource_attrs = qry.all() elif ref_key_norm == 'NETWORK': - results = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ + qry = db.DBSession.query(ResourceAttr, Attr.name, Attr.id, Attr.description).\ join(Attr, Attr.id == ResourceAttr.attr_id).\ filter( ResourceAttr.network_id == network_id, - ResourceAttr.ref_key == ref_key_norm).all() - resource_attrs = results - - if template_id is not None: - attr_ids = [] - rs = db.DBSession.query(TypeAttr).join( - TemplateType, - TemplateType.id == TypeAttr.type_id).filter( - TemplateType.template_id == template_id).all() - for r in rs: - attr_ids.append(r.attr_id) - - resource_attrs = [ra for ra in resource_attrs if ra[0].attr_id in attr_ids] + ResourceAttr.ref_key == ref_key_norm) + if attr_ids is not None: + qry = qry.filter(ResourceAttr.attr_id.in_(attr_ids)) + resource_attrs = qry.all() # Convert results to JSONObjects with attribute data included result_objects = [] From 0fb6c75a2814170b2acbf9e0580a1b6387789a78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:54:29 +0000 Subject: [PATCH 10/11] Add tests for get_all_attributes_in_network: basic, deduplication, and permissions Co-authored-by: knoxsp <9108420+knoxsp@users.noreply.github.com> --- tests/test_network.py | 63 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/test_network.py b/tests/test_network.py index 5953a7fc..5eb179bb 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -167,6 +167,69 @@ def test_get_all_resource_attributes_in_network(self, client, network_with_data) #Find the attribute that ALL nodes have. assert len(all_network_resource_attrs) == len(network_with_data.nodes) + def test_get_all_attributes_in_network(self, client, network_with_data): + """ + Test that get_all_attributes_in_network returns unique attribute + definitions (not resource attributes) for all resource types in the + network: network-level, nodes, links, and groups. + """ + net = network_with_data + + all_attrs = client.get_all_attributes_in_network(net.id) + + assert len(all_attrs) > 0 + + # Collect all attr_ids actually used across every resource type in the network + expected_attr_ids = set() + for ra in net.attributes: + expected_attr_ids.add(ra.attr_id) + for node in net.nodes: + for ra in node.attributes: + expected_attr_ids.add(ra.attr_id) + for link in net.links: + for ra in link.attributes: + expected_attr_ids.add(ra.attr_id) + for group in net.resourcegroups: + for ra in group.attributes: + expected_attr_ids.add(ra.attr_id) + + returned_attr_ids = {a.id for a in all_attrs} + + # Every attribute used in the network should be in the result + assert expected_attr_ids == returned_attr_ids + + def test_get_all_attributes_in_network_deduplication(self, client, network_with_data): + """ + Test that get_all_attributes_in_network de-duplicates attribute + definitions that appear on multiple resources (e.g. the same attr_id + shared by all nodes or shared between links and groups). + """ + net = network_with_data + + all_attrs = client.get_all_attributes_in_network(net.id) + + # Each attribute definition must appear exactly once + returned_ids = [a.id for a in all_attrs] + assert len(returned_ids) == len(set(returned_ids)), ( + "Duplicate attribute IDs found in get_all_attributes_in_network result" + ) + + def test_get_all_attributes_in_network_permissions(self, client, projectmaker, networkmaker): + """ + Test that a user without read permission on the network cannot call + get_all_attributes_in_network. + """ + # Create a project that is NOT shared with other users + private_proj = projectmaker.create(name=None, share=False) + net = networkmaker.create(project_id=private_proj.id) + + # UserD has not been granted access to this private network/project + client.login('UserD', 'password') + try: + with pytest.raises(hb.exceptions.HydraError): + client.get_all_attributes_in_network(net.id) + finally: + client.login('root', '') def test_get_network_1(self, client, networkmaker): """ From c798865b0dd8281366463fec4a1f1ac914fed487 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 12:18:59 +0000 Subject: [PATCH 11/11] Use get_template() to resolve attr_ids with inheritance support Co-authored-by: knoxsp <9108420+knoxsp@users.noreply.github.com> --- hydra_base/lib/attributes.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/hydra_base/lib/attributes.py b/hydra_base/lib/attributes.py index 5b49c460..15ef1744 100644 --- a/hydra_base/lib/attributes.py +++ b/hydra_base/lib/attributes.py @@ -1259,15 +1259,13 @@ def get_all_resource_attributes(ref_key, network_id, template_id=None, **kwargs) ref_key_norm = ref_key.upper() - # If a template_id is provided, retrieve the relevant attr_ids up front - # so the filter can be applied at the SQL level in each query branch below. + # If a template_id is provided, resolve attr_ids via get_template() so that + # inherited type attributes (from parent templates/types) are included. attr_ids = None if template_id is not None: - attr_ids = db.DBSession.query(TypeAttr.attr_id).join( - TemplateType, - TemplateType.id == TypeAttr.type_id).filter( - TemplateType.template_id == template_id).all() - attr_ids = [r.attr_id for r in attr_ids] + import hydra_base.lib.template as templatelib + template = templatelib.get_template(template_id, **kwargs) + attr_ids = list({ta.attr_id for tt in template.templatetypes for ta in tt.typeattrs}) if not attr_ids: return []