Skip to content

Query Builder Reference

This page is the single lookup reference for the Query Builder in SpecStar.

Use it when you already understand the basic idea and need the exact method names, operator shortcuts, metadata helpers, or pagination behavior.

For task-oriented examples, start with the Query Builder guide. For the bigger mental model, see the Query system overview.


Core building blocks

The Query Builder is organized around four practical pieces:

  • QB — the main entry point for data fields, metadata accessors, and grouped logic
  • Field — returned by QB["field"] or helpers such as QB.created_time()
  • ConditionBuilder — the chainable object produced after comparisons and helper calls
  • Query — the pagination and sorting layer used by methods such as sort(), limit(), offset(), page(), and first()

In practice, most users only need to remember two patterns:

QB["status"].eq("active")
QB.created_time().last_n_days(7).sort("-created_time").page(1, 20)

Operator shortcuts

These operators are supported in normal Python usage and are also mirrored by the safe HTTP qb parser where applicable.

Syntax Meaning Equivalent helper
QB["age"] == 18 equality QB["age"].eq(18)
QB["age"] != 18 inequality QB["age"].ne(18)
QB["age"] > 18 greater than QB["age"].gt(18)
QB["age"] >= 18 greater than or equal QB["age"].gte(18)
QB["age"] < 18 less than QB["age"].lt(18)
QB["age"] <= 18 less than or equal QB["age"].lte(18)
(cond1) & (cond2) logical AND QB.all(cond1, cond2)
(cond1) | (cond2) logical OR QB.any(cond1, cond2)
~cond logical NOT invert the current condition
~QB["field"] falsy-value shortcut QB["field"].is_falsy()
QB["tags"] << ["a", "b"] list membership QB["tags"].in_(["a", "b"])
QB["title"] >> "urgent" contains check QB["title"].contains("urgent")
QB["code"] % r"^[A-Z]+$" regex match QB["code"].regex(...)

In HTTP URLs, prefer the clearer helper style such as QB["title"].contains("urgent"), especially if the expression will be encoded by a client.


Field access patterns

Data fields

Use bracket notation for indexed data fields:

QB["name"]
QB["profile.email"]
QB["field-with-dash"]
QB["class"]

Resource metadata fields

Use helper accessors when you want to filter or sort on built-in resource metadata:

Helper Filters/sorts on
QB.resource_id() resource identifier
QB.current_revision_id() current revision identifier
QB.created_time() creation timestamp
QB.updated_time() last update timestamp
QB.created_by() creator
QB.updated_by() last updater
QB.is_deleted() soft-delete flag
QB.schema_version() schema version
QB.total_revision_count() revision count
QB.rev_status() status of the current revision (draft / stable)
QB.rev_created_by() user who created the current revision
QB.rev_updated_by() user who last updated the current revision
QB.rev_created_time() when the current revision was created
QB.rev_updated_time() when the current revision was last updated

The rev_* helpers target the current revision of each resource. SpecStar keeps these fields as denormalized mirror values in the meta store, so filtering by them is efficient and does not require reading each revision individually.

Example:

QB.resource_id().starts_with("task-")
QB.updated_by().ne("guest")
QB.is_deleted().is_false()
QB.rev_status().eq("draft")
QB.rev_created_by().one_of(["alice", "bob"])
QB.rev_created_time().last_n_days(7)

Comparison and range helpers

Method Purpose Example
eq(value) equals QB["status"].eq("active")
ne(value) not equals QB["status"].ne("archived")
gt(value) greater than QB["score"].gt(80)
gte(value) greater than or equal QB["score"].gte(80)
lt(value) less than QB["score"].lt(80)
lte(value) less than or equal QB["score"].lte(80)
between(min_val, max_val) inclusive range QB["price"].between(100, 500)
in_range(min_val, max_val) alias for between() QB["age"].in_range(18, 65)

String matching helpers

Method Purpose Example
contains(value) substring or containment match QB["title"].contains("urgent")
starts_with(value) prefix match QB["email"].starts_with("admin")
ends_with(value) suffix match QB["email"].ends_with("@example.com")
regex(pattern) regular expression QB["code"].regex(r"^[A-Z]{3}")
match(pattern) alias for regex() QB["email"].match(r".*@gmail\.com$")
like(pattern) SQL-like % / _ matching QB["name"].like("Alice%")
icontains(value) case-insensitive contains QB["title"].icontains("urgent")
istarts_with(value) case-insensitive prefix QB["name"].istarts_with("admin")
iends_with(value) case-insensitive suffix QB["email"].iends_with("@gmail.com")
not_contains(value) negated contains QB["description"].not_contains("spam")
not_starts_with(value) negated prefix QB["filename"].not_starts_with("tmp")
not_ends_with(value) negated suffix QB["filename"].not_ends_with(".tmp")

List, null, and value helpers

Method Purpose Example
in_(values) value is in a list QB["status"].in_(["draft", "review"])
not_in(values) value is not in a list QB["role"].not_in(["guest"])
one_of(values) alias for in_() QB["owner"].one_of(["alice", "bob"])
is_null(value=True) null check QB["deleted_at"].is_null()
is_not_null() not-null check QB["email"].is_not_null()
has_value() alias for is_not_null() QB["nickname"].has_value()
is_empty() empty string or null QB["description"].is_empty()
is_blank() empty, null, or whitespace-only QB["name"].is_blank()
is_true() equals True QB["verified"].is_true()
is_false() equals False QB["disabled"].is_false()
is_truthy() meaningful non-empty value QB["status"].is_truthy()
is_falsy() null, empty, false, or zero QB["optional_field"].is_falsy()
exists(value=True) exists operator QB["extra"].exists()
isna(value=True) missing / NA-style check QB["score"].isna()

Grouping, sorting, and pagination helpers

Grouping helpers

Method Purpose Example
filter(*conditions) AND additional conditions QB["age"].gt(18).filter(QB["status"].eq("active"))
exclude(*conditions) AND with negated conditions QB["status"].eq("active").exclude(QB["role"].eq("guest"))
and_(other) alias for & cond1.and_(cond2)
or_(other) alias for | cond1.or_(cond2)
QB.all(*conditions) explicit AND group QB.all(cond1, cond2, cond3)
QB.any(*conditions) explicit OR group QB.any(cond1, cond2)

Sorting and pagination helpers

Method Purpose Example
sort(*sorts) add one or more sort rules QB["status"].eq("active").sort("-created_time", "+name")
order_by(*sorts) alias for sort() query.order_by("-created_time")
limit(n) maximum result count QB["status"].eq("active").limit(10)
offset(n) skip first n results QB["status"].eq("active").offset(20)
page(page, size=20) 1-based pagination helper QB["status"].eq("active").page(2, 10)
first() set the limit to 1 QB["status"].eq("active").first()
build() produce ResourceMetaSearchQuery query.build()

Sort direction helpers

Helper Meaning
QB["name"].asc() ascending sort object
QB["created_time"].desc() descending sort object

Date and time helpers

These helpers operate on date-like fields and metadata timestamps.

Method Purpose Example
today(tz=None) current day range QB.created_time().today()
yesterday(tz=None) previous day range QB.created_time().yesterday()
this_week(tz=None, week_start=0) current week range QB.updated_time().this_week()
this_month(tz=None) current month range QB.created_time().this_month()
this_year(tz=None) current year range QB.created_time().this_year()
last_n_days(n, tz=None) values from the last N days QB.created_time().last_n_days(30)

The tz parameter accepts:

  • None for the local timezone
  • an integer offset such as 8 for UTC+8
  • a string offset such as "+8"
  • a real tzinfo object in normal Python code

Example:

QB.created_time().today(8)
QB.created_time().this_week(week_start=6)
QB.updated_time().last_n_days(14, "+8")

If you are manually writing an HTTP URL, remember that + should be URL-encoded. Using 8 is often simpler than writing "+8" by hand.


Transform helpers

length() creates a virtual field based on the length of the current field value.

Use it for string length, list size, or object key-count style checks:

QB["tags"].length() > 3
QB["name"].length().between(5, 20)
QB["items"].length() == 0

Important behavior notes

  • QB works best with indexed resource fields and built-in metadata fields.
  • In HTTP requests, do not combine qb with data_conditions, conditions, sorts, time-range/user filter parameters (created_time_start, created_time_end, updated_time_start, updated_time_end, created_bys, updated_bys), or revision filter parameters (rev_statuses, rev_created_bys, rev_updated_bys, rev_created_time_start, rev_created_time_end, rev_updated_time_start, rev_updated_time_end). Those conflicts return HTTP 422. Include those conditions inside the QB expression instead.
  • is_deleted is the one exception: it may be combined with qb. The server ANDs it into the QB conditions. Because Swagger sends is_deleted=false by default, QB expressions work in Swagger without any extra workaround.
  • Invalid or unsupported QB expressions return HTTP 400.
  • URL limit and offset override pagination values defined inside the QB expression itself.
  • QB["user.email"] refers to the stored field path used by the search layer. It should not be read as a promise of arbitrary deep object traversal beyond what is indexed.


Auto-generated API details

For the full docstring-backed API surface, see the generated module reference below.

specstar.query

Attributes

DEFAULT_QUERY_LIMIT module-attribute

DEFAULT_QUERY_LIMIT = _read_default_query_limit()

Default page size for list-style search endpoints.

Configurable at process startup through the SPECSTAR_DEFAULT_QUERY_LIMIT environment variable (legacy AUTOCRUD_DEFAULT_QUERY_LIMIT is still read with a DeprecationWarning during the migration window). Falls back to a very large first-page limit so users do not easily mistake pagination for missing data.

DataSearchFilter module-attribute

DataSearchFilter = DataSearchCondition | DataSearchGroup

_PREV_Query module-attribute

_PREV_Query = get('Query')

_PREV_ConditionBuilder module-attribute

_PREV_ConditionBuilder = get('ConditionBuilder')

_PREV_Field module-attribute

_PREV_Field = get('Field')

_PREV_QueryBuilderMeta module-attribute

_PREV_QueryBuilderMeta = get('QueryBuilderMeta')

_PREV_QB module-attribute

_PREV_QB = get('QB')

Classes

DataSearchCondition

Bases: Struct

Source code in specstar/query_types.py
class DataSearchCondition(Struct, kw_only=True, tag=True):
    field_path: str
    operator: DataSearchOperator
    value: Any
    transform: FieldTransform | None = None  # Optional field transformation
Attributes
field_path instance-attribute
field_path: str
operator instance-attribute
value instance-attribute
value: Any
transform class-attribute instance-attribute
transform: FieldTransform | None = None

DataSearchGroup

Bases: Struct

Source code in specstar/query_types.py
class DataSearchGroup(Struct, kw_only=True, tag=True):
    operator: DataSearchLogicOperator
    conditions: list["DataSearchCondition | DataSearchGroup"]
Attributes
operator instance-attribute
conditions instance-attribute
conditions: list['DataSearchCondition | DataSearchGroup']

DataSearchLogicOperator

Bases: StrEnum

Source code in specstar/query_types.py
class DataSearchLogicOperator(StrEnum):
    and_op = "and"
    or_op = "or"
    not_op = "not"
Attributes
and_op class-attribute instance-attribute
and_op = 'and'
or_op class-attribute instance-attribute
or_op = 'or'
not_op class-attribute instance-attribute
not_op = 'not'

DataSearchOperator

Bases: StrEnum

Source code in specstar/query_types.py
class DataSearchOperator(StrEnum):
    equals = "eq"
    not_equals = "ne"
    greater_than = "gt"
    greater_than_or_equal = "gte"
    less_than = "lt"
    less_than_or_equal = "lte"
    contains = "contains"  # For string fields
    starts_with = "starts_with"  # For string fields
    ends_with = "ends_with"  # For string fields
    in_list = "in"
    not_in_list = "not_in"
    is_null = "is_null"
    exists = "exists"
    isna = "isna"
    regex = "regex"
Attributes
equals class-attribute instance-attribute
equals = 'eq'
not_equals class-attribute instance-attribute
not_equals = 'ne'
greater_than class-attribute instance-attribute
greater_than = 'gt'
greater_than_or_equal class-attribute instance-attribute
greater_than_or_equal = 'gte'
less_than class-attribute instance-attribute
less_than = 'lt'
less_than_or_equal class-attribute instance-attribute
less_than_or_equal = 'lte'
contains class-attribute instance-attribute
contains = 'contains'
starts_with class-attribute instance-attribute
starts_with = 'starts_with'
ends_with class-attribute instance-attribute
ends_with = 'ends_with'
in_list class-attribute instance-attribute
in_list = 'in'
not_in_list class-attribute instance-attribute
not_in_list = 'not_in'
is_null class-attribute instance-attribute
is_null = 'is_null'
exists class-attribute instance-attribute
exists = 'exists'
isna class-attribute instance-attribute
isna = 'isna'
regex class-attribute instance-attribute
regex = 'regex'

FieldTransform

Bases: StrEnum

Field transformation functions that can be applied before comparison.

Source code in specstar/query_types.py
class FieldTransform(StrEnum):
    """Field transformation functions that can be applied before comparison."""

    length = "len"  # Get length of string, list, dict, etc.
Attributes
length class-attribute instance-attribute
length = 'len'

ResourceDataSearchSort

Bases: Struct

Source code in specstar/query_types.py
class ResourceDataSearchSort(Struct, kw_only=True, tag=True):
    direction: ResourceMetaSortDirection = ResourceMetaSortDirection.ascending
    field_path: str
Attributes
direction class-attribute instance-attribute
field_path instance-attribute
field_path: str

ResourceMetaSearchQuery

Bases: Struct

Source code in specstar/query_types.py
class ResourceMetaSearchQuery(Struct, kw_only=True):
    is_deleted: bool | UnsetType = UNSET
    """Filter by deletion status of the resource."""

    created_time_start: dt.datetime | UnsetType = UNSET
    """Filter resources created >= this time."""
    created_time_end: dt.datetime | UnsetType = UNSET
    """Filter resources created <= this time."""
    updated_time_start: dt.datetime | UnsetType = UNSET
    """Filter resources updated >= this time."""
    updated_time_end: dt.datetime | UnsetType = UNSET
    """Filter resources updated <= this time."""

    created_bys: list[str] | UnsetType = UNSET
    """Filter resources created by these users."""
    updated_bys: list[str] | UnsetType = UNSET
    """Filter resources updated by these users."""

    rev_statuses: list[str] | UnsetType = UNSET
    """Filter by ``ResourceMeta.rev_status`` (current-revision status)."""

    rev_created_bys: list[str] | UnsetType = UNSET
    """Filter by users who created the current revision."""
    rev_updated_bys: list[str] | UnsetType = UNSET
    """Filter by users who last updated the current revision."""

    rev_created_time_start: dt.datetime | UnsetType = UNSET
    """Filter resources whose current revision was created >= this time."""
    rev_created_time_end: dt.datetime | UnsetType = UNSET
    """Filter resources whose current revision was created <= this time."""
    rev_updated_time_start: dt.datetime | UnsetType = UNSET
    """Filter resources whose current revision was last updated >= this time."""
    rev_updated_time_end: dt.datetime | UnsetType = UNSET
    """Filter resources whose current revision was last updated <= this time."""

    data_conditions: list[DataSearchFilter] | UnsetType = UNSET
    """Deprecated. Use `conditions` instead. Conditions to filter resources based on their indexed data fields."""

    conditions: list[DataSearchFilter] | UnsetType = UNSET
    """Conditions to filter resources based on their metadata or indexed data fields."""

    limit: int = DEFAULT_QUERY_LIMIT
    """Maximum number of results to return."""
    offset: int = 0
    """Number of results to skip before starting to collect the result set."""

    sorts: list[ResourceMetaSearchSort | ResourceDataSearchSort] | UnsetType = UNSET
    """Sorting criteria for the search results."""
