An easier way to validate data in python
Project description
Validatedata
An easier way to validate data in python.
Validatedata is for when you want expressive, inline validation rules without defining model classes. It is not a Pydantic alternative — it is a different tool for a different workflow: scripts, lightweight APIs, CLI tools, and anywhere defining a full model class feels like overkill.
Installation
pip install validatedata
For extended phone number validation (national, international, and region-specific formats):
pip install phonenumbers
Quick Start
from validatedata import validate_data
# with shorthand
rule={
'username': 'str|min:3|max:32',
'email': 'email',
'age': 'int|min:18',
}
result = validate_data(
data={'username': 'alice', 'email': 'alice@example.com', 'age': 25},
rule=rule
)
if result.ok:
print('valid!')
else:
print(result.errors)
With the
keyswrapperrule = {'keys': { 'username': 'str|min:3|max:32', 'email': 'email', 'age': 'int|min:18', }}The
keysform is recommended when you need to pair field rules with top-level options (such asstrict_keysin a future release).
Three Ways to Validate
1. validate_types decorator
Validates function arguments against their Python type annotations. Works with or without brackets.
from validatedata import validate_types
@validate_types
def create_user(username: str, age: int):
return f'{username} ({age})'
create_user('alice', 30) # works
create_user('alice', 'thirty') # raises ValidationError
# with options — brackets required
@validate_types(raise_exceptions=False)
def create_user(username: str, age: int):
return f'{username} ({age})'
result = create_user('alice', 'thirty')
# returns {'errors': [...]} instead of raising
2. validate decorator
from validatedata import validate
signup_rules = [
{
'type': 'str',
'expression': r'^[^\d\W_]+[\w\d_-]{2,31}$',
'expression-message': 'invalid username'
},
'email:msg:invalid email address',
{
'type': 'str',
'expression': r'(?=\S*[a-z])(?=\S*[A-Z])(?=\S*\d)(?=\S*[^\w\s])\S{8,}$',
'message': 'password must contain uppercase, lowercase, number and symbol'
}
]
class User:
@validate(signup_rules, raise_exceptions=True)
def signup(self, username, email, password):
return 'Account Created'
user = User()
user.signup('alice_99', 'alice@example.com', 'Secure@123') # works
user.signup('alice_99', 'not-an-email', 'weak') # raises ValidationError
Async functions are supported. The decorator behaves identically:
from validatedata import validate
@validate(signup_rules, raise_exceptions=True)
async def signup(self, username, email, password):
await db.save(username, email, password)
return 'Account Created'
validate_types works the same way with async functions.
Class methods:
class User:
@classmethod
@validate(rule=['str', 'str'], is_class=True)
def format_name(cls, firstname, lastname):
return f'{firstname} {lastname}'
3. validate_data function
from validatedata import validate_data
rules = [
{'type': 'int', 'range': (1, 'any'), 'range-message': 'must be greater than zero'},
{'type': 'int', 'range': (1, 'any')}
]
result = validate_data(data=[a, b], rule=rules)
if result.ok:
total = a + b
else:
print(result.errors)
Dict input:
rules = {'keys': {
'username': {'type': 'str', 'range': (3, 32)},
'age': {'type': 'int', 'range': (18, 'any'), 'range-message': 'must be 18 or older'}
}}
result = validate_data(data={'username': 'alice', 'age': 25}, rule=rules)
Parameters
validate and validate_data:
| Parameter | Type | Default | Description |
|---|---|---|---|
rule |
str, list, tuple, dict | required | validation rules matching the data by index |
raise_exceptions |
bool | False |
raise ValidationError on failure instead of returning errors |
is_class |
bool | False |
set to True for classmethods without self |
mutate |
bool | False |
apply transforms to the original values and return them |
kwds |
dict | — | extra config: log_errors, group_errors |
validate_types:
Same as above except raise_exceptions defaults to True.
Set log_errors=True to log background errors: @validate(rules, kwds={'log_errors': True})
Set group_errors=False to return a flat error list instead of grouped by field.
Return Value
A SimpleNamespace with:
result.ok—Trueif all validation passedresult.errors— list of errors (grouped by field by default)result.data— transformed data, only present whenmutate=True
result = validate_data(...)
if result.ok:
pass
else:
for error_group in result.errors:
print(error_group)
Types
Basic types
| Type | Description |
|---|---|
bool |
Boolean |
color |
Color in any format. Use format key to specify: hex, rgb, hsl, named |
date |
Date or datetime string |
email |
Email address |
even |
Even integer |
float |
Float |
int |
Integer |
ip |
IPv4 or IPv6 address |
odd |
Odd integer |
phone |
Phone number. E.164 built-in. Extended formats require pip install phonenumbers |
prime |
Prime number |
semver |
Semantic version e.g. 1.0.0, 2.1.0-alpha.1 |
slug |
URL-friendly string e.g. my-blog-post |
str |
String |
url |
URL with protocol e.g. https://example.com |
uuid |
UUID string |
Extended types
dict, list, object, regex, set, tuple
Rules
| Rule | Type | Description |
|---|---|---|
contains |
str or tuple | values expected to be present |
depends_on |
dict | validate only when a sibling field meets a condition |
endswith |
object | value the data must end with |
excludes |
str or tuple | values not permitted |
expression |
str | regular expression the data must match |
fields |
dict | rules for nested dict fields |
items |
dict | rule applied to each item in a list or tuple |
length |
int | exact expected length |
nullable |
bool | allow None as a valid value. Default False |
options |
tuple | permitted values |
range |
tuple | permitted range. Use 'any' for an open bound |
startswith |
object | value the data must start with |
strict |
bool | skip type casting. Default False |
transform |
callable or dict | function applied to the value before validation |
type |
str | type expected. Always required |
unique |
bool | list or tuple must contain no duplicates |
Custom Error Messages
Add a {rule}-message key to override any default error:
rules = [{
'type': 'int',
'range': (18, 'any'),
'range-message': 'you must be at least 18 years old'
}, {
'type': 'str',
'range': (3, 32),
'range-message': 'username must be between 3 and 32 characters'
}, {
'type': 'email',
'message': 'please enter a valid email address'
}]
Shorthand Rule Strings
Rules can be expressed as compact strings instead of dicts. There are two syntaxes: the original colon syntax for simple cases, and the newer pipe syntax for anything more expressive. Both work side by side in the same rule list.
Colon syntax (original)
'str' # string
'str:20' # string of exactly 20 characters
'int:10' # int of exactly 10 digits
'email' # email address
'email:msg:invalid email address' # with custom error message
'int:1:to:100' # int in range 1 to 100
'regex:[A-Z]{3}' # must match regex
Pipe syntax
The pipe syntax uses | to chain modifiers onto a type. It supports the full set of validation rules, optional transforms, and custom messages — all in one readable string.
General shape:
type [| transform ...] [| modifier ...] [| msg:message]
Transforms must come before validators. msg: must always be last.
Type
Any supported type name is valid as the first token:
'str|...'
'int|...'
'email|...'
'url|...'
'uuid|...'
# ...any type from the Types section
Flags
'int|strict' # no type coercion — value must already be the right type
'email|nullable' # None is accepted as a valid value
'int|strict|nullable' # both
Range
'int|min:18' # >= 18, no upper limit
'int|max:100' # no lower limit, <= 100
'int|min:0|max:100' # between 0 and 100 inclusive
'int|between:0,100' # shorthand for the above
'str|min:3|max:32' # string length between 3 and 32
'float|min:0.5|max:9.9' # float range
'list|min:1|max:10' # list must have between 1 and 10 items
min and max can be used independently for open bounds. between is a convenience alias for min + max together — they cannot be combined.
Note:
validatedatadoes not impose a maximum size on lists or tuples. If you are validating untrusted input in a web API or other public-facing context, always set an explicit upper bound to prevent memory exhaustion from unexpectedly large payloads.
Enums and exclusions
'str|in:admin,user,guest' # value must be one of these
'str|not_in:root,superuser' # value must not be any of these
String constraints
'str|starts_with:https' # must start with this prefix
'str|ends_with:.pdf' # must end with this suffix
'str|contains:@' # must contain this substring
'list|unique' # no duplicate values
Values can safely contain | — the parser only splits on | when followed by a recognised keyword:
'str|starts_with:image/png|min:3' # 'image/png' is treated as one value
Format
For types that support format variants:
'color|format:hex' # #fff or #ffffff
'color|format:rgb' # rgb(255, 0, 0)
'color|format:hsl' # hsl(0, 100%, 50%)
'color|format:named' # red, cornflowerblue, etc.
'phone|format:national' # (415) 555-2671 — requires: pip install phonenumbers
'phone|format:e164' # +14155552671 — built-in
Transforms
Named transforms are applied to the value before validation runs. They are the only modifiers that must come before validators.
'str|strip|min:3|max:32' # strip whitespace, then check length
'str|lower|in:admin,user,guest' # lowercase, then check options
'str|strip|lower|min:3|max:32' # chain as many as needed
Available named transforms: strip, lstrip, rstrip, lower, upper, title.
To get the transformed value back, pass mutate=True to validate_data or @validate:
result = validate_data([' Alice '], ['str|strip|lower'], mutate=True)
result.data # ['alice']
Regex
'str|re:[A-Z]{3}' # must match pattern
'str|min:8|re:(?=.*[A-Z])(?=.*\d).+' # combined with other modifiers
The pattern is everything after re: up to the next recognised modifier or end of string. Patterns can safely contain : and |:
'str|re:https?://\S+' # colons in pattern are safe
'str|re:(?=.*[A-Z]|.*\d).+' # pipes in pattern are safe
Custom error message
msg: must be the last modifier. The message text can contain any characters including |:
'str|min:3|max:32|msg:must be 3 to 32 characters'
'int|min:18|msg:you must be 18 or older'
'str|re:[A-Z]+|msg:uppercase letters only'
'int|min:0|msg:must be positive | or zero' # | inside message is fine
Mixing syntaxes
Colon shorthand, pipe shorthand, and dict rules can all coexist in the same rule list:
rules = [
{'type': 'str', 'expression': r'^[\w-]{3,32}$', 'expression-message': 'invalid username'},
'email|nullable|msg:invalid email',
'str|min:8|re:(?=.*[A-Z])(?=.*\d).+|msg:password too weak',
]
Reference table
| Modifier | Example | Description |
|---|---|---|
strict |
int|strict |
No type coercion |
nullable |
email|nullable |
Allow None |
unique |
list|unique |
No duplicate values |
min:N |
int|min:18 |
Minimum value or length |
max:N |
int|max:100 |
Maximum value or length |
between:N,M |
int|between:0,100 |
Range shorthand |
in:a,b,c |
str|in:admin,user |
Allowed values |
not_in:a,b |
str|not_in:root |
Excluded values |
starts_with:x |
str|starts_with:https |
Required prefix |
ends_with:x |
str|ends_with:.pdf |
Required suffix |
contains:x |
str|contains:@ |
Required substring |
format:x |
color|format:hex |
Format variant |
strip |
str|strip|min:3 |
Remove surrounding whitespace |
lstrip |
str|lstrip|min:3 |
Remove leading whitespace |
rstrip |
str|rstrip|min:3 |
Remove trailing whitespace |
lower |
str|lower|in:yes,no |
Lowercase before validating |
upper |
str|upper|starts_with:ADM |
Uppercase before validating |
title |
str|title|min:3 |
Title case before validating |
re:pattern |
str|re:[A-Z]{3} |
Regex pattern |
msg:text |
str|min:3|msg:too short |
Custom error message — must be last |
Real-world examples
# user signup fields
rules = [
'str|strip|min:3|max:32|msg:username must be 3 to 32 characters',
'email|nullable|msg:invalid email address',
'str|min:8|re:(?=.*[A-Z])(?=.*\d).+|msg:password must contain uppercase and a number',
]
# role with enum
'str|in:admin,editor,viewer|msg:invalid role'
# optional hex colour
'color|format:hex|nullable'
# URL that must use HTTPS
'url|starts_with:https|msg:must be a secure URL'
# slugified identifier
'slug|min:3|max:64|msg:invalid slug'
# age gate
'int|strict|min:18|max:120|msg:invalid age'
# phone — any format, optional
'phone|nullable'
# deduplicated tag list
'list|unique|min:1|max:10'
# transform then validate
'str|strip|lower|in:yes,no,maybe|msg:invalid response'
Range Rule
The 'any' keyword is used as an open bound:
Note:
validatedatadoes not impose a maximum size on lists or tuples. If you are validating untrusted input in a web API or other public-facing context, always set an explicit upper bound to prevent memory exhaustion from unexpectedly large payloads.
{'type': 'int', 'range': (1, 'any')} # >= 1, no upper limit
{'type': 'int', 'range': ('any', 100)} # no lower limit, <= 100
{'type': 'int', 'range': (1, 100)} # >= 1 and <= 100
{'type': 'date', 'range': ('01-Jan-2021', 'any')} # from Jan 2021 onwards
{'type': 'date', 'range': ('any', '31-Dec-2025')} # up to Dec 2025
# on str — checks string length
{'type': 'str', 'range': (3, 32)} # len(s) >= 3 and len(s) <= 32
# on list/tuple — checks number of elements
{'type': 'list', 'range': (1, 10)} # between 1 and 10 items
Examples
Color validation
# accept any color format
{'type': 'color'}
# specific formats
{'type': 'color', 'format': 'hex'} # #ff0000 or #fff
{'type': 'color', 'format': 'rgb'} # rgb(255, 0, 0)
{'type': 'color', 'format': 'hsl'} # hsl(0, 100%, 50%)
{'type': 'color', 'format': 'named'} # red, cornflowerblue, etc.
result = validate_data(
data={'primary': '#ff0000', 'background': 'white'},
rule={'keys': {
'primary': {'type': 'color', 'format': 'hex'},
'background': {'type': 'color', 'format': 'named'}
}}
)
Phone validation
# E.164 format — built-in, no extra install
{'type': 'phone'} # +14155552671
{'type': 'phone', 'format': 'e164'} # same
# extended formats — requires: pip install phonenumbers
{'type': 'phone', 'format': 'national'} # (415) 555-2671
{'type': 'phone', 'format': 'international'} # +1 415-555-2671
{'type': 'phone', 'region': 'GB'} # region-specific validation
New types
# url
validate_data(['https://example.com'], [{'type': 'url'}])
# ip — accepts both IPv4 and IPv6
validate_data(['192.168.1.1'], [{'type': 'ip'}])
validate_data(['2001:db8::1'], [{'type': 'ip'}])
# uuid
validate_data(['550e8400-e29b-41d4-a716-446655440000'], [{'type': 'uuid'}])
# slug
validate_data(['my-blog-post'], [{'type': 'slug'}])
# semver
validate_data(['1.2.3'], [{'type': 'semver'}])
validate_data(['2.0.0-alpha.1'], [{'type': 'semver'}])
# prime
validate_data([7], [{'type': 'prime'}])
# even and odd
validate_data([4], [{'type': 'even'}])
validate_data([3], [{'type': 'odd'}])
Nullable fields
rules = {'keys': {
'name': {'type': 'str'},
'middle_name': {'type': 'str', 'nullable': True}, # optional
'age': {'type': 'int'}
}}
validate_data({'name': 'Alice', 'middle_name': None, 'age': 30}, rules).ok # True
validate_data({'name': 'Alice', 'middle_name': 'Jane', 'age': 30}, rules).ok # True
Unique collections
rules = [{'type': 'list', 'unique': True}]
validate_data([[1, 2, 3]], rules).ok # True
validate_data([[1, 2, 2]], rules).ok # False — duplicates
Transform
Simple — pass a callable:
rules = [{'type': 'str', 'transform': str.strip, 'length': 5}]
validate_data([' hello '], rules).ok # True — stripped before length check
Complex — access sibling fields:
rules = {'keys': {
'role': {'type': 'str'},
'username': {
'type': 'str',
'transform': {
'func': lambda value, data: value.upper() if data.get('role') == 'admin' else value,
'pass_data': True
}
}
}}
With mutate=True — get back the transformed values:
result = validate_data(
data=[' alice ', ' bob '],
rule=[
{'type': 'str', 'transform': str.strip},
{'type': 'str', 'transform': str.strip}
],
mutate=True
)
result.ok # True
result.data # ['alice', 'bob']
Using mutate=True with the decorator passes transformed values into the function:
@validate(rules, mutate=True)
def save_user(username):
# username arrives already stripped
db.save(username)
Conditional validation with depends_on
Validate a field only when a sibling field meets a condition:
# simple equality check
rules = {'keys': {
'role': {'type': 'str'},
'permissions': {
'type': 'str',
'depends_on': {'field': 'role', 'value': 'admin'},
'options': ('full', 'read', 'none')
}
}}
# permissions only validated when role is 'admin'
validate_data({'role': 'user', 'permissions': 'anything'}, rules).ok # True
validate_data({'role': 'admin', 'permissions': 'full'}, rules).ok # True
validate_data({'role': 'admin', 'permissions': 'anything'}, rules).ok # False
Callable condition for complex logic:
rules = {'keys': {
'age': {'type': 'int'},
'guardian_name': {
'type': 'str',
'depends_on': {
'field': 'age',
'condition': lambda age: age < 18
},
'message': 'guardian name required for users under 18'
}
}}
Custom object types
class Address:
pass
rules = [{'type': 'object', 'object': Address, 'message': 'Address object expected'}]
address = Address()
validate_data([address], rules).ok # True
validate_data(['not an address'], rules).ok # False
Nested data structures
When rules contain fields or items, errors are automatically returned as path-prefixed flat strings instead of the default grouped format.
Nested dict:
rules = {'keys': {
'user': {
'type': 'dict',
'fields': {
'username': {'type': 'str', 'range': (3, 32)},
'email': {'type': 'email'},
'age': {'type': 'int', 'range': (18, 'any')}
}
}
}}
result = validate_data(
data={'user': {'username': 'al', 'email': 'not-an-email', 'age': 25}},
rule=rules
)
result.ok # False
result.errors # ['user.username: invalid string length', 'user.email: invalid email']
Deeply nested:
rules = {'keys': {
'company': {
'type': 'dict',
'fields': {
'name': {'type': 'str'},
'address': {
'type': 'dict',
'fields': {
'street': {'type': 'str'},
'city': {'type': 'str'},
'postcode': {'type': 'str', 'length': 6}
}
}
}
}
}}
result = validate_data(
data={'company': {'name': 'Acme', 'address': {'street': '1 Main St', 'city': 'Lagos', 'postcode': '123'}}},
rule=rules
)
result.errors # ['company.address.postcode: value is not of required length']
Mirror-structure shorthand (0.4.0+):
Instead of wrapping every nested dict in {'type': 'dict', 'fields': {...}}, you can write a rule that mirrors the shape of your data:
data = {
'app': {
'name': 'QuickScript',
'version': '1.0.0',
}
}
# before — explicit form
rule = {'keys': {
'app': {
'type': 'dict',
'fields': {
'name': {'type': 'str', 'range': (3, 'any')},
'version': {'type': 'semver'},
}
}
}}
# after — rule mirrors the data
rule = {
'app': {
'name': 'str|min:3',
'version': 'semver',
}
}
Error paths are identical in both forms. Nesting can go up to 100 levels deep — exceeding this raises a ValueError. See the mirror-rules guide for the full reference.
List of typed items:
rules = [{'type': 'list', 'items': {'type': 'int', 'range': (1, 100)}}]
result = validate_data([[10, 50, 200, 5]], rules)
result.errors # ['[0][2]: number out of range']
List of dicts:
rules = [{'type': 'list', 'items': {
'type': 'dict',
'fields': {
'name': {'type': 'str'},
'score': {'type': 'int', 'range': (0, 100)}
}
}}]
result = validate_data(
data=[[
{'name': 'Alice', 'score': 95},
{'name': 'Bob', 'score': 150}, # invalid
]],
rule=rules
)
result.errors # ['[0][1].score: number out of range']
raise_exceptions
from validatedata import validate, ValidationError
rules = [{'type': 'email', 'message': 'invalid email'}]
@validate(rules, raise_exceptions=True)
def send_email(address):
...
try:
send_email('not-an-email')
except ValidationError as e:
print(e) # invalid email
contains, excludes, options
# contains — value must include these
{'type': 'str', 'contains': '@'}
{'type': 'list', 'contains': ('admin', 'user')}
# excludes — value must not include these
{'type': 'str', 'excludes': ('forbidden', 'banned')}
# options — value must be one of these (equal to)
{'type': 'str', 'options': ('active', 'inactive', 'pending')}
# not equal to — achieved with excludes
{'type': 'str', 'excludes': ('deleted',)}
startswith and endswith
# strings
{'type': 'str', 'startswith': 'https'}
{'type': 'str', 'endswith': '.pdf'}
# lists
{'type': 'list', 'startswith': 'header'}
{'type': 'list', 'endswith': 'footer'}
strict mode
By default validatedata casts values before checking type (strict=False), so "42" passes as an int. Set strict=True to require exact types:
{'type': 'int', 'strict': True} # "42" will fail, only 42 passes
{'type': 'str', 'strict': True} # 42 will fail, only "42" passes
Real-World Example: API Request Validation
from validatedata import validate, validate_data
# validate a product creation request
product_rules = {'keys': {
'name': {'type': 'str', 'range': (2, 100)},
'slug': {'type': 'slug', 'message': 'slug must be lowercase with hyphens only'},
'price': {'type': 'float', 'range': (0, 'any'), 'range-message': 'price must be positive'},
'version': {'type': 'semver'},
'homepage': {'type': 'url', 'nullable': True},
'tags': {'type': 'list', 'unique': True, 'nullable': True},
'variants': {
'type': 'list',
'items': {
'type': 'dict',
'fields': {
'sku': {'type': 'uuid'},
'color': {'type': 'color'},
'stock': {'type': 'int', 'range': (0, 'any')}
}
}
}
}}
result = validate_data(data=request_body, rule=product_rules)
if not result.ok:
return {'status': 400, 'errors': result.errors}
Additional Notes
depends_ononly works whendatais a dict since it needs access to sibling fields- Nested data (
fields,items) automatically switches error format to path-prefixed strings - The current version does not support
depends_onacross nested levels transformruns before type checking, so the transformed value is what gets validated
Contributing
Contributions are welcome!
Before starting work on a new feature or non-trivial change, please open an issue first. This helps avoid duplicate effort and lets us align on scope and approach before any code is written.
Getting Started
- Open an issue describing what you'd like to add or change
- You'll be informed if there's someone working on it and given the green light if it's the right call
- Fork the repository and create a branch off
main
git checkout -b feature/your-feature-name
- Make your changes and add tests where appropriate
- Open a pull request referencing the issue
For bug fixes and small improvements, feel free to skip the issue and go straight to a PR.
License
MIT
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 validatedata-0.4.1.tar.gz.
File metadata
- Download URL: validatedata-0.4.1.tar.gz
- Upload date:
- Size: 46.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d15aa33659dd6b84696caef0b20378757cd04a4bb8f4f5a72a2fc947091b9ffd
|
|
| MD5 |
d44daa90cc5e34f44a8bd5906505af6e
|
|
| BLAKE2b-256 |
4568dfdb33a9122399dfcf9cef168c88419b8d230acb95424942a01cfda20667
|
Provenance
The following attestation bundles were made for validatedata-0.4.1.tar.gz:
Publisher:
release.yml on Edward-K1/validatedata
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
validatedata-0.4.1.tar.gz -
Subject digest:
d15aa33659dd6b84696caef0b20378757cd04a4bb8f4f5a72a2fc947091b9ffd - Sigstore transparency entry: 1102541285
- Sigstore integration time:
-
Permalink:
Edward-K1/validatedata@7db2a197b48d7ae78745ca8ef33d943b9dfaa0c6 -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/Edward-K1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7db2a197b48d7ae78745ca8ef33d943b9dfaa0c6 -
Trigger Event:
release
-
Statement type:
File details
Details for the file validatedata-0.4.1-py3-none-any.whl.
File metadata
- Download URL: validatedata-0.4.1-py3-none-any.whl
- Upload date:
- Size: 24.8 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 |
aedb0b61446d6b5c00185df18f7794693d498675912821e057acd2d550f615ab
|
|
| MD5 |
e2ec4e0dd5152e5eb233fde1f2beb27f
|
|
| BLAKE2b-256 |
8422cc0f59aa61b57abb87b7971733130a6606c3e7ac5fe41da48cf1a24ed5d6
|
Provenance
The following attestation bundles were made for validatedata-0.4.1-py3-none-any.whl:
Publisher:
release.yml on Edward-K1/validatedata
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
validatedata-0.4.1-py3-none-any.whl -
Subject digest:
aedb0b61446d6b5c00185df18f7794693d498675912821e057acd2d550f615ab - Sigstore transparency entry: 1102541320
- Sigstore integration time:
-
Permalink:
Edward-K1/validatedata@7db2a197b48d7ae78745ca8ef33d943b9dfaa0c6 -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/Edward-K1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7db2a197b48d7ae78745ca8ef33d943b9dfaa0c6 -
Trigger Event:
release
-
Statement type: