semolina.fields

Field descriptors for Semolina semantic models.

Descriptor classes for defining typed fields in semantic view models. Fields use the descriptor protocol for class-level access and validation.

Classes

NullsOrdering

How to position NULL values in ORDER BY clause.

Field

Base descriptor for semantic view fields.

Metric

Descriptor for metric fields (aggregatable measures).

Dimension

Descriptor for dimension fields (groupable attributes).

Fact

Descriptor for fact fields (raw numeric values).

OrderTerm

Wrapper specifying sort direction and NULL handling for a field in order_by().

Module Contents

class semolina.fields.NullsOrdering[source]

Bases: enum.Enum

How to position NULL values in ORDER BY clause.

FIRST[source]

NULLS FIRST - NULL values appear before non-NULL values

LAST[source]

NULLS LAST - NULL values appear after non-NULL values

DEFAULT[source]

No NULLS clause - let backend decide

class semolina.fields.Field(source: str | None = None)[source]

Bases: Generic[T]

Base descriptor for semantic view fields.

Fields are descriptors that: - Validate field names at class creation time (__set_name__) - Return themselves for class-level access (Sales.revenue) - Prevent instance-level access, modification, and deletion - Support Python comparison operators to create Predicate filters

Operators:

Field descriptors support Python comparison operators that return typed Predicate nodes:

Users.country == 'US'      # -> Exact(field_name='country', value='US')
Users.country != 'CA'      # -> NotEqual(field_name='country', value='CA')
Users.revenue > 1000       # -> Gt(field_name='revenue', value=1000)
Users.revenue >= 1000      # -> Gte(...)
Users.revenue < 100        # -> Lt(...)
Users.revenue <= 100       # -> Lte(...)

Combine with & (AND) and | (OR):

(Users.country == 'US') | (Users.country == 'CA')  # -> Or(...)
(Users.revenue > 1000) & (Users.country == 'US')   # -> And(...)
Named filter methods:

Additional filter methods for common SQL operations:

Users.country.in_(['US', 'CA'])      # -> In(...)
Users.revenue.between(100, 1000)     # -> Between(...)
Users.name.like('%Smith%')            # -> Like(...)
Users.name.ilike('%smith%')           # -> ILike(...)
Users.name.startswith('A')            # -> StartsWith(...)
Users.name.endswith('son')            # -> EndsWith(...)
Users.country.iexact('united states') # -> IExact(...)
Users.country.isnull()                # -> IsNull(...)
between(lo: Any, hi: Any) → semolina.filters.Between[source]

Create range predicate: field BETWEEN lo AND hi.

Parameters:
  • lo – Lower bound (inclusive).

  • hi – Upper bound (inclusive).

Returns:

Between predicate with value as (lo, hi) tuple.

Raises:

TypeError – If either bound is None. For an open-ended range, use >= or <=.

Example

>>> Sales.revenue.between(100, 1000)
Between(field_name='revenue', value=(100, 1000))
in_(values: collections.abc.Iterable[Any]) → semolina.filters.In[source]

Create membership predicate: field IN (values).

Parameters:

values – The values to match: a list, tuple, set, or any other iterable, including a generator. They are copied into a tuple now, so changing the list afterwards does not change the predicate. An empty collection matches no rows.

Returns:

In predicate holding a tuple of the given values.

Raises:

TypeError – If values is a single str or bytes, which would otherwise be read as its characters, or is not iterable. Also if any value is None: to include missing values, OR the in_() with .isnull().

Example

>>> Sales.country.in_(['US', 'CA', 'UK'])
In(field_name='country', value=('US', 'CA', 'UK'))
like(pattern: str) → semolina.filters.Like[source]

Create case-sensitive pattern match: field LIKE pattern.

Parameters:

pattern – SQL LIKE pattern (use % and _ wildcards).

Returns:

Like predicate with the given pattern.

Example

>>> Users.name.like('%Smith%')
Like(field_name='name', value='%Smith%')
ilike(pattern: str) → semolina.filters.ILike[source]

Create case-insensitive pattern match: field ILIKE pattern.

Parameters:

pattern – SQL ILIKE pattern (use % and _ wildcards).

Returns:

ILike predicate with the given pattern.

Example

>>> Users.name.ilike('%smith%')
ILike(field_name='name', value='%smith%')
startswith(prefix: str) → semolina.filters.StartsWith[source]

Create case-sensitive prefix match: field LIKE 'prefix%'.

The prefix is matched literally: a % or _ in it is escaped (with ESCAPE '!'), not treated as a wildcard.

Parameters:

prefix – The prefix string to match.

Returns:

StartsWith predicate with the given prefix.

Raises:

TypeError – If the prefix is None.

Example

>>> Users.name.startswith('A')
StartsWith(field_name='name', value='A')
istartswith(prefix: str) → semolina.filters.IStartsWith[source]

Create case-insensitive prefix match: field ILIKE 'prefix%'.