Attributes
is_deleted class-attribute instance-attribute
is_deleted: bool | UnsetType = UNSET

Filter by deletion status of the resource.

created_time_start class-attribute instance-attribute
created_time_start: datetime | UnsetType = UNSET

Filter resources created >= this time.

created_time_end class-attribute instance-attribute
created_time_end: datetime | UnsetType = UNSET

Filter resources created <= this time.

updated_time_start class-attribute instance-attribute
updated_time_start: datetime | UnsetType = UNSET

Filter resources updated >= this time.

updated_time_end class-attribute instance-attribute
updated_time_end: datetime | UnsetType = UNSET

Filter resources updated <= this time.

created_bys class-attribute instance-attribute
created_bys: list[str] | UnsetType = UNSET

Filter resources created by these users.

updated_bys class-attribute instance-attribute
updated_bys: list[str] | UnsetType = UNSET

Filter resources updated by these users.

rev_statuses class-attribute instance-attribute
rev_statuses: list[str] | UnsetType = UNSET

Filter by ResourceMeta.rev_status (current-revision status).

rev_created_bys class-attribute instance-attribute
rev_created_bys: list[str] | UnsetType = UNSET

Filter by users who created the current revision.

rev_updated_bys class-attribute instance-attribute
rev_updated_bys: list[str] | UnsetType = UNSET

Filter by users who last updated the current revision.

rev_created_time_start class-attribute instance-attribute
rev_created_time_start: datetime | UnsetType = UNSET

Filter resources whose current revision was created >= this time.

rev_created_time_end class-attribute instance-attribute
rev_created_time_end: datetime | UnsetType = UNSET

Filter resources whose current revision was created <= this time.

rev_updated_time_start class-attribute instance-attribute
rev_updated_time_start: datetime | UnsetType = UNSET

Filter resources whose current revision was last updated >= this time.

rev_updated_time_end class-attribute instance-attribute
rev_updated_time_end: datetime | UnsetType = UNSET

Filter resources whose current revision was last updated <= this time.

data_conditions class-attribute instance-attribute
data_conditions: list[DataSearchFilter] | UnsetType = UNSET

Deprecated. Use conditions instead. Conditions to filter resources based on their indexed data fields.

conditions class-attribute instance-attribute
conditions: list[DataSearchFilter] | UnsetType = UNSET

Conditions to filter resources based on their metadata or indexed data fields.

limit class-attribute instance-attribute
limit: int = DEFAULT_QUERY_LIMIT

Maximum number of results to return.

offset class-attribute instance-attribute
offset: int = 0

Number of results to skip before starting to collect the result set.

sorts class-attribute instance-attribute
sorts: (
    list[ResourceMetaSearchSort | ResourceDataSearchSort]
    | UnsetType
) = UNSET

Sorting criteria for the search results.

ResourceMetaSearchSort

Bases: Struct

Source code in specstar/query_types.py
class ResourceMetaSearchSort(Struct, kw_only=True, tag=True):
    direction: ResourceMetaSortDirection = ResourceMetaSortDirection.ascending
    key: ResourceMetaSortKey
Attributes
direction class-attribute instance-attribute
key instance-attribute

ResourceMetaSortDirection

Bases: StrEnum

Source code in specstar/query_types.py
class ResourceMetaSortDirection(StrEnum):
    ascending = "+"
    descending = "-"
Attributes
ascending class-attribute instance-attribute
ascending = '+'
descending class-attribute instance-attribute
descending = '-'

ResourceMetaSortKey

Bases: StrEnum

Built-in metadata sort keys supported by QB and search APIs.

Source code in specstar/query_types.py
class ResourceMetaSortKey(StrEnum):
    """Built-in metadata sort keys supported by QB and search APIs."""

    created_time = "created_time"
    updated_time = "updated_time"
    resource_id = "resource_id"
    current_revision_id = "current_revision_id"
    created_by = "created_by"
    updated_by = "updated_by"
    is_deleted = "is_deleted"
    schema_version = "schema_version"
    total_revision_count = "total_revision_count"
    rev_created_time = "rev_created_time"
    rev_updated_time = "rev_updated_time"
Attributes
created_time class-attribute instance-attribute
created_time = 'created_time'
updated_time class-attribute instance-attribute
updated_time = 'updated_time'
resource_id class-attribute instance-attribute
resource_id = 'resource_id'
current_revision_id class-attribute instance-attribute
current_revision_id = 'current_revision_id'
created_by class-attribute instance-attribute
created_by = 'created_by'
updated_by class-attribute instance-attribute
updated_by = 'updated_by'
is_deleted class-attribute instance-attribute
is_deleted = 'is_deleted'
schema_version class-attribute instance-attribute
schema_version = 'schema_version'
total_revision_count class-attribute instance-attribute
total_revision_count = 'total_revision_count'
rev_created_time class-attribute instance-attribute
rev_created_time = 'rev_created_time'
rev_updated_time class-attribute instance-attribute
rev_updated_time = 'rev_updated_time'

Query

Builder for ResourceMetaSearchQuery.

Source code in specstar/query.py
class Query:
    """Builder for ResourceMetaSearchQuery."""

    def __init__(self, condition: DataSearchFilter | None = None):
        self._condition = condition
        self._limit: int = DEFAULT_QUERY_LIMIT
        self._offset: int = 0
        self._sorts: list[ResourceMetaSearchSort | ResourceDataSearchSort] = []

    def limit(self, limit: int) -> Self:
        self._limit = limit
        return self

    def offset(self, offset: int) -> Self:
        self._offset = offset
        return self

    def sort(
        self, *sorts: ResourceMetaSearchSort | ResourceDataSearchSort | str
    ) -> Self:
        """Add sorting criteria.

        Args:
            *sorts: Sort objects or field name strings.
                   Strings can be prefixed with '+' (ascending) or '-' (descending).
                   If no prefix, defaults to ascending.

        Returns:
            Self for chaining

        Example:
            query.sort(QB.created_time().desc())
            query.sort("-created_time", "+name")  # created_time desc, name asc
            query.sort("age")  # age ascending (default)
        """
        for s in sorts:
            if isinstance(s, str):
                # Parse string format: "+field" or "-field" or "field"
                if s.startswith("-"):
                    field_name = s[1:]
                    direction = ResourceMetaSortDirection.descending
                elif s.startswith("+"):
                    field_name = s[1:]
                    direction = ResourceMetaSortDirection.ascending
                else:
                    field_name = s
                    direction = ResourceMetaSortDirection.ascending

                # Check if it's a meta field
                if field_name in ResourceMetaSortKey.__members__:
                    sort_obj = ResourceMetaSearchSort(
                        direction=direction, key=ResourceMetaSortKey(field_name)
                    )
                else:
                    sort_obj = ResourceDataSearchSort(
                        direction=direction, field_path=field_name
                    )
                self._sorts.append(sort_obj)
            else:
                self._sorts.append(s)
        return self

    def order_by(
        self, *sorts: ResourceMetaSearchSort | ResourceDataSearchSort | str
    ) -> Self:
        """Alias for sort(). Add sorting criteria.

        Args:
            *sorts: Sort objects or field name strings.
                   Strings can be prefixed with '+' (ascending) or '-' (descending).

        Returns:
            Self for chaining

        Example:
            query.order_by("-created_time", "+name")
            query.order_by(QB.age().desc())
        """
        return self.sort(*sorts)

    def page(self, page: int, size: int = 20) -> Self:
        """Set pagination parameters.

        Args:
            page: Page number (1-based, first page is 1)
            size: Number of items per page (default: 20)

        Returns:
            Self for chaining

        Example:
            QB.status.eq("active").page(1, 10)  # First page, 10 items
            QB.status.eq("active").page(2, 20)  # Second page, 20 items
            QB.status.eq("active").page(3)      # Third page, default 20 items
        """
        if page < 1:
            raise ValueError(f"Page number must be >= 1, got {page}")
        if size < 1:
            raise ValueError(f"Page size must be >= 1, got {size}")

        self._offset = (page - 1) * size
        self._limit = size
        return self

    def first(self) -> Self:
        """Set limit to 1 to retrieve only the first result.

        Returns:
            Self for chaining

        Example:
            QB.status.eq("active").sort(QB.created_time.desc()).first()
        """
        self._limit = 1
        return self

    def build(self) -> ResourceMetaSearchQuery:
        conditions = [self._condition] if self._condition else UNSET
        return ResourceMetaSearchQuery(
            conditions=conditions,
            limit=self._limit,
            offset=self._offset,
            sorts=self._sorts if self._sorts else UNSET,
        )
Functions
limit
limit(limit: int) -> Self
Source code in specstar/query.py
def limit(self, limit: int) -> Self:
    self._limit = limit
    return self
offset
offset(offset: int) -> Self
Source code in specstar/query.py
def offset(self, offset: int) -> Self:
    self._offset = offset
    return self
sort
sort(
    *sorts: ResourceMetaSearchSort
    | ResourceDataSearchSort
    | str,
) -> Self

Add sorting criteria.

PARAMETER DESCRIPTION
*sorts

Sort objects or field name strings. Strings can be prefixed with '+' (ascending) or '-' (descending). If no prefix, defaults to ascending.

TYPE: ResourceMetaSearchSort | ResourceDataSearchSort | str DEFAULT: ()

RETURNS DESCRIPTION
Self

Self for chaining

Example

query.sort(QB.created_time().desc()) query.sort("-created_time", "+name") # created_time desc, name asc query.sort("age") # age ascending (default)

Source code in specstar/query.py
def sort(
    self, *sorts: ResourceMetaSearchSort | ResourceDataSearchSort | str
) -> Self:
    """Add sorting criteria.

    Args:
        *sorts: Sort objects or field name strings.
               Strings can be prefixed with '+' (ascending) or '-' (descending).
               If no prefix, defaults to ascending.

    Returns:
        Self for chaining

    Example:
        query.sort(QB.created_time().desc())
        query.sort("-created_time", "+name")  # created_time desc, name asc
        query.sort("age")  # age ascending (default)
    """
    for s in sorts:
        if isinstance(s, str):
            # Parse string format: "+field" or "-field" or "field"
            if s.startswith("-"):
                field_name = s[1:]
                direction = ResourceMetaSortDirection.descending
            elif s.startswith("+"):
                field_name = s[1:]
                direction = ResourceMetaSortDirection.ascending
            else:
                field_name = s
                direction = ResourceMetaSortDirection.ascending

            # Check if it's a meta field
            if field_name in ResourceMetaSortKey.__members__:
                sort_obj = ResourceMetaSearchSort(
                    direction=direction, key=ResourceMetaSortKey(field_name)
                )
            else:
                sort_obj = ResourceDataSearchSort(
                    direction=direction, field_path=field_name
                )
            self._sorts.append(sort_obj)
        else:
            self._sorts.append(s)
    return self
order_by
order_by(
    *sorts: ResourceMetaSearchSort
    | ResourceDataSearchSort
    | str,
) -> Self

Alias for sort(). Add sorting criteria.

PARAMETER DESCRIPTION
*sorts

Sort objects or field name strings. Strings can be prefixed with '+' (ascending) or '-' (descending).

TYPE: ResourceMetaSearchSort | ResourceDataSearchSort | str DEFAULT: ()

RETURNS DESCRIPTION
Self

Self for chaining

Example

query.order_by("-created_time", "+name") query.order_by(QB.age().desc())

Source code in specstar/query.py
def order_by(
    self, *sorts: ResourceMetaSearchSort | ResourceDataSearchSort | str
) -> Self:
    """Alias for sort(). Add sorting criteria.

    Args:
        *sorts: Sort objects or field name strings.
               Strings can be prefixed with '+' (ascending) or '-' (descending).

    Returns:
        Self for chaining

    Example:
        query.order_by("-created_time", "+name")
        query.order_by(QB.age().desc())
    """
    return self.sort(*sorts)
page
page(page: int, size: int = 20) -> Self

Set pagination parameters.

PARAMETER DESCRIPTION
page

Page number (1-based, first page is 1)

TYPE: int

size

Number of items per page (default: 20)

TYPE: int DEFAULT: 20

RETURNS DESCRIPTION
Self

Self for chaining

Example

QB.status.eq("active").page(1, 10) # First page, 10 items QB.status.eq("active").page(2, 20) # Second page, 20 items QB.status.eq("active").page(3) # Third page, default 20 items

Source code in specstar/query.py
def page(self, page: int, size: int = 20) -> Self:
    """Set pagination parameters.

    Args:
        page: Page number (1-based, first page is 1)
        size: Number of items per page (default: 20)

    Returns:
        Self for chaining

    Example:
        QB.status.eq("active").page(1, 10)  # First page, 10 items
        QB.status.eq("active").page(2, 20)  # Second page, 20 items
        QB.status.eq("active").page(3)      # Third page, default 20 items
    """
    if page < 1:
        raise ValueError(f"Page number must be >= 1, got {page}")
    if size < 1:
        raise ValueError(f"Page size must be >= 1, got {size}")

    self._offset = (page - 1) * size
    self._limit = size
    return self
first
first() -> Self

Set limit to 1 to retrieve only the first result.

RETURNS DESCRIPTION
Self

Self for chaining

Example

QB.status.eq("active").sort(QB.created_time.desc()).first()

Source code in specstar/query.py
def first(self) -> Self:
    """Set limit to 1 to retrieve only the first result.

    Returns:
        Self for chaining

    Example:
        QB.status.eq("active").sort(QB.created_time.desc()).first()
    """
    self._limit = 1
    return self
build
Source code in specstar/query.py
def build(self) -> ResourceMetaSearchQuery:
    conditions = [self._condition] if self._condition else UNSET
    return ResourceMetaSearchQuery(
        conditions=conditions,
        limit=self._limit,
        offset=self._offset,
        sorts=self._sorts if self._sorts else UNSET,
    )

ConditionBuilder

Bases: Query

Wraps a DataSearchFilter and allows combining with other conditions.

