graflo.util.transform¶
Data transformation utilities for graph operations.
This module provides utility functions for transforming and standardizing data in various formats, particularly for graph database operations. It includes functions for date parsing, string standardization, and data cleaning.
Key Functions
- standardize: Standardize string keys and names
- parse_date_*: Various date parsing functions for different formats
- cast_ibes_analyst: Parse and standardize analyst names
- clear_first_level_nones: Clean dictionaries by removing None values
- parse_multi_item: Parse complex multi-item strings
- pick_unique_dict: Remove duplicate structures by content hash
- affix_gated_key: Admit a value only when it carries its marker affixes
Example
name = standardize("John. Doe, Smith") date = parse_date_standard("2023-01-01") analyst = cast_ibes_analyst("ADKINS/NARRA")
Attributes¶
ORDINAL_SUFFIX = ['st', 'nd', 'rd', 'th']
module-attribute
¶
T = TypeVar('T')
module-attribute
¶
logger = logging.getLogger(__name__)
module-attribute
¶
Functions:¶
affix_gated_key(value, *, prefix='', suffix='', casefold=True, strip_chars=None)
¶
Return value stripped of its marker affixes, or None if unmarked.
The affixes are the admission test, not a best-effort cleanup: a value
carrying them is stripped and accepted as canonical key material, and a
value missing either one yields None. Contrast
:func:gated_normalized_key, which gates on a sibling field and whose
strip_prefix is a silent no-op when absent — there, marked and unmarked
values normalize to the same key and fuse.
Both affixes are required when given: a marker can be a prefix (ext_), a
suffix (-legacy), or the pair that brackets a key. Each defaults to
"", which every string carries, so naming one leaves the other
unconstrained and naming neither admits everything while stripping nothing —
the convention that lets a source participating unconditionally reuse the
same function, and thus the same normal form, as one that is filtered.
None is a fall-through, not a drop. It is an empty value to identity
digests, so an identity-funnel branch listing the output field is skipped
and the record lands on its side-local branch — still ingested, just not
into the cross-source cluster.
The marker test is case-sensitive even when casefold is set: casefolding applies to the surviving key, after the affixes have been removed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str | None
|
Raw key material, expected to carry the affixes. |
required |
prefix
|
str
|
Leading marker admitting value; removed from it. |
''
|
suffix
|
str
|
Trailing marker admitting value; removed from it. |
''
|
casefold
|
bool
|
Casefold the surviving key. |
True
|
strip_chars
|
str | None
|
Characters stripped from both ends of value before the
affixes are tested ( |
None
|
Returns:
| Type | Description |
|---|---|
str | None
|
The normalized key, or |
str | None
|
affix, or is empty once both are removed. |
Source code in graflo/util/transform.py
camel_to_snake(s)
cached
¶
Convert camelCase/PascalCase names to snake_case.
Source code in graflo/util/transform.py
cast_ibes_analyst(s)
¶
Splits and normalizes analyst name strings.
Handles various name formats like 'ADKINS/NARRA' or 'ARFSTROM J'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
s
|
str
|
Analyst name string. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple[str, str]
|
(last_name, first_initial) |
Examples:
>>> cast_ibes_analyst('ADKINS/NARRA')
('ADKINS', 'N')
>>> cast_ibes_analyst('ARFSTROM J')
('ARFSTROM', 'J')
Source code in graflo/util/transform.py
clear_first_level_nones(docs, keys_keep_nones=None)
¶
Removes None values from dictionaries, with optional key exceptions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
docs
|
list
|
List of dictionaries to clean. |
required |
keys_keep_nones
|
list
|
Keys to keep even if their value is None. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[dict]
|
Cleaned list of dictionaries. |
Example
docs = [{"a": 1, "b": None}, {"a": None, "b": 2}] clear_first_level_nones(docs, keys_keep_nones=["a"]) [{"a": 1}, {"a": None, "b": 2}]
Source code in graflo/util/transform.py
coalesce_fields(doc, *, fields)
¶
First non-empty value among fields on doc, or None.
The branch selector for the column-presence form of a routed derivation.
When one resource derives a canonical attribute several ways — one per
class its vertex_router collapses onto the aligned class, each keying
from its own column — each derivation writes its own scratch field and
returns None for the branches it does not serve. This picks the one
that fired. (A derivation keyed by member needs none of this: its step
carries a when guard and writes the attribute directly.)
A single writer per canonical attribute is the point. Two steps writing the
same key work on a plain vertex step, whose buffer extraction skips
None, but not behind a vertex_router: the router merges the buffer
into one observation dict, where a later None overwrites an earlier
real value.
Called with strategy: all, so a branch whose own columns are absent
from the document skips without taking the coalesce down with it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc
|
dict[str, Any]
|
The merged observation. |
required |
fields
|
list[str]
|
Scratch field names, in priority order. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The first present, non-empty value, or |
Source code in graflo/util/transform.py
gated_normalized_key(gate, value, *, prefix, strip_prefix=None, casefold=True, strip_chars=None)
¶
Return the normalized value when gate starts with prefix, else None.
Designed for conditional entity equivalence: emit a shared match key only
for records that participate in it. None is treated as an empty value
by identity digests, so an identity-funnel branch listing the output field
is skipped and the record falls through to its side-local branch.
An empty prefix makes the gate always pass, which lets the other side of an equivalence reuse the same function (and thus the same normal form) unconditionally.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gate
|
str | None
|
Field deciding participation (e.g. |
required |
value
|
str | None
|
Raw key material to normalize. |
required |
prefix
|
str
|
Required prefix of gate; |
required |
strip_prefix
|
str | None
|
Prefix removed from value when present. |
None
|
casefold
|
bool
|
Casefold the normalized value. |
True
|
strip_chars
|
str | None
|
Characters stripped from both ends of value
( |
None
|
Returns:
| Type | Description |
|---|---|
str | None
|
The normalized key, or |
str | None
|
the gate does not match. |
Source code in graflo/util/transform.py
gated_tagged_key(gate, value, *, tag, sep=':', prefix='')
¶
:func:tagged_key behind a gate, for a routed source.
When one resource contributes several side-local keys — one per class its
vertex_router collapses onto the aligned class — the router's
discriminator selects which one applies. None when the gate does not
match, which is an empty value to identity digests. This is the explicit,
hand-written form (LocalKeySource.gate); a source keyed by member gets
its gate derived from the router as a when guard on the step instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gate
|
str | None
|
Field deciding which branch this document is (the discriminator). |
required |
value
|
object
|
The side-local key material. |
required |
tag
|
str | None
|
Namespace prefix identifying the branch; empty for none. |
required |
sep
|
str
|
Separator between tag and the key. |
':'
|
prefix
|
str
|
Required prefix of gate; |
''
|
Returns:
| Type | Description |
|---|---|
str | None
|
|
str | None
|
missing or empty. |
Source code in graflo/util/transform.py
parse_date_conf(input_str)
¶
Parse a date string in YYYYMMDD format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_str
|
str
|
Date string in YYYYMMDD format. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple[int, int, int]
|
(year, month, day) as integers. |
Example
parse_date_conf("20230101") (2023, 1, 1)
Source code in graflo/util/transform.py
parse_date_ibes(date0, time0)
¶
Converts IBES date and time to ISO 8601 format datetime.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
date0
|
str / int
|
Date in YYYYMMDD format. |
required |
time0
|
str
|
Time in HH:MM:SS format. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Datetime in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ). |
Example
parse_date_ibes(20160126, "9:35:52") '2016-01-26T09:35:52Z'
Source code in graflo/util/transform.py
parse_date_reference(input_str)
¶
Extract year from a date reference string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_str
|
str
|
Date reference string. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
int |
int | str
|
Year from the date reference. |
Example
parse_date_reference("1923, May 10") 1923
Source code in graflo/util/transform.py
parse_date_standard(input_str)
¶
Parse a date string in YYYY-MM-DD format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_str
|
str
|
Date string in YYYY-MM-DD format. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple[int, int, int]
|
(year, month, day) as integers. |
Example
parse_date_standard("2023-01-01") (2023, 1, 1)
Source code in graflo/util/transform.py
parse_date_standard_to_epoch(input_str)
¶
Convert standard date string to Unix epoch timestamp.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_str
|
str
|
Date string in YYYY-MM-DD format. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
float |
float
|
Unix epoch timestamp. |
Example
parse_date_standard_to_epoch("2023-01-01") 1672531200.0
Source code in graflo/util/transform.py
parse_date_yahoo(date0)
¶
Convert Yahoo Finance date to ISO 8601 format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
date0
|
str
|
Date in YYYY-MM-DD format. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Datetime in ISO 8601 format with noon time. |
Example
parse_date_yahoo("2023-01-01") '2023-01-01T12:00:00Z'
Source code in graflo/util/transform.py
parse_multi_item(s, mapper, direct)
¶
Parses complex multi-item strings into structured data.
Supports parsing strings with quoted or bracketed items.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
s
|
str
|
Input string to parse. |
required |
mapper
|
dict
|
Mapping of input keys to output keys. |
required |
direct
|
list
|
Direct keys to extract. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
defaultdict |
defaultdict[str, list]
|
Parsed items with lists as values. |
Example
Parsing '[name: John, age: 30] [name: Jane, age: 25]' with
mapper={"name": "full_name"} and direct=["age"] produces grouped
lists under each mapped key.
Source code in graflo/util/transform.py
pick_unique_dict(docs)
¶
Remove duplicate structures from a list by content hash.
Uses a hash-based approach that handles nested dicts, lists/tuples, datetime objects, and Decimal types. Preserves original objects and insertion order. Works for vertex dicts and edge triples alike.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
docs
|
list[T]
|
List of structures to deduplicate. |
required |
Returns:
| Type | Description |
|---|---|
list[T]
|
List of unique structures (preserving original objects). |
Example
docs = [{"a": 1}, {"a": 1}, {"b": 2}] pick_unique_dict(docs) [{"a": 1}, {"b": 2}]
Source code in graflo/util/transform.py
remove_prefix(s, prefix)
cached
¶
remove_suffix(s, suffix)
cached
¶
round_str(x, **kwargs)
¶
Round a string number to specified precision.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
str
|
String representation of a number. |
required |
**kwargs
|
Additional arguments for round() function. |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
float |
float
|
Rounded number. |
Example
round_str("3.14159", ndigits=2) 3.14
Source code in graflo/util/transform.py
snake_to_camel(s, upper_first=False)
cached
¶
Convert snake_case names to camelCase or PascalCase.
Source code in graflo/util/transform.py
split_keep_part(s, sep='/', keep=-1)
¶
Split a string and keep specified parts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
s
|
str
|
String to split. |
required |
sep
|
str
|
Separator to split on. |
'/'
|
keep
|
int or list
|
Index or indices to keep. |
-1
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Joined string of kept parts. |
Example
split_keep_part("a/b/c", keep=0) 'a' split_keep_part("a/b/c", keep=[0, 2]) 'a/c'
Source code in graflo/util/transform.py
standardize(k)
¶
Standardizes a string key by removing periods and splitting.
Handles comma and space-separated strings, normalizing their format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k
|
str
|
Input string to be standardized. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Cleaned and standardized string. |
Example
standardize("John. Doe, Smith") 'John,Doe,Smith' standardize("John Doe Smith") 'John,Doe,Smith'
Source code in graflo/util/transform.py
tagged_key(value, *, tag, sep=':')
¶
Namespace a side-local key: tag "a" turns "f2" into "a:f2".
None/empty value returns None, which is an empty value to
identity digests — the funnel branch listing the output field is skipped.
Disambiguation is resource knowledge: each resource tags its own local
keys, so a class-level fallback identity stays side-agnostic while
cross-resource collisions become impossible. Normalization beyond a
whitespace strip is a separate concern — compose another transform step.
An empty tag ("" or None) is the neutral element: the key is
returned as-is, with no separator. That is for values already unique
across every source of the class — a UUID, an IRI, an id the source itself
prefixes — where a namespace would only be noise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
object
|
The side-local key material. |
required |
tag
|
str | None
|
Namespace prefix identifying the resource/side; empty for none. |
required |
sep
|
str
|
Separator between tag and the key. |
':'
|
Returns:
| Type | Description |
|---|---|
str | None
|
|
str | None
|
|
Source code in graflo/util/transform.py
try_int(x)
¶
Attempt to convert a value to integer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Value to convert. |
required |
Returns:
| Type | Description |
|---|---|
|
int or original value: Integer if conversion successful, original value otherwise. |
Example
try_int("123") 123 try_int("abc") 'abc'