aboutsummaryrefslogtreecommitdiff
path: root/venv/lib/python3.8/site-packages/dash/_grouping.py
diff options
context:
space:
mode:
authorsotech117 <michael_foiani@brown.edu>2025-07-31 17:27:24 -0400
committersotech117 <michael_foiani@brown.edu>2025-07-31 17:27:24 -0400
commit5bf22fc7e3c392c8bd44315ca2d06d7dca7d084e (patch)
tree8dacb0f195df1c0788d36dd0064f6bbaa3143ede /venv/lib/python3.8/site-packages/dash/_grouping.py
parentb832d364da8c2efe09e3f75828caf73c50d01ce3 (diff)
add code for analysis of data
Diffstat (limited to 'venv/lib/python3.8/site-packages/dash/_grouping.py')
-rw-r--r--venv/lib/python3.8/site-packages/dash/_grouping.py250
1 files changed, 250 insertions, 0 deletions
diff --git a/venv/lib/python3.8/site-packages/dash/_grouping.py b/venv/lib/python3.8/site-packages/dash/_grouping.py
new file mode 100644
index 0000000..7271d97
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/dash/_grouping.py
@@ -0,0 +1,250 @@
+"""
+This module contains a collection of utility function for dealing with property
+groupings.
+
+Terminology:
+
+For the purpose of grouping and ungrouping, tuples/lists and dictionaries are considered
+"composite values" and all other values are considered "scalar values".
+
+A "grouping value" is either composite or scalar.
+
+A "schema" is a grouping value that can be used to encode an expected grouping
+structure
+
+"""
+from .exceptions import InvalidCallbackReturnValue
+from ._utils import AttributeDict, stringify_id
+
+
+def flatten_grouping(grouping, schema=None):
+ """
+ Convert a grouping value to a list of scalar values
+
+ :param grouping: grouping value to flatten
+ :param schema: If provided, a grouping value representing the expected structure of
+ the input grouping value. If not provided, the grouping value is its own schema.
+ A schema is required in order to be able treat tuples and dicts in the input
+ grouping as scalar values.
+
+ :return: list of the scalar values in the input grouping
+ """
+ stack = []
+ result = []
+
+ # Avoid repeated recursive Python calls by using an explicit stack
+ push = stack.append
+ pop = stack.pop
+
+ # Only validate once at the top if schema is provided
+ if schema is None:
+ schema = grouping
+ else:
+ validate_grouping(grouping, schema)
+
+ push((grouping, schema))
+ while stack:
+ group, sch = pop()
+ # Inline isinstance checks for perf
+ typ = type(sch)
+ if typ is tuple or typ is list:
+ # Avoid double recursion / excessive list construction
+ for ge, se in zip(group, sch):
+ push((ge, se))
+ elif typ is dict:
+ for k in sch:
+ push((group[k], sch[k]))
+ else:
+ result.append(group)
+ result.reverse() # Since we LIFO, leaf values are in reverse order
+ return result
+
+
+def grouping_len(grouping):
+ """
+ Get the length of a grouping. The length equal to the number of scalar values
+ contained in the grouping, which is equivalent to the length of the list that would
+ result from calling flatten_grouping on the grouping value.
+
+ :param grouping: The grouping value to calculate the length of
+ :return: non-negative integer
+ """
+ if isinstance(grouping, (tuple, list)):
+ return sum([grouping_len(group_el) for group_el in grouping])
+
+ if isinstance(grouping, dict):
+ return sum([grouping_len(group_el) for group_el in grouping.values()])
+
+ return 1
+
+
+def make_grouping_by_index(schema, flat_values):
+ """
+ Make a grouping like the provided grouping schema, with scalar values drawn from a
+ flat list by index.
+
+ Note: Scalar values in schema are not used
+
+ :param schema: Grouping value encoding the structure of the grouping to return
+ :param flat_values: List of values with length matching the grouping_len of schema.
+ Elements of flat_values will become the scalar values in the resulting grouping
+ """
+
+ def _perform_make_grouping_like(value, next_values):
+ if isinstance(value, (tuple, list)):
+ return list(
+ _perform_make_grouping_like(el, next_values)
+ for i, el in enumerate(value)
+ )
+
+ if isinstance(value, dict):
+ return {
+ k: _perform_make_grouping_like(v, next_values)
+ for i, (k, v) in enumerate(value.items())
+ }
+
+ return next_values.pop(0)
+
+ if not isinstance(flat_values, list):
+ raise ValueError(
+ "The flat_values argument must be a list. "
+ f"Received value of type {type(flat_values)}"
+ )
+
+ expected_length = len(flatten_grouping(schema))
+ if len(flat_values) != expected_length:
+ raise ValueError(
+ f"The specified grouping pattern requires {expected_length} "
+ f"elements but received {len(flat_values)}\n"
+ f" Grouping pattern: {repr(schema)}\n"
+ f" Values: {flat_values}"
+ )
+
+ return _perform_make_grouping_like(schema, list(flat_values))
+
+
+def map_grouping(fn, grouping):
+ """
+ Map a function over all of the scalar values of a grouping, maintaining the
+ grouping structure
+
+ :param fn: Single-argument function that accepts and returns scalar grouping values
+ :param grouping: The grouping to map the function over
+ :return: A new grouping with the same structure as input grouping with scalar
+ values updated by the input function.
+ """
+ if isinstance(grouping, (tuple, list)):
+ return [map_grouping(fn, g) for g in grouping]
+
+ if isinstance(grouping, dict):
+ return AttributeDict({k: map_grouping(fn, g) for k, g in grouping.items()})
+
+ return fn(grouping)
+
+
+def make_grouping_by_key(schema, source, default=None):
+ """
+ Create a grouping from a schema by using the schema's scalar values to look up
+ items in the provided source object.
+
+ :param schema: A grouping of potential keys in source
+ :param source: Dict-like object to use to look up scalar grouping value using
+ scalar grouping values as keys
+ :param default: Default scalar value to use if grouping scalar key is not present
+ in source
+ :return: grouping
+ """
+ return map_grouping(lambda s: source.get(s, default), schema)
+
+
+class SchemaTypeValidationError(InvalidCallbackReturnValue):
+ def __init__(self, value, full_schema, path, expected_type):
+ super().__init__(
+ msg=f"""
+ Schema: {full_schema}
+ Path: {repr(path)}
+ Expected type: {expected_type}
+ Received value of type {type(value)}:
+ {repr(value)}
+ """
+ )
+
+ @classmethod
+ def check(cls, value, full_schema, path, expected_type):
+ if not isinstance(value, expected_type):
+ raise SchemaTypeValidationError(value, full_schema, path, expected_type)
+
+
+class SchemaLengthValidationError(InvalidCallbackReturnValue):
+ def __init__(self, value, full_schema, path, expected_len):
+ super().__init__(
+ msg=f"""
+ Schema: {full_schema}
+ Path: {repr(path)}
+ Expected length: {expected_len}
+ Received value of length {len(value)}:
+ {repr(value)}
+ """
+ )
+
+ @classmethod
+ def check(cls, value, full_schema, path, expected_len):
+ if len(value) != expected_len:
+ raise SchemaLengthValidationError(value, full_schema, path, expected_len)
+
+
+class SchemaKeysValidationError(InvalidCallbackReturnValue):
+ def __init__(self, value, full_schema, path, expected_keys):
+ super().__init__(
+ msg=f"""
+ Schema: {full_schema}
+ Path: {repr(path)}
+ Expected keys: {expected_keys}
+ Received value with keys {set(value.keys())}:
+ {repr(value)}
+ """
+ )
+
+ @classmethod
+ def check(cls, value, full_schema, path, expected_keys):
+ if set(value.keys()) != set(expected_keys):
+ raise SchemaKeysValidationError(value, full_schema, path, expected_keys)
+
+
+def validate_grouping(grouping, schema, full_schema=None, path=()):
+ """
+ Validate that the provided grouping conforms to the provided schema.
+ If not, raise a SchemaValidationError
+ """
+ if full_schema is None:
+ full_schema = schema
+
+ if isinstance(schema, (tuple, list)):
+ SchemaTypeValidationError.check(grouping, full_schema, path, (tuple, list))
+ SchemaLengthValidationError.check(grouping, full_schema, path, len(schema))
+
+ for i, (g, s) in enumerate(zip(grouping, schema)):
+ validate_grouping(g, s, full_schema=full_schema, path=path + (i,))
+ elif isinstance(schema, dict):
+ SchemaTypeValidationError.check(grouping, full_schema, path, dict)
+ SchemaKeysValidationError.check(grouping, full_schema, path, set(schema))
+ for k in schema:
+ validate_grouping(
+ grouping[k], schema[k], full_schema=full_schema, path=path + (k,)
+ )
+ else:
+ pass
+
+
+def update_args_group(g, triggered):
+ if isinstance(g, dict):
+ str_id = stringify_id(g["id"])
+ prop_id = f"{str_id}.{g['property']}"
+
+ new_values = {
+ "value": g.get("value"),
+ "str_id": str_id,
+ "triggered": prop_id in triggered,
+ "id": AttributeDict(g["id"]) if isinstance(g["id"], dict) else g["id"],
+ }
+ g.update(new_values)