Source code in specstar/query.py
class ConditionBuilder(Query):
    """Wraps a DataSearchFilter and allows combining with other conditions."""

    def __init__(self, condition: DataSearchFilter | None):
        super().__init__(condition)

    def __and__(
        self, other: "ConditionBuilder | DataSearchFilter"
    ) -> "ConditionBuilder":
        if isinstance(other, Query):
            other_cond = other._condition
        else:
            other_cond = other

        # If strict type checking complains about None, we assume valid usage for now or handle None
        if self._condition is None:
            return ConditionBuilder(other_cond)
        if other_cond is None:
            return self

        return ConditionBuilder(
            DataSearchGroup(
                operator=DataSearchLogicOperator.and_op,
                conditions=[self._condition, other_cond],
            )
        )

    def __or__(
        self, other: "ConditionBuilder | DataSearchFilter"
    ) -> "ConditionBuilder":
        if isinstance(other, Query):
            other_cond = other._condition
        else:
            other_cond = other

        if self._condition is None:
            return ConditionBuilder(other_cond)
        if other_cond is None:
            return self

        return ConditionBuilder(
            DataSearchGroup(
                operator=DataSearchLogicOperator.or_op,
                conditions=[self._condition, other_cond],
            )
        )

    def __invert__(self) -> "ConditionBuilder":
        if self._condition is None:
            raise ValueError("Cannot negate an empty condition")
        return ConditionBuilder(
            DataSearchGroup(
                operator=DataSearchLogicOperator.not_op, conditions=[self._condition]
            )
        )

    def and_(self, other) -> "ConditionBuilder":
        return self & other

    def or_(self, other) -> "ConditionBuilder":
        return self | other

    def filter(self, *conditions: "ConditionBuilder") -> "ConditionBuilder":
        """Add AND conditions to the query. More readable than using &.

        Args:
            *conditions: Additional conditions to AND with current condition

        Returns:
            ConditionBuilder with combined conditions

        Example:
            QB["age"].gt(18).filter(QB["status"].eq("active"), QB["verified"].eq(True))
            # Equivalent to: (QB["age"] > 18) & (QB["status"] == "active") & (QB["verified"] == True)
        """
        result = self
        for cond in conditions:
            result = result & cond
        return result

    def exclude(self, *conditions: "ConditionBuilder") -> "ConditionBuilder":
        """Add NOT conditions to the query. More readable than using ~.

        Args:
            *conditions: Conditions to exclude (will be negated and ANDed)

        Returns:
            ConditionBuilder with excluded conditions

        Example:
            QB["status"].eq("active").exclude(QB["role"].eq("guest"), QB["deleted"].eq(True))
            # Equivalent to: (status == "active") & ~(role == "guest") & ~(deleted == True)
        """
        result = self
        for cond in conditions:
            result = result & (~cond)
        return result

    # Dunder methods for chained comparisons. ``__eq__`` / ``__ne__``
    # are intentionally overridden to return a ``ConditionBuilder``
    # rather than ``bool`` — this enables the query DSL syntax
    # ``QB.foo == 1``. The override violates Liskov; suppressed
    # below since the change is intentional.
    # These allow: 3 <= QB.field <= 5, QB.foo == QB.bar == 2
    def __eq__(self, value: Any) -> "ConditionBuilder":  # ty: ignore[invalid-method-override]
        """Support chained comparison: condition == value.

        Example:
            QB.foo == QB.bar == 2  # Both foo and bar equal to 2
        """
        if not isinstance(self._condition, DataSearchCondition):
            raise TypeError(
                "Can only use comparison operators on single conditions, not logical groups"
            )
        return ConditionBuilder(
            DataSearchCondition(
                field_path=self._condition.field_path,
                operator=DataSearchOperator.equals,
                value=value,
            )
        )

    def __ne__(self, value: Any) -> "ConditionBuilder":  # ty: ignore[invalid-method-override]
        """Support chained comparison: condition != value."""
        if not isinstance(self._condition, DataSearchCondition):
            raise TypeError(
                "Can only use comparison operators on single conditions, not logical groups"
            )
        return ConditionBuilder(
            DataSearchCondition(
                field_path=self._condition.field_path,
                operator=DataSearchOperator.not_equals,
                value=value,
            )
        )

    def __ge__(self, value: Any) -> "ConditionBuilder":
        """Support chained comparison: condition >= value (for right side of chain)."""
        if not isinstance(self._condition, DataSearchCondition):
            raise TypeError(
                "Can only use comparison operators on single conditions, not logical groups"
            )
        return ConditionBuilder(
            DataSearchCondition(
                field_path=self._condition.field_path,
                operator=DataSearchOperator.greater_than_or_equal,
                value=value,
            )
        )

    def __gt__(self, value: Any) -> "ConditionBuilder":
        """Support chained comparison: condition > value."""
        if not isinstance(self._condition, DataSearchCondition):
            raise TypeError(
                "Can only use comparison operators on single conditions, not logical groups"
            )
        return ConditionBuilder(
            DataSearchCondition(
                field_path=self._condition.field_path,
                operator=DataSearchOperator.greater_than,
                value=value,
            )
        )

    def __le__(self, value: Any) -> "ConditionBuilder":
        """Support chained comparison: condition <= value."""
        if not isinstance(self._condition, DataSearchCondition):
            raise TypeError(
                "Can only use comparison operators on single conditions, not logical groups"
            )
        return ConditionBuilder(
            DataSearchCondition(
                field_path=self._condition.field_path,
                operator=DataSearchOperator.less_than_or_equal,
                value=value,
            )
        )

    def __lt__(self, value: Any) -> "ConditionBuilder":
        """Support chained comparison: condition < value."""
        if not isinstance(self._condition, DataSearchCondition):
            raise TypeError(
                "Can only use comparison operators on single conditions, not logical groups"
            )
        return ConditionBuilder(
            DataSearchCondition(
                field_path=self._condition.field_path,
                operator=DataSearchOperator.less_than,
                value=value,
            )
        )
Functions
and_
and_(other) -> ConditionBuilder
Source code in specstar/query.py
def and_(self, other) -> "ConditionBuilder":
    return self & other
or_
or_(other) -> ConditionBuilder
Source code in specstar/query.py
def or_(self, other) -> "ConditionBuilder":
    return self | other
filter
filter(*conditions: ConditionBuilder) -> ConditionBuilder

Add AND conditions to the query. More readable than using &.

PARAMETER DESCRIPTION
*conditions

Additional conditions to AND with current condition

TYPE: ConditionBuilder DEFAULT: ()

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with combined conditions

Example

QB["age"].gt(18).filter(QB["status"].eq("active"), QB["verified"].eq(True))

Equivalent to: (QB["age"] > 18) & (QB["status"] == "active") & (QB["verified"] == True)
Source code in specstar/query.py
def filter(self, *conditions: "ConditionBuilder") -> "ConditionBuilder":
    """Add AND conditions to the query. More readable than using &.

    Args:
        *conditions: Additional conditions to AND with current condition

    Returns:
        ConditionBuilder with combined conditions

    Example:
        QB["age"].gt(18).filter(QB["status"].eq("active"), QB["verified"].eq(True))
        # Equivalent to: (QB["age"] > 18) & (QB["status"] == "active") & (QB["verified"] == True)
    """
    result = self
    for cond in conditions:
        result = result & cond
    return result
exclude
exclude(*conditions: ConditionBuilder) -> ConditionBuilder

Add NOT conditions to the query. More readable than using ~.

PARAMETER DESCRIPTION
*conditions

Conditions to exclude (will be negated and ANDed)

TYPE: ConditionBuilder DEFAULT: ()

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with excluded conditions

Example

QB["status"].eq("active").exclude(QB["role"].eq("guest"), QB["deleted"].eq(True))

Equivalent to: (status == "active") & ~(role == "guest") & ~(deleted == True)
Source code in specstar/query.py
def exclude(self, *conditions: "ConditionBuilder") -> "ConditionBuilder":
    """Add NOT conditions to the query. More readable than using ~.

    Args:
        *conditions: Conditions to exclude (will be negated and ANDed)

    Returns:
        ConditionBuilder with excluded conditions

    Example:
        QB["status"].eq("active").exclude(QB["role"].eq("guest"), QB["deleted"].eq(True))
        # Equivalent to: (status == "active") & ~(role == "guest") & ~(deleted == True)
    """
    result = self
    for cond in conditions:
        result = result & (~cond)
    return result
limit
limit(limit: int) -> Self
Source code in specstar/query.py
def limit(self, limit: int) -> Self:
    self._limit = limit
    return self
offset
offset(offset: int) -> Self
Source code in specstar/query.py
def offset(self, offset: int) -> Self:
    self._offset = offset
    return self
sort
sort(
    *sorts: ResourceMetaSearchSort
    | ResourceDataSearchSort
    | str,
) -> Self

Add sorting criteria.

PARAMETER DESCRIPTION
*sorts

Sort objects or field name strings. Strings can be prefixed with '+' (ascending) or '-' (descending). If no prefix, defaults to ascending.

TYPE: ResourceMetaSearchSort | ResourceDataSearchSort | str DEFAULT: ()

RETURNS DESCRIPTION
Self

Self for chaining

Example

query.sort(QB.created_time().desc()) query.sort("-created_time", "+name") # created_time desc, name asc query.sort("age") # age ascending (default)

Source code in specstar/query.py
def sort(
    self, *sorts: ResourceMetaSearchSort | ResourceDataSearchSort | str
) -> Self:
    """Add sorting criteria.

    Args:
        *sorts: Sort objects or field name strings.
               Strings can be prefixed with '+' (ascending) or '-' (descending).
               If no prefix, defaults to ascending.

    Returns:
        Self for chaining

    Example:
        query.sort(QB.created_time().desc())
        query.sort("-created_time", "+name")  # created_time desc, name asc
        query.sort("age")  # age ascending (default)
    """
    for s in sorts:
        if isinstance(s, str):
            # Parse string format: "+field" or "-field" or "field"
            if s.startswith("-"):
                field_name = s[1:]
                direction = ResourceMetaSortDirection.descending
            elif s.startswith("+"):
                field_name = s[1:]
                direction = ResourceMetaSortDirection.ascending
            else:
                field_name = s
                direction = ResourceMetaSortDirection.ascending

            # Check if it's a meta field
            if field_name in ResourceMetaSortKey.__members__:
                sort_obj = ResourceMetaSearchSort(
                    direction=direction, key=ResourceMetaSortKey(field_name)
                )
            else:
                sort_obj = ResourceDataSearchSort(
                    direction=direction, field_path=field_name
                )
            self._sorts.append(sort_obj)
        else:
            self._sorts.append(s)
    return self
order_by
order_by(
    *sorts: ResourceMetaSearchSort
    | ResourceDataSearchSort
    | str,
) -> Self

Alias for sort(). Add sorting criteria.

PARAMETER DESCRIPTION
*sorts

Sort objects or field name strings. Strings can be prefixed with '+' (ascending) or '-' (descending).

TYPE: ResourceMetaSearchSort | ResourceDataSearchSort | str DEFAULT: ()

RETURNS DESCRIPTION
Self

Self for chaining

Example

query.order_by("-created_time", "+name") query.order_by(QB.age().desc())

Source code in specstar/query.py
def order_by(
    self, *sorts: ResourceMetaSearchSort | ResourceDataSearchSort | str
) -> Self:
    """Alias for sort(). Add sorting criteria.

    Args:
        *sorts: Sort objects or field name strings.
               Strings can be prefixed with '+' (ascending) or '-' (descending).

    Returns:
        Self for chaining

    Example:
        query.order_by("-created_time", "+name")
        query.order_by(QB.age().desc())
    """
    return self.sort(*sorts)
page
page(page: int, size: int = 20) -> Self

Set pagination parameters.

PARAMETER DESCRIPTION
page

Page number (1-based, first page is 1)

TYPE: int

size

Number of items per page (default: 20)

TYPE: int DEFAULT: 20

RETURNS DESCRIPTION
Self

Self for chaining

Example

QB.status.eq("active").page(1, 10) # First page, 10 items QB.status.eq("active").page(2, 20) # Second page, 20 items QB.status.eq("active").page(3) # Third page, default 20 items

Source code in specstar/query.py
def page(self, page: int, size: int = 20) -> Self:
    """Set pagination parameters.

    Args:
        page: Page number (1-based, first page is 1)
        size: Number of items per page (default: 20)

    Returns:
        Self for chaining

    Example:
        QB.status.eq("active").page(1, 10)  # First page, 10 items
        QB.status.eq("active").page(2, 20)  # Second page, 20 items
        QB.status.eq("active").page(3)      # Third page, default 20 items
    """
    if page < 1:
        raise ValueError(f"Page number must be >= 1, got {page}")
    if size < 1:
        raise ValueError(f"Page size must be >= 1, got {size}")

    self._offset = (page - 1) * size
    self._limit = size
    return self
first
first() -> Self

Set limit to 1 to retrieve only the first result.

RETURNS DESCRIPTION
Self

Self for chaining

Example

QB.status.eq("active").sort(QB.created_time.desc()).first()

Source code in specstar/query.py
def first(self) -> Self:
    """Set limit to 1 to retrieve only the first result.

    Returns:
        Self for chaining

    Example:
        QB.status.eq("active").sort(QB.created_time.desc()).first()
    """
    self._limit = 1
    return self
build
Source code in specstar/query.py
def build(self) -> ResourceMetaSearchQuery:
    conditions = [self._condition] if self._condition else UNSET
    return ResourceMetaSearchQuery(
        conditions=conditions,
        limit=self._limit,
        offset=self._offset,
        sorts=self._sorts if self._sorts else UNSET,
    )

Field

Bases: ConditionBuilder

