optionsfactory


Nameoptionsfactory JSON
Version 1.0.11 PyPI version JSON
download
home_pagehttps://github.com/johnomotani/optionsfactory
SummaryHandles user-provided options with flexible defaults, documentation and checking
upload_time2023-01-08 16:46:16
maintainer
docs_urlNone
authorJohn Omotani
requires_python>=3.6
licenseOSI Approved :: GNU General Public License v3 or later (GPLv3+)
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            `OptionsFactory`
================

https://github.com/johnomotani/optionsfactory

`OptionsFactory` allows you to define a set of options, which can have (if you like):
default values (which may be expressions depending on other options); documentation for
each option; an allowed type or list of types; a check that the value option is on an
allowed list; checks that the value of an option satisfies some tests.


Installation
------------

`OptionsFactory` can be installed using `pip`
```
pip install --user optionsfactory
```
or `conda`
```
conda install -c conda-forge optionsfactory
```


Usage
-----

Once the options are defined in an `OptionsFactory`, you create a particular instance of
the options by passing some user settings (a dict or YAML file). The `OptionsFactory`
uses the values passed, sets the remaining options from the default values or
expressions and returns an `Options` object. Options are immutable so that you do not
have to worry about the options being accidentally changed during execution - however,
see [`MutableOptionsFactory`](#mutableoptionsfactory) if you want to be able to update the
options dynamically.

For example, some simple options might be implemented like this:
```python
from optionsfactory import OptionsFactory


class A:

    # The keyword arguments define the options and give the default values
    options_factory = OptionsFactory(a=1, b=2)

    def __init__(self, user_options = None):
        self.options = self.options_factory.create(user_options)

        # options can be accessed like a dict
        myvalue = 2 * options["a"]

        #... or as attributes
        mynewvalue = 3 + options.a
```

It might also be useful for some classes to allow the options to be set from keyword
arguments, for example
```python
from optionsfactory import OptionsFactory


class B:

    # The keyword arguments define the options and give the default values
    options_factory = OptionsFactory(a=1, b=2)

    def __init__(self, **kwargs):
        self.options = self.options_factory.create(kwargs)
```
The `create()` method will not alter the argument passed to it.

The options will then combine explicitly set values and defaults:
```python
>>> b1 = B() # uses default values
>>> b1.options.a
1
>>> b1.options.b
2
>>> b2 = B(b=4) # override one of the defaults
>>> b2.options.a
1
>>> b2.options.b
4
```

More flexibility is available by using expressions to set the default values.
```python
from optionsfactory import OptionsFactory


class C:

    # The keyword arguments define the options and give the default values
    options_factory = OptionsFactory(a=lambda options: options.b + 5, b=2)

    def __init__(self, **kwargs):
        self.options = self.options_factory.create(kwargs)
```
could be used like:
```python
>>> c1 = C() # only default values
>>> c1.options.a
7
>>> c1.options.b
2
>>> c2 = C(a=1, b=3) # override both options, expression not used
>>> c2.options.a
1
>>> c2.options.b
>>> 3
>>> c3 = C(b=4) # User-set value of b evaluated in default expression for a
>>> c3.options.a
9
>>> c3.options.b
4
```
Circular dependencies in expressions will be detected and raise a ValueError.


`WithMeta`
----------

`WithMeta` objects are used to store the defaults within `OptionsFactory`, and can be
used to define options with extra information, e.g.
```python
from optionsfactory import OptionsFactory, WithMeta
from optionsfactory.checks import is_positive, is_None


class D:
    options_factory = OptionsFactory(
        a=WithMeta(1, doc="option a"),
        b=WithMeta(2, value_type=int),
        c=WithMeta(3, allowed=[1, 2, 3]),
        d=WithMeta(4, check_all=is_positive),
        e=WithMeta(5, check_any=lambda x: x < 6),
        f=WithMeta(6, doc="option f", value_type=[int, float], allowed=[6, 7, 8, 9.5]),
        g=WithMeta(
            7,
            doc="option g",
            value_type=[int, None],
            check_all=[is_positive, lambda x: x < 10],
            check_any=[lambda x: x < 2, lambda x: x > 6],
        ),
    )

    def __init__(self, **kwargs):
        self.options = self.options_factory.create(kwargs)
```
The first argument to `WithMeta` gives the default value for the option, and the
remaining keyword arguments are all optional. Using `WithMeta` the values behave just as
the simple default values described above:
```python
>>> d = D(b=12)
>>> d.options.a
1
>>> d.options["b"]
12
```

### documentation

Documentation defined in the factory initialisation can be accessed from either the
`OptionsFactory` or the `Options` instance via a `doc` property, that gives a `dict`
with the documentation for each option:
```python
>>> D.options_factory.doc["a"]  # Get doc from the factory
'option a'
>>> D.options_factory.doc["b"]  # No doc was defined for this option
>>> d = D()
>>> d.options.doc["f"]  # Get doc from the Options instance
'option f'
```


### `value_type`

The `value_type` argument can be used to give a type or sequence of types that
the option is allowed to have. Trying to set an option with a non-allowed type
raises a
`ValueError`:
```python
>>> d2 = D(d=-2)
ValueError: The value -2 of key=d is not compatible with check_all
>>> d3 = D(f=8)
>>> d3.options.f
8
>>> d4 = D(f=9.5)
>>> d4.options.f
9.5
>>> d5 = D(f="a string")
TypeError: a string is not of type (<class 'int'>, <class 'float'>) for key=f
```


### checking values

There are two ways of checking the values that are set for options.

#### `allowed`

The `allowed` keyword sets a list of allowed options. A `ValueError` is raised if the
value being set is not in the list. `allowed` cannot be set if either of `check_all` or
`check_any` is. Example:
```python
>>> d6 = D(c=2)
ValueError: 2 is not in the allowed values (1, 2, 3) for key=c
>>> d7 = D(c=4)
ValueError: 4 is not in the allowed values (1, 2, 3) for key=c
```

#### `check_all` and `check_any`

These arguments can be passed a list of expressions. The expressions passed to
`check_all` must all evaluate to `True`, or an `ValueError` is raised. At least one of
the expressions passed to `check_any` is raised. The choice of `check_any` or
`check_all` is a matter of convenience and clarity - the effect of either could be
achieved with a single, sufficiently complicated, expression. They can both be set at
the same time, although this probably not often useful. Neither can be set if the
`allowed` keyword is. Example:
```python
>>> d8 = D(d=14)
>>> d8.options.d
14
>>> d9 = D(d=-1)
ValueError: The value -1 of key=d is not compatible with check_all
>>> d10 = D(e=5)
>>> d10.options.e
5
>>> d11 = D(e=6)
ValueError: The value 6 of key=e is not compatible with check_any
>>> d12 = D(g=1)
>>> d12.options.g
1
>>> d13 = D(g=-1)
ValueError: The value -1 of key=g is not compatible with check_all
>>> d14 = D(g=5)
ValueError: The value 5 of key=g is not compatible with check_any
>>> d15 = D(g=9)
>>> d15.options.g
9
>>> d16 = D(g=10)
ValueError: The value 10 of key=g is not compatible with check_all
```

Default expressions
-------------------

Much more flexibility is offered for default values by using expressions. These are
single-argument functions (lambda expressions are often useful), which return the
desired default value when passed an `Options` object, from which the values of other
options (which may or may not be expressions themselves) can be accessed. See the `class
C` example above. When [nested options](#nested_options) are used, expressions can also
access values in subsections or parent sections of the options tree:
```python
from optionsfactory import OptionsFactory


class E:
    options_factory = OptionsFactory(
        a=1,
        b=lambda options: options.a + options.subsection1.c + options.subsection2.e,
        subsection1=OptionsFactory(
            c=lambda options: options.parent.a + options.parent.subsection2.e,
            subsubsection=OptionsFactory(d=2),
        ),
        subsection2=OptionsFactory(
            e=lambda options: options.parent.subsection1.subsubsection.d,
        ),
    )

    def __init__(self, **kwargs):
        self.options = self.options_factory.create(kwargs)
```
If we initialise `E` with just the defaults
```python
>>> e = E()
>>> e.options.a
1
>>> e.options.b
6
>>> e.options.subsection1.c
3
>>> e.options.subsection1.subsubsection.d
2
>>> e.options.subsection2.e
2
```


OptionsFactory extension for subclasses
---------------------------------------

Sometimes it can be useful to create an extended version of an `OptionsFactory`. For
example a child class might have some extra options that are not needed in its parent
class, or might require different default values. The `OptionsFatory.add()` method
creates a new `OptionsFactory` from an existing one, with the keyword arguments adding
to or overriding the options in the original factory. When overriding existing options,
pass a value or expression to keep existing documentation and checks, or a `WithMeta`
object to provide new documentation and checks. Example:
```python
from optionsfactory import OptionsFactory


class Parent:
    options_factory = OptionsFactory(
        a=WithMeta(1, doc="option a"),
        b=WithMeta(2, doc="option b"),
        c=WithMeta(3, doc="option c"),
    )

    def __init__(self, **kwargs):
        self.optiotns = self.options_factory(kwargs)

class Child(Parent):
    options_factory = Parent.options_factory.add(
        # Keep 'a' unchanged
        b=4,  # Change the default value of 'b', but keep the documentation
        c=WithMeta(5, doc="child option c"),  # New default and attributes for 'c'
        d=WithMeta(6, doc="new option d"),  # New option not present in the parent
    )
```
and if we create a `Child` instance
```python
>>> child = Child()
>>> child.options.a
1
>>> child.options.doc["a"]
'option a'
>>> child.options.b
4
>>> child.options.doc["b"]
'option b'
>>> child.options.c
5
>>> child.options.doc["c"]
'child option c'
>>> child.options.d
6
>>> child.options.doc["d"]
'new option d'
```


Nested options
--------------

Nested options are created by passing another `OptionsFactory` as a keyword argument in
the OptionsFactory constructor. See the [nested structure](#nested_structure) example
below.


Collecting defaults
-------------------

It can be useful to collect options from several factories together into a higher-level
factory. For example if a `class Container` contains members of several classes, it
might be useful for the `options_factory` of `Container` to have options for all those
members, but to define the options, defaults, documentation, etc. in the particular
classes. This can be done by passing `OptionsFactory` objects as positional arguments to
the `OptionsFactory` constructor - see the [flat structure](#flat_structure) example
below.

Other Features
--------------

### load from YAML

The user settings can be loaded from a YAML file (if `PyYAML` is available - install the
`optionsfactory[yaml]` variant to ensure this):
```python
>>> with open(filename) as f:
>>>     options = options_factory.create_from_yaml(f)
```


### save to YAML

The options can also be saved to a YAML file, either the non-default values only
```python
>>> with open(filename, 'w') as f:
>>>     options.to_yaml(f)
```
or all values including defaults, by passing `True` to the `with_defaults` argument
```python
>>> with open(filename, 'w') as f:
>>>     options.to_yaml(f, True)  # saves options with default values as well
```


### Pickling (with dill)

`Options` objects can be pickled using `dill`. This is tested. Pickling of
`MutableOptions` objects is not currently supported.


Examples
--------

Here are a couple of more complicated examples of the patterns that `OptionsFactory` was
designed for.


### flat structure

`class A` contains members of types `B` and `C`, so `A.options_factory` collects the
default values, documentation, etc. from `B.options_factory` and `C.options_factory` by
taking them as positional arguments to the constructor. Then the `Options` object of `A`
is used to create the `Options` objects for `B` and `C`, which will have only the
options relevant to themselves in, because `B.options_factory` and `C.options_factory`
ignore any undefined options in the argument passed to `create`.

```python
class A:
    options_factory = OptionsFactory(
        B.options_factory,
        C.options_factory,
        a_opt1 = WithMeta(1, allowed=[1, 3, 7]),
        a_opt2 = WithMeta(2, value_type=[int, float]),
    )

    def __init__(self, user_options):
        self.options = self.options_factory(user_options)
        self.b = B(self.options)
        self.c = C(self.options)

class B:
    options_factory = OptionsFactory(
        b_opt = WithMeta(3.0, checks=is_positive),
    )

    def __init__(self, options):
        self.options = self.options_factory.create(options)

class C:
    options_factory = OptionsFactory(
        c_opt = WithMeta("c-value", value_type=str)
    )

    def __init__(self, options):
        self.options = self.options_factory.create(options)
```

If `B` or `C` were intended to also be used as user-facing classes, which want to get
their options from `**kwargs`, it would also be possible to have
```python
def __init__(self, **kwargs):
    self.options = self.options_factory(kwargs)
```
and create the objects in `A`'s constructor like `self.b = B(**self.options)`.


### nested structure

Similar to the flat structure above, but keeping the options for different member
objects separated in different sections:
```python
class A:
    options_factory = OptionsFactory(
        B=B.options_factory,
        C=C.options_factory,
        a_opt1 = WithMeta(1, allowed=[1, 3, 7]),
        a_opt2 = WithMeta(2, value_type=[int, float]),
    )

    def __init__(self, user_options):
        self.options = self.options_factory(user_options)
        self.b = B(self.options.B)
        self.c = C(self.options.C)

class B:
    options_factory = OptionsFactory(
        b_opt = WithMeta(3.0, checks=is_positive),
    )

    def __init__(self, options):
        self.options = options

class C:
    options_factory = OptionsFactory(
        c_opt = WithMeta("c-value", value_type=str)
    )

    def __init__(self, options):
        self.options = options
```

If `A` needs to change the default options for one of the nested sections, can use the
`add()` method like `B={"b_opt": 7.0)`.

Default expressions in a nested options structure can use values from sub-sections, or
from parent sections, see [Default expressions](#default_expressions).

If `B` or `C` should be allowed to be created independently of a containing class like
`A`, then you can instead define the constructor as
```python
class B:
    options_factory = OptionsFactory(
        b_opt = WithMeta(3.0, checks=is_positive),
    )

    def __init__(self, options):
        self.options = self.options_factory(options)
```
This will have the same effect as the above code when called with `self.b =
B(self.options.B)` but also allows creating a `B` like `another_b = B({"b_opt": 4.0})`.
(This version will be slightly more expensive than the one above because the `Options`
object will be converted to a `dict`-like iterable and a new `Options` created by
parsing that iterable.)


### global options

Not recommended, but you could create a global options object for your package/program.
For example in a file `mypackage.py`
```python
from optionsfactory import OptionsFactory


global_options = None


options_factory = OptionsFactory(
    opt1 = 1,
    opt2 = 2,
)


def setup(input_options):
    global global_options
    global_options = options_factory.create(input_options)
```

MutableOptionsFactory
=====================

`MutableOptionsFactory` is almost identical to `OptionsFactory`, but creates
`MutableOptions` objects which can be modified after being created (it also has a
`create_immutable()` method, equivalent to `OptionsFactory.create()` to create
`Options` objects). `MutableOptions` behave like `Options` with the exception that
values can be set, or reset to the default value (using `del`) after the object is
created. Default values are re-calculated if any option is changed. Example:
```python
>>> from options_factory import MutableOptionsFactory
>>> factory = MutableOptionsFactory(a=1, b=lambda options: 2.0*options.a)
>>> mutable_options = factory.create({"a": 3, "b": 4})
>>> mutable_options.a
3
>>> mutable_options.b
4
>>> del mutable_options.b
>>> mutable_options.b
6.0
>>> mutable_options.a = 5
>>> mutable_options.b
10.0
>>> mutable_options["a"] = 7.5
>>> mutable_options.b
15.0
>>> del mutable_options["a"]
>>> mutable_options.b
2.0
>>> mutable_options.a
1
```


Expressions for non-default values
==================================

Passing expressions for non-default values should work, although it has not been tested
yet. Expressions cannot at present be loaded from YAML files.


Acknowledgements
================

Thanks to [Ben Dudson](https://github.com/bendudson) and [Peter
Hill](https://github.com/ZedThree) for discussion on options handling in the [hypnotoad
project](https://github.com/boutproject/hypnotoad/pull/33) and ideas in
[frozen_options](https://github.com/bendudson/frozen-options).

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/johnomotani/optionsfactory",
    "name": "optionsfactory",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.6",
    "maintainer_email": "",
    "keywords": "",
    "author": "John Omotani",
    "author_email": "john.omotani@cantab.net",
    "download_url": "https://files.pythonhosted.org/packages/0d/f4/b0629e980389a0b5a5599e2be4de045a47097ecc82327cd67d04678f2e6b/optionsfactory-1.0.11.tar.gz",
    "platform": null,
    "description": "`OptionsFactory`\n================\n\nhttps://github.com/johnomotani/optionsfactory\n\n`OptionsFactory` allows you to define a set of options, which can have (if you like):\ndefault values (which may be expressions depending on other options); documentation for\neach option; an allowed type or list of types; a check that the value option is on an\nallowed list; checks that the value of an option satisfies some tests.\n\n\nInstallation\n------------\n\n`OptionsFactory` can be installed using `pip`\n```\npip install --user optionsfactory\n```\nor `conda`\n```\nconda install -c conda-forge optionsfactory\n```\n\n\nUsage\n-----\n\nOnce the options are defined in an `OptionsFactory`, you create a particular instance of\nthe options by passing some user settings (a dict or YAML file). The `OptionsFactory`\nuses the values passed, sets the remaining options from the default values or\nexpressions and returns an `Options` object. Options are immutable so that you do not\nhave to worry about the options being accidentally changed during execution - however,\nsee [`MutableOptionsFactory`](#mutableoptionsfactory) if you want to be able to update the\noptions dynamically.\n\nFor example, some simple options might be implemented like this:\n```python\nfrom optionsfactory import OptionsFactory\n\n\nclass A:\n\n    # The keyword arguments define the options and give the default values\n    options_factory = OptionsFactory(a=1, b=2)\n\n    def __init__(self, user_options = None):\n        self.options = self.options_factory.create(user_options)\n\n        # options can be accessed like a dict\n        myvalue = 2 * options[\"a\"]\n\n        #... or as attributes\n        mynewvalue = 3 + options.a\n```\n\nIt might also be useful for some classes to allow the options to be set from keyword\narguments, for example\n```python\nfrom optionsfactory import OptionsFactory\n\n\nclass B:\n\n    # The keyword arguments define the options and give the default values\n    options_factory = OptionsFactory(a=1, b=2)\n\n    def __init__(self, **kwargs):\n        self.options = self.options_factory.create(kwargs)\n```\nThe `create()` method will not alter the argument passed to it.\n\nThe options will then combine explicitly set values and defaults:\n```python\n>>> b1 = B() # uses default values\n>>> b1.options.a\n1\n>>> b1.options.b\n2\n>>> b2 = B(b=4) # override one of the defaults\n>>> b2.options.a\n1\n>>> b2.options.b\n4\n```\n\nMore flexibility is available by using expressions to set the default values.\n```python\nfrom optionsfactory import OptionsFactory\n\n\nclass C:\n\n    # The keyword arguments define the options and give the default values\n    options_factory = OptionsFactory(a=lambda options: options.b + 5, b=2)\n\n    def __init__(self, **kwargs):\n        self.options = self.options_factory.create(kwargs)\n```\ncould be used like:\n```python\n>>> c1 = C() # only default values\n>>> c1.options.a\n7\n>>> c1.options.b\n2\n>>> c2 = C(a=1, b=3) # override both options, expression not used\n>>> c2.options.a\n1\n>>> c2.options.b\n>>> 3\n>>> c3 = C(b=4) # User-set value of b evaluated in default expression for a\n>>> c3.options.a\n9\n>>> c3.options.b\n4\n```\nCircular dependencies in expressions will be detected and raise a ValueError.\n\n\n`WithMeta`\n----------\n\n`WithMeta` objects are used to store the defaults within `OptionsFactory`, and can be\nused to define options with extra information, e.g.\n```python\nfrom optionsfactory import OptionsFactory, WithMeta\nfrom optionsfactory.checks import is_positive, is_None\n\n\nclass D:\n    options_factory = OptionsFactory(\n        a=WithMeta(1, doc=\"option a\"),\n        b=WithMeta(2, value_type=int),\n        c=WithMeta(3, allowed=[1, 2, 3]),\n        d=WithMeta(4, check_all=is_positive),\n        e=WithMeta(5, check_any=lambda x: x < 6),\n        f=WithMeta(6, doc=\"option f\", value_type=[int, float], allowed=[6, 7, 8, 9.5]),\n        g=WithMeta(\n            7,\n            doc=\"option g\",\n            value_type=[int, None],\n            check_all=[is_positive, lambda x: x < 10],\n            check_any=[lambda x: x < 2, lambda x: x > 6],\n        ),\n    )\n\n    def __init__(self, **kwargs):\n        self.options = self.options_factory.create(kwargs)\n```\nThe first argument to `WithMeta` gives the default value for the option, and the\nremaining keyword arguments are all optional. Using `WithMeta` the values behave just as\nthe simple default values described above:\n```python\n>>> d = D(b=12)\n>>> d.options.a\n1\n>>> d.options[\"b\"]\n12\n```\n\n### documentation\n\nDocumentation defined in the factory initialisation can be accessed from either the\n`OptionsFactory` or the `Options` instance via a `doc` property, that gives a `dict`\nwith the documentation for each option:\n```python\n>>> D.options_factory.doc[\"a\"]  # Get doc from the factory\n'option a'\n>>> D.options_factory.doc[\"b\"]  # No doc was defined for this option\n>>> d = D()\n>>> d.options.doc[\"f\"]  # Get doc from the Options instance\n'option f'\n```\n\n\n### `value_type`\n\nThe `value_type` argument can be used to give a type or sequence of types that\nthe option is allowed to have. Trying to set an option with a non-allowed type\nraises a\n`ValueError`:\n```python\n>>> d2 = D(d=-2)\nValueError: The value -2 of key=d is not compatible with check_all\n>>> d3 = D(f=8)\n>>> d3.options.f\n8\n>>> d4 = D(f=9.5)\n>>> d4.options.f\n9.5\n>>> d5 = D(f=\"a string\")\nTypeError: a string is not of type (<class 'int'>, <class 'float'>) for key=f\n```\n\n\n### checking values\n\nThere are two ways of checking the values that are set for options.\n\n#### `allowed`\n\nThe `allowed` keyword sets a list of allowed options. A `ValueError` is raised if the\nvalue being set is not in the list. `allowed` cannot be set if either of `check_all` or\n`check_any` is. Example:\n```python\n>>> d6 = D(c=2)\nValueError: 2 is not in the allowed values (1, 2, 3) for key=c\n>>> d7 = D(c=4)\nValueError: 4 is not in the allowed values (1, 2, 3) for key=c\n```\n\n#### `check_all` and `check_any`\n\nThese arguments can be passed a list of expressions. The expressions passed to\n`check_all` must all evaluate to `True`, or an `ValueError` is raised. At least one of\nthe expressions passed to `check_any` is raised. The choice of `check_any` or\n`check_all` is a matter of convenience and clarity - the effect of either could be\nachieved with a single, sufficiently complicated, expression. They can both be set at\nthe same time, although this probably not often useful. Neither can be set if the\n`allowed` keyword is. Example:\n```python\n>>> d8 = D(d=14)\n>>> d8.options.d\n14\n>>> d9 = D(d=-1)\nValueError: The value -1 of key=d is not compatible with check_all\n>>> d10 = D(e=5)\n>>> d10.options.e\n5\n>>> d11 = D(e=6)\nValueError: The value 6 of key=e is not compatible with check_any\n>>> d12 = D(g=1)\n>>> d12.options.g\n1\n>>> d13 = D(g=-1)\nValueError: The value -1 of key=g is not compatible with check_all\n>>> d14 = D(g=5)\nValueError: The value 5 of key=g is not compatible with check_any\n>>> d15 = D(g=9)\n>>> d15.options.g\n9\n>>> d16 = D(g=10)\nValueError: The value 10 of key=g is not compatible with check_all\n```\n\nDefault expressions\n-------------------\n\nMuch more flexibility is offered for default values by using expressions. These are\nsingle-argument functions (lambda expressions are often useful), which return the\ndesired default value when passed an `Options` object, from which the values of other\noptions (which may or may not be expressions themselves) can be accessed. See the `class\nC` example above. When [nested options](#nested_options) are used, expressions can also\naccess values in subsections or parent sections of the options tree:\n```python\nfrom optionsfactory import OptionsFactory\n\n\nclass E:\n    options_factory = OptionsFactory(\n        a=1,\n        b=lambda options: options.a + options.subsection1.c + options.subsection2.e,\n        subsection1=OptionsFactory(\n            c=lambda options: options.parent.a + options.parent.subsection2.e,\n            subsubsection=OptionsFactory(d=2),\n        ),\n        subsection2=OptionsFactory(\n            e=lambda options: options.parent.subsection1.subsubsection.d,\n        ),\n    )\n\n    def __init__(self, **kwargs):\n        self.options = self.options_factory.create(kwargs)\n```\nIf we initialise `E` with just the defaults\n```python\n>>> e = E()\n>>> e.options.a\n1\n>>> e.options.b\n6\n>>> e.options.subsection1.c\n3\n>>> e.options.subsection1.subsubsection.d\n2\n>>> e.options.subsection2.e\n2\n```\n\n\nOptionsFactory extension for subclasses\n---------------------------------------\n\nSometimes it can be useful to create an extended version of an `OptionsFactory`. For\nexample a child class might have some extra options that are not needed in its parent\nclass, or might require different default values. The `OptionsFatory.add()` method\ncreates a new `OptionsFactory` from an existing one, with the keyword arguments adding\nto or overriding the options in the original factory. When overriding existing options,\npass a value or expression to keep existing documentation and checks, or a `WithMeta`\nobject to provide new documentation and checks. Example:\n```python\nfrom optionsfactory import OptionsFactory\n\n\nclass Parent:\n    options_factory = OptionsFactory(\n        a=WithMeta(1, doc=\"option a\"),\n        b=WithMeta(2, doc=\"option b\"),\n        c=WithMeta(3, doc=\"option c\"),\n    )\n\n    def __init__(self, **kwargs):\n        self.optiotns = self.options_factory(kwargs)\n\nclass Child(Parent):\n    options_factory = Parent.options_factory.add(\n        # Keep 'a' unchanged\n        b=4,  # Change the default value of 'b', but keep the documentation\n        c=WithMeta(5, doc=\"child option c\"),  # New default and attributes for 'c'\n        d=WithMeta(6, doc=\"new option d\"),  # New option not present in the parent\n    )\n```\nand if we create a `Child` instance\n```python\n>>> child = Child()\n>>> child.options.a\n1\n>>> child.options.doc[\"a\"]\n'option a'\n>>> child.options.b\n4\n>>> child.options.doc[\"b\"]\n'option b'\n>>> child.options.c\n5\n>>> child.options.doc[\"c\"]\n'child option c'\n>>> child.options.d\n6\n>>> child.options.doc[\"d\"]\n'new option d'\n```\n\n\nNested options\n--------------\n\nNested options are created by passing another `OptionsFactory` as a keyword argument in\nthe OptionsFactory constructor. See the [nested structure](#nested_structure) example\nbelow.\n\n\nCollecting defaults\n-------------------\n\nIt can be useful to collect options from several factories together into a higher-level\nfactory. For example if a `class Container` contains members of several classes, it\nmight be useful for the `options_factory` of `Container` to have options for all those\nmembers, but to define the options, defaults, documentation, etc. in the particular\nclasses. This can be done by passing `OptionsFactory` objects as positional arguments to\nthe `OptionsFactory` constructor - see the [flat structure](#flat_structure) example\nbelow.\n\nOther Features\n--------------\n\n### load from YAML\n\nThe user settings can be loaded from a YAML file (if `PyYAML` is available - install the\n`optionsfactory[yaml]` variant to ensure this):\n```python\n>>> with open(filename) as f:\n>>>     options = options_factory.create_from_yaml(f)\n```\n\n\n### save to YAML\n\nThe options can also be saved to a YAML file, either the non-default values only\n```python\n>>> with open(filename, 'w') as f:\n>>>     options.to_yaml(f)\n```\nor all values including defaults, by passing `True` to the `with_defaults` argument\n```python\n>>> with open(filename, 'w') as f:\n>>>     options.to_yaml(f, True)  # saves options with default values as well\n```\n\n\n### Pickling (with dill)\n\n`Options` objects can be pickled using `dill`. This is tested. Pickling of\n`MutableOptions` objects is not currently supported.\n\n\nExamples\n--------\n\nHere are a couple of more complicated examples of the patterns that `OptionsFactory` was\ndesigned for.\n\n\n### flat structure\n\n`class A` contains members of types `B` and `C`, so `A.options_factory` collects the\ndefault values, documentation, etc. from `B.options_factory` and `C.options_factory` by\ntaking them as positional arguments to the constructor. Then the `Options` object of `A`\nis used to create the `Options` objects for `B` and `C`, which will have only the\noptions relevant to themselves in, because `B.options_factory` and `C.options_factory`\nignore any undefined options in the argument passed to `create`.\n\n```python\nclass A:\n    options_factory = OptionsFactory(\n        B.options_factory,\n        C.options_factory,\n        a_opt1 = WithMeta(1, allowed=[1, 3, 7]),\n        a_opt2 = WithMeta(2, value_type=[int, float]),\n    )\n\n    def __init__(self, user_options):\n        self.options = self.options_factory(user_options)\n        self.b = B(self.options)\n        self.c = C(self.options)\n\nclass B:\n    options_factory = OptionsFactory(\n        b_opt = WithMeta(3.0, checks=is_positive),\n    )\n\n    def __init__(self, options):\n        self.options = self.options_factory.create(options)\n\nclass C:\n    options_factory = OptionsFactory(\n        c_opt = WithMeta(\"c-value\", value_type=str)\n    )\n\n    def __init__(self, options):\n        self.options = self.options_factory.create(options)\n```\n\nIf `B` or `C` were intended to also be used as user-facing classes, which want to get\ntheir options from `**kwargs`, it would also be possible to have\n```python\ndef __init__(self, **kwargs):\n    self.options = self.options_factory(kwargs)\n```\nand create the objects in `A`'s constructor like `self.b = B(**self.options)`.\n\n\n### nested structure\n\nSimilar to the flat structure above, but keeping the options for different member\nobjects separated in different sections:\n```python\nclass A:\n    options_factory = OptionsFactory(\n        B=B.options_factory,\n        C=C.options_factory,\n        a_opt1 = WithMeta(1, allowed=[1, 3, 7]),\n        a_opt2 = WithMeta(2, value_type=[int, float]),\n    )\n\n    def __init__(self, user_options):\n        self.options = self.options_factory(user_options)\n        self.b = B(self.options.B)\n        self.c = C(self.options.C)\n\nclass B:\n    options_factory = OptionsFactory(\n        b_opt = WithMeta(3.0, checks=is_positive),\n    )\n\n    def __init__(self, options):\n        self.options = options\n\nclass C:\n    options_factory = OptionsFactory(\n        c_opt = WithMeta(\"c-value\", value_type=str)\n    )\n\n    def __init__(self, options):\n        self.options = options\n```\n\nIf `A` needs to change the default options for one of the nested sections, can use the\n`add()` method like `B={\"b_opt\": 7.0)`.\n\nDefault expressions in a nested options structure can use values from sub-sections, or\nfrom parent sections, see [Default expressions](#default_expressions).\n\nIf `B` or `C` should be allowed to be created independently of a containing class like\n`A`, then you can instead define the constructor as\n```python\nclass B:\n    options_factory = OptionsFactory(\n        b_opt = WithMeta(3.0, checks=is_positive),\n    )\n\n    def __init__(self, options):\n        self.options = self.options_factory(options)\n```\nThis will have the same effect as the above code when called with `self.b =\nB(self.options.B)` but also allows creating a `B` like `another_b = B({\"b_opt\": 4.0})`.\n(This version will be slightly more expensive than the one above because the `Options`\nobject will be converted to a `dict`-like iterable and a new `Options` created by\nparsing that iterable.)\n\n\n### global options\n\nNot recommended, but you could create a global options object for your package/program.\nFor example in a file `mypackage.py`\n```python\nfrom optionsfactory import OptionsFactory\n\n\nglobal_options = None\n\n\noptions_factory = OptionsFactory(\n    opt1 = 1,\n    opt2 = 2,\n)\n\n\ndef setup(input_options):\n    global global_options\n    global_options = options_factory.create(input_options)\n```\n\nMutableOptionsFactory\n=====================\n\n`MutableOptionsFactory` is almost identical to `OptionsFactory`, but creates\n`MutableOptions` objects which can be modified after being created (it also has a\n`create_immutable()` method, equivalent to `OptionsFactory.create()` to create\n`Options` objects). `MutableOptions` behave like `Options` with the exception that\nvalues can be set, or reset to the default value (using `del`) after the object is\ncreated. Default values are re-calculated if any option is changed. Example:\n```python\n>>> from options_factory import MutableOptionsFactory\n>>> factory = MutableOptionsFactory(a=1, b=lambda options: 2.0*options.a)\n>>> mutable_options = factory.create({\"a\": 3, \"b\": 4})\n>>> mutable_options.a\n3\n>>> mutable_options.b\n4\n>>> del mutable_options.b\n>>> mutable_options.b\n6.0\n>>> mutable_options.a = 5\n>>> mutable_options.b\n10.0\n>>> mutable_options[\"a\"] = 7.5\n>>> mutable_options.b\n15.0\n>>> del mutable_options[\"a\"]\n>>> mutable_options.b\n2.0\n>>> mutable_options.a\n1\n```\n\n\nExpressions for non-default values\n==================================\n\nPassing expressions for non-default values should work, although it has not been tested\nyet. Expressions cannot at present be loaded from YAML files.\n\n\nAcknowledgements\n================\n\nThanks to [Ben Dudson](https://github.com/bendudson) and [Peter\nHill](https://github.com/ZedThree) for discussion on options handling in the [hypnotoad\nproject](https://github.com/boutproject/hypnotoad/pull/33) and ideas in\n[frozen_options](https://github.com/bendudson/frozen-options).\n",
    "bugtrack_url": null,
    "license": "OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
    "summary": "Handles user-provided options with flexible defaults, documentation and checking",
    "version": "1.0.11",
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5a24ebbb488767aa50b182d395db9eb602a625ed3637f3ab99c7885235cd0236",
                "md5": "1110f5d9bf546531a366af4f132e50c2",
                "sha256": "7f7bf0f546a2bb9f8a9cc89c866bf9c5b0bf2f985579fd8e222c4a7b31b96c3d"
            },
            "downloads": -1,
            "filename": "optionsfactory-1.0.11-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "1110f5d9bf546531a366af4f132e50c2",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.6",
            "size": 25848,
            "upload_time": "2023-01-08T16:46:15",
            "upload_time_iso_8601": "2023-01-08T16:46:15.348650Z",
            "url": "https://files.pythonhosted.org/packages/5a/24/ebbb488767aa50b182d395db9eb602a625ed3637f3ab99c7885235cd0236/optionsfactory-1.0.11-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0df4b0629e980389a0b5a5599e2be4de045a47097ecc82327cd67d04678f2e6b",
                "md5": "40478ffa5331f6a0943e95d734f665bc",
                "sha256": "dc5e316b2734ed210bcff333c800ec06d97c7cf75f41d79d7a0a7c6fa608d403"
            },
            "downloads": -1,
            "filename": "optionsfactory-1.0.11.tar.gz",
            "has_sig": false,
            "md5_digest": "40478ffa5331f6a0943e95d734f665bc",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6",
            "size": 41943,
            "upload_time": "2023-01-08T16:46:16",
            "upload_time_iso_8601": "2023-01-08T16:46:16.649168Z",
            "url": "https://files.pythonhosted.org/packages/0d/f4/b0629e980389a0b5a5599e2be4de045a47097ecc82327cd67d04678f2e6b/optionsfactory-1.0.11.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-01-08 16:46:16",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": "johnomotani",
    "github_project": "optionsfactory",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "optionsfactory"
}
        
Elapsed time: 0.03724s