# config2py
Simplified reading and writing configurations from various sources and formats.
To install: ```pip install config2py```
[Documentation](https://i2mint.github.io/config2py/)
# The cherry on top: config_getter
```python
from config2py import config_getter
```
Let's start with an extremely convenient, no questions asked, object.
Later, we'll look under the hood to show the many tools that support it, and can be shaped to fit many desired behaviors.
What `config2py.config_getter(key)` will do is:
* search for `key` in your environment variables, and if not found...
* ... search for it in a default local directory (more on that later), and if not found...
* ... ask the user to enter the value that key should have, and then put it in the local directory mentioned above.
<img width="341" alt="image" src="https://github.com/i2mint/config2py/assets/1906276/09f287a8-05f9-4590-8664-10feda9ad617">
_Note: The "... ask the user to enter the value..." will be activated only when in an interactive environment (python console, jupyter notebook, etc.), as decided by the `config2py.is_repl()` function_
```python
config_getter('HOME') # if you are using Linux/MacOS
# config_getter('USERPROFILE') # if you are using Windows
```
'/Users/thorwhalen'
Now, normally all systems come with a `HOME` environment variable (or a `USERPROFILE` on windows), so the above should always work fine.
But see what happens if you ask for a key that is not an environment variable:
```python
my_config_val = config_getter('_TEST_NON_EXISTING_KEY_') # triggers a user input dialog
# ... I enter 'my config value' in the dialog, and then...
```
```python
my_config_val
```
'my config value'
But if I do that again (even on a different day, somewhere else (on my same computer), in a different session), it will get me the value I entered in the user input dialog.
```python
my_config_val = config_getter('_TEST_NON_EXISTING_KEY_') # does not trigger input dialog
my_config_val
```
'my config value'
And of course, we give you a means to delete that value, since `config_getter` has a `local_configs` mapping (think `dict`) to the local files where it has been stored.
You can do all the usual stuff you do with a `dict` (except the effects will be on local files),
like list the keys (with `list(.)`), get values for a key (with `.[key]`), ask for the number of keys (`len(.)`), and, well, delete stuff:
```python
if '_TEST_NON_EXISTING_KEY_' in config_getter.configs:
del config_getter.configs['_TEST_NON_EXISTING_KEY_']
```
This tool allows you to:
* not have to set up any special configs stuff (unless you want/need to)
* enables you to share your notebooks (CLIs etc.) with others without having to polute the code with configs-setup gunk...
* ... including when you put local file/folder paths (or worse, secrets) in your notebook or code, which others then have to edit (instead, here, just enter a probably-unique name for the needed resource, then enter your filepath in the user input dialog instead)
This is very convenient situation where user input (via things like `__builtins__.input` or `getpass.getpass` etc) is available. But **you should not use this to manage configurations/resources anywhere were there's not a user to see and respond to the builtin user input dialog**
Don't fret though, this `config_getter` is just our no-BS entry point to much more.
Let's have a slight look under its hood to see what else we can do with it.
And of course, if you're that type, you can already have a look at [the documentation](https://i2mint.github.io/config2py/)
## `simple_config_getter`: Controlling your config_getter a bit more
If you look up for the definition of the `config_getter` function you imported above, you'll find this: `config_getter = simple_config_getter()`.
That is, it was created by `simple_config_getter` with its default arguments.
Let's have a look at what these are.
In fact, `simple_config_getter` is a function to make configuration getters that ressemble the one we've seen above:
<img width="341" alt="image" src="https://github.com/i2mint/config2py/assets/1906276/09f287a8-05f9-4590-8664-10feda9ad617">
But where you can control what the central store (by default "Local App Data Files" store) is, and whether to first search in environment variables or not, and whether to ask the user for the value, if not found before, or not.
```python
from config2py import simple_config_getter, get_configs_local_store
from i2 import Sig
print(*str(Sig(simple_config_getter)).split(','), sep='\n')
```
(configs_src: str = '.../.config/config2py/configs'
*
first_look_in_env_vars: bool = True
ask_user_if_key_not_found: bool = None
config_store_factory: Callable = <function get_configs_local_store at 0x10a457370>)
`first_look_in_env_vars` specifies whether to look into environment variables first, or not.
`ask_user_if_key_not_found` specifies whether to ask the user if a configuration key is not found. The default is `None`, which will result in checking if you're running in an interactive environment or not.
When you use `config2py` in production though, you should definitely specify `ask_user_if_key_not_found=False` to make that choice explicit.
The `configs_src` default is automatically set to be the `config2py/configs` folder of your systems's "App Data" folder (also configurable via a `CONFIG2PY_APP_DATA_FOLDER` environment variable).
Your central store will be `config_store_factory(configs_src)`, and since you can also specify `config_store_factory`, you have total control over the store.
The default `config_store_factory` is `get_configs_local_store` which will give you a locally persisted store where if `configs_src`:
* is a directory, it's assumed to be a folder of text files.
* is a file, it's assumed to be an ini or cfg file.
* is a string, it's assumed to be an app name, from which to create a config folder for with the default method
# Setting the config key search path
If you check out the code for `simple_config_getter`, you'll find that all it it is simply setting the `sources` argument for the `get_config` function.
Something more or less like:
```python
configs = config_store_factory(configs_src)
source = [
os.environ, # search in environment variables first
configs, # then search in configs
user_gettable(configs) # if not found, ask the user and store in
]
config_getter = get_config(sources=source)
```
So you see that you can easily define your own sources for configs, and in what order they should be searched. If you don't want that "ask the user for the value" thing, you can just remove the `user_gettable(local_configs)` part. If you wanted instead to add a place to look before the environment variables -- say, you want to look in to local variables of the scope the config getter is **defined** (not called), you can stick `locals()` in front of the `os.environ`.
So you see that you can easily define your own sources for configs, and in what order they should be searched. If you don't want that "ask the user for the value" thing, you can just remove the `user_gettable(local_configs)` part. If you wanted instead to add a place to look before the environment variables -- say, you want to look in to local variables of the scope the config getter is **defined** (not called), you can stick `locals()` in front of the `os.environ`.
Let's work through a custom-made `config_getter`.
```python
from config2py import get_config, user_gettable
from dol import TextFiles
import os
my_configs = TextFiles('~/.my_configs/') # Note, to run this, you'd need to have such a directory!
# (But you can also use my_configs = dict() if you want.)
config_getter = get_config(sources=[locals(), os.environ, my_configs, user_gettable(my_configs)])
```
Now let's see what happens when we do:
```python
config_getter('SOME_CONFIG_KEY')
```
Well, it will first look in `locals()`, which is a dictionary containing local variables
where the `config_getter` was **defined** (careful -- not called!!).
This is desirable sometimes when you define your `config_getter` in a module that has other python variables you'd like to use.
Assuming it doesn't find such a key in `locals()` it goes on to try to find it in
`os.environ`, which is a dict containing system environment variables.
Assuming it doesn't find it there either (that is, doesn't find a file with that name in
the directory `~/.my_configs/`), it will prompt the user to enter the value of that key.
The function finally returns with the value that the user entered.
But there's more!
Now look at what's in `my_configs`!
If you've used `TextFiles`, look in the folder to see that there's a new file.
Either way, if you do:
```python
my_configs['SOME_CONFIG_KEY']
```
You'll now see the value the user entered.
This means what? This means that the next time you try to get the config:
```python
config_getter('SOME_CONFIG_KEY')
```
It will return the value that the user entered last time, without prompting the
user again.
# A few notable tools you can import from config2py
* `get_config`: Get a config value from a list of sources. See more below.
* `user_gettable`: Create a ``GettableContainer`` that asks the user for a value, optionally saving it.
* `ask_user_for_input`: Ask the user for input, optionally masking, validating and transforming the input.
* `get_app_data_folder`: Returns the full path of a directory suitable for storing application-specific data for a given app name.
* `get_configs_local_store`: Get a local store (mapping interface of local files) of configs for a given app or package name
* `configs`: A default store instance for configs, defaulting to a local store under a default configuration local directory.
## get_config
Get a config value from a list of sources.
This function acts as a mini-framework to construct config accessors including defining
multiple sources of where to find these configs,
A source can be a function or a ``GettableContainer``.
(A ``GettableContainer`` is anything that can be indexed with brackets: ``obj[k]``,
like ``dict``, ``list``, ``str``, etc..).
Let's take two sources: a ``dict`` and a ``Callable``.
>>> def func(k):
... if k == 'foo':
... return 'quux'
... elif k == 'green':
... return 'eggs'
... else:
... raise RuntimeError(f"I don't handle that: {k}")
>>> dict_ = {'foo': 'bar', 'baz': 'qux'}
>>> sources = [func, dict_]
See that ``get_config`` go through the sources in the order they were listed,
and returns the first value it finds (or manages to compute) for the key:
``get_config`` finds ``'foo'`` in the very first source (``func``):
>>> get_config('foo', sources)
'quux'
But ``baz`` makes ``func`` raise an error, so it goes to the next source: ``dict_``.
There, it finds ``'baz'`` and returns its value:
>>> get_config('baz', sources)
'qux'
On the other hand, no one manages to find a config value for ``'no_a_key'``, so
``get_config`` raises an error:
>>> get_config('no_a_key', sources)
Traceback (most recent call last):
...
config2py.errors.ConfigNotFound: Could not find config for key: no_a_key
But if you provide a default value, it will return that instead:
>>> get_config('no_a_key', sources, default='default')
'default'
You can also provide a function that will be called on the value before it is
returned. This is useful if you want to do some post-processing on the value,
or if you want to make sure that the value is of a certain type:
This "search the next source if the previous one fails" behavior may not be what
you want in some situations, since you'd be hiding some errors that you might
want to be aware of. This is why allow you to specify what exceptions should
actually be considered as "config not found" exceptions, through the
``config_not_found_exceptions`` argument, which defaults to ``Exception``.
Further, your sources may return a value, but not one that you consider valid:
For example, a sentinel like ``None``. In this case you may want the search to
continue. This is what the ``val_is_valid`` argument is for. It is a function
that takes a value and returns a boolean. If it returns ``False``, the search
will continue. If it returns ``True``, the search will stop and the value will
be returned.
Finally, we have ``egress : Callable[[KT, TT], VT]``.
This is a function that takes a key and a value, and
returns a value. It is called after the value has been found, and its return
value is the one that is returned by ``get_config``. This is useful if you want
to do some post-processing on the value, or before you return the value, or if you
want to do some caching.
>>> config_store = dict()
>>> def store_before_returning(k, v):
... config_store[k] = v
... return v
>>> get_config('foo', sources, egress=store_before_returning)
'quux'
>>> config_store
{'foo': 'quux'}
Note that a source can be a callable or a ``GettableContainer`` (most of the
time, a ``Mapping`` (e.g. ``dict``)).
Here, you should be compelled to use the resources of ``dol``
(https://pypi.org/project/dol/) which will allow you to make ``Mapping``s for all
sorts of data sources.
For more info, see: https://github.com/i2mint/config2py/issues/4
# user_gettable
So, what's that `user_gettable`?
It's a way for you to specify that the system should ask the user for a key, and optionally save it somewhere, plus many other parameters (like what to ask the user, etc.)
```python
from config2py.base import user_gettable
s = user_gettable()
s['SOME_KEY']
# will trigger a prompt for the user to enter the value of SOME_KEY
# ... and when they do (say they entered 'SOME_VAL') it will return that value
# And if you specify a save_to store (usually a persistent MutableMapping made with the dol package)
# then it will save the value to that store for future use
d = dict(some='store')
s = user_gettable(save_to=d)
s['SOME_KEY']
```
More on that another day...
```python
```
Raw data
{
"_id": null,
"home_page": "https://github.com/i2mint/config2py",
"name": "config2py",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": null,
"author": "OtoSense",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/aa/7b/932af9aac553b82c9399f3e0b51d4fbac4550b182c03c5e269e30fc6b982/config2py-0.1.36.tar.gz",
"platform": "any",
"description": "# config2py\n\nSimplified reading and writing configurations from various sources and formats.\n\nTo install:\t```pip install config2py```\n\n[Documentation](https://i2mint.github.io/config2py/)\n\n\n\n# The cherry on top: config_getter\n\n```python\nfrom config2py import config_getter\n```\n\nLet's start with an extremely convenient, no questions asked, object.\nLater, we'll look under the hood to show the many tools that support it, and can be shaped to fit many desired behaviors. \n\nWhat `config2py.config_getter(key)` will do is:\n* search for `key` in your environment variables, and if not found...\n* ... search for it in a default local directory (more on that later), and if not found...\n* ... ask the user to enter the value that key should have, and then put it in the local directory mentioned above.\n\n<img width=\"341\" alt=\"image\" src=\"https://github.com/i2mint/config2py/assets/1906276/09f287a8-05f9-4590-8664-10feda9ad617\">\n\n_Note: The \"... ask the user to enter the value...\" will be activated only when in an interactive environment (python console, jupyter notebook, etc.), as decided by the `config2py.is_repl()` function_\n\n```python\nconfig_getter('HOME') # if you are using Linux/MacOS\n# config_getter('USERPROFILE') # if you are using Windows\n```\n\n '/Users/thorwhalen'\n\n\nNow, normally all systems come with a `HOME` environment variable (or a `USERPROFILE` on windows), so the above should always work fine. \nBut see what happens if you ask for a key that is not an environment variable:\n\n\n```python\nmy_config_val = config_getter('_TEST_NON_EXISTING_KEY_') # triggers a user input dialog\n# ... I enter 'my config value' in the dialog, and then...\n```\n\n\n```python\nmy_config_val\n```\n\n 'my config value'\n\n\n\nBut if I do that again (even on a different day, somewhere else (on my same computer), in a different session), it will get me the value I entered in the user input dialog.\n\n\n```python\nmy_config_val = config_getter('_TEST_NON_EXISTING_KEY_') # does not trigger input dialog\nmy_config_val\n```\n\n 'my config value'\n\n\n\nAnd of course, we give you a means to delete that value, since `config_getter` has a `local_configs` mapping (think `dict`) to the local files where it has been stored. \nYou can do all the usual stuff you do with a `dict` (except the effects will be on local files), \nlike list the keys (with `list(.)`), get values for a key (with `.[key]`), ask for the number of keys (`len(.)`), and, well, delete stuff:\n\n\n```python\nif '_TEST_NON_EXISTING_KEY_' in config_getter.configs:\n del config_getter.configs['_TEST_NON_EXISTING_KEY_']\n```\n\nThis tool allows you to:\n* not have to set up any special configs stuff (unless you want/need to)\n* enables you to share your notebooks (CLIs etc.) with others without having to polute the code with configs-setup gunk...\n* ... including when you put local file/folder paths (or worse, secrets) in your notebook or code, which others then have to edit (instead, here, just enter a probably-unique name for the needed resource, then enter your filepath in the user input dialog instead)\n\nThis is very convenient situation where user input (via things like `__builtins__.input` or `getpass.getpass` etc) is available. But **you should not use this to manage configurations/resources anywhere were there's not a user to see and respond to the builtin user input dialog**\n\nDon't fret though, this `config_getter` is just our no-BS entry point to much more. \nLet's have a slight look under its hood to see what else we can do with it. \n\nAnd of course, if you're that type, you can already have a look at [the documentation](https://i2mint.github.io/config2py/)\n\n\n## `simple_config_getter`: Controlling your config_getter a bit more\n\nIf you look up for the definition of the `config_getter` function you imported above, you'll find this: `config_getter = simple_config_getter()`. \nThat is, it was created by `simple_config_getter` with its default arguments. \nLet's have a look at what these are.\n\nIn fact, `simple_config_getter` is a function to make configuration getters that ressemble the one we've seen above:\n\n<img width=\"341\" alt=\"image\" src=\"https://github.com/i2mint/config2py/assets/1906276/09f287a8-05f9-4590-8664-10feda9ad617\">\n\nBut where you can control what the central store (by default \"Local App Data Files\" store) is, and whether to first search in environment variables or not, and whether to ask the user for the value, if not found before, or not. \n\n```python\nfrom config2py import simple_config_getter, get_configs_local_store\nfrom i2 import Sig\n\nprint(*str(Sig(simple_config_getter)).split(','), sep='\\n')\n```\n\n (configs_src: str = '.../.config/config2py/configs'\n *\n first_look_in_env_vars: bool = True\n ask_user_if_key_not_found: bool = None\n config_store_factory: Callable = <function get_configs_local_store at 0x10a457370>)\n\n`first_look_in_env_vars` specifies whether to look into environment variables first, or not.\n\n`ask_user_if_key_not_found` specifies whether to ask the user if a configuration key is not found. The default is `None`, which will result in checking if you're running in an interactive environment or not. \nWhen you use `config2py` in production though, you should definitely specify `ask_user_if_key_not_found=False` to make that choice explicit.\n\nThe `configs_src` default is automatically set to be the `config2py/configs` folder of your systems's \"App Data\" folder (also configurable via a `CONFIG2PY_APP_DATA_FOLDER` environment variable). \n\nYour central store will be `config_store_factory(configs_src)`, and since you can also specify `config_store_factory`, you have total control over the store.\n\nThe default `config_store_factory` is `get_configs_local_store` which will give you a locally persisted store where if `configs_src`:\n* is a directory, it's assumed to be a folder of text files.\n* is a file, it's assumed to be an ini or cfg file.\n* is a string, it's assumed to be an app name, from which to create a config folder for with the default method\n\n\n# Setting the config key search path\n\nIf you check out the code for `simple_config_getter`, you'll find that all it it is simply setting the `sources` argument for the `get_config` function. \nSomething more or less like:\n\n```python\nconfigs = config_store_factory(configs_src)\nsource = [\n os.environ, # search in environment variables first\n configs, # then search in configs\n user_gettable(configs) # if not found, ask the user and store in \n]\nconfig_getter = get_config(sources=source)\n```\n\nSo you see that you can easily define your own sources for configs, and in what order they should be searched. If you don't want that \"ask the user for the value\" thing, you can just remove the `user_gettable(local_configs)` part. If you wanted instead to add a place to look before the environment variables -- say, you want to look in to local variables of the scope the config getter is **defined** (not called), you can stick `locals()` in front of the `os.environ`.\n\nSo you see that you can easily define your own sources for configs, and in what order they should be searched. If you don't want that \"ask the user for the value\" thing, you can just remove the `user_gettable(local_configs)` part. If you wanted instead to add a place to look before the environment variables -- say, you want to look in to local variables of the scope the config getter is **defined** (not called), you can stick `locals()` in front of the `os.environ`.\n\nLet's work through a custom-made `config_getter`.\n\n```python\nfrom config2py import get_config, user_gettable\nfrom dol import TextFiles\nimport os\n\nmy_configs = TextFiles('~/.my_configs/') # Note, to run this, you'd need to have such a directory!\n# (But you can also use my_configs = dict() if you want.)\nconfig_getter = get_config(sources=[locals(), os.environ, my_configs, user_gettable(my_configs)])\n```\n\nNow let's see what happens when we do:\n\n```python\nconfig_getter('SOME_CONFIG_KEY')\n```\n\nWell, it will first look in `locals()`, which is a dictionary containing local variables\nwhere the `config_getter` was **defined** (careful -- not called!!). \nThis is desirable sometimes when you define your `config_getter` in a module that has other python variables you'd like to use. \n\nAssuming it doesn't find such a key in `locals()` it goes on to try to find it in \n`os.environ`, which is a dict containing system environment variables. \n\nAssuming it doesn't find it there either (that is, doesn't find a file with that name in \nthe directory `~/.my_configs/`), it will prompt the user to enter the value of that key.\nThe function finally returns with the value that the user entered.\n\nBut there's more!\n\nNow look at what's in `my_configs`! \nIf you've used `TextFiles`, look in the folder to see that there's a new file.\nEither way, if you do:\n\n```python\nmy_configs['SOME_CONFIG_KEY']\n```\n\nYou'll now see the value the user entered.\n\nThis means what? This means that the next time you try to get the config:\n\n```python\nconfig_getter('SOME_CONFIG_KEY')\n```\n\nIt will return the value that the user entered last time, without prompting the \nuser again.\n\n\n# A few notable tools you can import from config2py\n\n* `get_config`: Get a config value from a list of sources. See more below.\n* `user_gettable`: Create a ``GettableContainer`` that asks the user for a value, optionally saving it.\n* `ask_user_for_input`: Ask the user for input, optionally masking, validating and transforming the input.\n* `get_app_data_folder`: Returns the full path of a directory suitable for storing application-specific data for a given app name.\n* `get_configs_local_store`: Get a local store (mapping interface of local files) of configs for a given app or package name\n* `configs`: A default store instance for configs, defaulting to a local store under a default configuration local directory.\n\n## get_config\n\nGet a config value from a list of sources.\n\nThis function acts as a mini-framework to construct config accessors including defining \nmultiple sources of where to find these configs, \n\nA source can be a function or a ``GettableContainer``.\n(A ``GettableContainer`` is anything that can be indexed with brackets: ``obj[k]``,\nlike ``dict``, ``list``, ``str``, etc..).\n\nLet's take two sources: a ``dict`` and a ``Callable``.\n\n >>> def func(k):\n ... if k == 'foo':\n ... return 'quux'\n ... elif k == 'green':\n ... return 'eggs'\n ... else:\n ... raise RuntimeError(f\"I don't handle that: {k}\")\n >>> dict_ = {'foo': 'bar', 'baz': 'qux'}\n >>> sources = [func, dict_]\n\n\nSee that ``get_config`` go through the sources in the order they were listed,\nand returns the first value it finds (or manages to compute) for the key:\n\n``get_config`` finds ``'foo'`` in the very first source (``func``):\n\n >>> get_config('foo', sources)\n 'quux'\n\nBut ``baz`` makes ``func`` raise an error, so it goes to the next source: ``dict_``.\nThere, it finds ``'baz'`` and returns its value:\n\n >>> get_config('baz', sources)\n 'qux'\n\nOn the other hand, no one manages to find a config value for ``'no_a_key'``, so\n``get_config`` raises an error:\n\n >>> get_config('no_a_key', sources)\n Traceback (most recent call last):\n ...\n config2py.errors.ConfigNotFound: Could not find config for key: no_a_key\n\nBut if you provide a default value, it will return that instead:\n\n >>> get_config('no_a_key', sources, default='default')\n 'default'\n\nYou can also provide a function that will be called on the value before it is\nreturned. This is useful if you want to do some post-processing on the value,\nor if you want to make sure that the value is of a certain type:\n\nThis \"search the next source if the previous one fails\" behavior may not be what\nyou want in some situations, since you'd be hiding some errors that you might\nwant to be aware of. This is why allow you to specify what exceptions should\nactually be considered as \"config not found\" exceptions, through the\n``config_not_found_exceptions`` argument, which defaults to ``Exception``.\n\nFurther, your sources may return a value, but not one that you consider valid:\nFor example, a sentinel like ``None``. In this case you may want the search to\ncontinue. This is what the ``val_is_valid`` argument is for. It is a function\nthat takes a value and returns a boolean. If it returns ``False``, the search\nwill continue. If it returns ``True``, the search will stop and the value will\nbe returned.\n\nFinally, we have ``egress : Callable[[KT, TT], VT]``.\nThis is a function that takes a key and a value, and\nreturns a value. It is called after the value has been found, and its return\nvalue is the one that is returned by ``get_config``. This is useful if you want\nto do some post-processing on the value, or before you return the value, or if you\nwant to do some caching.\n\n >>> config_store = dict()\n >>> def store_before_returning(k, v):\n ... config_store[k] = v\n ... return v\n >>> get_config('foo', sources, egress=store_before_returning)\n 'quux'\n >>> config_store\n {'foo': 'quux'}\n\n Note that a source can be a callable or a ``GettableContainer`` (most of the\n time, a ``Mapping`` (e.g. ``dict``)).\n Here, you should be compelled to use the resources of ``dol``\n (https://pypi.org/project/dol/) which will allow you to make ``Mapping``s for all\n sorts of data sources.\n\nFor more info, see: https://github.com/i2mint/config2py/issues/4\n\n\n\n\n# user_gettable\n\nSo, what's that `user_gettable`? \n\nIt's a way for you to specify that the system should ask the user for a key, and optionally save it somewhere, plus many other parameters (like what to ask the user, etc.)\n\n\n```python\nfrom config2py.base import user_gettable\n\ns = user_gettable()\ns['SOME_KEY'] \n# will trigger a prompt for the user to enter the value of SOME_KEY\n# ... and when they do (say they entered 'SOME_VAL') it will return that value\n\n# And if you specify a save_to store (usually a persistent MutableMapping made with the dol package)\n# then it will save the value to that store for future use\nd = dict(some='store')\ns = user_gettable(save_to=d)\ns['SOME_KEY'] \n```\n\nMore on that another day...\n\n\n```python\n\n```\n",
"bugtrack_url": null,
"license": "apache-2.0",
"summary": "Simplified reading and writing configurations from various sources and formats",
"version": "0.1.36",
"project_urls": {
"Homepage": "https://github.com/i2mint/config2py"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "c64c9be1a2f2b875720f7aec0a14ffa2a0bc11a4b44f8b8949106b19bad7149c",
"md5": "642debcc173cabe72cbe59ab2fd0ae97",
"sha256": "a823d2a8b5889547891d93b23fe3864fc404fe58a2aa8d5ab14542b97f0db0e4"
},
"downloads": -1,
"filename": "config2py-0.1.36-py3-none-any.whl",
"has_sig": false,
"md5_digest": "642debcc173cabe72cbe59ab2fd0ae97",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 32193,
"upload_time": "2024-08-01T13:14:55",
"upload_time_iso_8601": "2024-08-01T13:14:55.016297Z",
"url": "https://files.pythonhosted.org/packages/c6/4c/9be1a2f2b875720f7aec0a14ffa2a0bc11a4b44f8b8949106b19bad7149c/config2py-0.1.36-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "aa7b932af9aac553b82c9399f3e0b51d4fbac4550b182c03c5e269e30fc6b982",
"md5": "a0142464f323e16a2765b1cb71ff00a7",
"sha256": "b723065e150516daa049f835665a06abb18d05257fb548006487f99bf50e71c4"
},
"downloads": -1,
"filename": "config2py-0.1.36.tar.gz",
"has_sig": false,
"md5_digest": "a0142464f323e16a2765b1cb71ff00a7",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 32495,
"upload_time": "2024-08-01T13:14:56",
"upload_time_iso_8601": "2024-08-01T13:14:56.596762Z",
"url": "https://files.pythonhosted.org/packages/aa/7b/932af9aac553b82c9399f3e0b51d4fbac4550b182c03c5e269e30fc6b982/config2py-0.1.36.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-08-01 13:14:56",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "i2mint",
"github_project": "config2py",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "config2py"
}