Source code in specstar/query.py
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
class Field(ConditionBuilder):
    def __init__(self, name: str, transform: FieldTransform | None = None):
        self.name = name
        self.transform = transform
        # Default behavior: Field acts as is_truthy() condition
        # This allows QB["foo"] to be used directly in logical operations
        super().__init__(self._create_truthy_condition())

    def _create_truthy_condition(self) -> DataSearchGroup:
        """Create truthy condition: not null, not false, not 0, not empty string, not empty list."""
        return DataSearchGroup(
            operator=DataSearchLogicOperator.and_op,
            conditions=[
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.is_null,
                    value=False,
                ),
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.not_equals,
                    value=False,
                ),
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.not_equals,
                    value=0,
                ),
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.not_equals,
                    value="",
                ),
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.not_equals,
                    value=[],
                ),
            ],
        )

    def _cond(
        self, op: DataSearchOperator, val: Any, transform: FieldTransform | None = None
    ) -> ConditionBuilder:
        return ConditionBuilder(
            DataSearchCondition(
                field_path=self.name,
                operator=op,
                value=val,
                transform=transform if transform is not None else self.transform,
            )
        )

    def eq(self, value: Any) -> ConditionBuilder:
        return self._cond(DataSearchOperator.equals, value)

    def ne(self, value: Any) -> ConditionBuilder:
        return self._cond(DataSearchOperator.not_equals, value)

    def gt(self, value: Any) -> ConditionBuilder:
        return self._cond(DataSearchOperator.greater_than, value)

    def gte(self, value: Any) -> ConditionBuilder:
        return self._cond(DataSearchOperator.greater_than_or_equal, value)

    def lt(self, value: Any) -> ConditionBuilder:
        return self._cond(DataSearchOperator.less_than, value)

    def lte(self, value: Any) -> ConditionBuilder:
        return self._cond(DataSearchOperator.less_than_or_equal, value)

    # Dunder methods for Pythonic syntax
    def __eq__(self, value: Any) -> ConditionBuilder:
        """Support field == value syntax."""
        return self.eq(value)

    def __ne__(self, value: Any) -> ConditionBuilder:
        """Support field != value syntax."""
        return self.ne(value)

    def __gt__(self, value: Any) -> ConditionBuilder:
        """Support field > value syntax."""
        return self.gt(value)

    def __ge__(self, value: Any) -> ConditionBuilder:
        """Support field >= value syntax."""
        return self.gte(value)

    def __lt__(self, value: Any) -> ConditionBuilder:
        """Support field < value syntax."""
        return self.lt(value)

    def __le__(self, value: Any) -> ConditionBuilder:
        """Support field <= value syntax."""
        return self.lte(value)

    def __mod__(self, value: str) -> ConditionBuilder:
        """Support field % pattern syntax for regex."""
        return self.regex(value)

    def __lshift__(self, value: list[Any]) -> ConditionBuilder:
        """Support field << [values] syntax for in_list."""
        return self.in_(value)

    def __rshift__(self, value: Any) -> ConditionBuilder:
        """Support field >> value syntax for contains."""
        return self.contains(value)

    def __invert__(self) -> ConditionBuilder:
        """Support ~field syntax for is_falsy().

        Override parent's NOT logic to directly return is_falsy() condition.
        This is more intuitive: ~field means "field is falsy", not "NOT (field is truthy)".

        Note: While logically equivalent, this returns the OR group directly
        instead of wrapping the truthy condition in a NOT group.

        Checks if field value is falsy (None, False, 0, "", []).

        Example:
            ~QB["comment"]  # Falsy values
            # Equivalent to: QB["comment"].is_falsy()
        """
        return self.is_falsy()

    def between(self, min_val: Any, max_val: Any) -> ConditionBuilder:
        """Support range query: field.between(min, max).

        Cleaner alternative to: (field >= min) & (field <= max)

        Args:
            min_val: Minimum value (inclusive)
            max_val: Maximum value (inclusive)

        Returns:
            ConditionBuilder with AND condition

        Example:
            QB["age"].between(18, 65)
            QB["price"].between(100, 500)
            QB.created_time().between(start_date, end_date)
        """
        return ConditionBuilder(
            DataSearchGroup(
                operator=DataSearchLogicOperator.and_op,
                conditions=[
                    DataSearchCondition(
                        field_path=self.name,
                        operator=DataSearchOperator.greater_than_or_equal,
                        value=min_val,
                        transform=self.transform,
                    ),
                    DataSearchCondition(
                        field_path=self.name,
                        operator=DataSearchOperator.less_than_or_equal,
                        value=max_val,
                        transform=self.transform,
                    ),
                ],
            )
        )

    def in_range(self, min_val: Any, max_val: Any) -> ConditionBuilder:
        """Alias for between(). Check if field value is within a range.

        Args:
            min_val: Minimum value (inclusive)
            max_val: Maximum value (inclusive)

        Returns:
            ConditionBuilder with range condition

        Example:
            QB["age"].in_range(18, 65)
            QB["price"].in_range(100, 500)
        """
        return self.between(min_val, max_val)

    def contains(self, value: Any) -> ConditionBuilder:
        return self._cond(DataSearchOperator.contains, value)

    def starts_with(self, value: Any) -> ConditionBuilder:
        return self._cond(DataSearchOperator.starts_with, value)

    def ends_with(self, value: Any) -> ConditionBuilder:
        return self._cond(DataSearchOperator.ends_with, value)

    def in_(self, value: list[Any]) -> ConditionBuilder:
        return self._cond(DataSearchOperator.in_list, value)

    def not_in(self, value: list[Any]) -> ConditionBuilder:
        return self._cond(DataSearchOperator.not_in_list, value)

    def is_null(self, value: bool = True) -> ConditionBuilder:
        return self._cond(DataSearchOperator.is_null, value)

    def is_not_null(self) -> ConditionBuilder:
        """Check if field is not null.

        Returns:
            ConditionBuilder with is_null(False) condition

        Example:
            QB["email"].is_not_null()
            QB["optional_field"].is_not_null()
        """
        return self._cond(DataSearchOperator.is_null, False)

    def has_value(self) -> ConditionBuilder:
        """Alias for is_not_null(). Check if field has a value (not null).

        Returns:
            ConditionBuilder with is_null(False) condition

        Example:
            QB["email"].has_value()
            QB["description"].has_value()
        """
        return self.is_not_null()

    def is_true(self) -> ConditionBuilder:
        """Check if field value is True.

        Returns:
            ConditionBuilder with equals(True) condition

        Example:
            QB["verified"].is_true()
            QB["active"].is_true()
        """
        return self.eq(True)

    def is_false(self) -> ConditionBuilder:
        """Check if field value is False.

        Returns:
            ConditionBuilder with equals(False) condition

        Example:
            QB["deleted"].is_false()
            QB["disabled"].is_false()
        """
        return self.eq(False)

    def is_truthy(self) -> ConditionBuilder:
        """Check if field has a truthy value (not null, not empty, not false, not 0).

        Returns:
            ConditionBuilder for: value != None AND value != False AND value != 0 AND value != "" AND value != []

        Example:
            QB["status"].is_truthy()  # Has meaningful value
            QB["count"].is_truthy()   # Not 0
            QB["tags"].is_truthy()    # Not empty list
        """
        return ConditionBuilder(self._create_truthy_condition())

    def is_falsy(self) -> ConditionBuilder:
        """Check if field has a falsy value (null, empty, false, or 0).

        Returns:
            ConditionBuilder for NOT(is_truthy): negation of truthy condition

        Example:
            QB["optional_field"].is_falsy()  # Empty or unset
            QB["count"].is_falsy()           # Zero or null
            QB["tags"].is_falsy()            # Empty list or null
        """
        # Falsy is the logical negation of truthy
        return ConditionBuilder(
            DataSearchGroup(
                operator=DataSearchLogicOperator.not_op,
                conditions=[self._create_truthy_condition()],
            )
        )

    def exists(self, value: bool = True) -> ConditionBuilder:
        return self._cond(DataSearchOperator.exists, value)

    def isna(self, value: bool = True) -> ConditionBuilder:
        return self._cond(DataSearchOperator.isna, value)

    def regex(self, value: str) -> ConditionBuilder:
        return self._cond(DataSearchOperator.regex, value)

    def match(self, value: str) -> ConditionBuilder:
        r"""Alias for regex(). Match field value against a regular expression pattern.

        Args:
            value: Regular expression pattern

        Returns:
            ConditionBuilder

        Example:
            QB["email"].match(r".*@gmail\.com$")
            QB["code"].match(r"^[A-Z]{3}-\d{4}$")
        """
        return self.regex(value)

    # Aliases and convenience methods
    def one_of(self, value: list[Any]) -> ConditionBuilder:
        """Alias for in_() with more Pythonic naming.

        Args:
            value: List of values to check against

        Returns:
            ConditionBuilder

        Example:
            QB.status.one_of(["active", "pending", "approved"])
        """
        return self.in_(value)

    def icontains(self, value: str) -> ConditionBuilder:
        """Case-insensitive contains using regex.

        Args:
            value: String to search for (case-insensitive)

        Returns:
            ConditionBuilder with regex condition

        Example:
            QB.name.icontains("alice")  # matches "Alice", "ALICE", "alice"
        """
        import re

        escaped = re.escape(value)
        return self.regex(f"(?i){escaped}")

    def istarts_with(self, value: str) -> ConditionBuilder:
        """Case-insensitive starts_with using regex.

        Args:
            value: Prefix to search for (case-insensitive)

        Returns:
            ConditionBuilder with regex condition

        Example:
            QB.email.istarts_with("admin")  # matches "Admin@", "ADMIN@", "admin@"
        """
        import re

        escaped = re.escape(value)
        return self.regex(f"(?i)^{escaped}")

    def iends_with(self, value: str) -> ConditionBuilder:
        """Case-insensitive ends_with using regex.

        Args:
            value: Suffix to search for (case-insensitive)

        Returns:
            ConditionBuilder with regex condition

        Example:
            QB.email.iends_with("@gmail.com")  # matches "@Gmail.com", "@GMAIL.COM"
        """
        import re

        escaped = re.escape(value)
        return self.regex(f"(?i){escaped}$")

    def not_contains(self, value: Any) -> ConditionBuilder:
        """Negation of contains - field does not contain value.

        Args:
            value: Value that should not be contained

        Returns:
            ConditionBuilder with NOT(contains) condition

        Example:
            QB.description.not_contains("spam")
        """
        return ~self.contains(value)

    def not_starts_with(self, value: Any) -> ConditionBuilder:
        """Negation of starts_with - field does not start with value.

        Args:
            value: Prefix that should not match

        Returns:
            ConditionBuilder with NOT(starts_with) condition

        Example:
            QB.email.not_starts_with("spam")
        """
        return ~self.starts_with(value)

    def not_ends_with(self, value: Any) -> ConditionBuilder:
        """Negation of ends_with - field does not end with value.

        Args:
            value: Suffix that should not match

        Returns:
            ConditionBuilder with NOT(ends_with) condition

        Example:
            QB.filename.not_ends_with(".tmp")
        """
        return ~self.ends_with(value)

    def is_empty(self) -> ConditionBuilder:
        """Check if field is empty string or null.

        Returns:
            ConditionBuilder with OR condition (empty string or null)

        Example:
            QB.description.is_empty()  # matches "" or null
        """
        return ConditionBuilder(
            DataSearchGroup(
                operator=DataSearchLogicOperator.or_op,
                conditions=[
                    DataSearchCondition(
                        field_path=self.name,
                        operator=DataSearchOperator.equals,
                        value="",
                    ),
                    DataSearchCondition(
                        field_path=self.name,
                        operator=DataSearchOperator.is_null,
                        value=True,
                    ),
                ],
            )
        )

    def is_blank(self) -> ConditionBuilder:
        """Check if field is empty, null, or contains only whitespace.

        Returns:
            ConditionBuilder with OR condition

        Example:
            QB.name.is_blank()  # matches "", null, "  ", "\\t\\n"
        """
        return ConditionBuilder(
            DataSearchGroup(
                operator=DataSearchLogicOperator.or_op,
                conditions=[
                    DataSearchCondition(
                        field_path=self.name,
                        operator=DataSearchOperator.equals,
                        value="",
                    ),
                    DataSearchCondition(
                        field_path=self.name,
                        operator=DataSearchOperator.is_null,
                        value=True,
                    ),
                    DataSearchCondition(
                        field_path=self.name,
                        operator=DataSearchOperator.regex,
                        value=r"^\s*$",
                    ),
                ],
            )
        )

    def like(self, pattern: str) -> ConditionBuilder:
        """SQL LIKE pattern matching with % and _ wildcards.

        Converts SQL LIKE patterns to appropriate operators:
        - %pattern% -> contains (if no _ inside)
        - pattern% -> starts_with (if no _ inside)
        - %pattern -> ends_with (if no _ inside)
        - Other patterns with % or _ -> regex

        Args:
            pattern: SQL LIKE pattern with % (any chars) and _ (single char)

        Returns:
            ConditionBuilder with appropriate condition

        Example:
            QB.name.like("Alice%")      # starts with "Alice"
            QB.email.like("%@gmail.com") # ends with "@gmail.com"
            QB.desc.like("%urgent%")    # contains "urgent"
            QB.code.like("A_C")         # matches "ABC", "A1C", etc. (regex)
        """
        import re

        # %pattern%
        if pattern.startswith("%") and pattern.endswith("%") and len(pattern) > 2:
            inner = pattern[1:-1]
            if "_" not in inner:
                return self.contains(inner)

        # pattern%
        if pattern.endswith("%") and not pattern.startswith("%"):
            prefix = pattern[:-1]
            if "_" not in prefix:
                return self.starts_with(prefix)

        # %pattern
        if pattern.startswith("%") and not pattern.endswith("%"):
            suffix = pattern[1:]
            if "_" not in suffix:
                return self.ends_with(suffix)

        # Convert SQL LIKE to regex
        # Strategy: manually replace % and _, then escape the rest
        # Step 1: Replace % with placeholder \x00 and _ with \x01
        temp = pattern.replace("%", "\x00").replace("_", "\x01")
        # Step 2: Escape all regex special chars
        regex_pattern = re.escape(temp)
        # Step 3: Convert placeholders to regex: \x00 -> .*, \x01 -> .
        regex_pattern = regex_pattern.replace("\x00", ".*").replace("\x01", ".")
        # Step 4: Add anchors for exact match
        regex_pattern = f"^{regex_pattern}$"

        return self.regex(regex_pattern)

    # Date/Time convenience methods
    def _parse_timezone(self, tz: Any) -> Any:
        """Parse timezone parameter to tzinfo object.

        Args:
            tz: Can be:
                - None: returns None (use local timezone)
                - int: UTC offset in hours (e.g., 8 for UTC+8, -4 for UTC-4)
                - str: UTC offset as string (e.g., "+8", "-4")
                - tzinfo: passed through as-is

        Returns:
            tzinfo object or None

        Example:
            _parse_timezone(None) -> None
            _parse_timezone(8) -> timezone(timedelta(hours=8))
            _parse_timezone("+8") -> timezone(timedelta(hours=8))
            _parse_timezone("-4") -> timezone(timedelta(hours=-4))
            _parse_timezone(ZoneInfo("UTC")) -> ZoneInfo("UTC")
        """
        if tz is None:
            return None

        # If already a tzinfo object, return as-is
        import datetime as dt

        if isinstance(tz, dt.tzinfo):
            return tz

        # Parse int or str offset
        if isinstance(tz, (int, str)):
            try:
                offset_hours = int(tz)
                return dt.timezone(dt.timedelta(hours=offset_hours))
            except (ValueError, TypeError):
                raise ValueError(
                    f"Invalid timezone offset: {tz}. Expected int or string like '+8' or '-4'"
                )

        raise TypeError(
            f"Invalid timezone type: {type(tz)}. Expected None, int, str, or tzinfo"
        )

    def today(self, tz: Any = None) -> ConditionBuilder:
        """Match values within today (00:00:00 to 23:59:59.999999).

        Args:
            tz: Timezone. Can be:
                - None: uses local timezone
                - int/str: UTC offset in hours (e.g., 8 or "+8" for UTC+8)
                - tzinfo: timezone object (e.g., ZoneInfo("UTC"))

        Returns:
            ConditionBuilder with between condition for today's date range

        Example:
            QB.created_time.today()  # Today in local timezone
            QB.created_time.today(8)  # Today in UTC+8
            QB.created_time.today("+8")  # Today in UTC+8
            QB.created_time.today("-4")  # Today in UTC-4
            QB.created_time.today(ZoneInfo("UTC"))  # Today in UTC
            QB.created_time.today(ZoneInfo("Asia/Taipei"))  # Today in Taipei
        """
        import datetime as dt

        tz = self._parse_timezone(tz)

        if tz is None:
            # Use local timezone
            now = dt.datetime.now().astimezone()
        else:
            now = dt.datetime.now(tz)

        start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)
        end_of_day = now.replace(hour=23, minute=59, second=59, microsecond=999999)

        return self.between(start_of_day, end_of_day)

    def this_week(self, tz: Any = None, week_start: int = 0) -> ConditionBuilder:
        """Match values within this week.

        Args:
            tz: Timezone. Can be:
                - None: uses local timezone
                - int/str: UTC offset in hours (e.g., 8 or "+8" for UTC+8)
                - tzinfo: timezone object (e.g., ZoneInfo("UTC"))
            week_start: Day of week to start (0=Monday, 6=Sunday). Default is 0 (Monday).

        Returns:
            ConditionBuilder with between condition for this week's date range

        Example:
            QB.created_time.this_week()  # This week (Mon-Sun) in local timezone
            QB.created_time.this_week(week_start=6)  # This week (Sun-Sat)
            QB.created_time.this_week(8)  # This week in UTC+8
            QB.created_time.this_week("+8")  # This week in UTC+8
            QB.created_time.this_week(ZoneInfo("UTC"))  # This week in UTC
        """
        import datetime as dt

        tz = self._parse_timezone(tz)

        if tz is None:
            now = dt.datetime.now().astimezone()
        else:
            now = dt.datetime.now(tz)

        # Calculate days since week_start
        current_weekday = now.weekday()  # 0=Monday, 6=Sunday
        days_since_start = (current_weekday - week_start) % 7

        # Start of week (at 00:00:00)
        start_of_week = (now - dt.timedelta(days=days_since_start)).replace(
            hour=0, minute=0, second=0, microsecond=0
        )
        # End of week (at 23:59:59.999999)
        end_of_week = (start_of_week + dt.timedelta(days=6)).replace(
            hour=23, minute=59, second=59, microsecond=999999
        )

        return self.between(start_of_week, end_of_week)

    def last_n_days(self, n: int, tz: Any = None) -> ConditionBuilder:
        """Match values from the last N days (inclusive of today).

        Args:
            n: Number of days to look back (including today)
            tz: Timezone. Can be:
                - None: uses local timezone
                - int/str: UTC offset in hours (e.g., 8 or "+8" for UTC+8)
                - tzinfo: timezone object (e.g., ZoneInfo("UTC"))

        Returns:
            ConditionBuilder with gte condition for N days ago

        Example:
            QB.created_time().last_n_days(7)  # Last 7 days in local timezone
            QB.created_time().last_n_days(30, 8)  # Last 30 days in UTC+8
            QB.created_time().last_n_days(30, "+8")  # Last 30 days in UTC+8
            QB.created_time().last_n_days(30, ZoneInfo("UTC"))  # Last 30 days in UTC
        """
        import datetime as dt

        tz = self._parse_timezone(tz)

        if tz is None:
            now = dt.datetime.now().astimezone()
        else:
            now = dt.datetime.now(tz)

        # N days ago at 00:00:00
        n_days_ago = (now - dt.timedelta(days=n - 1)).replace(
            hour=0, minute=0, second=0, microsecond=0
        )

        return self.gte(n_days_ago)

    def yesterday(self, tz: Any = None) -> ConditionBuilder:
        """Match values from yesterday (00:00:00 to 23:59:59.999999).

        Args:
            tz: Timezone parameter (None, int, str, or tzinfo)

        Returns:
            ConditionBuilder with between condition for yesterday's date range

        Example:
            QB.created_time().yesterday()
            QB.created_time().yesterday(8)  # Yesterday in UTC+8
        """
        import datetime as dt

        tz = self._parse_timezone(tz)

        if tz is None:
            now = dt.datetime.now().astimezone()
        else:
            now = dt.datetime.now(tz)

        yesterday = now - dt.timedelta(days=1)
        start = yesterday.replace(hour=0, minute=0, second=0, microsecond=0)
        end = yesterday.replace(hour=23, minute=59, second=59, microsecond=999999)

        return self.between(start, end)

    def this_month(self, tz: Any = None) -> ConditionBuilder:
        """Match values within this month (1st to last day).

        Args:
            tz: Timezone parameter (None, int, str, or tzinfo)

        Returns:
            ConditionBuilder with between condition for this month's date range

        Example:
            QB.created_time().this_month()
            QB.created_time().this_month("+8")
        """
        import datetime as dt

        tz = self._parse_timezone(tz)

        if tz is None:
            now = dt.datetime.now().astimezone()
        else:
            now = dt.datetime.now(tz)

        # First day of month at 00:00:00
        start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)

        # Last day of month at 23:59:59.999999
        if now.month == 12:
            next_month = now.replace(year=now.year + 1, month=1, day=1)
        else:
            next_month = now.replace(month=now.month + 1, day=1)
        end_of_month = next_month - dt.timedelta(microseconds=1)

        return self.between(start_of_month, end_of_month)

    def this_year(self, tz: Any = None) -> ConditionBuilder:
        """Match values within this year (Jan 1 to Dec 31).

        Args:
            tz: Timezone parameter (None, int, str, or tzinfo)

        Returns:
            ConditionBuilder with between condition for this year's date range

        Example:
            QB.created_time().this_year()
            QB.created_time().this_year(ZoneInfo("UTC"))
        """
        import datetime as dt

        tz = self._parse_timezone(tz)

        if tz is None:
            now = dt.datetime.now().astimezone()
        else:
            now = dt.datetime.now(tz)

        # Jan 1 at 00:00:00
        start_of_year = now.replace(
            month=1, day=1, hour=0, minute=0, second=0, microsecond=0
        )

        # Dec 31 at 23:59:59.999999
        end_of_year = now.replace(
            month=12, day=31, hour=23, minute=59, second=59, microsecond=999999
        )

        return self.between(start_of_year, end_of_year)

    # Field transformation methods
    def length(self) -> "Field":
        """Get a virtual field representing the length of this field's value.

        This creates a field reference that can be used to query the length of:
        - Strings: character count
        - Lists/Arrays: number of elements
        - Dicts/Objects: number of keys

        Returns:
            Field instance with length transform applied

        Example:
            QB["tags"].length() > 3           # More than 3 tags
            QB["name"].length().between(5, 20) # Name length 5-20 chars
            QB["items"].length() == 0          # Empty list
            QB["description"].length() >= 100  # At least 100 characters

        Note:
            The actual length calculation is performed by the storage backend
            when executing the query using the FieldTransform.length transform.
            The returned Field also acts as is_truthy() by default.
        """
        return Field(self.name, transform=FieldTransform.length)

    # Sorting
    def asc(self) -> ResourceDataSearchSort | ResourceMetaSearchSort:
        if self.name in ResourceMetaSortKey.__members__:
            return ResourceMetaSearchSort(
                direction=ResourceMetaSortDirection.ascending,
                key=ResourceMetaSortKey(self.name),
            )
        return ResourceDataSearchSort(
            direction=ResourceMetaSortDirection.ascending, field_path=self.name
        )

    def desc(self) -> ResourceDataSearchSort | ResourceMetaSearchSort:
        if self.name in ResourceMetaSortKey.__members__:
            return ResourceMetaSearchSort(
                direction=ResourceMetaSortDirection.descending,
                key=ResourceMetaSortKey(self.name),
            )
        return ResourceDataSearchSort(
            direction=ResourceMetaSortDirection.descending, field_path=self.name
        )
Attributes
name instance-attribute
name = name
transform instance-attribute
transform = transform
Functions
eq
eq(value: Any) -> ConditionBuilder
Source code in specstar/query.py
def eq(self, value: Any) -> ConditionBuilder:
    return self._cond(DataSearchOperator.equals, value)
ne
ne(value: Any) -> ConditionBuilder
Source code in specstar/query.py
def ne(self, value: Any) -> ConditionBuilder:
    return self._cond(DataSearchOperator.not_equals, value)
gt
gt(value: Any) -> ConditionBuilder
Source code in specstar/query.py
def gt(self, value: Any) -> ConditionBuilder:
    return self._cond(DataSearchOperator.greater_than, value)
gte
gte(value: Any) -> ConditionBuilder
Source code in specstar/query.py
def gte(self, value: Any) -> ConditionBuilder:
    return self._cond(DataSearchOperator.greater_than_or_equal, value)
lt
lt(value: Any) -> ConditionBuilder
Source code in specstar/query.py
def lt(self, value: Any) -> ConditionBuilder:
    return self._cond(DataSearchOperator.less_than, value)
lte
lte(value: Any) -> ConditionBuilder
Source code in specstar/query.py
def lte(self, value: Any) -> ConditionBuilder:
    return self._cond(DataSearchOperator.less_than_or_equal, value)
between
between(min_val: Any, max_val: Any) -> ConditionBuilder

Support range query: field.between(min, max).

Cleaner alternative to: (field >= min) & (field <= max)

PARAMETER DESCRIPTION
min_val

Minimum value (inclusive)

TYPE: Any

max_val

Maximum value (inclusive)

TYPE: Any

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with AND condition

Example

QB["age"].between(18, 65) QB["price"].between(100, 500) QB.created_time().between(start_date, end_date)

Source code in specstar/query.py
def between(self, min_val: Any, max_val: Any) -> ConditionBuilder:
    """Support range query: field.between(min, max).

    Cleaner alternative to: (field >= min) & (field <= max)

    Args:
        min_val: Minimum value (inclusive)
        max_val: Maximum value (inclusive)

    Returns:
        ConditionBuilder with AND condition

    Example:
        QB["age"].between(18, 65)
        QB["price"].between(100, 500)
        QB.created_time().between(start_date, end_date)
    """
    return ConditionBuilder(
        DataSearchGroup(
            operator=DataSearchLogicOperator.and_op,
            conditions=[
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.greater_than_or_equal,
                    value=min_val,
                    transform=self.transform,
                ),
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.less_than_or_equal,
                    value=max_val,
                    transform=self.transform,
                ),
            ],
        )
    )
in_range
in_range(min_val: Any, max_val: Any) -> ConditionBuilder

Alias for between(). Check if field value is within a range.

PARAMETER DESCRIPTION
min_val

Minimum value (inclusive)

TYPE: Any

max_val

Maximum value (inclusive)

TYPE: Any

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with range condition

Example

QB["age"].in_range(18, 65) QB["price"].in_range(100, 500)

Source code in specstar/query.py
def in_range(self, min_val: Any, max_val: Any) -> ConditionBuilder:
    """Alias for between(). Check if field value is within a range.

    Args:
        min_val: Minimum value (inclusive)
        max_val: Maximum value (inclusive)

    Returns:
        ConditionBuilder with range condition

    Example:
        QB["age"].in_range(18, 65)
        QB["price"].in_range(100, 500)
    """
    return self.between(min_val, max_val)
contains
contains(value: Any) -> ConditionBuilder
Source code in specstar/query.py
def contains(self, value: Any) -> ConditionBuilder:
    return self._cond(DataSearchOperator.contains, value)
starts_with
starts_with(value: Any) -> ConditionBuilder
Source code in specstar/query.py
def starts_with(self, value: Any) -> ConditionBuilder:
    return self._cond(DataSearchOperator.starts_with, value)
ends_with
ends_with(value: Any) -> ConditionBuilder
Source code in specstar/query.py
def ends_with(self, value: Any) -> ConditionBuilder:
    return self._cond(DataSearchOperator.ends_with, value)
in_
in_(value: list[Any]) -> ConditionBuilder
Source code in specstar/query.py
def in_(self, value: list[Any]) -> ConditionBuilder:
    return self._cond(DataSearchOperator.in_list, value)
not_in
not_in(value: list[Any]) -> ConditionBuilder
Source code in specstar/query.py
def not_in(self, value: list[Any]) -> ConditionBuilder:
    return self._cond(DataSearchOperator.not_in_list, value)
is_null
is_null(value: bool = True) -> ConditionBuilder
Source code in specstar/query.py
def is_null(self, value: bool = True) -> ConditionBuilder:
    return self._cond(DataSearchOperator.is_null, value)
is_not_null
is_not_null() -> ConditionBuilder

Check if field is not null.

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with is_null(False) condition

Example

QB["email"].is_not_null() QB["optional_field"].is_not_null()

Source code in specstar/query.py
def is_not_null(self) -> ConditionBuilder:
    """Check if field is not null.

    Returns:
        ConditionBuilder with is_null(False) condition

    Example:
        QB["email"].is_not_null()
        QB["optional_field"].is_not_null()
    """
    return self._cond(DataSearchOperator.is_null, False)
has_value
has_value() -> ConditionBuilder

Alias for is_not_null(). Check if field has a value (not null).

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with is_null(False) condition

Example

QB["email"].has_value() QB["description"].has_value()

Source code in specstar/query.py
def has_value(self) -> ConditionBuilder:
    """Alias for is_not_null(). Check if field has a value (not null).

    Returns:
        ConditionBuilder with is_null(False) condition

    Example:
        QB["email"].has_value()
        QB["description"].has_value()
    """
    return self.is_not_null()
is_true
is_true() -> ConditionBuilder

Check if field value is True.

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with equals(True) condition

Example

QB["verified"].is_true() QB["active"].is_true()

Source code in specstar/query.py
def is_true(self) -> ConditionBuilder:
    """Check if field value is True.

    Returns:
        ConditionBuilder with equals(True) condition

    Example:
        QB["verified"].is_true()
        QB["active"].is_true()
    """
    return self.eq(True)
is_false
is_false() -> ConditionBuilder

Check if field value is False.

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with equals(False) condition

Example

QB["deleted"].is_false() QB["disabled"].is_false()

Source code in specstar/query.py
def is_false(self) -> ConditionBuilder:
    """Check if field value is False.

    Returns:
        ConditionBuilder with equals(False) condition

    Example:
        QB["deleted"].is_false()
        QB["disabled"].is_false()
    """
    return self.eq(False)
is_truthy
is_truthy() -> ConditionBuilder

Check if field has a truthy value (not null, not empty, not false, not 0).

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder for: value != None AND value != False AND value != 0 AND value != "" AND value != []

Example

QB["status"].is_truthy() # Has meaningful value QB["count"].is_truthy() # Not 0 QB["tags"].is_truthy() # Not empty list

Source code in specstar/query.py
def is_truthy(self) -> ConditionBuilder:
    """Check if field has a truthy value (not null, not empty, not false, not 0).

    Returns:
        ConditionBuilder for: value != None AND value != False AND value != 0 AND value != "" AND value != []

    Example:
        QB["status"].is_truthy()  # Has meaningful value
        QB["count"].is_truthy()   # Not 0
        QB["tags"].is_truthy()    # Not empty list
    """
    return ConditionBuilder(self._create_truthy_condition())
is_falsy
is_falsy() -> ConditionBuilder

Check if field has a falsy value (null, empty, false, or 0).

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder for NOT(is_truthy): negation of truthy condition

Example

QB["optional_field"].is_falsy() # Empty or unset QB["count"].is_falsy() # Zero or null QB["tags"].is_falsy() # Empty list or null

Source code in specstar/query.py
def is_falsy(self) -> ConditionBuilder:
    """Check if field has a falsy value (null, empty, false, or 0).

    Returns:
        ConditionBuilder for NOT(is_truthy): negation of truthy condition

    Example:
        QB["optional_field"].is_falsy()  # Empty or unset
        QB["count"].is_falsy()           # Zero or null
        QB["tags"].is_falsy()            # Empty list or null
    """
    # Falsy is the logical negation of truthy
    return ConditionBuilder(
        DataSearchGroup(
            operator=DataSearchLogicOperator.not_op,
            conditions=[self._create_truthy_condition()],
        )
    )
