Skip to content

Commit 2c0de38

Browse files
committed
Updated documentation, fixed minor errors, such as missing key validation
1 parent 7d52d3c commit 2c0de38

File tree

6 files changed

+233
-128
lines changed

6 files changed

+233
-128
lines changed

.markdownlint.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"MD013": false,
3+
"MD024": false,
34
"MD030": false,
45
"MD041": false
56
}

README.md

Lines changed: 179 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -20,134 +20,232 @@ pip install tomlval
2020

2121
The package is available for Python 3.11 and newer.
2222

23-
## Concepts
23+
## Usage
2424

25-
Before using the package, there are some concepts you may need to understand for the most optimal use of the package.
25+
### Handlers
2626

27-
### Key
27+
Handlers are the validation functions used to validate the value of keys in the input data.
2828

29-
A key is the name of a field in a TOML file, such as `name`, `person.name`, etc. Keys must conform to the TOML specification, which means keys are either **snake_case** or **SCREAMING_SNAKE_CASE**. For the validator, keys may also include wildcards, such as `*name`, `person.*`, etc.
29+
#### Types
3030

31-
### Handler
31+
A handler must be one of `type`, `Callable`. This means any object of type `type` is valid, and and `Callable`, such as `lambda` functions as well as named functions are valid.
3232

33-
A _handler_ is a function that is called for a certain key. Handlers can be of the following types:
33+
#### Parameters
3434

35-
- Types, such as `int`, `str`, `float`.
36-
- Tuples/Lists of types, such as `(int, str)`, `[int, str]`.
37-
- Anonymous functions (`lambda`)
38-
- Named functions (Callable objects, such as `def my_handler(key, value): ...`)
35+
The handler will dynamically be passed either the `key` and/or `value` argument based of what parameters are defined. No parameters are also okay.
3936

40-
The following argument configurations are supported:
37+
Examples of valid handlers are:
4138

42-
- `fn()`
43-
- `fn(key)`
44-
- `fn(value)`
45-
- `fn(key, value)`
39+
- **Types:** `str`, `int`, `datetime.datetime`, ...
40+
- **Anonymous functions:** `lambda: ...`, `lambda key: ...`, `lambda value: ...`, `lambda key, value: ...`
41+
- **Named functions:** `def my_fn()`, `def my_fn(key)`, `def my_fn(value)`, `def my_fn(key, value)`
4642

47-
If the handler has any other parameters than `key` or `value`, the validator will raise a `TOMLHandlerError`.
43+
If a handler accepts any parameters which are not `key` or `value`, a `TOMLHandlerError` will be raised.
4844

49-
Handlers may return any type, but is is recommended to use the return type as an error message if the value is invalid. The validator considers a `None` return value a successful validation.
45+
#### Return Types
46+
47+
A handler returns an error, meaning _nullish_ values tell the validator that the test passes. The reason for this design is that the handler may return error messages or any value your program needs.
5048

5149
### Schema
5250

53-
The schema is used to give the validator default handlers and an ability to make sure certain keys exist. The schema is defined in the `TOMLSchema` class, and is passed to the `TOMLValidator` class. To create a schema, you pass a dictionary with the keys and their respective allowed types.
51+
A schema is an _optional_ structure used to add functionality to the validator, this includes validation for missing keys and default handlers for keys.
52+
53+
#### Keys
54+
55+
Keys follow the TOML specification, meaning keys must be in either `snake_case` or `SCREAMING_SNAKE_CASE`. This project adds some special notation in the form of suffixing a key with `?` to make it optional, adding `[]` to the end to make the key an array and wildcard regex pattern support. The importance of keys are based of specificity, so `my.key` would dominate both `my.*` and `*`.
56+
57+
This means the following keys are examples of valid keys:
5458

55-
Here is an example of a schema:
59+
- `name`, `user.name`: Specific key
60+
- `*_name`, `user.*`, `*name*`, `user.*.name`: Wildcard keys
61+
- `last_name?`, `user.name?`, `array?[].key`: Optional keys
62+
- `array[]`, `array?[]`, `array[].key`: Nested arrays
5663

