The Criteria Pattern is a Python package that simplifies and standardizes criteria based filtering, validation and selection.
Project description
๐ค๐ป Criteria Pattern
The Criteria Pattern is a Python ๐ package that simplifies and standardizes criteria based filtering ๐ค๐ป, validation and selection. This package provides a set of prebuilt ๐ท๐ป objects and utilities that you can drop into your existing projects and not have to implement yourself.
These utilities ๐ ๏ธ are useful when you need complex filtering logic. It also enforces ๐ฎ๐ป best practices so all your filtering processes follow a uniform standard.
Easy to install and integrate, this is a must have for any Python developer looking to simplify their workflow, enforce design patterns and use the full power of modern ORMs and SQL ๐๏ธ in their projects ๐.
Table of Contents
- ๐ฅ Installation
- ๐ Documentation
- โจ Features
- ๐ป Utilization
- ๐งฑ Core Concepts
- ๐งฎ Supported Operators
- ๐ Available Converters
- ๐ Security For User-Facing APIs
- ๐ก๏ธ SQL Conversion And Safety
- ๐ฆ Request Body Examples
- ๐งญ Structured URL Query Examples
- ๐ฏ Real-Life Case: Multi-tenant User Search Service
- ๐ Simple URL Query Examples
- ๐งช Testing Helpers
- ๐ค Contributing
- ๐ License
๐ฅ Installation
You can install Criteria Pattern using pip:
pip install criteria-pattern
๐ Documentation
The root README is the entry point. Deeper guides live in this repository and are linked here:
๐งฑ Usage Guide: Core models, composition rules, operators and pagination.๐ Converter Guide: SQL converters, request converters, placeholder styles and mapping.๐ Security Guide: User-facing criteria safety and injection prevention.๐งช Testing Guide: Object mother helpers and testing recommendations.
This project's DeepWiki documentation is also available for generated repository navigation.
โจ Features
Criteria Pattern gives you a typed, reusable way to describe queries before deciding how they should be executed.
- ๐งฑ Composable criteria objects with filters, orders, page size and page number.
- ๐ Boolean composition with
&,|and~forAND,ORandNOTlogic. - ๐ 20 filter operators covering equality, comparison, pattern matching, ranges, null checks and list membership.
- โ๏ธ Ordering primitives with duplicate order-field protection.
- ๐ Pagination primitives that enforce positive integer values and consistent page-number usage.
- ๐๏ธ SQL converters for PostgreSQL, MySQL, MariaDB and SQLite.
- ๐งพ Request converters for decoded request bodies, structured URL queries and compact suffix-based URL queries.
- ๐ก๏ธ Security-oriented allowlist validation for tables, columns, fields, operators, directions and pagination bounds.
- ๐งฐ Field and operator mapping so public API names can be translated to internal model or database names.
- ๐งช Object mother testing helpers for downstream projects that want realistic criteria fixtures.
๐ป Utilization
from criteria_pattern import Criteria, Filter, Operator
from criteria_pattern.converters import CriteriaToPostgresqlConverter
is_adult = Criteria(filters=[Filter(field='age', operator=Operator.GREATER_OR_EQUAL, value=18)])
email_is_gmail = Criteria(filters=[Filter(field='email', operator=Operator.ENDS_WITH, value='@gmail.com')])
email_is_yahoo = Criteria(filters=[Filter(field='email', operator=Operator.ENDS_WITH, value='@yahoo.com')])
query, parameters = CriteriaToPostgresqlConverter.convert(
criteria=is_adult & (email_is_gmail | email_is_yahoo),
table='user',
valid_columns=['age', 'email'],
valid_operators=[Operator.GREATER_OR_EQUAL, Operator.ENDS_WITH],
)
print(query)
print(parameters)
# >>> SELECT * FROM "user" WHERE ("age" >= %(parameter_0)s AND ("email" LIKE '%%' || %(parameter_1)s OR "email" LIKE '%%' || %(parameter_2)s));
# >>> {'parameter_0': 18, 'parameter_1': '@gmail.com', 'parameter_2': '@yahoo.com'}
๐งฑ Core Concepts
The main model is Criteria. A criteria can contain filters, orders and pagination:
from criteria_pattern import Criteria, Direction, Filter, Operator, Order
criteria = Criteria(
filters=[
Filter(field='status', operator=Operator.EQUAL, value='ACTIVE'),
Filter(field='age', operator=Operator.GREATER_OR_EQUAL, value=18),
],
orders=[
Order(field='created_at', direction=Direction.DESC),
],
page_size=20,
page_number=1,
)
Criteria can be composed without losing the boolean structure:
from criteria_pattern import Criteria, Filter, Operator
is_active = Criteria(filters=[Filter(field='status', operator=Operator.EQUAL, value='ACTIVE')])
is_adult = Criteria(filters=[Filter(field='age', operator=Operator.GREATER_OR_EQUAL, value=18)])
has_company_email = Criteria(filters=[Filter(field='email', operator=Operator.ENDS_WITH, value='@acme.com')])
criteria = is_active & (is_adult | has_company_email)
not_archived = ~Criteria(filters=[Filter(field='archived_at', operator=Operator.IS_NOT_NULL, value=None)])
Pagination is optional. page_size can be used alone for LIMIT, while page_number must be used with page_size so converters can calculate the offset.
๐งฎ Supported Operators
| Operator | Meaning | Expected value |
|---|---|---|
EQUAL |
Field equals value | scalar |
NOT_EQUAL |
Field does not equal value | scalar |
GREATER |
Field is greater than value | scalar |
GREATER_OR_EQUAL |
Field is greater than or equal to value | scalar |
LESS |
Field is less than value | scalar |
LESS_OR_EQUAL |
Field is less than or equal to value | scalar |
LIKE |
SQL-like pattern match | scalar pattern |
NOT_LIKE |
SQL-like pattern negation | scalar pattern |
CONTAINS |
Field contains value | scalar |
NOT_CONTAINS |
Field does not contain value | scalar |
STARTS_WITH |
Field starts with value | scalar |
NOT_STARTS_WITH |
Field does not start with value | scalar |
ENDS_WITH |
Field ends with value | scalar |
NOT_ENDS_WITH |
Field does not end with value | scalar |
BETWEEN |
Field is between two values | two values |
NOT_BETWEEN |
Field is not between two values | two values |
IS_NULL |
Field is null | ignored / None |
IS_NOT_NULL |
Field is not null | ignored / None |
IN |
Field is one of many values | one or more values |
NOT_IN |
Field is not one of many values | one or more values |
๐ Available Converters
The package includes converters for SQL generation and request parsing:
criteria_pattern.converters.CriteriaToPostgresqlConverter: Converts aCriteriaobject into PostgreSQL SQL + parameters.criteria_pattern.converters.CriteriaToMysqlConverter: Converts aCriteriaobject into MySQL SQL + parameters.criteria_pattern.converters.CriteriaToMariadbConverter: Converts aCriteriaobject into MariaDB SQL + parameters.criteria_pattern.converters.CriteriaToSqliteConverter: Converts aCriteriaobject into SQLite SQL + parameters.criteria_pattern.converters.SimpleUrlToCriteriaConverter: Parses simple public URL query parameters into aCriteriaobject.criteria_pattern.converters.UrlToCriteriaConverter: Parses URL query parameters into aCriteriaobject.criteria_pattern.converters.BodyToCriteriaConverter: Parses decoded request bodies into aCriteriaobject.
SQL converter output uses the placeholder style expected by each database family:
| Converter | Placeholder style | Parameters |
|---|---|---|
CriteriaToPostgresqlConverter |
%(parameter_0)s |
dict[str, object] |
CriteriaToMysqlConverter |
%s |
list[object] |
CriteriaToMariadbConverter |
%s |
list[object] |
CriteriaToSqliteConverter |
:parameter_0 |
dict[str, object] |
๐ Security For User-Facing APIs
When criteria comes from a URL, JSON body, form, dashboard or any other user-facing surface, treat every field, operator, direction and pagination value as untrusted.
Criteria Pattern parameterizes filter values for SQL converters, quotes SQL identifiers per dialect, and enables allowlist validation by default. SQL identifiers still cannot be safely parameterized by database drivers, so for user-facing APIs you must pass explicit valid_* allowlists instead of relying on implicit defaults derived from the current criteria.
Use this rule of thumb:
| Input kind | Risk | Recommended protection |
|---|---|---|
Filter values like 'Doe', 18 or ['ACTIVE'] |
SQL value injection | Handled by converter parameters |
| Table names | SQL identifier injection | valid_tables allowlist (validation on by default) |
| Selected columns | SQL identifier injection | valid_columns allowlist (validation on by default) |
| Filter and order fields | SQL identifier injection | valid_fields when parsing; valid_columns with mapped SQL column names when converting |
| Operators | Query behavior abuse | valid_operators allowlist with only the operators you expose |
| Directions | Query behavior abuse | valid_directions allowlist |
| Page size and page number | Expensive queries / overflow | strict max_page_size / max_page_number (validation on by default) |
The safest user-facing flow is:
- Keep allowlists in application code, not in request data.
- Parse request input with
BodyToCriteriaConverter,UrlToCriteriaConverterorSimpleUrlToCriteriaConverter. - Map public field names to internal field or column names with
fields_mapping. - Enable field, operator, direction and pagination validation in the request converter.
- Enable table, column, criteria, operator, direction and pagination validation again in the SQL converter.
from criteria_pattern import Direction, Operator
from criteria_pattern.converters import BodyToCriteriaConverter, CriteriaToPostgresqlConverter
body = {
'filters': [{'field': 'q', 'operator': 'contains', 'value': 'Doe'}],
'orders': [{'field': 'created', 'direction': 'DESC'}],
'page_size': 20,
'page_number': 1,
}
fields_mapping = {'q': 'name', 'created': 'created_at'}
valid_fields = ['name', 'created_at']
valid_operators = [Operator.CONTAINS]
valid_directions = [Direction.DESC]
criteria = BodyToCriteriaConverter.convert(
body=body,
fields_mapping=fields_mapping,
check_field_injection=True,
check_operator_injection=True,
check_direction_injection=True,
check_pagination_bounds=True,
valid_fields=valid_fields,
valid_operators=valid_operators,
valid_directions=valid_directions,
max_page_size=100,
max_page_number=1000,
)
query, parameters = CriteriaToPostgresqlConverter.convert(
criteria=criteria,
table='users',
columns=['id', 'name', 'created_at'],
check_table_injection=True,
check_column_injection=True,
check_criteria_injection=True,
check_operator_injection=True,
check_direction_injection=True,
check_pagination_bounds=True,
valid_tables=['users'],
valid_columns=['id', 'name', 'created_at'],
valid_operators=valid_operators,
valid_directions=valid_directions,
max_page_size=100,
max_page_number=1000,
)
This keeps the public API flexible while ensuring SQL identifiers and query behavior are constrained by code you control.
๐ก๏ธ SQL Conversion And Safety
Filter values are parameterized, which protects the values passed to predicates. Identifier validation is separate and must be enabled explicitly when identifiers can be influenced by users.
from criteria_pattern import Criteria, Direction, Filter, Operator, Order
from criteria_pattern.converters import CriteriaToPostgresqlConverter
criteria = Criteria(
filters=[Filter(field='public_name', operator=Operator.CONTAINS, value='Doe')],
orders=[Order(field='created_at', direction=Direction.DESC)],
page_size=20,
page_number=2,
)
query, parameters = CriteriaToPostgresqlConverter.convert(
criteria=criteria,
table='users',
columns=['id', 'name', 'email'],
columns_mapping={'public_name': 'name'},
check_table_injection=True,
check_column_injection=True,
check_criteria_injection=True,
check_operator_injection=True,
check_direction_injection=True,
check_pagination_bounds=True,
valid_tables=['users'],
valid_columns=['id', 'name', 'email', 'created_at', 'public_name'],
valid_operators=[Operator.CONTAINS],
valid_directions=[Direction.DESC],
max_page_size=100,
max_page_number=1000,
)
print(query)
print(parameters)
# >>> SELECT "id", "name", "email" FROM "users" WHERE "name" LIKE '%%' || %(parameter_0)s || '%%' ORDER BY "created_at" DESC LIMIT %(limit_1)s OFFSET %(offset_2)s;
# >>> {'parameter_0': 'Doe', 'limit_1': 20, 'offset_2': 20}
Use columns_mapping when your public API fields should not expose your database column names directly. When check_criteria_injection is enabled, include the accepted public criteria field names in valid_columns too.
๐ฆ Request Body Examples
Use BodyToCriteriaConverter when your API receives a decoded dictionary, for example from JSON.
from criteria_pattern import Direction, Operator
from criteria_pattern.converters import BodyToCriteriaConverter
body = {
'filters': [
{'field': 'full_name', 'operator': 'contains', 'value': 'Doe'},
{'field': 'status', 'operator': 'IN', 'value': ['ACTIVE', 'PENDING']},
{'field': 'price', 'operator': 'BETWEEN', 'value': [10, 100]},
],
'orders': [
{'field': 'created_at', 'direction': 'desc'},
],
'page_size': 20,
'page_number': 1,
}
criteria = BodyToCriteriaConverter.convert(
body=body,
fields_mapping={'full_name': 'name'},
operator_mapping={'after': Operator.GREATER},
check_field_injection=True,
check_operator_injection=True,
check_direction_injection=True,
valid_fields=['name', 'status', 'price', 'created_at'],
valid_operators=[Operator.CONTAINS, Operator.IN, Operator.BETWEEN],
valid_directions=[Direction.DESC],
)
print(criteria.filters[0].field, criteria.filters[0].operator, criteria.filters[0].value)
# >>> name CONTAINS Doe
Accepted body keys are filters, orders, page_size and page_number. Unknown keys, missing required filter/order keys and invalid value shapes raise IntegrityError.
๐งญ Structured URL Query Examples
Use UrlToCriteriaConverter when you want an explicit URL format that can express filters, orders and pagination.
from criteria_pattern import Direction, Operator
from criteria_pattern.converters import UrlToCriteriaConverter
url = (
'https://api.example.com/users?'
'filters[0][field]=name&filters[0][operator]=CONTAINS&filters[0][value]=Doe&'
'filters[1][field]=age&filters[1][operator]=GREATER_OR_EQUAL&filters[1][value]=18&'
'orders[0][field]=created_at&orders[0][direction]=DESC&'
'page_size=20&page_number=1'
)
criteria = UrlToCriteriaConverter.convert(
url=url,
valid_fields=['name', 'age', 'created_at'],
valid_operators=[Operator.CONTAINS, Operator.GREATER_OR_EQUAL],
valid_directions=[Direction.DESC],
)
print(criteria.filters[0].field, criteria.filters[0].operator, criteria.filters[0].value)
# >>> name CONTAINS Doe
print(criteria.orders[0].field, criteria.orders[0].direction)
# >>> created_at DESC
Structured URL values are converted to useful primitive types where possible: booleans, null / none, integers and floats.
๐ฏ Real-Life Case: Multi-tenant User Search Service
Imagine an admin dashboard where each request must:
- Always restrict results to the current tenant.
- Optionally filter active users.
- Search only users with company emails.
- Sort by newest users first.
With Criteria Pattern, each concern is a small reusable criteria object. You combine them using & and |, then convert once to SQL:
from criteria_pattern import Criteria, Direction, Filter, Operator, Order
from criteria_pattern.converters import CriteriaToPostgresqlConverter
class UserSearchService:
def __init__(self, tenant_id: str) -> None:
self.tenant_id = tenant_id
def build_query(self, *, only_active: bool, corporate_domain: str) -> tuple[str, dict[str, object]]:
tenant_scope = Criteria(filters=[Filter(field='tenant_id', operator=Operator.EQUAL, value=self.tenant_id)])
active_scope = Criteria(filters=[Filter(field='is_active', operator=Operator.EQUAL, value=True)])
email_scope = Criteria(filters=[Filter(field='email', operator=Operator.ENDS_WITH, value=corporate_domain)])
sort_scope = Criteria(orders=[Order(field='created_at', direction=Direction.DESC)])
criteria = tenant_scope & email_scope & sort_scope
if only_active:
criteria = criteria & active_scope
return CriteriaToPostgresqlConverter.convert(
criteria=criteria,
table='users',
valid_columns=['tenant_id', 'is_active', 'email', 'created_at'],
valid_operators=[Operator.EQUAL, Operator.ENDS_WITH],
valid_directions=[Direction.DESC],
)
service = UserSearchService(tenant_id='tenant_123')
query, parameters = service.build_query(only_active=True, corporate_domain='@acme.com')
print(query)
print(parameters)
๐ Simple URL Query Examples
Use SimpleUrlToCriteriaConverter when you want a compact public query format where each parameter becomes one AND filter. Plain parameters use equality, and suffixes map to operators.
from criteria_pattern import Operator
from criteria_pattern.converters import SimpleUrlToCriteriaConverter
criteria = SimpleUrlToCriteriaConverter.convert(
url='https://api.example.com/users?name=Doe&age_gte=18&page_size=20&page_number=1',
valid_fields=['name', 'age'],
valid_operators=[Operator.EQUAL, Operator.GREATER_OR_EQUAL],
)
print(criteria.filters[0].field, criteria.filters[0].operator, criteria.filters[0].value)
# >>> name EQUAL Doe
print(criteria.filters[1].field, criteria.filters[1].operator, criteria.filters[1].value)
# >>> age GREATER_OR_EQUAL 18
print(criteria.page_size, criteria.page_number)
# >>> 20 1
Common suffixes:
| URL parameter | Parsed filter |
|---|---|
name=Doe |
Filter(field='name', operator=Operator.EQUAL, value='Doe') |
name_eq=Doe |
Filter(field='name', operator=Operator.EQUAL, value='Doe') |
status_ne=DELETED |
Filter(field='status', operator=Operator.NOT_EQUAL, value='DELETED') |
price_gt=10 |
Filter(field='price', operator=Operator.GREATER, value=10) |
price_gte=10 |
Filter(field='price', operator=Operator.GREATER_OR_EQUAL, value=10) |
price_lt=100 |
Filter(field='price', operator=Operator.LESS, value=100) |
price_lte=100 |
Filter(field='price', operator=Operator.LESS_OR_EQUAL, value=100) |
email_contains=gmail.com |
Filter(field='email', operator=Operator.CONTAINS, value='gmail.com') |
name_starts_with=Ad |
Filter(field='name', operator=Operator.STARTS_WITH, value='Ad') |
email_ends_with=.com |
Filter(field='email', operator=Operator.ENDS_WITH, value='.com') |
price_between=10,100 |
Filter(field='price', operator=Operator.BETWEEN, value=[10, 100]) |
age_not_between=18&age_not_between=30 |
Filter(field='age', operator=Operator.NOT_BETWEEN, value=[18, 30]) |
status_in=ACTIVE&status_in=PENDING |
Filter(field='status', operator=Operator.IN, value=['ACTIVE', 'PENDING']) |
status_not_in=DELETED |
Filter(field='status', operator=Operator.NOT_IN, value=['DELETED']) |
deleted_at_is_null=true |
Filter(field='deleted_at', operator=Operator.IS_NULL, value=None) |
deleted_at_is_not_null=true |
Filter(field='deleted_at', operator=Operator.IS_NOT_NULL, value=None) |
Comma-separated values are also supported for list operators:
criteria = SimpleUrlToCriteriaConverter.convert(
url='https://api.example.com/users?status_in=ACTIVE,PENDING,BLOCKED',
valid_fields=['status'],
valid_operators=[Operator.IN],
)
print(criteria.filters[0].value)
# >>> ['ACTIVE', 'PENDING', 'BLOCKED']
You can map public field names to internal field names:
criteria = SimpleUrlToCriteriaConverter.convert(
url='https://api.example.com/users?full_name_contains=Doe',
fields_mapping={'full_name': 'name'},
valid_fields=['name'],
valid_operators=[Operator.CONTAINS],
)
print(criteria.filters[0].field, criteria.filters[0].operator, criteria.filters[0].value)
# >>> name CONTAINS Doe
You can also extend or override URL suffixes:
criteria = SimpleUrlToCriteriaConverter.convert(
url='https://api.example.com/users?created_at_after=2026-05-18',
suffix_operator_mapping={'after': Operator.GREATER},
valid_fields=['created_at'],
valid_operators=[Operator.GREATER],
)
print(criteria.filters[0].field, criteria.filters[0].operator, criteria.filters[0].value)
# >>> created_at GREATER 2026-05-18
๐งช Testing Helpers
The package includes object mother helpers for tests in downstream projects. They are useful when you need valid random criteria objects and want to override only the fields that matter for a specific test.
from criteria_pattern import Filter, Operator
from criteria_pattern.models.testing.mothers import CriteriaMother
from criteria_pattern.models.testing.mothers.filter import FilterMother
criteria = CriteriaMother.with_filters(
filters=[
Filter(field='status', operator=Operator.EQUAL, value='ACTIVE'),
FilterMother.create(field='age', operator=Operator.GREATER_OR_EQUAL, value=18),
]
)
print(criteria.has_filters())
# >>> True
Available helpers include CriteriaMother, FilterMother, FiltersMother, OrderMother, OrdersMother, PageSizeMother, PageNumberMother, plus field/operator/direction mothers under the filter and order testing packages.
๐ค Contributing
We love community help! Before you open an issue or pull request, please read:
Thank you for helping make ๐ค๐ป Criteria Pattern package awesome! ๐
๐ License
This project is licensed under the terms of the MIT license.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file criteria_pattern-4.0.0.tar.gz.
File metadata
- Download URL: criteria_pattern-4.0.0.tar.gz
- Upload date:
- Size: 56.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc17fe0116ba0e6e559aa3994299a7325ef79d1d2907da0ad20ad3422624d503
|
|
| MD5 |
50f68a2646cfdcfe486c08dc4e70db98
|
|
| BLAKE2b-256 |
1d12b590d8cebf2af8210389ac406dcac25ab4a3f789b22dd935246829ebea84
|
Provenance
The following attestation bundles were made for criteria_pattern-4.0.0.tar.gz:
Publisher:
ci.yaml on adriamontoto/criteria-pattern
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
criteria_pattern-4.0.0.tar.gz -
Subject digest:
dc17fe0116ba0e6e559aa3994299a7325ef79d1d2907da0ad20ad3422624d503 - Sigstore transparency entry: 1693360866
- Sigstore integration time:
-
Permalink:
adriamontoto/criteria-pattern@6ee2ccfc9477109463232581ba6b7b3a288bf803 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/adriamontoto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yaml@6ee2ccfc9477109463232581ba6b7b3a288bf803 -
Trigger Event:
push
-
Statement type:
File details
Details for the file criteria_pattern-4.0.0-py3-none-any.whl.
File metadata
- Download URL: criteria_pattern-4.0.0-py3-none-any.whl
- Upload date:
- Size: 81.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a1232cfc31695e2377ebe5579c881eab644fde610c6f8ca3bf13606a517e562
|
|
| MD5 |
fe74e5f18ef81038a2b5f635fb37b912
|
|
| BLAKE2b-256 |
27d7fcf653197707a17d381dc5dd238b9df94c87632313374051b07471e6b28e
|
Provenance
The following attestation bundles were made for criteria_pattern-4.0.0-py3-none-any.whl:
Publisher:
ci.yaml on adriamontoto/criteria-pattern
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
criteria_pattern-4.0.0-py3-none-any.whl -
Subject digest:
6a1232cfc31695e2377ebe5579c881eab644fde610c6f8ca3bf13606a517e562 - Sigstore transparency entry: 1693361331
- Sigstore integration time:
-
Permalink:
adriamontoto/criteria-pattern@6ee2ccfc9477109463232581ba6b7b3a288bf803 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/adriamontoto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yaml@6ee2ccfc9477109463232581ba6b7b3a288bf803 -
Trigger Event:
push
-
Statement type: