Smart way to get attribute value of specific item

As in the subject - is there any smart way to get value of specific attribute of specific item? For example what is the value of attribute “size” of item “Shirt_420”? Right now I am getting all attributes for given item and iterate through them as long as I find the “size” attribute, but there must be a smarter way of doing it…

When you create a variant from template, the attribute values are stored in the Variant Attributes table in the Item master. You can retrieve these values using either a client-side or server-side script, depending on the scenario.

Thanks, this is exactly my question: how to retrieve it on server side in a smart way.
Something like this feels really clunky, non-pythonic and limiting e.g. in Jinja templates.

def get_attributes(item_code):
			card = frappe.get_doc("Item", item_code).as_dict()
			for attr in card["attributes"]:
				match attr["attribute"]:
					case "Condition":
						condition = attr["attribute_value"]
					# other attributes
			return finish

You want to set in print format or in the field or transaction. Please explain more.

Use case does not matter so much as the issue is the same everywhere: getting specific attribute value of a given item and finding item(s) that match specific attribute values seems to be overly complicated in ERPNext in comparison to other ERPs. Now i see that the challenge was also described 2 years ago: Python Query of Item by Attributes (get_all on child table values) - #6 by batonac
Has something changed since then? In this thread I asked about a solution that would ideally look like that:

attribute_value = frappe.get_attribute_value(item_code, attribute_name)

but I am also interested in use cases presented by author in that thread:

item = frappe.get_all(‘Item’, filters={
‘variant_of’: ‘Grand Teton’,
‘attributes’: {“Width - ft”: 12, “Length - ft”: 12, “Bents”: 2}
})

It seems like a really basic functionality for which most of developers will need to create own custom helper functions if they do not exist natively. But maybe something has changed in last two years?

One potential bypass that I am considering is to add custom fields to every item representing every attribute. So e.g. fields attribute_size and attribute_color that can be used in get_all. But it feels non-Pythonic.

Any ideas would be much appreciated.

To get the value of a specific attribute (like “Size”) for an item (like “Shirt_420”), you can create a simple function. Instead of looping through all attributes, this function directly finds and returns the value you need. Here’s an example:

def get_attribute_value(item_code, attribute_name):
    item = frappe.get_doc("Item", item_code)
    for attr in item.attributes:
        if attr.attribute == attribute_name:
            return attr.attribute_value
    return None

How to Use It:

If you want to find the “Size” of “Shirt_420,” just call:

size = get_attribute_value("Shirt_420", "Size")
print(size)

Thanks a lot. Is there any simple way to get all items with size “x” and color “red”?

Yes, you can get all items with specific attributes like size “x” and color “red” by using a custom function to filter items. Here’s a simple way to do it:

Example:

def get_items_by_attributes(size_value, color_value):
    items = frappe.get_all("Item", fields=["name"], filters={
        "variant_of": ["is", None]
    })

    matching_items = []
    for item in items:
        attributes = frappe.get_all("Item Variant Attribute", filters={
            "parent": item.name
        }, fields=["attribute", "attribute_value"])

        size_matches = any(attr.attribute == "Size" and attr.attribute_value == size_value for attr in attributes)
        color_matches = any(attr.attribute == "Color" and attr.attribute_value == color_value for attr in attributes)

        if size_matches and color_matches:
            matching_items.append(item.name)
    
    return matching_items

To find items with size “x” and color “red”:

items = get_items_by_attributes("x", "red")
print(items)
1 Like

The proposed solution is great, except that a query-based filtering will always be faster than post-processing in Python, which is why I prefer to get specifc results directly using the Frappe ORM or Query Builder.

For example:

import frappe

def get_items_by_attributes(attributes_dict):
    ItemVariantAttribute = frappe.qb.DocType("Item Variant Attribute")

    query = (
        frappe.qb.from_(ItemVariantAttribute)
        .select(ItemVariantAttribute.parent)
        .where(
            (ItemVariantAttribute.variant_of.isnotnull()) &
            (ItemVariantAttribute.attribute.isin(list(attributes_dict.keys()))) &
            (ItemVariantAttribute.attribute_value.isin(list(attributes_dict.values())))
        )
        .groupby(ItemVariantAttribute.parent)
        .having(frappe.query_builder.functions.Count('*') == len(attributes_dict))
    )

    return query.run(pluck="parent")

# Usage example:
attributes_to_match = {
    "Width - ft": 12,
    "Length - ft": 12
}
# or
attributes_to_match = {
    "Size": "Large",
    "Color": "Red"
}
matching_variants = get_items_by_attributes(attributes_to_match)

Here’s a ticket tracking some of the outstanding ORM needs/requests:
Frappe ORM: Issues #17844. Feel free to contribute to the conversation or vote with your reactions.

1 Like

@batonac TBH I have ended simply adding custom fields representing 3 attributes to every item from specific Item Group on creation through hooks. As attribute values rather do not change for given item, it is quite convenient as I can use native frape.get_all and if necessary in future I can index those fields to accelerate queries. Do you see any other disadvantages of this approach? We store >100k items so any optimizations are much appreciated.

I’ve marked your solution as “real” option as it is Item-agnostic

I don’t blame you for using the custom field approach… You’ve already described the benefits of this well. The main disadvantages of this approach (generally speaking) are lack of convention and scalability. Anytime you move outside of the established conventions of the software you increase your own management burden (data, docs, controls, etc), and the approach isn’t incredibly scalable for expanding the number of item attributes.

That’s not to suggest it’s the wrong approach, but just to try to answer the question of disadvantages. That said, the query I suggested is fairly rudimentary and benefits from the rigorous optimization of SQL code paths. I’d be surprised if you’d see anything but a negligible difference between a query like above and one utilizing your custom fields. It would be great if you could test it and let us know though.

Revisiting this… since the title of the thread is “Smart way to get attribute value of specific item”, I believe the actual answer should be:

frappe.db.get_value("Item Variant Attribute",{"parent": "[item code]","attribute": "[attribute name]"},"attribute_value")

Adapted to your original example, the following code should return the value Red (based on the assumption that the item name and item code are identical in this case):

frappe.db.get_value("Item Variant Attribute",{"parent": "Variant Item 1-RED-XS","attribute": "Colour"},"attribute_value")

I believe this is the most elegant and direct way Frappe ORM can directly return an attribute value for a specific item, and should be the answer in this thread.