57-
```python
64+
All keys can be written in dot-notation, meaning a deeply nested object/array can be written in a simpler form. For example:
65+
66+
```py
5867
{
59-
"single_type": str,
60-
"list_of_strings": [str],
61-
"mixed_list:" [str, int],
62-
"multiple_types": (int, float),
63-
"optional?": str,
64-
"nested": {
65-
"key": str
68+
"very": {
69+
"deeply": {
70+
"nested": {
71+
"object": {
72+
"key": str
73+
}
74+
}
75+
}
6676
}
6777
}
6878
```
6979

70-
When a schema is defined, the validator will also check if values are missing and if their types are correct. If a handler is defined for a key, the validator will use the handler instead of the type defined in the schema.
80+
can be written as `"very.deeply.nested.object.key": str`. This notation also supports optionality and arrays. This would work by just suffixing the word with `?` and if an array, suffix the `?` with `[]`.
81+
82+
#### Defining a Schema
83+
84+
In order to define a new schema, you can use the following code as reference:
85+
86+
```py
87+
from tomlval import TOMLSchema
88+
89+
def my_fn(key, value):
90+
return "some-error"
91+
92+
def default_handler() -> str:
93+
""" Default handler for all keys """
94+
return "invalid-key"
95+
96+
schema = TOMLSchema({
97+
"single_type": str,
98+
"multiple_types": (int, float),
99+
"single_handler": lambda: "error-message",
100+
"multiple_handlers": (lambda: "error-message", str, my_fn),
101+
"optional?": str
102+
"list_of_strings": [str],
103+
"nested_dictionary": {
104+
"key": str,
105+
...
106+
},
107+
"nested_array": [
108+
{
109+
"key": str,
110+
...
111+
},
112+
...
113+
],
114+
})
115+
```
116+
117+
_Note: When a nested array includes dictionaries with different structures, they will be merged. If the merge fails, a `TOMLSchemaMergeError` will be raised._
71118

72119
### Validator
73120

74-
The validator is the core of the package. It is used to validate a TOML file. A schema is optionally passed to the validator, and handlers are added using the `add_handler` method. Once you feel ready, you can call the `validate` method with the data you want to validate as an argument to get a dictionary of errors.
121+
The validator defines the blueprint for how data should be validated. This is defined in the optional schema, or handlers can be manually added using the `add_handler(key, fn)` method. Handlers, like keys, are prioritized based of the key priority.
122+
123+
#### Examples
75124

76-
Currently, there are two type of error structures, for type errors and all other errors.
125+
##### Basic
77126

78-
Type errors are structured as follows:
127+
This examples includes the most basic use case, where a default handler is defined manually:
79128

80-
```python
81-
"key": (message, (value, expected_type, actual_type))
129+
```py
130+
from tomlval import TOMLValidator
131+
132+
validator = TOMLValidator()
133+
validator.add_handler("*", lambda: "invalid-key")
82134
```
83135

84-
_`expected_type` and `actual_type` can be either `type` or `tuple[type]`_
136+
##### With a Schema
137+
138+
This example includes a schema, assume the schema is populated with the structure and handlers you require.
85139

86-
All other errors have a slightly simpler structure:
140+
```py
141+
from tomlval import TOMLValidator, TOMLSchema
87142

88-
```python
89-
"key": (message, value)
143+
schema = TOMLSchema({...})
144+
validator = TOMLValidator(schema)
90145
```
91146

92-
The point of the validator is to parse the data and get the errors in a clean and easy way. **What you do with the errors is up to you.**
147+
##### Customizing a Defined Schema
93148

94-
## Example
149+
This example includes a case where you might have defined a _shared_ schema somewhere in your code but you need to customize specific keys:
95150

96-
Here is a full example of how to use the validator.
151+
```py
152+
from tomlval import TOMLValidator
153+
from .schema import schema
97154

98-
```python
99-
import pathlib
100-
import tomllib
101-
import datetime
102-
from tomlval import TOMLValidator, TOMLSchema
155+
def validate_age(value):
156+
if value <= 0:
157+
return "value-to-low"
158+
return None
103159

104-
# Load data from file
105-
path = pathlib.Path("data.toml")
160+
validator = TOMLValidator(schema)
161+
validator.add_handler("user.age", validate_age)
162+
```
106163

107-
with path.open("rb") as file:
108-
data_file = tomllib.load(file)
164+
##### Customizing The Default Callbacks
109165

110-
# Use a dictionary
111-
data_dict = {
112-
"first_name": "John",
113-
"last_name": "Doe",
114-
"age": 25
115-
}
166+
For some people, it might not be the best option to return an error message, and instead some other value might be preferred or even a more verbose error message. In this case, the `on_missing` and `on_type_mismatch` callbacks are changed:
116167

117-
# Define schema (optional)
118-
structure = {
119-
"first_name": str,
120-
"last_name": str,
121-
"age": int,
122-
"email": str,
123-
"phone": str,
124-
"birthday": datetime.datetime,
125-
"address": {
126-
"street": str,
127-
"city": str,
128-
"zip": int
129-
}
130-
}
168+
```py
169+
from tomlval import TOMLValidator
170+
from .schema import schema
171+
172+
def on_missing(key: str):
173+
return f"'{key}' is missing"
174+
175+
def on_type_mismatch(key: str, expected: type, got: type)
176+
return f"The argument '{key}' expected type '{expected.__name__}', got '{got.__name__}'"
177+
178+
validator = TOMLValidator(
179+
schema,
180+
on_missing=on_missing,
181+
on_type_mismatch=on_type_mismatch
182+
)
183+
```
184+
185+
### Validation
186+
187+
Now that you have defined your schema and validator, the validator is now ready to be used on TOML data.
188+
189+
In order to use the validator, the `validate(data)` method is used. It accepts any dictionary as an argument and outputs a flat dictionary of all keys in dot-notation with each key's respective error value.
131190

132-
schema = TOMLSchema(structure) # If the struture is invalid, a TOMLSchemaError is raised
191+
#### Examples
192+
193+
##### Validate File
194+
195+
This example shows a use-case where a TOML file is validated.
196+
197+
```py
198+
import tomllib
199+
from datetime import datetime
200+
from pathlib import Path
201+
from tomlval import TOMLSchema, TOMLValidator
202+
203+
# Read file
204+
file_path = Path("example.toml")
205+
with file_path.open("rb") as file:
206+
data = tomllib.load(file)
207+
208+
# Define schema
209+
schema = TOMLSchema({
210+
"*_name": str,
211+
"age": lambda value: "invalid-age" if age <= 0 else None,
212+
"birthday": datetime,
213+
"*": lambda: "invalid-key"
214+
})
133215

134216
# Define validator
135217
validator = TOMLValidator(schema)
136218

137-
# Add handlers
138-
validator.add_handler("*_name", lambda key: None if key in ["first_name", "last_name"] else "invalid-key")
139-
validator.add_handler("age", lambda value: None if 18 < value < 100 else "invalid-age")
140-
validator.add_handler("*", lambda: "invalid-key")
141-
142-
# Validate the data
143-
errors_file = validator.validate(data_file)
144-
errors_dict = validator.validate(data_dict)
219+
# Validate data
220+
errors = validator.validate(data)
145221
```
146222

147-
## Future Plans
223+
##### Validate Dictionary
224+
225+
Instead of loading a file, you might have pre-loaded TOML-data in the form of a dictionary.
148226

149-
Future plans are found in the [TODO](TODO.md) file.
227+
```py
228+
import tomllib
229+
from datetime import datetime
230+
from pathlib import Path
231+
from tomlval import TOMLSchema, TOMLValidator
232+
from .data import data
233+
234+
# Define schema
235+
schema = TOMLSchema({
236+
"*_name": str,
237+
"age": lambda value: "invalid-age" if age <= 0 else None,
238+
"birthday": datetime,
239+
"*": lambda: "invalid-key"
240+
})
241+
242+
# Define validator
243+
validator = TOMLValidator(schema)
244+
245+
# Validate data
246+
errors = validator.validate(data)
247+
```
150248

151249
## License
152250

153-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
251+
This project is licensed under the MIT License - seea the [LICENSE](LICENSE) file for details.

TODO.md

Lines changed: 0 additions & 4 deletions
This file was deleted.

tomlval/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@
22

33
from .errors import *
44
from .toml_schema import TOMLSchema
5-
6-
# from .toml_validator import TOMLValidator
5+
from .toml_validator import TOMLValidator

0 commit comments

Comments
 (0)