Faker¶
Faker is the value engine. Once the analyzer and the factory have decided what a column should hold, Faker is almost always what produces the actual value, both inside the tool at codegen time (for a handful of scaffolding strings) and, far more importantly, inside the generated generator code that the tool emits and hands back to you.
Faker calls are the output, not just a dependency¶
src/test_data_workbench/adaptation/generator_factory.py does not call
Faker itself to produce data. It writes Python source text that calls Faker,
via Jinja2 templates (see Jinja2). The distinction matters for
Design Principles: Faker is a runtime dependency of the
generated project, not a hidden implementation detail that disappears once
generation is done. A generated UsersGenerator imports faker and calls
it directly, in code you can read.
Two layers pick the Faker method¶
The factory decides which Faker call to emit through two layers, checked in
order, both visible in _get_column_generator and the entity-specific
generator methods:
Column-name patterns first. A column named anything containing
email, phone, name, address, city, country, or
url gets the matching Faker call regardless of its declared SQL type:
if 'email' in col_name_lower:
return 'self.fake.email()'
elif 'phone' in col_name_lower:
return 'self.fake.phone_number()'
elif 'name' in col_name_lower:
return 'self.fake.name()'
...
Declared type second. When no name pattern matches, faker_mapping
maps the column’s SQL type substring to a Faker method: varchar/text
to self.fake.text, integer/bigint to self.fake.random_int,
decimal/numeric to self.fake.pydecimal, boolean to
self.fake.boolean, date to self.fake.date, timestamp to
self.fake.date_time, uuid to self.fake.uuid4. A generic
varchar/text type is deferred to name-based temporal detection first
(_temporal_kind), because SQLite in particular stores dates with TEXT
affinity and the declared type alone can’t be trusted to mean “just text.”
Both layers are the same fallback-ladder philosophy from
The Fallback Ladder applied at column granularity: specific
signal (the name) beats generic signal (the type), and there is always a
default (self.fake.text(max_nb_chars=50)) so a column with no useful name
or type still gets something plausible.
Entity-specific Faker usage¶
The specialized generators layer on top of the generic mapping with
domain-appropriate calls: self.fake.catch_phrase() for product names,
self.fake.text(max_nb_chars=200) for descriptions,
self.fake.text(max_nb_chars=300) for review comments. These are choices
about which Faker provider reads right for a field, not just which one
type-checks; a catch phrase looks more like a product name than generic lorem
ipsum text does.
Uniqueness is handled by hand, not by fake.unique¶
Generated user/customer generators need unique emails across a batch. Rather
than relying on Faker’s built-in .unique proxy, the emitted code tracks
what it has produced itself:
def _unique_email(self) -> str:
"""Generate unique email address."""
while True:
email = self.fake.email()
if email not in self._generated_emails:
self._generated_emails.add(email)
return email
This is deliberate: the generated code is meant to be self-contained and readable per Design Principles, and a loop-and-retry against a local set is easier for someone unfamiliar with Faker to read and modify than a stateful proxy object’s semantics. It is also exactly the uniqueness mechanism Test Data Principles describes as necessary for any uniquely-constrained column.
Determinism: Faker.seed(), not per-instance seeding¶
Both the tool’s own CLI seeding helper
(src/test_data_workbench/cli/_seeding.py) and every generated generator’s
__init__ seed the same way:
if seed is not None:
Faker.seed(seed)
random.seed(seed)
Faker.seed() is a classmethod: it seeds Faker’s shared random source
process-wide, not just one Faker() instance. That is what makes the
byte-for-byte reproducibility in Design Principles work
across a whole generated dataset, every generator instantiated afterward
draws from the same seeded state, not an independently-seeded one. _seeding.py
seeds once, at CLI entry, before any analysis or generation begins, which is
what closes the ordering leak: seeding after some Faker calls have already
consumed random state would make the run depend on how much work happened
before the seed was set.