| Name | appsettings2 JSON |
| Version |
1.1.27
JSON |
| download |
| home_page | None |
| Summary | Unifies configuration sources into a Configuration object that can be bound to complex types, or accessed directly for configuration data. |
| upload_time | 2024-09-09 16:02:47 |
| maintainer | None |
| docs_url | None |
| author | Shaun Wilson |
| requires_python | <4.0,>=3.11 |
| license | MIT |
| keywords |
|
| VCS |
 |
| bugtrack_url |
|
| requirements |
No requirements were recorded.
|
| Travis-CI |
No Travis.
|
| coveralls test coverage |
No coveralls.
|
# `appsettings2`
A python library that unifies configuration sources into a `Configuration` object that can be bound to complex types, or accessed directly for configuration data.
It can be installed from [PyPI](https://pypi.org/project/appsettings2/) through the usual methods.
This README is a high-level overview of core features. For complete documentation please visit [https://appsettings2.readthedocs.io/](https://appsettings2.readthedocs.io/).
## Quick Start
Using `appsettings2` is straightforward:
1. Construct a `ConfigurationBuilder` object.
2. Add one or more `ConfigurationProvider` objects to it.
3. Call `build(...)` to get a `Configuration` object populated with configuration data.
```python
from appsettings2 import *
from appsettings2.providers import *
config = ConfigurationBuilder()\
.addProvider(JsonConfigurationProvider(f'appsettings.json'))\
.addProvider(JsonConfigurationProvider(f'appsettings.Development.json', required=False))\
.addProvider(EnvironmentConfigurationProvider())\
.build()
print(config)
```
In the above example two JSON Configuration Providers are added to the builder. The first will fail if "appsettings.json" is not found, the second will succeed whether or not the "appsettings.Development.json" exists (because `required=False`). This allows for an optional development configuration to exist on development workstations without requiring a code change.
A third Configuration Provider is also added, an `EnvironmentConfigurationProvider`, which allows configuration to be loaded from Environment Variables.
The order of providers determines precedence of configuration data. Providers added last will take precedence over providers added first. Values which are not provided by later registrations will not override values provided by earlier registrations.
Given the example above, this means that configuration data provided via Environment Variables will override configuration data provided via JSON files.
## Accessing Configuration Data
There are multiple ways of accessing Configuration data:
* Using dynamic attributes which represent your configuration data.
* Using configuration keys by calling `get(...)` and `set(...)` methods.
* A dictionary-like interface exposing configuration data via indexer syntax.
* Binding the configuration data to a class/object you define.
### Direct Access via `Configuration` object
Consider the following code which demonstrates the first three methods mentioned above. All three of these methods are equivalent and return the same underlying value.
```python
# access `Configuration` attributes
value = configuration.ConnectionStrings.SampleDb
# access using `get(...)`
value = configuration.get('ConnectionStrings__SampleDb')
value = configuration.get('ConnectionStrings:SampleDb')
value = configuration.get('ConnectionStrings').get('SampleDb')
# access using indexer
value = configuration['ConnectionStrings__SampleDb']
value = configuration['ConnectionStrings:SampleDb']
value = configuration['ConnectionStrings']['SampleDb']
```
When accessing hierarchical data by "key" you can use a double-underscore delimiter or a colon delimiter, they are equivalent.
Devs and Ops from different walks will likely prefer one form over the other, so both are supported.
Additionally, keys are case-insensitive (attribute names are not.)
### Binding `Configuration` to Objects
It's possible to bind configuration data to complex types. While some of the implementation is currently naive, it should work well for the vast majority of use cases. If you find your particular case does not work well please reach out to me and I will work with you to implement a sensible solution. Consider the following Python code:
```python
json = """{
"ConnectionStrings": {
"SampleDb": "my_cxn_string"
},
"EnableSwagger": true,
"MaxBatchSize": 100
}"""
class ConnStrs:
"""An ugly class name to demonstrate the class name does not matter."""
SampleDB:str
class AppSettings:
ConnectionStrings:ConnStrs
EnableSwagger:bool
MaxBatchSize:int
configuration = ConfigurationBuilder()\
.addProvider(JsonConfigurationProvider(json=json))
.build()
settings = AppSettings()
configuration.bind(settings)
print(settings.ConnectionStrings.SampleDB) # prints "my_cxn_string"
```
The resulting `settings` object will contain all of the configuration data properly typed according to type hints.
It is also possible to bind to a subset of a configuration, building upon the above, consider the following:
```python
connectionStrings = configuration.bind(ConnStrs(), 'ConnectionStrings')
print(connectionStrings.SampleDB)
```
Lastly, a cautious eye may have noticed that the input configuration and class definition have a casing difference. `SampleDb` vs `SampleDB` -- by design binding is case-insensitive. This ensures that automation/configuration systems which can only communicate in upper-case can be used to populate complex objects which follow a strict naming convention without burdening devs/devops with extra work.
### Accessing `Configuration` Dictionary-like
`Configuration` is dict-like and in most cases can be used as if it were a dict where strict type checks would not otherwise prevent it.
### Transform `Configuration` to Dictionary
You can transform a `Configuration` instance into a dictionary (as a copy.) If you have some chunk of code that can consume a dictionary but can't consume a Python object (it happens) then you can get at a proper dictionary instance as follows:
```python
configuration = builder.build()
d = configuration.toDictionary()
print(d)
```
## Providers
* Command-Line args, via [CommandLineConfigurationProvider](https://appsettings2.readthedocs.io/en/latest/ref/providers/CommandLineConfigurationProvider.html).
* Environment variables, via [EnvironmentConfigurationProvider](https://appsettings2.readthedocs.io/en/latest/ref/providers/EnvironmentConfigurationProvider.html).
* JSON, via [JsonConfigurationProvider](https://appsettings2.readthedocs.io/en/latest/ref/providers/JsonConfigurationProvider).
* TOML, via [TomlConfigurationProvider](https://appsettings2.readthedocs.io/en/latest/ref/providers/TomlConfigurationProvider).
* YAML, via [YamlConfigurationProvider](https://appsettings2.readthedocs.io/en/latest/ref/providers/YamlConfigurationProvider).
### Custom Provider Development
You can implement custom Configuration Providers by subclassing `ConfigurationProvider` and implementing `populateConfiguration(...)`.
For a peek at the simplicity of provider implementation, this is `ConfigurationProvider`:
```python
class ConfigurationProvider(abstract):
@abstractmethod
def populateConfiguration(self, configuration:Configuration) -> None:
"""The ConfigurationProvider will populate the provided Configuration instance."""
pass
```
Essentially, you read your configuration source and write the configuration data into the specified `Configuration` object. Much of the complexity in dealing with hierarchy and allocation is encapsulated within the impl of `Configuration`. As a result, most providers are less than 20 lines of functional code.
## Contact
You can reach me on [Discord](https://discordapp.com/users/307684202080501761) or [open an Issue on Github](https://github.com/wilson0x4d/appsettings2/issues/new/choose).
Raw data
{
"_id": null,
"home_page": null,
"name": "appsettings2",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.11",
"maintainer_email": null,
"keywords": null,
"author": "Shaun Wilson",
"author_email": "mrshaunwilson@msn.com",
"download_url": "https://files.pythonhosted.org/packages/46/e5/00cdfc32b310e9350c3e830185e121d06c5db5f68ac7cb29cbe2f0dc7a45/appsettings2-1.1.27.tar.gz",
"platform": null,
"description": "# `appsettings2`\n\nA python library that unifies configuration sources into a `Configuration` object that can be bound to complex types, or accessed directly for configuration data.\n\nIt can be installed from [PyPI](https://pypi.org/project/appsettings2/) through the usual methods.\n\nThis README is a high-level overview of core features. For complete documentation please visit [https://appsettings2.readthedocs.io/](https://appsettings2.readthedocs.io/).\n\n## Quick Start\n\nUsing `appsettings2` is straightforward:\n\n1. Construct a `ConfigurationBuilder` object.\n2. Add one or more `ConfigurationProvider` objects to it.\n3. Call `build(...)` to get a `Configuration` object populated with configuration data.\n\n```python\nfrom appsettings2 import *\nfrom appsettings2.providers import *\n\nconfig = ConfigurationBuilder()\\\n .addProvider(JsonConfigurationProvider(f'appsettings.json'))\\\n .addProvider(JsonConfigurationProvider(f'appsettings.Development.json', required=False))\\\n .addProvider(EnvironmentConfigurationProvider())\\\n .build()\n\nprint(config)\n```\n\nIn the above example two JSON Configuration Providers are added to the builder. The first will fail if \"appsettings.json\" is not found, the second will succeed whether or not the \"appsettings.Development.json\" exists (because `required=False`). This allows for an optional development configuration to exist on development workstations without requiring a code change.\n\nA third Configuration Provider is also added, an `EnvironmentConfigurationProvider`, which allows configuration to be loaded from Environment Variables.\n\nThe order of providers determines precedence of configuration data. Providers added last will take precedence over providers added first. Values which are not provided by later registrations will not override values provided by earlier registrations.\n\nGiven the example above, this means that configuration data provided via Environment Variables will override configuration data provided via JSON files.\n\n## Accessing Configuration Data\n\nThere are multiple ways of accessing Configuration data:\n\n* Using dynamic attributes which represent your configuration data.\n* Using configuration keys by calling `get(...)` and `set(...)` methods.\n* A dictionary-like interface exposing configuration data via indexer syntax.\n* Binding the configuration data to a class/object you define.\n\n### Direct Access via `Configuration` object\n\nConsider the following code which demonstrates the first three methods mentioned above. All three of these methods are equivalent and return the same underlying value.\n\n```python\n# access `Configuration` attributes\nvalue = configuration.ConnectionStrings.SampleDb\n# access using `get(...)`\nvalue = configuration.get('ConnectionStrings__SampleDb')\nvalue = configuration.get('ConnectionStrings:SampleDb')\nvalue = configuration.get('ConnectionStrings').get('SampleDb')\n# access using indexer\nvalue = configuration['ConnectionStrings__SampleDb']\nvalue = configuration['ConnectionStrings:SampleDb']\nvalue = configuration['ConnectionStrings']['SampleDb']\n```\n\nWhen accessing hierarchical data by \"key\" you can use a double-underscore delimiter or a colon delimiter, they are equivalent.\n\nDevs and Ops from different walks will likely prefer one form over the other, so both are supported.\n\nAdditionally, keys are case-insensitive (attribute names are not.)\n\n### Binding `Configuration` to Objects\n\nIt's possible to bind configuration data to complex types. While some of the implementation is currently naive, it should work well for the vast majority of use cases. If you find your particular case does not work well please reach out to me and I will work with you to implement a sensible solution. Consider the following Python code:\n\n```python\njson = \"\"\"{\n \"ConnectionStrings\": {\n \"SampleDb\": \"my_cxn_string\"\n },\n \"EnableSwagger\": true,\n \"MaxBatchSize\": 100\n}\"\"\"\n\nclass ConnStrs:\n \"\"\"An ugly class name to demonstrate the class name does not matter.\"\"\"\n SampleDB:str\n\nclass AppSettings:\n ConnectionStrings:ConnStrs\n EnableSwagger:bool\n MaxBatchSize:int\n\nconfiguration = ConfigurationBuilder()\\\n .addProvider(JsonConfigurationProvider(json=json))\n .build()\n\nsettings = AppSettings()\nconfiguration.bind(settings)\n\nprint(settings.ConnectionStrings.SampleDB) # prints \"my_cxn_string\"\n```\n\nThe resulting `settings` object will contain all of the configuration data properly typed according to type hints.\n\nIt is also possible to bind to a subset of a configuration, building upon the above, consider the following:\n\n```python\nconnectionStrings = configuration.bind(ConnStrs(), 'ConnectionStrings')\nprint(connectionStrings.SampleDB)\n```\n\nLastly, a cautious eye may have noticed that the input configuration and class definition have a casing difference. `SampleDb` vs `SampleDB` -- by design binding is case-insensitive. This ensures that automation/configuration systems which can only communicate in upper-case can be used to populate complex objects which follow a strict naming convention without burdening devs/devops with extra work.\n\n### Accessing `Configuration` Dictionary-like\n\n`Configuration` is dict-like and in most cases can be used as if it were a dict where strict type checks would not otherwise prevent it.\n\n### Transform `Configuration` to Dictionary\n\nYou can transform a `Configuration` instance into a dictionary (as a copy.) If you have some chunk of code that can consume a dictionary but can't consume a Python object (it happens) then you can get at a proper dictionary instance as follows:\n\n```python\nconfiguration = builder.build()\nd = configuration.toDictionary()\nprint(d)\n```\n\n## Providers\n\n* Command-Line args, via [CommandLineConfigurationProvider](https://appsettings2.readthedocs.io/en/latest/ref/providers/CommandLineConfigurationProvider.html).\n* Environment variables, via [EnvironmentConfigurationProvider](https://appsettings2.readthedocs.io/en/latest/ref/providers/EnvironmentConfigurationProvider.html).\n* JSON, via [JsonConfigurationProvider](https://appsettings2.readthedocs.io/en/latest/ref/providers/JsonConfigurationProvider).\n* TOML, via [TomlConfigurationProvider](https://appsettings2.readthedocs.io/en/latest/ref/providers/TomlConfigurationProvider).\n* YAML, via [YamlConfigurationProvider](https://appsettings2.readthedocs.io/en/latest/ref/providers/YamlConfigurationProvider).\n\n### Custom Provider Development\n\nYou can implement custom Configuration Providers by subclassing `ConfigurationProvider` and implementing `populateConfiguration(...)`.\n\nFor a peek at the simplicity of provider implementation, this is `ConfigurationProvider`:\n\n```python\nclass ConfigurationProvider(abstract):\n\n @abstractmethod\n def populateConfiguration(self, configuration:Configuration) -> None:\n \"\"\"The ConfigurationProvider will populate the provided Configuration instance.\"\"\"\n pass\n```\n\nEssentially, you read your configuration source and write the configuration data into the specified `Configuration` object. Much of the complexity in dealing with hierarchy and allocation is encapsulated within the impl of `Configuration`. As a result, most providers are less than 20 lines of functional code.\n\n## Contact\n\nYou can reach me on [Discord](https://discordapp.com/users/307684202080501761) or [open an Issue on Github](https://github.com/wilson0x4d/appsettings2/issues/new/choose).\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Unifies configuration sources into a Configuration object that can be bound to complex types, or accessed directly for configuration data.",
"version": "1.1.27",
"project_urls": {
"documentation": "https://appsettings2.readthedocs.io/",
"homepage": "https://github.com/wilson0x4d/appsettings2",
"repository": "https://github.com/wilson0x4d/appsettings2.git"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "e655da8bccd4ba6ce31ff3c983442f9bacd975a30ab01a8952c6e9f1923568a9",
"md5": "71e0822ab92f917238b53338f9fa7205",
"sha256": "dcab8d4506222da8eace29c2053b52f9979ed2fc22d97394b35216201f6c7e34"
},
"downloads": -1,
"filename": "appsettings2-1.1.27-py3-none-any.whl",
"has_sig": false,
"md5_digest": "71e0822ab92f917238b53338f9fa7205",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.11",
"size": 15278,
"upload_time": "2024-09-09T16:02:45",
"upload_time_iso_8601": "2024-09-09T16:02:45.872742Z",
"url": "https://files.pythonhosted.org/packages/e6/55/da8bccd4ba6ce31ff3c983442f9bacd975a30ab01a8952c6e9f1923568a9/appsettings2-1.1.27-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "46e500cdfc32b310e9350c3e830185e121d06c5db5f68ac7cb29cbe2f0dc7a45",
"md5": "7daf06c88ff0fec1d0ca8d4d71a2a337",
"sha256": "cef439ab58ba9583a87e3d6466c27bc168df32714464b3d1d4bd0892c87739f5"
},
"downloads": -1,
"filename": "appsettings2-1.1.27.tar.gz",
"has_sig": false,
"md5_digest": "7daf06c88ff0fec1d0ca8d4d71a2a337",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.11",
"size": 12141,
"upload_time": "2024-09-09T16:02:47",
"upload_time_iso_8601": "2024-09-09T16:02:47.808057Z",
"url": "https://files.pythonhosted.org/packages/46/e5/00cdfc32b310e9350c3e830185e121d06c5db5f68ac7cb29cbe2f0dc7a45/appsettings2-1.1.27.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-09-09 16:02:47",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "wilson0x4d",
"github_project": "appsettings2",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "appsettings2"
}