exists
exists(value: bool = True) -> ConditionBuilder
Source code in specstar/query.py
def exists(self, value: bool = True) -> ConditionBuilder:
    return self._cond(DataSearchOperator.exists, value)
isna
isna(value: bool = True) -> ConditionBuilder
Source code in specstar/query.py
def isna(self, value: bool = True) -> ConditionBuilder:
    return self._cond(DataSearchOperator.isna, value)
regex
regex(value: str) -> ConditionBuilder
Source code in specstar/query.py
def regex(self, value: str) -> ConditionBuilder:
    return self._cond(DataSearchOperator.regex, value)
match
match(value: str) -> ConditionBuilder

Alias for regex(). Match field value against a regular expression pattern.

PARAMETER DESCRIPTION
value

Regular expression pattern

TYPE: str

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder

Example

QB["email"].match(r".*@gmail.com\(") QB["code"].match(r"^[A-Z]{3}-\d{4}\)")

Source code in specstar/query.py
def match(self, value: str) -> ConditionBuilder:
    r"""Alias for regex(). Match field value against a regular expression pattern.

    Args:
        value: Regular expression pattern

    Returns:
        ConditionBuilder

    Example:
        QB["email"].match(r".*@gmail\.com$")
        QB["code"].match(r"^[A-Z]{3}-\d{4}$")
    """
    return self.regex(value)
one_of
one_of(value: list[Any]) -> ConditionBuilder

Alias for in_() with more Pythonic naming.

PARAMETER DESCRIPTION
value

List of values to check against

TYPE: list[Any]

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder

Example

QB.status.one_of(["active", "pending", "approved"])

Source code in specstar/query.py
def one_of(self, value: list[Any]) -> ConditionBuilder:
    """Alias for in_() with more Pythonic naming.

    Args:
        value: List of values to check against

    Returns:
        ConditionBuilder

    Example:
        QB.status.one_of(["active", "pending", "approved"])
    """
    return self.in_(value)
icontains
icontains(value: str) -> ConditionBuilder

Case-insensitive contains using regex.

PARAMETER DESCRIPTION
value

String to search for (case-insensitive)

TYPE: str

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with regex condition

Example

QB.name.icontains("alice") # matches "Alice", "ALICE", "alice"

Source code in specstar/query.py
def icontains(self, value: str) -> ConditionBuilder:
    """Case-insensitive contains using regex.

    Args:
        value: String to search for (case-insensitive)

    Returns:
        ConditionBuilder with regex condition

    Example:
        QB.name.icontains("alice")  # matches "Alice", "ALICE", "alice"
    """
    import re

    escaped = re.escape(value)
    return self.regex(f"(?i){escaped}")
istarts_with
istarts_with(value: str) -> ConditionBuilder

Case-insensitive starts_with using regex.

PARAMETER DESCRIPTION
value

Prefix to search for (case-insensitive)

TYPE: str

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with regex condition

Example

QB.email.istarts_with("admin") # matches "Admin@", "ADMIN@", "admin@"

Source code in specstar/query.py
def istarts_with(self, value: str) -> ConditionBuilder:
    """Case-insensitive starts_with using regex.

    Args:
        value: Prefix to search for (case-insensitive)

    Returns:
        ConditionBuilder with regex condition

    Example:
        QB.email.istarts_with("admin")  # matches "Admin@", "ADMIN@", "admin@"
    """
    import re

    escaped = re.escape(value)
    return self.regex(f"(?i)^{escaped}")
iends_with
iends_with(value: str) -> ConditionBuilder

Case-insensitive ends_with using regex.

PARAMETER DESCRIPTION
value

Suffix to search for (case-insensitive)

TYPE: str

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with regex condition

Example

QB.email.iends_with("@gmail.com") # matches "@Gmail.com", "@GMAIL.COM"

Source code in specstar/query.py
def iends_with(self, value: str) -> ConditionBuilder:
    """Case-insensitive ends_with using regex.

    Args:
        value: Suffix to search for (case-insensitive)

    Returns:
        ConditionBuilder with regex condition

    Example:
        QB.email.iends_with("@gmail.com")  # matches "@Gmail.com", "@GMAIL.COM"
    """
    import re

    escaped = re.escape(value)
    return self.regex(f"(?i){escaped}$")
not_contains
not_contains(value: Any) -> ConditionBuilder

Negation of contains - field does not contain value.

PARAMETER DESCRIPTION
value

Value that should not be contained

TYPE: Any

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with NOT(contains) condition

Example

QB.description.not_contains("spam")

Source code in specstar/query.py
def not_contains(self, value: Any) -> ConditionBuilder:
    """Negation of contains - field does not contain value.

    Args:
        value: Value that should not be contained

    Returns:
        ConditionBuilder with NOT(contains) condition

    Example:
        QB.description.not_contains("spam")
    """
    return ~self.contains(value)
not_starts_with
not_starts_with(value: Any) -> ConditionBuilder

Negation of starts_with - field does not start with value.

PARAMETER DESCRIPTION
value

Prefix that should not match

TYPE: Any

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with NOT(starts_with) condition

Example

QB.email.not_starts_with("spam")

Source code in specstar/query.py
def not_starts_with(self, value: Any) -> ConditionBuilder:
    """Negation of starts_with - field does not start with value.

    Args:
        value: Prefix that should not match

    Returns:
        ConditionBuilder with NOT(starts_with) condition

    Example:
        QB.email.not_starts_with("spam")
    """
    return ~self.starts_with(value)
not_ends_with
not_ends_with(value: Any) -> ConditionBuilder

Negation of ends_with - field does not end with value.

PARAMETER DESCRIPTION
value

Suffix that should not match

TYPE: Any

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with NOT(ends_with) condition

Example

QB.filename.not_ends_with(".tmp")

Source code in specstar/query.py
def not_ends_with(self, value: Any) -> ConditionBuilder:
    """Negation of ends_with - field does not end with value.

    Args:
        value: Suffix that should not match

    Returns:
        ConditionBuilder with NOT(ends_with) condition

    Example:
        QB.filename.not_ends_with(".tmp")
    """
    return ~self.ends_with(value)
is_empty
is_empty() -> ConditionBuilder

Check if field is empty string or null.

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with OR condition (empty string or null)

Example

QB.description.is_empty() # matches "" or null

Source code in specstar/query.py
def is_empty(self) -> ConditionBuilder:
    """Check if field is empty string or null.

    Returns:
        ConditionBuilder with OR condition (empty string or null)

    Example:
        QB.description.is_empty()  # matches "" or null
    """
    return ConditionBuilder(
        DataSearchGroup(
            operator=DataSearchLogicOperator.or_op,
            conditions=[
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.equals,
                    value="",
                ),
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.is_null,
                    value=True,
                ),
            ],
        )
    )
is_blank
is_blank() -> ConditionBuilder

Check if field is empty, null, or contains only whitespace.

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with OR condition

Example

QB.name.is_blank() # matches "", null, " ", "\t\n"

Source code in specstar/query.py
def is_blank(self) -> ConditionBuilder:
    """Check if field is empty, null, or contains only whitespace.

    Returns:
        ConditionBuilder with OR condition

    Example:
        QB.name.is_blank()  # matches "", null, "  ", "\\t\\n"
    """
    return ConditionBuilder(
        DataSearchGroup(
            operator=DataSearchLogicOperator.or_op,
            conditions=[
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.equals,
                    value="",
                ),
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.is_null,
                    value=True,
                ),
                DataSearchCondition(
                    field_path=self.name,
                    operator=DataSearchOperator.regex,
                    value=r"^\s*$",
                ),
            ],
        )
    )
like
like(pattern: str) -> ConditionBuilder

SQL LIKE pattern matching with % and _ wildcards.

Converts SQL LIKE patterns to appropriate operators: - %pattern% -> contains (if no _ inside) - pattern% -> starts_with (if no _ inside) - %pattern -> ends_with (if no _ inside) - Other patterns with % or _ -> regex

PARAMETER DESCRIPTION
pattern

SQL LIKE pattern with % (any chars) and _ (single char)

TYPE: str

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with appropriate condition

Example

QB.name.like("Alice%") # starts with "Alice" QB.email.like("%@gmail.com") # ends with "@gmail.com" QB.desc.like("%urgent%") # contains "urgent" QB.code.like("A_C") # matches "ABC", "A1C", etc. (regex)

Source code in specstar/query.py
def like(self, pattern: str) -> ConditionBuilder:
    """SQL LIKE pattern matching with % and _ wildcards.

    Converts SQL LIKE patterns to appropriate operators:
    - %pattern% -> contains (if no _ inside)
    - pattern% -> starts_with (if no _ inside)
    - %pattern -> ends_with (if no _ inside)
    - Other patterns with % or _ -> regex

    Args:
        pattern: SQL LIKE pattern with % (any chars) and _ (single char)

    Returns:
        ConditionBuilder with appropriate condition

    Example:
        QB.name.like("Alice%")      # starts with "Alice"
        QB.email.like("%@gmail.com") # ends with "@gmail.com"
        QB.desc.like("%urgent%")    # contains "urgent"
        QB.code.like("A_C")         # matches "ABC", "A1C", etc. (regex)
    """
    import re

    # %pattern%
    if pattern.startswith("%") and pattern.endswith("%") and len(pattern) > 2:
        inner = pattern[1:-1]
        if "_" not in inner:
            return self.contains(inner)

    # pattern%
    if pattern.endswith("%") and not pattern.startswith("%"):
        prefix = pattern[:-1]
        if "_" not in prefix:
            return self.starts_with(prefix)

    # %pattern
    if pattern.startswith("%") and not pattern.endswith("%"):
        suffix = pattern[1:]
        if "_" not in suffix:
            return self.ends_with(suffix)

    # Convert SQL LIKE to regex
    # Strategy: manually replace % and _, then escape the rest
    # Step 1: Replace % with placeholder \x00 and _ with \x01
    temp = pattern.replace("%", "\x00").replace("_", "\x01")
    # Step 2: Escape all regex special chars
    regex_pattern = re.escape(temp)
    # Step 3: Convert placeholders to regex: \x00 -> .*, \x01 -> .
    regex_pattern = regex_pattern.replace("\x00", ".*").replace("\x01", ".")
    # Step 4: Add anchors for exact match
    regex_pattern = f"^{regex_pattern}$"

    return self.regex(regex_pattern)
today
today(tz: Any = None) -> ConditionBuilder

Match values within today (00:00:00 to 23:59:59.999999).

PARAMETER DESCRIPTION
tz

Timezone. Can be: - None: uses local timezone - int/str: UTC offset in hours (e.g., 8 or "+8" for UTC+8) - tzinfo: timezone object (e.g., ZoneInfo("UTC"))

TYPE: Any DEFAULT: None

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with between condition for today's date range

Example

QB.created_time.today() # Today in local timezone QB.created_time.today(8) # Today in UTC+8 QB.created_time.today("+8") # Today in UTC+8 QB.created_time.today("-4") # Today in UTC-4 QB.created_time.today(ZoneInfo("UTC")) # Today in UTC QB.created_time.today(ZoneInfo("Asia/Taipei")) # Today in Taipei

Source code in specstar/query.py
def today(self, tz: Any = None) -> ConditionBuilder:
    """Match values within today (00:00:00 to 23:59:59.999999).

    Args:
        tz: Timezone. Can be:
            - None: uses local timezone
            - int/str: UTC offset in hours (e.g., 8 or "+8" for UTC+8)
            - tzinfo: timezone object (e.g., ZoneInfo("UTC"))

    Returns:
        ConditionBuilder with between condition for today's date range

    Example:
        QB.created_time.today()  # Today in local timezone
        QB.created_time.today(8)  # Today in UTC+8
        QB.created_time.today("+8")  # Today in UTC+8
        QB.created_time.today("-4")  # Today in UTC-4
        QB.created_time.today(ZoneInfo("UTC"))  # Today in UTC
        QB.created_time.today(ZoneInfo("Asia/Taipei"))  # Today in Taipei
    """
    import datetime as dt

    tz = self._parse_timezone(tz)

    if tz is None:
        # Use local timezone
        now = dt.datetime.now().astimezone()
    else:
        now = dt.datetime.now(tz)

    start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)
    end_of_day = now.replace(hour=23, minute=59, second=59, microsecond=999999)

    return self.between(start_of_day, end_of_day)
this_week
this_week(
    tz: Any = None, week_start: int = 0
) -> ConditionBuilder

Match values within this week.

PARAMETER DESCRIPTION
tz

Timezone. Can be: - None: uses local timezone - int/str: UTC offset in hours (e.g., 8 or "+8" for UTC+8) - tzinfo: timezone object (e.g., ZoneInfo("UTC"))

TYPE: Any DEFAULT: None

week_start

Day of week to start (0=Monday, 6=Sunday). Default is 0 (Monday).

TYPE: int DEFAULT: 0

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with between condition for this week's date range

Example

QB.created_time.this_week() # This week (Mon-Sun) in local timezone QB.created_time.this_week(week_start=6) # This week (Sun-Sat) QB.created_time.this_week(8) # This week in UTC+8 QB.created_time.this_week("+8") # This week in UTC+8 QB.created_time.this_week(ZoneInfo("UTC")) # This week in UTC

Source code in specstar/query.py
def this_week(self, tz: Any = None, week_start: int = 0) -> ConditionBuilder:
    """Match values within this week.

    Args:
        tz: Timezone. Can be:
            - None: uses local timezone
            - int/str: UTC offset in hours (e.g., 8 or "+8" for UTC+8)
            - tzinfo: timezone object (e.g., ZoneInfo("UTC"))
        week_start: Day of week to start (0=Monday, 6=Sunday). Default is 0 (Monday).

    Returns:
        ConditionBuilder with between condition for this week's date range

    Example:
        QB.created_time.this_week()  # This week (Mon-Sun) in local timezone
        QB.created_time.this_week(week_start=6)  # This week (Sun-Sat)
        QB.created_time.this_week(8)  # This week in UTC+8
        QB.created_time.this_week("+8")  # This week in UTC+8
        QB.created_time.this_week(ZoneInfo("UTC"))  # This week in UTC
    """
    import datetime as dt

    tz = self._parse_timezone(tz)

    if tz is None:
        now = dt.datetime.now().astimezone()
    else:
        now = dt.datetime.now(tz)

    # Calculate days since week_start
    current_weekday = now.weekday()  # 0=Monday, 6=Sunday
    days_since_start = (current_weekday - week_start) % 7

    # Start of week (at 00:00:00)
    start_of_week = (now - dt.timedelta(days=days_since_start)).replace(
        hour=0, minute=0, second=0, microsecond=0
    )
    # End of week (at 23:59:59.999999)
    end_of_week = (start_of_week + dt.timedelta(days=6)).replace(
        hour=23, minute=59, second=59, microsecond=999999
    )

    return self.between(start_of_week, end_of_week)
last_n_days
last_n_days(n: int, tz: Any = None) -> ConditionBuilder

Match values from the last N days (inclusive of today).

PARAMETER DESCRIPTION
n

Number of days to look back (including today)

TYPE: int

tz

Timezone. Can be: - None: uses local timezone - int/str: UTC offset in hours (e.g., 8 or "+8" for UTC+8) - tzinfo: timezone object (e.g., ZoneInfo("UTC"))

