Working with attributes

This is a collection of recipes for working with attributes.

Reading attributes

One way of reading attributes is while iterating the results of a query:

with db.transaction() as tr:
    with tr.query_data(...) as cursor:
        for rec in cursor:
            # Print the variable code and value
            print(f"{rec['var']}:", rec["variable"])

            # This returns a Dict[str, wreport.Var]
            attrs = rec.query_attrs()

            # Print all the attributes
            for name, value in attrs.items():
                print(f"  {name}:", value)

Note that, since they are not always needed, attributes are not retrieved by default in queries, and running query_attrs will make a new query to retrieve them, potentially adding a database query for each resulting variable.

If you know you are going to also need the attributes, you can add query=attrs to the query, and attributes will be read together with variable data, resulting in a more efficient query:

with db.transaction() as tr:
    with tr.query_data({"query": "attrs"}) as cursor:
        for rec in cursor:
            # Print the variable code and value
            print(f"{rec['var']}:", rec["variable"])

            # This returns a Dict[str, wreport.Var]
            # and since query=attrs was used, it won't make an extra
            database query:
            attrs = rec.query_attrs()

            # Print all the attributes
            for name, value in attrs.items():
                print(f"  {name}:", value)

If you are not processing attributes together as you iterate data, you can use the context_id output parameter (see Parameters used when reading query results for data values) to store references to variables, that you can use to access their attributes at any later time:

interesting_vars = []
with db.transaction() as tr:
    with tr.query_data(...) as cursor:
        for rec in cursor:
            if ...:
                interesting_vars.append(rec["context_id")

    for context_id in interesting_vars:
        attrs = tr.attr_query_data(contex_id)
        if attrs["B33196"].enqi() == 1:
            ...

Although querying attributes separately is often not needed, this possibility becomes useful when modifying attributes. See below.

Updating attributes

You can insert or remove attributes while iterating the results of a query:

with db.transaction() as tr:
    with tr.query_data({"var": "B12101"}) as cursor:
        for rec in cursor:
            # Check that the temperature is within a given range
            if rec.enqd() < 150 or req.enqd() > 310:
                # If it's not, add/update "Data has been invalidated"...
                rec.insert_attrs({"B33196": 1})
                # ...and remove "Percent confidence"
                rec.remove_attrs(["B33007"])

If you are not processing attributes together as you iterate data, you can use the context_id output parameter (see Parameters used when reading query results for data values) to store references to variables, that you can use to work on their attributes at a later time.

For example, this identifies sequences of consecutive data whose values never change, and invalidates all of them except the last one (see issue227):

invalid_list = []
prev = None
with db.transaction() as tr:
    for row in tr.query_data(....):
        value = row["variable"].get()
        if prev == value:
            invalid_list.append(row["context_id"])

        prev = value

    for ctxid in invalid_list:
        tr.attr_insert_data(ctxid, {"B33196": 1})
        tr.attr_remove_data(ctxid, ["B33007"])