graflo.architecture.schema.naming¶
Declared naming style for the identifiers a schema invents.
A schema names three kinds of thing, and only two of them are free choices.
Vertex types and relations are invented — nothing outside the schema decides
whether a customer concept is Customer, customer or customers.
Properties are not: a field name binds to a key in the source document, so
renaming one without also rewriting ingestion produces a column that never
populates.
That asymmetry does not stop properties having a convention — the documents
GraFlo ingests are JSON, so camelCase is the sensible default — but it does mean
styling them is a rewrite rather than a choice. Whoever sets a
property_case other than preserve owes the matching
transform.rename, which :meth:NamingConvention.rename_map computes.
Without it the schema declares customerEmail while the document still holds
customer_email, and the column populates with nothing, silently.
Declaring the convention is what makes it enforceable and reusable: an agent inferring a second schema against the same registry can read the style the first one was authored in rather than guessing, and a reviewer can tell a deliberate name from an accidental one.
Purely declarative, like :mod:~graflo.architecture.schema.semantics: nothing
in identity, storage naming or ingestion consults it. It records the intent that
produced the names; it does not rewrite them.
Merging schemas across conventions is the failure this module exists to
prevent. Matching vertices and edges by raw name means a manifest written in
PascalCase combined with one written in snake_case yields a graph carrying both
Customer and customer as unrelated types, with the data split between
them and nothing raising.
Both combining paths now compare on :func:canonical_key:
merge_manifests treats two spellings of one concept as a name collision
and applies its name_conflict policy to them, and three-way merge keys every
slot on the canonical form so the two sides conflict rather than diverging.
The scope is deliberately narrow — type names only. Property names are not
folded: a field name binds to a key in the source document, so treating
customer_email and customerEmail as one property would fuse two columns
fed by two different keys. Resource, connector and transform names are not
folded either; those are addresses looked up exactly, where two similar names
split nothing.
Attributes¶
DEFAULT_NAMING = NamingConvention()
module-attribute
¶
__all__ = ['DEFAULT_NAMING', 'NameCase', 'NamingConvention', 'apply_case', 'canonical_key', 'canonical_slug', 'convert', 'same_concept', 'singularize', 'split_words']
module-attribute
¶
Classes¶
NameCase
¶
Bases: BaseEnum
Surface style for a generated identifier.
PRESERVE means "whatever the source called it" and is meaningful only
for properties, where the name is not the schema's to choose.
Source code in graflo/architecture/schema/naming.py
Attributes¶
CAMEL = 'camel'
class-attribute
instance-attribute
¶
KEBAB = 'kebab'
class-attribute
instance-attribute
¶
PASCAL = 'pascal'
class-attribute
instance-attribute
¶
PRESERVE = 'preserve'
class-attribute
instance-attribute
¶
SNAKE = 'snake'
class-attribute
instance-attribute
¶
UPPER_SNAKE = 'upper_snake'
class-attribute
instance-attribute
¶
NamingConvention
¶
Bases: ConfigBaseModel
How a schema spells the identifiers it invents.
Defaults follow the convention GraFlo declares canonical: singular PascalCase vertex types, camelCase relations, and camelCase properties.
Source code in graflo/architecture/schema/naming.py
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 | |
Attributes¶
property_case = PydanticField(default=NameCase.CAMEL, description='Style for property names, e.g. ``customerEmail``. camelCase is the default because the documents GraFlo ingests and emits are JSON. **A property name binds to a key in the source document**, so any value other than ``preserve`` obliges the author to emit the rename that :meth:`NamingConvention.rename_map` computes — without it the column silently never populates.')
class-attribute
instance-attribute
¶
relation_case = PydanticField(default=NameCase.CAMEL, description='Style for edge relation names, e.g. ``placedBy``, ``reportsTo``. Matches the RDF/OWL object-property convention, so a relation grounded via ``semantics.iri`` reads the same either side.')
class-attribute
instance-attribute
¶
singular_vertex_names = PydanticField(default=True, description='Name a vertex for one instance rather than the collection: a resource called ``customers`` holds ``Customer`` records.')
class-attribute
instance-attribute
¶
vertex_case = PydanticField(default=NameCase.PASCAL, description='Style for vertex type names, e.g. ``Customer``, ``OrderLine``.')
class-attribute
instance-attribute
¶
Methods:¶
describe()
¶
One-line human- and LLM-readable statement of the convention.
Source code in graflo/architecture/schema/naming.py
prop(name)
¶
relation(name)
¶
rename_map(source_names)
¶
Source field name -> convention-styled name, for names that change.
The other half of a non-preserve property_case. Declaring a
property as customerEmail when the document key is
customer_email produces a column that never populates and no error,
so the convention is only safe when this map is emitted alongside it as
a transform.rename.
Entries are omitted where the name is already in the target style, so the resulting transform is empty exactly when no rewriting is needed.
Source code in graflo/architecture/schema/naming.py
vertex(name)
¶
Render a vertex type name in this convention.
Applies the singular rule it declares: only the last word is
singularized, so order_lines becomes OrderLine and not
OrderLine via a mangled order.
Source code in graflo/architecture/schema/naming.py
Functions:¶
apply_case(name, case)
¶
Render name in case. PRESERVE returns it untouched.
Accepts the raw string as well as the enum member: ConfigBaseModel sets
use_enum_values=True, so a field declared NameCase holds "pascal"
once validated. Comparing by identity works only for unvalidated defaults,
which is the kind of difference that passes every test written against a
default-constructed object and fails on the first configured one.
Source code in graflo/architecture/schema/naming.py
canonical_key(name)
¶
Convention-independent identity of a name, as its lowercase words.
Customer, customer, customers and CUSTOMER all key to
("customer",); OrderLine, order_line and orderLine all key
to ("order", "line").
This is what merges must compare on. Schema merge — GraFlo's
evolution ops, and any agent extending a stored manifest — matches vertices
and edges by name. Two manifests authored under different conventions
therefore merge into a graph holding both Customer and customer as
unrelated types, with the source data split between them and nothing
raising. Comparing canonical keys is what detects that; the trailing plural
is folded too, since Customers and Customer are one concept in every
schema anyone means to write.
Source code in graflo/architecture/schema/naming.py
canonical_slug(name)
¶
:func:canonical_key as one string, for dict keys and messages.
Falls back to the lowercased name when the words cannot be recovered, so the result is always a usable key.
Source code in graflo/architecture/schema/naming.py
convert(name, to_case)
¶
Restyle an identifier into to_case, whatever it is currently in.
No source convention need be given: :func:split_words recovers the words
from any of the styles this module emits, so conversion is one-way by
construction and cannot be told the wrong origin.
Source code in graflo/architecture/schema/naming.py
same_concept(left, right)
¶
Whether two identifiers denote the same concept under any convention.
singularize(word)
¶
Crude singular of one lowercase word.
Covers the regular English plurals that appear in table and column names.
Where a suffix is genuinely ambiguous — analyses is either analysis
or analyse, houses is house — it takes the conservative branch
and strips only the s.
That bias is deliberate. This decides what a vertex type is called, and
the two failure modes are not equal: under-stripping leaves a recognisable
English word, while over-stripping coins a non-word (Analys) and renames
the concept to something no other source will ever match.
Source code in graflo/architecture/schema/naming.py
split_words(name)
¶
Break an identifier into lowercase words.
Handles the forms that actually turn up in source data and in model output:
customerEmail, customer_email, Customer-Email, CUSTOMER_EMAIL.