TYPE: Any DEFAULT: None

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with gte condition for N days ago

Example

QB.created_time().last_n_days(7) # Last 7 days in local timezone QB.created_time().last_n_days(30, 8) # Last 30 days in UTC+8 QB.created_time().last_n_days(30, "+8") # Last 30 days in UTC+8 QB.created_time().last_n_days(30, ZoneInfo("UTC")) # Last 30 days in UTC

Source code in specstar/query.py
def last_n_days(self, n: int, tz: Any = None) -> ConditionBuilder:
    """Match values from the last N days (inclusive of today).

    Args:
        n: Number of days to look back (including today)
        tz: Timezone. Can be:
            - None: uses local timezone
            - int/str: UTC offset in hours (e.g., 8 or "+8" for UTC+8)
            - tzinfo: timezone object (e.g., ZoneInfo("UTC"))

    Returns:
        ConditionBuilder with gte condition for N days ago

    Example:
        QB.created_time().last_n_days(7)  # Last 7 days in local timezone
        QB.created_time().last_n_days(30, 8)  # Last 30 days in UTC+8
        QB.created_time().last_n_days(30, "+8")  # Last 30 days in UTC+8
        QB.created_time().last_n_days(30, ZoneInfo("UTC"))  # Last 30 days in UTC
    """
    import datetime as dt

    tz = self._parse_timezone(tz)

    if tz is None:
        now = dt.datetime.now().astimezone()
    else:
        now = dt.datetime.now(tz)

    # N days ago at 00:00:00
    n_days_ago = (now - dt.timedelta(days=n - 1)).replace(
        hour=0, minute=0, second=0, microsecond=0
    )

    return self.gte(n_days_ago)
yesterday
yesterday(tz: Any = None) -> ConditionBuilder

Match values from yesterday (00:00:00 to 23:59:59.999999).

PARAMETER DESCRIPTION
tz

Timezone parameter (None, int, str, or tzinfo)

TYPE: Any DEFAULT: None

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with between condition for yesterday's date range

Example

QB.created_time().yesterday() QB.created_time().yesterday(8) # Yesterday in UTC+8

Source code in specstar/query.py
def yesterday(self, tz: Any = None) -> ConditionBuilder:
    """Match values from yesterday (00:00:00 to 23:59:59.999999).

    Args:
        tz: Timezone parameter (None, int, str, or tzinfo)

    Returns:
        ConditionBuilder with between condition for yesterday's date range

    Example:
        QB.created_time().yesterday()
        QB.created_time().yesterday(8)  # Yesterday in UTC+8
    """
    import datetime as dt

    tz = self._parse_timezone(tz)

    if tz is None:
        now = dt.datetime.now().astimezone()
    else:
        now = dt.datetime.now(tz)

    yesterday = now - dt.timedelta(days=1)
    start = yesterday.replace(hour=0, minute=0, second=0, microsecond=0)
    end = yesterday.replace(hour=23, minute=59, second=59, microsecond=999999)

    return self.between(start, end)
this_month
this_month(tz: Any = None) -> ConditionBuilder

Match values within this month (1st to last day).

PARAMETER DESCRIPTION
tz

Timezone parameter (None, int, str, or tzinfo)

TYPE: Any DEFAULT: None

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with between condition for this month's date range

Example

QB.created_time().this_month() QB.created_time().this_month("+8")

Source code in specstar/query.py
def this_month(self, tz: Any = None) -> ConditionBuilder:
    """Match values within this month (1st to last day).

    Args:
        tz: Timezone parameter (None, int, str, or tzinfo)

    Returns:
        ConditionBuilder with between condition for this month's date range

    Example:
        QB.created_time().this_month()
        QB.created_time().this_month("+8")
    """
    import datetime as dt

    tz = self._parse_timezone(tz)

    if tz is None:
        now = dt.datetime.now().astimezone()
    else:
        now = dt.datetime.now(tz)

    # First day of month at 00:00:00
    start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)

    # Last day of month at 23:59:59.999999
    if now.month == 12:
        next_month = now.replace(year=now.year + 1, month=1, day=1)
    else:
        next_month = now.replace(month=now.month + 1, day=1)
    end_of_month = next_month - dt.timedelta(microseconds=1)

    return self.between(start_of_month, end_of_month)
this_year
this_year(tz: Any = None) -> ConditionBuilder

Match values within this year (Jan 1 to Dec 31).

PARAMETER DESCRIPTION
tz

Timezone parameter (None, int, str, or tzinfo)

TYPE: Any DEFAULT: None

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with between condition for this year's date range

Example

QB.created_time().this_year() QB.created_time().this_year(ZoneInfo("UTC"))

Source code in specstar/query.py
def this_year(self, tz: Any = None) -> ConditionBuilder:
    """Match values within this year (Jan 1 to Dec 31).

    Args:
        tz: Timezone parameter (None, int, str, or tzinfo)

    Returns:
        ConditionBuilder with between condition for this year's date range

    Example:
        QB.created_time().this_year()
        QB.created_time().this_year(ZoneInfo("UTC"))
    """
    import datetime as dt

    tz = self._parse_timezone(tz)

    if tz is None:
        now = dt.datetime.now().astimezone()
    else:
        now = dt.datetime.now(tz)

    # Jan 1 at 00:00:00
    start_of_year = now.replace(
        month=1, day=1, hour=0, minute=0, second=0, microsecond=0
    )

    # Dec 31 at 23:59:59.999999
    end_of_year = now.replace(
        month=12, day=31, hour=23, minute=59, second=59, microsecond=999999
    )

    return self.between(start_of_year, end_of_year)
length
length() -> Field

Get a virtual field representing the length of this field's value.

This creates a field reference that can be used to query the length of: - Strings: character count - Lists/Arrays: number of elements - Dicts/Objects: number of keys

RETURNS DESCRIPTION
Field

Field instance with length transform applied

Example

QB["tags"].length() > 3 # More than 3 tags QB["name"].length().between(5, 20) # Name length 5-20 chars QB["items"].length() == 0 # Empty list QB["description"].length() >= 100 # At least 100 characters

Note

The actual length calculation is performed by the storage backend when executing the query using the FieldTransform.length transform. The returned Field also acts as is_truthy() by default.

Source code in specstar/query.py
def length(self) -> "Field":
    """Get a virtual field representing the length of this field's value.

    This creates a field reference that can be used to query the length of:
    - Strings: character count
    - Lists/Arrays: number of elements
    - Dicts/Objects: number of keys

    Returns:
        Field instance with length transform applied

    Example:
        QB["tags"].length() > 3           # More than 3 tags
        QB["name"].length().between(5, 20) # Name length 5-20 chars
        QB["items"].length() == 0          # Empty list
        QB["description"].length() >= 100  # At least 100 characters

    Note:
        The actual length calculation is performed by the storage backend
        when executing the query using the FieldTransform.length transform.
        The returned Field also acts as is_truthy() by default.
    """
    return Field(self.name, transform=FieldTransform.length)
asc
Source code in specstar/query.py
def asc(self) -> ResourceDataSearchSort | ResourceMetaSearchSort:
    if self.name in ResourceMetaSortKey.__members__:
        return ResourceMetaSearchSort(
            direction=ResourceMetaSortDirection.ascending,
            key=ResourceMetaSortKey(self.name),
        )
    return ResourceDataSearchSort(
        direction=ResourceMetaSortDirection.ascending, field_path=self.name
    )
desc
Source code in specstar/query.py
def desc(self) -> ResourceDataSearchSort | ResourceMetaSearchSort:
    if self.name in ResourceMetaSortKey.__members__:
        return ResourceMetaSearchSort(
            direction=ResourceMetaSortDirection.descending,
            key=ResourceMetaSortKey(self.name),
        )
    return ResourceDataSearchSort(
        direction=ResourceMetaSortDirection.descending, field_path=self.name
    )
limit
limit(limit: int) -> Self
Source code in specstar/query.py
def limit(self, limit: int) -> Self:
    self._limit = limit
    return self
offset
offset(offset: int) -> Self
Source code in specstar/query.py
def offset(self, offset: int) -> Self:
    self._offset = offset
    return self
sort
sort(
    *sorts: ResourceMetaSearchSort
    | ResourceDataSearchSort
    | str,
) -> Self

Add sorting criteria.

PARAMETER DESCRIPTION
*sorts

Sort objects or field name strings. Strings can be prefixed with '+' (ascending) or '-' (descending). If no prefix, defaults to ascending.

TYPE: ResourceMetaSearchSort | ResourceDataSearchSort | str DEFAULT: ()

RETURNS DESCRIPTION
Self

Self for chaining

Example

query.sort(QB.created_time().desc()) query.sort("-created_time", "+name") # created_time desc, name asc query.sort("age") # age ascending (default)

Source code in specstar/query.py
def sort(
    self, *sorts: ResourceMetaSearchSort | ResourceDataSearchSort | str
) -> Self:
    """Add sorting criteria.

    Args:
        *sorts: Sort objects or field name strings.
               Strings can be prefixed with '+' (ascending) or '-' (descending).
               If no prefix, defaults to ascending.

    Returns:
        Self for chaining

    Example:
        query.sort(QB.created_time().desc())
        query.sort("-created_time", "+name")  # created_time desc, name asc
        query.sort("age")  # age ascending (default)
    """
    for s in sorts:
        if isinstance(s, str):
            # Parse string format: "+field" or "-field" or "field"
            if s.startswith("-"):
                field_name = s[1:]
                direction = ResourceMetaSortDirection.descending
            elif s.startswith("+"):
                field_name = s[1:]
                direction = ResourceMetaSortDirection.ascending
            else:
                field_name = s
                direction = ResourceMetaSortDirection.ascending

            # Check if it's a meta field
            if field_name in ResourceMetaSortKey.__members__:
                sort_obj = ResourceMetaSearchSort(
                    direction=direction, key=ResourceMetaSortKey(field_name)
                )
            else:
                sort_obj = ResourceDataSearchSort(
                    direction=direction, field_path=field_name
                )
            self._sorts.append(sort_obj)
        else:
            self._sorts.append(s)
    return self
order_by
order_by(
    *sorts: ResourceMetaSearchSort
    | ResourceDataSearchSort
    | str,
) -> Self

Alias for sort(). Add sorting criteria.

PARAMETER DESCRIPTION
*sorts

Sort objects or field name strings. Strings can be prefixed with '+' (ascending) or '-' (descending).

TYPE: ResourceMetaSearchSort | ResourceDataSearchSort | str DEFAULT: ()

RETURNS DESCRIPTION
Self

Self for chaining

Example

query.order_by("-created_time", "+name") query.order_by(QB.age().desc())

Source code in specstar/query.py
def order_by(
    self, *sorts: ResourceMetaSearchSort | ResourceDataSearchSort | str
) -> Self:
    """Alias for sort(). Add sorting criteria.

    Args:
        *sorts: Sort objects or field name strings.
               Strings can be prefixed with '+' (ascending) or '-' (descending).

    Returns:
        Self for chaining

    Example:
        query.order_by("-created_time", "+name")
        query.order_by(QB.age().desc())
    """
    return self.sort(*sorts)
page
page(page: int, size: int = 20) -> Self

Set pagination parameters.

PARAMETER DESCRIPTION
page

Page number (1-based, first page is 1)

TYPE: int

size

Number of items per page (default: 20)

TYPE: int DEFAULT: 20

RETURNS DESCRIPTION
Self

Self for chaining

Example

QB.status.eq("active").page(1, 10) # First page, 10 items QB.status.eq("active").page(2, 20) # Second page, 20 items QB.status.eq("active").page(3) # Third page, default 20 items

Source code in specstar/query.py
def page(self, page: int, size: int = 20) -> Self:
    """Set pagination parameters.

    Args:
        page: Page number (1-based, first page is 1)
        size: Number of items per page (default: 20)

    Returns:
        Self for chaining

    Example:
        QB.status.eq("active").page(1, 10)  # First page, 10 items
        QB.status.eq("active").page(2, 20)  # Second page, 20 items
        QB.status.eq("active").page(3)      # Third page, default 20 items
    """
    if page < 1:
        raise ValueError(f"Page number must be >= 1, got {page}")
    if size < 1:
        raise ValueError(f"Page size must be >= 1, got {size}")

    self._offset = (page - 1) * size
    self._limit = size
    return self
first
first() -> Self

Set limit to 1 to retrieve only the first result.

RETURNS DESCRIPTION
Self

Self for chaining

Example

QB.status.eq("active").sort(QB.created_time.desc()).first()

Source code in specstar/query.py
def first(self) -> Self:
    """Set limit to 1 to retrieve only the first result.

    Returns:
        Self for chaining

    Example:
        QB.status.eq("active").sort(QB.created_time.desc()).first()
    """
    self._limit = 1
    return self
build
Source code in specstar/query.py
def build(self) -> ResourceMetaSearchQuery:
    conditions = [self._condition] if self._condition else UNSET
    return ResourceMetaSearchQuery(
        conditions=conditions,
        limit=self._limit,
        offset=self._offset,
        sorts=self._sorts if self._sorts else UNSET,
    )
and_
and_(other) -> ConditionBuilder
Source code in specstar/query.py
def and_(self, other) -> "ConditionBuilder":
    return self & other
or_
or_(other) -> ConditionBuilder
Source code in specstar/query.py
def or_(self, other) -> "ConditionBuilder":
    return self | other
filter
filter(*conditions: ConditionBuilder) -> ConditionBuilder

Add AND conditions to the query. More readable than using &.

PARAMETER DESCRIPTION
*conditions

Additional conditions to AND with current condition

TYPE: ConditionBuilder DEFAULT: ()

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with combined conditions

Example

QB["age"].gt(18).filter(QB["status"].eq("active"), QB["verified"].eq(True))

Equivalent to: (QB["age"] > 18) & (QB["status"] == "active") & (QB["verified"] == True)
Source code in specstar/query.py
def filter(self, *conditions: "ConditionBuilder") -> "ConditionBuilder":
    """Add AND conditions to the query. More readable than using &.

    Args:
        *conditions: Additional conditions to AND with current condition

    Returns:
        ConditionBuilder with combined conditions

    Example:
        QB["age"].gt(18).filter(QB["status"].eq("active"), QB["verified"].eq(True))
        # Equivalent to: (QB["age"] > 18) & (QB["status"] == "active") & (QB["verified"] == True)
    """
    result = self
    for cond in conditions:
        result = result & cond
    return result
exclude
exclude(*conditions: ConditionBuilder) -> ConditionBuilder

Add NOT conditions to the query. More readable than using ~.

PARAMETER DESCRIPTION
*conditions

Conditions to exclude (will be negated and ANDed)

TYPE: ConditionBuilder DEFAULT: ()

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with excluded conditions

Example

QB["status"].eq("active").exclude(QB["role"].eq("guest"), QB["deleted"].eq(True))