The prefix is matched literally: a % or _ in it is escaped (with ESCAPE '!'), not treated as a wildcard.

Parameters:

prefix – The prefix string to match (case-insensitive).

Returns:

IStartsWith predicate with the given prefix.

Raises:

TypeError – If the prefix is None.

Example

>>> Users.name.istartswith('a')
IStartsWith(field_name='name', value='a')
endswith(suffix: str) → semolina.filters.EndsWith[source]

Create case-sensitive suffix match: field LIKE '%suffix'.

The suffix is matched literally: a % or _ in it is escaped (with ESCAPE '!'), not treated as a wildcard.

Parameters:

suffix – The suffix string to match.

Returns:

EndsWith predicate with the given suffix.

Raises:

TypeError – If the suffix is None.

Example

>>> Users.name.endswith('son')
EndsWith(field_name='name', value='son')
iendswith(suffix: str) → semolina.filters.IEndsWith[source]

Create case-insensitive suffix match: field ILIKE '%suffix'.

The suffix is matched literally: a % or _ in it is escaped (with ESCAPE '!'), not treated as a wildcard.

Parameters:

suffix – The suffix string to match (case-insensitive).

Returns:

IEndsWith predicate with the given suffix.

Raises:

TypeError – If the suffix is None.

Example

>>> Users.name.iendswith('SON')
IEndsWith(field_name='name', value='SON')
iexact(value: str) → semolina.filters.IExact[source]

Create case-insensitive equality: field ILIKE value (no wildcards).

The value is matched literally: a % or _ in it is escaped (with ESCAPE '!'), not treated as a wildcard.

Parameters:

value – The value to compare (case-insensitive).

Returns:

IExact predicate with the given value.

Raises:

TypeError – If the value is None.

Example

>>> Users.country.iexact('united states')
IExact(field_name='country', value='united states')
isnull() → semolina.filters.IsNull[source]

Create null check predicate: field IS NULL.

Returns:

IsNull predicate with value=True.

Example

>>> Users.country.isnull()
IsNull(field_name='country', value=True)
lookup(lookup_cls: type[semolina.filters.Lookup[Any]], value: Any) → semolina.filters.Lookup[Any][source]

Build a predicate from a built-in Lookup class.

The operators and named methods cover every built-in lookup, so you rarely need this. It is not an extension point: only the Lookup subclasses in semolina.filters compile, and a subclass of your own raises NotImplementedError at to_sql() or execute().

Parameters:
  • lookup_cls – A built-in Lookup subclass to instantiate.

  • value – The value for the lookup.

Returns:

Instance of lookup_cls with this field’s name and the given value.

Example

>>> from semolina.filters import Exact
>>> Users.country.lookup(Exact, 'US')
Exact(field_name='country', value='US')
asc(nulls: NullsOrdering | None = None) → OrderTerm[source]

Return ascending OrderTerm for use in order_by().

Parameters:

nulls – How to position NULL values (NullsOrdering.FIRST, NullsOrdering.LAST, or None for default)

Returns:

OrderTerm with descending=False

Example

>>> term = Sales.revenue.asc()
>>> term.descending
False
desc(nulls: NullsOrdering | None = None) → OrderTerm[source]

Return descending OrderTerm for use in order_by().

Parameters:

nulls – How to position NULL values (NullsOrdering.FIRST, NullsOrdering.LAST, or None for default)

Returns:

OrderTerm with descending=True

Example

>>> term = Sales.revenue.desc()
>>> term.descending
True
class semolina.fields.Metric(source: str | None = None)[source]

Bases: Field[T]

Descriptor for metric fields (aggregatable measures).

Metrics represent quantitative measurements that can be aggregated, such as SUM(revenue), AVG(price), COUNT(*).

class semolina.fields.Dimension(source: str | None = None)[source]

Bases: Field[T]

Descriptor for dimension fields (groupable attributes).

Dimensions represent categorical or descriptive attributes used for grouping and filtering, such as country, product_category, date.

class semolina.fields.Fact(source: str | None = None)[source]

Bases: Field[T]

Descriptor for fact fields (raw numeric values).

Facts represent raw numeric values in fact tables that may be used in calculations but aren’t pre-aggregated, such as unit_price, quantity.

class semolina.fields.OrderTerm[source]

Wrapper specifying sort direction and NULL handling for a field in order_by().

Created via Field.asc() or Field.desc() methods.

field[source]

The Field to sort by

descending[source]

If True, sort descending (DESC). If False, sort ascending (ASC).

nulls[source]

How to position NULL values (NullsOrdering.FIRST, NullsOrdering.LAST, or NullsOrdering.DEFAULT)

Example

>>> term = Sales.revenue.desc()
>>> term.descending
True
>>> term_nulls = Sales.revenue.desc(NullsOrdering.FIRST)
>>> term_nulls.nulls
NULLS FIRST