<p align="center">
<img
width="256"
src="https://i.imgur.com/X4sdZt7.png"
alt="Carica Logo"
/>
</p>
<h1 align="center">Carica - A Python Configurator</h1>
<p align="center">
<a href="https://github.com/Trimatix/Carica/actions"
><img
src="https://img.shields.io/github/actions/workflow/status/Trimatix/Carica/testing.yml?branch=main"
alt="GitHub Actions workflow status"
/></a>
<a href="https://github.com/Trimatix/Carica/projects/1?card_filter_query=label%3Abug"
><img
src="https://img.shields.io/github/issues-search?color=eb4034&label=bug%20reports&query=repo%3ATrimatix%2FCarica%20is%3Aopen%20label%3Abug"
alt="GitHub open bug reports"
/></a>
<a href="https://github.com/Trimatix/Carica/actions"
><img
src="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/Trimatix/2551cac90336c1d1073d8615407cc72d/raw/Carica__heads_main.json"
alt="Test coverage"
/></a>
</p>
<p align="center">
<a href="https://pypi.org/project/Carica"
><img
src='https://badgen.net/pypi/v/Carica/'
alt="Pypi package version"
/></a>
<a href="https://pypi.org/project/Carica"
><img
src="https://img.shields.io/pypi/pyversions/Carica.svg"
alt="Minimum supported Python version"
/></a>
<a href="https://pepy.tech/project/carica"
><img
src="https://static.pepy.tech/badge/carica"
alt="Total PyPi Downloads"
</p>
<p align="center">
<a href="https://sonarcloud.io/dashboard?id=Trimatix_Carica"
><img
src="https://sonarcloud.io/api/project_badges/measure?project=Trimatix_Carica&metric=bugs"
alt="SonarCloud bugs analysis"
/></a>
<a href="https://sonarcloud.io/dashboard?id=Trimatix_Carica"
><img
src="https://sonarcloud.io/api/project_badges/measure?project=Trimatix_Carica&metric=code_smells"
alt="SonarCloud code smells analysis"
/></a>
<a href="https://sonarcloud.io/dashboard?id=Trimatix_Carica"
><img
src="https://sonarcloud.io/api/project_badges/measure?project=Trimatix_Carica&metric=alert_status"
alt="SonarCloud quality gate status"
/></a>
</p>
Carica is a python application configurator, interfacing between a pure python config module, and TOML representation of that module.
<hr>
### Credits
A huge thank you goes to [@sdispater](https://github.com/sdispater), author of the fantastic [tomlkit library](https://github.com/sdispater/tomlkit), which makes this project's variable docstrings retaining features possible.
## Project Goals
Python applications can be configured in a number of ways, each with its own advantages and limitations.
<details>
<summary>Common Configuration Methods</summary>
<table>
<tbody>
<tr>
<th align="center">Method</th>
<th align="center">Advantages</th>
<th align="center">Problems</th>
</tr>
<tr>
<td>Environment variables/Command line arguments</td>
<td>
<ul>
<li>Easy to handle in code</li>
<li>Container/venv safe</li>
</ul>
</td>
<td>
<ul>
<li>Not scalable to large numbers of variables</li>
<li>Primative data types only</li>
<li>Not human-friendly</li>
<li>No typing in code</li>
<li>No code autocompletion or other editor features</li>
<li>Difficult to version control</li>
</ul>
</td>
</tr>
<tr>
<td>TOML config file</td>
<td>
<ul>
<li>Container/venv safe</li>
<li>More scalable</li>
<li>More expressive, with tables</li>
<li>Easy to version control</li>
<li>Human friendly</li>
</ul>
</td>
<td>
<ul>
<li>Not easy to manage in code</li>
<li>No code autocompletion or other editor features</li>
<li>No dot syntax for objects</li>
<li>No typing in code</li>
</ul>
</td>
</tr>
<tr>
<td>Python module with variables</td>
<td>
<ul>
<li>Easy to handle in code</li>
<li>Easy to version control, with rich, human-readable diffs</li>
<li>Highly scalable</li>
<li>Completely expressive</li>
<li>Dot syntax for objects</li>
<li>Variable typing in code</li>
<li>Complete language and editor features</li>
</ul>
</td>
<td>
<ul>
<li>Not container/venv safe</li>
<li>Not human-friendly</li>
<li>Module must be accessible to the application namespace - difficult for packages</li>
</ul>
</td>
</tr>
</tbody>
</table>
</details>
Carica aims to mix the best bits from two of the most convenient configuration methods, acting as an interface between pure python modules and TOML config files.
## Basic Usage
To use Carica, your application configuration should be defined as a python module.
<details>
<summary>Example Application</summary>
*loginApp.py*
```py
import cfg
import some_credentials_manager
import re
print(cfg.welcome_message)
new_user_data = {}
for field_name, field_config in cfg.new_user_required_fields.items():
print(field_config['display'] + ":")
new_value = input()
if re.match(new_value, field_config['validation_regex']):
new_user_data[field_name] = new_value
else:
raise ValueError(f"The value for {field_name} did not pass validation")
some_credentials_manager.create_user(new_user_data)
```
*cfg.py*
```py
welcome_message = "Welcome to the application. Please create an account:"
new_user_required_fields = {
"username": {
"display": "user-name",
"validation_regex": "[a-z]+"
},
"password": {
"display": "pw",
"validation_regex": "\\b(?!password\\b)\\w+"
},
}
```
</details>
#### Default config generation
Carica is able to auto-generate a default TOML config file for your application, with the values specified in your python module as defaults:
```py
>>> import cfg
>>> import carica
>>> carica.makeDefaultCfg(cfg)
Created defaultCfg.toml
```
The above code will produce the following file:
*defaultCfg.toml*
```toml
welcome_message = "Welcome to the application. Please create an account:"
[new_user_required_fields]
[new_user_required_fields.username]
display = "user-name"
validation_regex = "[a-z]+"
[new_user_required_fields.password]
display = "pw"
validation_regex = "\\b(?!password\\b)\\w+"
```
### Loading a configuration file
Carica will map the variables given in your config file to those present in your python module.
Since the config python module contains default values, Carica does not require every variable to be specified:
*myConfig.toml*
```toml
[new_user_required_fields]
[new_user_required_fields.avatar]
display = "profile picture"
validation_regex = "[a-z]+"
```
```py
>>> import cfg
>>> import carica
>>> carica.loadCfg(cfg, "myConfig.toml")
Config successfully loaded: myConfig.toml
>>> import loginApp
Welcome to the application. Please create an account:
profile picture:
123
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "loginApp.py", line 14, in <module>
raise ValueError(f"The value for {field_name} did not pass validation")
ValueError: The value for avatar did not pass validation
```
### Variable Pseudo-Docstrings
When encountering a comment in your python config module, Carica will treat it as a variable 'docstring' in the following cases:
1. Inline comments on the same line as a variable declaration
2. Line comments immediately preceeding a variable declaration ('preceeding comments') *\*Beta feature: still in testing\**
3. Line comments immediately preceeding an existing preceeding comment *\*Beta feature: still in testing\**
Carica will consider your variable docstrings when building TOML config files:
*cfg.py*
```py
# This is shown to the user when the application is first launched
# No validation is performed on this string
welcome_message = "Welcome to the application. Please create an account:"
new_user_required_fields = { # Each field should specify a 'display' (formatted field name shown to users) and a 'validation_regex', which inputted values will be checked against
"username": {
"display": "user-name",
"validation_regex": "[a-z]+"
},
"password": {
"display": "pw",
"validation_regex": "\\b(?!password\\b)\\w+"
},
}
```
```py
>>> import cfg
>>> import carica
>>> carica.makeDefaultCfg(cfg)
Created defaultCfg.toml
```
The above code will produce the following file:
*defaultCfg.toml*
```toml
# This is shown to the user when the application is first launched
# No validation is performed on this string
welcome_message = "Welcome to the application. Please create an account:"
[new_user_required_fields] # Each field should specify a 'display' (formatted field name shown to users) and a 'validation_regex', which inputted values will be checked against
[new_user_required_fields.username]
display = "user-name"
validation_regex = "[a-z]+"
[new_user_required_fields.password]
display = "pw"
validation_regex = "\\b(?!password\\b)\\w+"
```
## Advanced Usage
Carica will handle non-primative variable types according to a very simple design pattern:
### The `SerializableType` type protocol
```py
class SerializableType:
def serialize(self, **kwargs): ...
@classmethod
def deserialize(cls, data, **kwargs): ...
```
Any type which defines `serialize` and `deserialize` member methods will be automatically serialized during config generation, and deserialized on config loading.
- `serialize` must return a representation of your object with primative types - types which can be written to toml.
- `deserialize` must be a class method, and should transform a serialized object representation into a new object.
Carica enforces this pattern on non-primative types using the `SerializableType` type protocol, which allows for duck-typed serializable types. This protocol is exposed for use with `isinstance`.
Projects which prefer strong typing may implement the `carica.ISerializable` interface to enforce this pattern with inheritence. Carica will validate serialized objects against the `carica.PrimativeType` type alias, which is also exposed for use.
### Example
*cfg.py*
```py
class MySerializableType:
def __init__(self, myField):
self.myField = myField
def serialize(self, **kwargs):
return {"myField": self.myField}
@classmethod
def deserialize(self, data, **kwargs):
return MySerializableClass(data["myField"])
mySerializableVar = MySerializableClass("hello")
```
#### Default config generation
```py
>>> import cfg
>>> import carica
>>> carica.makeDefaultCfg(cfg)
Created defaultCfg.toml
```
The above code will produce the following file:
*defaultCfg.toml*
```toml
[mySerializableVar]
myField = "hello"
```
#### Config file loading
*myConfig.toml*
```toml
[mySerializableVar]
myField = "some changed value"
```
```py
>>> import cfg
>>> import carica
>>> carica.loadCfg(cfg, "myConfig.toml")
Config successfully loaded: myConfig.toml
>>> cfg.mySerializableVar.myField
some changed value
```
### Premade models
Carica provides serializable models that are ready to use (or extend) in your code. These models can be found in the `carica.models` package, which is imported by default.
#### SerializableDataClass
Removes the need to write boilerplate serializing functionality for dataclasses. This class is intended to be extended, adding definitions for your dataclass's fields. Extensions of `SerializableDataClass` **must** themselves be decorated with `@dataclasses.dataclass` in order to function correctly.
#### SerializablePath
An OS-agnostic filesystem path, extending `pathlib.Path`. The serializing/deserializing behaviour added by this class is minimal, a serialized `SerializablePath` is simply the string representation of the path, for readability. All other behaviour of `pathlib.Path` applies, for example. `SerializablePath` can be instantiated from a single path: `SerializablePath("my/directory/path")`, or from path segments: `SerializablePath("my", "file", "path.toml")`.
#### SerializableTimedelta
`datetime.datetime` is already considered a primitive type by TomlKit, and so no serializability needs to be added for you to use this class in your configs. However, `datetime.timedelta` is not serializable by default. `SerializableTimedelta` solves this issue as a serializable subclass. As a subclass, all `timedelta` behaiour applies, including the usual constructor. In addition, `SerializableTimedelta.fromTimedelta` is a convenience class method that accepts a `datetime.timedelta` and constructs a new `SerializableTimedelta` from it.
#### Premade models example
The recommended usage pattern for `SerializableDataClass` is to separate your models into a separate module/package, allowing for 'schema' definition as python code. This pattern is not necessary, model definition *can* be done in your config file.
*configSchema.py*
```py
from carica.models import SerializableDataClass
from dataclasses import dataclass
@dataclass
class UserDataField(SerializableDataClass):
name: str
validation_regex: str
```
*config.py*
```py
from carica.models import SerializablePath, SerializableTimedelta
from configSchema import UserDataField
from datetime import datetime
new_user_required_fields = [
UserDataField(
name = "user-name"
validation_regex = "[a-z]+"
),
UserDataField(
name = "password"
validation_regex = "\\b(?!password\\b)\\w+"
)
]
database_path = SerializablePath("default/path.csv")
birthday = datetime(day=1, month=1, year=1500)
connection_timeout = SerializableTimedelta(minutes=5)
```
## Planned features
- Preceeding comments: This functionality is 'complete' in that it functions as intended and passes all unit tests, however an issue needs to be worked aruond before the feature can be enabled: In order to disambiguate between variables and table fields, the TOML spec requires that arrays and tables be placed at the end of a document. Carica currently depends upon documents being rendered with variables appearing in the same order as they appear in the python config module, which is not guaranteed. This leads to trailing and otherwise misplaced preceeding comments.
- Config mutation: Carica should allow for loading an existing config, changing some values, and then updating the TOML document with new values. This should retain all formatting from the original document, including variable ordering and any comments that are not present in the python module.
## Limitations
- No support for schema migration
- No support for asynchronous object serializing/deserializing
- Imperfect estimation of variables defined in python modules: Listing the variables defined within a scope is not a known feature of python, and so Carica estimates this information by iterating over the tokens in your module. Carica does not build an AST of your python module.
This means that certain name definition structures will result in false positives/negatives. This behaviour has not been extensively tested, but once such false positive has been identified:
When invoking a callable (such as a class or function) with a keyword argument on a new, unindented line, the argument
name will be falsely identified as a variable name. E.g:
```py
my_variable = dict(key1=value1,
key2=value2)
```
produces `my_variable` and `key2` as variable names.
Raw data
{
"_id": null,
"home_page": "https://github.com/Trimatix/carica",
"name": "carica",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "",
"keywords": "config,cfg,python,toml,generate,auto-generate,orm",
"author": "Jasper Law",
"author_email": "trimatix.music@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/bf/78/8332d37adcdfe165b2bf8d0533a114318b21987dcbb1df432b3774734bfa/carica-1.3.5.tar.gz",
"platform": null,
"description": "<p align=\"center\">\n <img\n width=\"256\"\n src=\"https://i.imgur.com/X4sdZt7.png\"\n alt=\"Carica Logo\"\n />\n</p>\n<h1 align=\"center\">Carica - A Python Configurator</h1>\n<p align=\"center\">\n <a href=\"https://github.com/Trimatix/Carica/actions\"\n ><img\n src=\"https://img.shields.io/github/actions/workflow/status/Trimatix/Carica/testing.yml?branch=main\"\n alt=\"GitHub Actions workflow status\"\n /></a>\n <a href=\"https://github.com/Trimatix/Carica/projects/1?card_filter_query=label%3Abug\"\n ><img\n src=\"https://img.shields.io/github/issues-search?color=eb4034&label=bug%20reports&query=repo%3ATrimatix%2FCarica%20is%3Aopen%20label%3Abug\"\n alt=\"GitHub open bug reports\"\n /></a>\n <a href=\"https://github.com/Trimatix/Carica/actions\"\n ><img\n src=\"https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/Trimatix/2551cac90336c1d1073d8615407cc72d/raw/Carica__heads_main.json\"\n alt=\"Test coverage\"\n /></a>\n</p>\n<p align=\"center\">\n <a href=\"https://pypi.org/project/Carica\"\n ><img\n src='https://badgen.net/pypi/v/Carica/'\n alt=\"Pypi package version\"\n /></a>\n <a href=\"https://pypi.org/project/Carica\"\n ><img\n src=\"https://img.shields.io/pypi/pyversions/Carica.svg\"\n alt=\"Minimum supported Python version\"\n /></a>\n <a href=\"https://pepy.tech/project/carica\"\n ><img\n src=\"https://static.pepy.tech/badge/carica\"\n alt=\"Total PyPi Downloads\"\n</p>\n<p align=\"center\">\n <a href=\"https://sonarcloud.io/dashboard?id=Trimatix_Carica\"\n ><img\n src=\"https://sonarcloud.io/api/project_badges/measure?project=Trimatix_Carica&metric=bugs\"\n alt=\"SonarCloud bugs analysis\"\n /></a>\n <a href=\"https://sonarcloud.io/dashboard?id=Trimatix_Carica\"\n ><img\n src=\"https://sonarcloud.io/api/project_badges/measure?project=Trimatix_Carica&metric=code_smells\"\n alt=\"SonarCloud code smells analysis\"\n /></a>\n <a href=\"https://sonarcloud.io/dashboard?id=Trimatix_Carica\"\n ><img\n src=\"https://sonarcloud.io/api/project_badges/measure?project=Trimatix_Carica&metric=alert_status\"\n alt=\"SonarCloud quality gate status\"\n /></a>\n</p>\n\n\nCarica is a python application configurator, interfacing between a pure python config module, and TOML representation of that module.\n\n<hr>\n\n### Credits\nA huge thank you goes to [@sdispater](https://github.com/sdispater), author of the fantastic [tomlkit library](https://github.com/sdispater/tomlkit), which makes this project's variable docstrings retaining features possible.\n\n## Project Goals\nPython applications can be configured in a number of ways, each with its own advantages and limitations.\n<details>\n<summary>Common Configuration Methods</summary>\n<table>\n\t<tbody>\n\t<tr>\n\t\t<th align=\"center\">Method</th>\n\t\t<th align=\"center\">Advantages</th>\n\t\t<th align=\"center\">Problems</th>\n\t</tr>\n\t<tr>\n\t\t<td>Environment variables/Command line arguments</td>\n\t\t<td>\n\t\t\t<ul>\n\t\t\t\t<li>Easy to handle in code</li>\n\t\t\t\t<li>Container/venv safe</li>\n\t\t\t</ul>\n\t\t</td>\n\t\t<td>\n\t\t\t<ul>\n\t\t\t\t<li>Not scalable to large numbers of variables</li>\n\t\t\t\t<li>Primative data types only</li>\n\t\t\t\t<li>Not human-friendly</li>\n\t\t\t\t<li>No typing in code</li>\n\t\t\t\t<li>No code autocompletion or other editor features</li>\n\t\t\t\t<li>Difficult to version control</li>\n\t\t\t</ul>\n\t\t</td>\n\t</tr>\n\t<tr>\n\t\t<td>TOML config file</td>\n\t\t<td>\n\t\t\t<ul>\n\t\t\t\t<li>Container/venv safe</li>\n\t\t\t\t<li>More scalable</li>\n\t\t\t\t<li>More expressive, with tables</li>\n\t\t\t\t<li>Easy to version control</li>\n\t\t\t\t<li>Human friendly</li>\n\t\t\t</ul>\n\t\t</td>\n\t\t<td>\n\t\t\t<ul>\n\t\t\t\t<li>Not easy to manage in code</li>\n\t\t\t\t<li>No code autocompletion or other editor features</li>\n\t\t\t\t<li>No dot syntax for objects</li>\n\t\t\t\t<li>No typing in code</li>\n\t\t\t</ul>\n\t\t</td>\n\t</tr>\n\t<tr>\n\t\t<td>Python module with variables</td>\n\t\t<td>\n\t\t\t<ul>\n\t\t\t\t<li>Easy to handle in code</li>\n\t\t\t\t<li>Easy to version control, with rich, human-readable diffs</li>\n\t\t\t\t<li>Highly scalable</li>\n\t\t\t\t<li>Completely expressive</li>\n\t\t\t\t<li>Dot syntax for objects</li>\n\t\t\t\t<li>Variable typing in code</li>\n\t\t\t\t<li>Complete language and editor features</li>\n\t\t\t</ul>\n\t\t</td>\n\t\t<td>\n\t\t\t<ul>\n\t\t\t\t<li>Not container/venv safe</li>\n\t\t\t\t<li>Not human-friendly</li>\n\t\t\t\t<li>Module must be accessible to the application namespace - difficult for packages</li>\n\t\t\t</ul>\n\t\t</td>\n\t</tr>\n\t</tbody>\n</table>\n</details>\n\nCarica aims to mix the best bits from two of the most convenient configuration methods, acting as an interface between pure python modules and TOML config files.\n\n## Basic Usage\nTo use Carica, your application configuration should be defined as a python module.\n\n<details>\n<summary>Example Application</summary>\n\n*loginApp.py*\n```py\nimport cfg\nimport some_credentials_manager\nimport re\n\nprint(cfg.welcome_message)\nnew_user_data = {}\n\nfor field_name, field_config in cfg.new_user_required_fields.items():\n print(field_config['display'] + \":\")\n new_value = input()\n if re.match(new_value, field_config['validation_regex']):\n new_user_data[field_name] = new_value\n else:\n raise ValueError(f\"The value for {field_name} did not pass validation\")\n\nsome_credentials_manager.create_user(new_user_data)\n```\n\n*cfg.py*\n```py\nwelcome_message = \"Welcome to the application. Please create an account:\"\n\nnew_user_required_fields = {\n \"username\": {\n \"display\": \"user-name\",\n \"validation_regex\": \"[a-z]+\"\n },\n \"password\": {\n \"display\": \"pw\",\n \"validation_regex\": \"\\\\b(?!password\\\\b)\\\\w+\"\n },\n}\n```\n</details>\n\n#### Default config generation\nCarica is able to auto-generate a default TOML config file for your application, with the values specified in your python module as defaults:\n\n```py\n>>> import cfg\n>>> import carica\n>>> carica.makeDefaultCfg(cfg)\nCreated defaultCfg.toml\n```\n\nThe above code will produce the following file:\n\n*defaultCfg.toml*\n```toml\nwelcome_message = \"Welcome to the application. Please create an account:\"\n\n[new_user_required_fields]\n[new_user_required_fields.username]\ndisplay = \"user-name\"\nvalidation_regex = \"[a-z]+\"\n \n[new_user_required_fields.password]\ndisplay = \"pw\"\nvalidation_regex = \"\\\\b(?!password\\\\b)\\\\w+\"\n```\n\n### Loading a configuration file\nCarica will map the variables given in your config file to those present in your python module.\nSince the config python module contains default values, Carica does not require every variable to be specified:\n\n*myConfig.toml*\n```toml\n[new_user_required_fields]\n[new_user_required_fields.avatar]\ndisplay = \"profile picture\"\nvalidation_regex = \"[a-z]+\"\n```\n\n\n```py\n>>> import cfg\n>>> import carica\n>>> carica.loadCfg(cfg, \"myConfig.toml\")\nConfig successfully loaded: myConfig.toml\n>>> import loginApp\nWelcome to the application. Please create an account:\nprofile picture:\n123\nTraceback (most recent call last):\n File \"<stdin>\", line 1, in <module>\n File \"loginApp.py\", line 14, in <module>\n raise ValueError(f\"The value for {field_name} did not pass validation\")\nValueError: The value for avatar did not pass validation\n```\n\n### Variable Pseudo-Docstrings\nWhen encountering a comment in your python config module, Carica will treat it as a variable 'docstring' in the following cases:\n\n1. Inline comments on the same line as a variable declaration\n2. Line comments immediately preceeding a variable declaration ('preceeding comments') *\\*Beta feature: still in testing\\**\n3. Line comments immediately preceeding an existing preceeding comment *\\*Beta feature: still in testing\\**\n\nCarica will consider your variable docstrings when building TOML config files:\n\n*cfg.py*\n```py\n# This is shown to the user when the application is first launched\n# No validation is performed on this string\nwelcome_message = \"Welcome to the application. Please create an account:\"\n\nnew_user_required_fields = { # Each field should specify a 'display' (formatted field name shown to users) and a 'validation_regex', which inputted values will be checked against\n \"username\": {\n \"display\": \"user-name\",\n \"validation_regex\": \"[a-z]+\"\n },\n \"password\": {\n \"display\": \"pw\",\n \"validation_regex\": \"\\\\b(?!password\\\\b)\\\\w+\"\n },\n}\n```\n\n```py\n>>> import cfg\n>>> import carica\n>>> carica.makeDefaultCfg(cfg)\nCreated defaultCfg.toml\n```\n\nThe above code will produce the following file:\n\n*defaultCfg.toml*\n```toml\n# This is shown to the user when the application is first launched\n# No validation is performed on this string\nwelcome_message = \"Welcome to the application. Please create an account:\"\n\n[new_user_required_fields] # Each field should specify a 'display' (formatted field name shown to users) and a 'validation_regex', which inputted values will be checked against\n[new_user_required_fields.username]\ndisplay = \"user-name\"\nvalidation_regex = \"[a-z]+\"\n \n[new_user_required_fields.password]\ndisplay = \"pw\"\nvalidation_regex = \"\\\\b(?!password\\\\b)\\\\w+\"\n```\n\n## Advanced Usage\nCarica will handle non-primative variable types according to a very simple design pattern:\n\n### The `SerializableType` type protocol\n```py\nclass SerializableType:\n def serialize(self, **kwargs): ...\n\n @classmethod\n def deserialize(cls, data, **kwargs): ...\n```\n\nAny type which defines `serialize` and `deserialize` member methods will be automatically serialized during config generation, and deserialized on config loading.\n\n- `serialize` must return a representation of your object with primative types - types which can be written to toml.\n- `deserialize` must be a class method, and should transform a serialized object representation into a new object.\n\nCarica enforces this pattern on non-primative types using the `SerializableType` type protocol, which allows for duck-typed serializable types. This protocol is exposed for use with `isinstance`.\n\nProjects which prefer strong typing may implement the `carica.ISerializable` interface to enforce this pattern with inheritence. Carica will validate serialized objects against the `carica.PrimativeType` type alias, which is also exposed for use.\n\n### Example\n\n*cfg.py*\n```py\nclass MySerializableType:\n def __init__(self, myField):\n self.myField = myField\n\n def serialize(self, **kwargs):\n return {\"myField\": self.myField}\n\n @classmethod\n def deserialize(self, data, **kwargs):\n return MySerializableClass(data[\"myField\"])\n\nmySerializableVar = MySerializableClass(\"hello\")\n```\n\n#### Default config generation\n```py\n>>> import cfg\n>>> import carica\n>>> carica.makeDefaultCfg(cfg)\nCreated defaultCfg.toml\n```\n\nThe above code will produce the following file:\n\n*defaultCfg.toml*\n```toml\n[mySerializableVar]\nmyField = \"hello\"\n```\n\n#### Config file loading\n*myConfig.toml*\n```toml\n[mySerializableVar]\nmyField = \"some changed value\"\n```\n\n```py\n>>> import cfg\n>>> import carica\n>>> carica.loadCfg(cfg, \"myConfig.toml\")\nConfig successfully loaded: myConfig.toml\n>>> cfg.mySerializableVar.myField\nsome changed value\n```\n\n### Premade models\nCarica provides serializable models that are ready to use (or extend) in your code. These models can be found in the `carica.models` package, which is imported by default.\n\n#### SerializableDataClass\nRemoves the need to write boilerplate serializing functionality for dataclasses. This class is intended to be extended, adding definitions for your dataclass's fields. Extensions of `SerializableDataClass` **must** themselves be decorated with `@dataclasses.dataclass` in order to function correctly.\n\n#### SerializablePath\nAn OS-agnostic filesystem path, extending `pathlib.Path`. The serializing/deserializing behaviour added by this class is minimal, a serialized `SerializablePath` is simply the string representation of the path, for readability. All other behaviour of `pathlib.Path` applies, for example. `SerializablePath` can be instantiated from a single path: `SerializablePath(\"my/directory/path\")`, or from path segments: `SerializablePath(\"my\", \"file\", \"path.toml\")`.\n\n#### SerializableTimedelta\n`datetime.datetime` is already considered a primitive type by TomlKit, and so no serializability needs to be added for you to use this class in your configs. However, `datetime.timedelta` is not serializable by default. `SerializableTimedelta` solves this issue as a serializable subclass. As a subclass, all `timedelta` behaiour applies, including the usual constructor. In addition, `SerializableTimedelta.fromTimedelta` is a convenience class method that accepts a `datetime.timedelta` and constructs a new `SerializableTimedelta` from it.\n\n#### Premade models example\nThe recommended usage pattern for `SerializableDataClass` is to separate your models into a separate module/package, allowing for 'schema' definition as python code. This pattern is not necessary, model definition *can* be done in your config file.\n\n*configSchema.py*\n```py\nfrom carica.models import SerializableDataClass\nfrom dataclasses import dataclass\n\n@dataclass\nclass UserDataField(SerializableDataClass):\n name: str\n validation_regex: str\n```\n*config.py*\n```py\nfrom carica.models import SerializablePath, SerializableTimedelta\nfrom configSchema import UserDataField\nfrom datetime import datetime\n\nnew_user_required_fields = [\n UserDataField(\n name = \"user-name\"\n validation_regex = \"[a-z]+\"\n ),\n\n UserDataField(\n name = \"password\"\n validation_regex = \"\\\\b(?!password\\\\b)\\\\w+\"\n )\n]\n\ndatabase_path = SerializablePath(\"default/path.csv\")\nbirthday = datetime(day=1, month=1, year=1500)\nconnection_timeout = SerializableTimedelta(minutes=5)\n```\n\n\n## Planned features\n- Preceeding comments: This functionality is 'complete' in that it functions as intended and passes all unit tests, however an issue needs to be worked aruond before the feature can be enabled: In order to disambiguate between variables and table fields, the TOML spec requires that arrays and tables be placed at the end of a document. Carica currently depends upon documents being rendered with variables appearing in the same order as they appear in the python config module, which is not guaranteed. This leads to trailing and otherwise misplaced preceeding comments.\n- Config mutation: Carica should allow for loading an existing config, changing some values, and then updating the TOML document with new values. This should retain all formatting from the original document, including variable ordering and any comments that are not present in the python module.\n## Limitations\n- No support for schema migration\n- No support for asynchronous object serializing/deserializing\n- Imperfect estimation of variables defined in python modules: Listing the variables defined within a scope is not a known feature of python, and so Carica estimates this information by iterating over the tokens in your module. Carica does not build an AST of your python module.\n\n This means that certain name definition structures will result in false positives/negatives. This behaviour has not been extensively tested, but once such false positive has been identified:\n \n When invoking a callable (such as a class or function) with a keyword argument on a new, unindented line, the argument\n name will be falsely identified as a variable name. E.g:\n ```py\n my_variable = dict(key1=value1,\n key2=value2)\n ```\n produces `my_variable` and `key2` as variable names.\n\n",
"bugtrack_url": null,
"license": "",
"summary": "Python module that populates variables from TOML config documents, and generates config documents from python variables.",
"version": "1.3.5",
"project_urls": {
"Bug Tracker": "https://github.com/Trimatix/carica/issues",
"Homepage": "https://github.com/Trimatix/carica"
},
"split_keywords": [
"config",
"cfg",
"python",
"toml",
"generate",
"auto-generate",
"orm"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "27a4009b114fed341f319c2ba79e7211221917436dec24bf76fce302a1a4d500",
"md5": "ba55ed316562abd86e7ad6e8eb85de95",
"sha256": "026a1eb8d604bc8c1f5bd49caa5a666b9f5c81a54d56d860efb2e9b1356b2e66"
},
"downloads": -1,
"filename": "carica-1.3.5-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ba55ed316562abd86e7ad6e8eb85de95",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 30892,
"upload_time": "2023-07-24T06:07:53",
"upload_time_iso_8601": "2023-07-24T06:07:53.517872Z",
"url": "https://files.pythonhosted.org/packages/27/a4/009b114fed341f319c2ba79e7211221917436dec24bf76fce302a1a4d500/carica-1.3.5-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "bf788332d37adcdfe165b2bf8d0533a114318b21987dcbb1df432b3774734bfa",
"md5": "a3c810f7d621d6cb06e7ffe966920ce0",
"sha256": "93c01dc42c17bb72d7688e54916b7360d9076a31cddb35c6ef3fa841f8501132"
},
"downloads": -1,
"filename": "carica-1.3.5.tar.gz",
"has_sig": false,
"md5_digest": "a3c810f7d621d6cb06e7ffe966920ce0",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 31820,
"upload_time": "2023-07-24T06:07:55",
"upload_time_iso_8601": "2023-07-24T06:07:55.259460Z",
"url": "https://files.pythonhosted.org/packages/bf/78/8332d37adcdfe165b2bf8d0533a114318b21987dcbb1df432b3774734bfa/carica-1.3.5.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-07-24 06:07:55",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Trimatix",
"github_project": "carica",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "carica"
}