Equivalent to: (status == "active") & ~(role == "guest") & ~(deleted == True)
Source code in specstar/query.py
def exclude(self, *conditions: "ConditionBuilder") -> "ConditionBuilder":
    """Add NOT conditions to the query. More readable than using ~.

    Args:
        *conditions: Conditions to exclude (will be negated and ANDed)

    Returns:
        ConditionBuilder with excluded conditions

    Example:
        QB["status"].eq("active").exclude(QB["role"].eq("guest"), QB["deleted"].eq(True))
        # Equivalent to: (status == "active") & ~(role == "guest") & ~(deleted == True)
    """
    result = self
    for cond in conditions:
        result = result & (~cond)
    return result

QueryBuilderMeta

Bases: type

Source code in specstar/query.py
class QueryBuilderMeta(type):
    def __getitem__(cls, name: str) -> Field:
        """Access data fields using bracket notation.

        Args:
            name: The field name (supports nested paths with dots)

        Returns:
            Field instance

        Example:
            QB["name"]  # Data field "name"
            QB["user.email"]  # Nested data field "user.email"
            QB["class"]  # Field name with reserved keyword
            QB["field-name"]  # Field name with special characters
        """
        return Field(name)

QB

Source code in specstar/query.py
class QB(metaclass=QueryBuilderMeta):
    # Meta Attributes - Resource metadata fields with type hints and IDE support
    @staticmethod
    def resource_id() -> Field:
        """Resource unique identifier.

        Returns:
            Field for resource_id

        Example:
            QB.resource_id().eq("abc-123")
            QB.resource_id() << ["id1", "id2", "id3"]
        """
        return Field("resource_id")

    @staticmethod
    def current_revision_id() -> Field:
        """Current revision identifier.

        Returns:
            Field for current_revision_id

        Example:
            QB.current_revision_id().eq("rev-456")
        """
        return Field("current_revision_id")

    @staticmethod
    def created_time() -> Field:
        """Resource creation timestamp.

        Returns:
            Field for created_time

        Example:
            QB.created_time() >= datetime(2024, 1, 1)
            QB.created_time().today()
            QB.created_time().last_n_days(7)
        """
        return Field("created_time")

    @staticmethod
    def updated_time() -> Field:
        """Resource last update timestamp.

        Returns:
            Field for updated_time

        Example:
            QB.updated_time().this_week()
            QB.updated_time() >= datetime(2024, 1, 1)
        """
        return Field("updated_time")

    @staticmethod
    def created_by() -> Field:
        """User who created the resource.

        Returns:
            Field for created_by

        Example:
            QB.created_by().eq("admin")
            QB.created_by() << ["user1", "user2"]
        """
        return Field("created_by")

    @staticmethod
    def updated_by() -> Field:
        """User who last updated the resource.

        Returns:
            Field for updated_by

        Example:
            QB.updated_by().eq("system")
            QB.updated_by().ne("guest")
        """
        return Field("updated_by")

    @staticmethod
    def is_deleted() -> Field:
        """Resource deletion status.

        Returns:
            Field for is_deleted

        Example:
            QB.is_deleted().eq(False)
            QB.is_deleted() == False
        """
        return Field("is_deleted")

    @staticmethod
    def schema_version() -> Field:
        """Resource schema version.

        Returns:
            Field for schema_version

        Example:
            QB.schema_version().eq("v2")
        """
        return Field("schema_version")

    @staticmethod
    def total_revision_count() -> Field:
        """Total number of revisions for the resource.

        Returns:
            Field for total_revision_count

        Example:
            QB.total_revision_count() > 5
        """
        return Field("total_revision_count")

    @staticmethod
    def rev_status() -> Field:
        """Status of the resource's current revision.

        Example:
            QB.rev_status().eq("draft")
        """
        return Field("rev_status")

    @staticmethod
    def rev_created_by() -> Field:
        """User who created the resource's current revision.

        Example:
            QB.rev_created_by().eq("alice")
            QB.rev_created_by() << ["alice", "bob"]
        """
        return Field("rev_created_by")

    @staticmethod
    def rev_updated_by() -> Field:
        """User who last updated the resource's current revision.

        Example:
            QB.rev_updated_by().eq("alice")
        """
        return Field("rev_updated_by")

    @staticmethod
    def rev_created_time() -> Field:
        """Creation timestamp of the resource's current revision.

        Example:
            QB.rev_created_time() >= datetime(2024, 1, 1)
        """
        return Field("rev_created_time")

    @staticmethod
    def rev_updated_time() -> Field:
        """Last-update timestamp of the resource's current revision.

        Example:
            QB.rev_updated_time().this_week()
        """
        return Field("rev_updated_time")

    # Combinators
    @staticmethod
    def all(*conditions: ConditionBuilder) -> ConditionBuilder:
        """Combine multiple conditions with AND logic.

        Args:
            *conditions: Variable number of ConditionBuilder instances.
                        If empty, returns a query with no conditions (matches all resources).

        Returns:
            ConditionBuilder with AND group, or no conditions if empty

        Example:
            QB.all(QB["age"] > 18, QB["status"] == "active", QB["score"] >= 80)
            # Equivalent to: (QB["age"] > 18) & (QB["status"] == "active") & (QB["score"] >= 80)

            QB.all()  # No conditions - matches all resources
        """
        if not conditions:
            # No conditions - return empty query (matches all)
            return ConditionBuilder(None)
        if len(conditions) == 1:
            return conditions[0]

        return ConditionBuilder(
            DataSearchGroup(
                operator=DataSearchLogicOperator.and_op,
                conditions=[
                    c._condition for c in conditions if c._condition is not None
                ],
            )
        )

    @staticmethod
    def any(*conditions: ConditionBuilder) -> ConditionBuilder:
        """Combine multiple conditions with OR logic.

        Args:
            *conditions: Variable number of ConditionBuilder instances

        Returns:
            ConditionBuilder with OR group

        Example:
            any(QB.status == "draft", QB.status == "pending", QB.status == "review")
            # Equivalent to: (QB.status == "draft") | (QB.status == "pending") | (QB.status == "review")
        """
        if not conditions:
            raise ValueError("any() requires at least one condition")
        if len(conditions) == 1:
            return conditions[0]

        return ConditionBuilder(
            DataSearchGroup(
                operator=DataSearchLogicOperator.or_op,
                conditions=[
                    c._condition for c in conditions if c._condition is not None
                ],
            )
        )
Functions
resource_id staticmethod
resource_id() -> Field

Resource unique identifier.

RETURNS DESCRIPTION
Field

Field for resource_id

Example

QB.resource_id().eq("abc-123") QB.resource_id() << ["id1", "id2", "id3"]

Source code in specstar/query.py
@staticmethod
def resource_id() -> Field:
    """Resource unique identifier.

    Returns:
        Field for resource_id

    Example:
        QB.resource_id().eq("abc-123")
        QB.resource_id() << ["id1", "id2", "id3"]
    """
    return Field("resource_id")
current_revision_id staticmethod
current_revision_id() -> Field

Current revision identifier.

RETURNS DESCRIPTION
Field

Field for current_revision_id

Example

QB.current_revision_id().eq("rev-456")

Source code in specstar/query.py
@staticmethod
def current_revision_id() -> Field:
    """Current revision identifier.

    Returns:
        Field for current_revision_id

    Example:
        QB.current_revision_id().eq("rev-456")
    """
    return Field("current_revision_id")
created_time staticmethod
created_time() -> Field

Resource creation timestamp.

RETURNS DESCRIPTION
Field

Field for created_time

Example

QB.created_time() >= datetime(2024, 1, 1) QB.created_time().today() QB.created_time().last_n_days(7)

Source code in specstar/query.py
@staticmethod
def created_time() -> Field:
    """Resource creation timestamp.

    Returns:
        Field for created_time

    Example:
        QB.created_time() >= datetime(2024, 1, 1)
        QB.created_time().today()
        QB.created_time().last_n_days(7)
    """
    return Field("created_time")
updated_time staticmethod
updated_time() -> Field

Resource last update timestamp.

RETURNS DESCRIPTION
Field

Field for updated_time

Example

QB.updated_time().this_week() QB.updated_time() >= datetime(2024, 1, 1)

Source code in specstar/query.py
@staticmethod
def updated_time() -> Field:
    """Resource last update timestamp.

    Returns:
        Field for updated_time

    Example:
        QB.updated_time().this_week()
        QB.updated_time() >= datetime(2024, 1, 1)
    """
    return Field("updated_time")
created_by staticmethod
created_by() -> Field

User who created the resource.

RETURNS DESCRIPTION
Field

Field for created_by

Example

QB.created_by().eq("admin") QB.created_by() << ["user1", "user2"]

Source code in specstar/query.py
@staticmethod
def created_by() -> Field:
    """User who created the resource.

    Returns:
        Field for created_by

    Example:
        QB.created_by().eq("admin")
        QB.created_by() << ["user1", "user2"]
    """
    return Field("created_by")
updated_by staticmethod
updated_by() -> Field

User who last updated the resource.

RETURNS DESCRIPTION
Field

Field for updated_by

Example

QB.updated_by().eq("system") QB.updated_by().ne("guest")

Source code in specstar/query.py
@staticmethod
def updated_by() -> Field:
    """User who last updated the resource.

    Returns:
        Field for updated_by

    Example:
        QB.updated_by().eq("system")
        QB.updated_by().ne("guest")
    """
    return Field("updated_by")
is_deleted staticmethod
is_deleted() -> Field

Resource deletion status.

RETURNS DESCRIPTION
Field

Field for is_deleted

Example

QB.is_deleted().eq(False) QB.is_deleted() == False

Source code in specstar/query.py
@staticmethod
def is_deleted() -> Field:
    """Resource deletion status.

    Returns:
        Field for is_deleted

    Example:
        QB.is_deleted().eq(False)
        QB.is_deleted() == False
    """
    return Field("is_deleted")
schema_version staticmethod
schema_version() -> Field

Resource schema version.

RETURNS DESCRIPTION
Field

Field for schema_version

Example

QB.schema_version().eq("v2")

Source code in specstar/query.py
@staticmethod
def schema_version() -> Field:
    """Resource schema version.

    Returns:
        Field for schema_version

    Example:
        QB.schema_version().eq("v2")
    """
    return Field("schema_version")
total_revision_count staticmethod
total_revision_count() -> Field

Total number of revisions for the resource.

RETURNS DESCRIPTION
Field

Field for total_revision_count

Example

QB.total_revision_count() > 5

Source code in specstar/query.py
@staticmethod
def total_revision_count() -> Field:
    """Total number of revisions for the resource.

    Returns:
        Field for total_revision_count

    Example:
        QB.total_revision_count() > 5
    """
    return Field("total_revision_count")
rev_status staticmethod
rev_status() -> Field

Status of the resource's current revision.

Example

QB.rev_status().eq("draft")

Source code in specstar/query.py
@staticmethod
def rev_status() -> Field:
    """Status of the resource's current revision.

    Example:
        QB.rev_status().eq("draft")
    """
    return Field("rev_status")
rev_created_by staticmethod
rev_created_by() -> Field

User who created the resource's current revision.

Example

QB.rev_created_by().eq("alice") QB.rev_created_by() << ["alice", "bob"]

Source code in specstar/query.py
@staticmethod
def rev_created_by() -> Field:
    """User who created the resource's current revision.

    Example:
        QB.rev_created_by().eq("alice")
        QB.rev_created_by() << ["alice", "bob"]
    """
    return Field("rev_created_by")
rev_updated_by staticmethod
rev_updated_by() -> Field

User who last updated the resource's current revision.

Example

QB.rev_updated_by().eq("alice")

Source code in specstar/query.py
@staticmethod
def rev_updated_by() -> Field:
    """User who last updated the resource's current revision.

    Example:
        QB.rev_updated_by().eq("alice")
    """
    return Field("rev_updated_by")
rev_created_time staticmethod
rev_created_time() -> Field

Creation timestamp of the resource's current revision.

Example

QB.rev_created_time() >= datetime(2024, 1, 1)

Source code in specstar/query.py
@staticmethod
def rev_created_time() -> Field:
    """Creation timestamp of the resource's current revision.

    Example:
        QB.rev_created_time() >= datetime(2024, 1, 1)
    """
    return Field("rev_created_time")
rev_updated_time staticmethod
rev_updated_time() -> Field

Last-update timestamp of the resource's current revision.

Example

QB.rev_updated_time().this_week()

Source code in specstar/query.py
@staticmethod
def rev_updated_time() -> Field:
    """Last-update timestamp of the resource's current revision.

    Example:
        QB.rev_updated_time().this_week()
    """
    return Field("rev_updated_time")
all staticmethod
all(*conditions: ConditionBuilder) -> ConditionBuilder

Combine multiple conditions with AND logic.

PARAMETER DESCRIPTION
*conditions

Variable number of ConditionBuilder instances. If empty, returns a query with no conditions (matches all resources).

TYPE: ConditionBuilder DEFAULT: ()

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with AND group, or no conditions if empty

Example

QB.all(QB["age"] > 18, QB["status"] == "active", QB["score"] >= 80)

Equivalent to: (QB["age"] > 18) & (QB["status"] == "active") & (QB["score"] >= 80)

QB.all() # No conditions - matches all resources

Source code in specstar/query.py
@staticmethod
def all(*conditions: ConditionBuilder) -> ConditionBuilder:
    """Combine multiple conditions with AND logic.

    Args:
        *conditions: Variable number of ConditionBuilder instances.
                    If empty, returns a query with no conditions (matches all resources).

    Returns:
        ConditionBuilder with AND group, or no conditions if empty

    Example:
        QB.all(QB["age"] > 18, QB["status"] == "active", QB["score"] >= 80)
        # Equivalent to: (QB["age"] > 18) & (QB["status"] == "active") & (QB["score"] >= 80)

        QB.all()  # No conditions - matches all resources
    """
    if not conditions:
        # No conditions - return empty query (matches all)
        return ConditionBuilder(None)
    if len(conditions) == 1:
        return conditions[0]

    return ConditionBuilder(
        DataSearchGroup(
            operator=DataSearchLogicOperator.and_op,
            conditions=[
                c._condition for c in conditions if c._condition is not None
            ],
        )
    )
any staticmethod
any(*conditions: ConditionBuilder) -> ConditionBuilder

Combine multiple conditions with OR logic.

PARAMETER DESCRIPTION
*conditions

Variable number of ConditionBuilder instances

TYPE: ConditionBuilder DEFAULT: ()

RETURNS DESCRIPTION
ConditionBuilder

ConditionBuilder with OR group

Example

any(QB.status == "draft", QB.status == "pending", QB.status == "review")

Equivalent to: (QB.status == "draft") | (QB.status == "pending") | (QB.status == "review")
Source code in specstar/query.py
@staticmethod
def any(*conditions: ConditionBuilder) -> ConditionBuilder:
    """Combine multiple conditions with OR logic.

    Args:
        *conditions: Variable number of ConditionBuilder instances

    Returns:
        ConditionBuilder with OR group

    Example:
        any(QB.status == "draft", QB.status == "pending", QB.status == "review")
        # Equivalent to: (QB.status == "draft") | (QB.status == "pending") | (QB.status == "review")
    """
    if not conditions:
        raise ValueError("any() requires at least one condition")
    if len(conditions) == 1:
        return conditions[0]

    return ConditionBuilder(
        DataSearchGroup(
            operator=DataSearchLogicOperator.or_op,
            conditions=[
                c._condition for c in conditions if c._condition is not None
            ],
        )
    )