{"id":42212912,"url":"https://github.com/foliant-docs/foliantcontrib.utils.combined_options","last_synced_at":"2026-01-27T01:06:55.101Z","repository":{"id":57431796,"uuid":"170662760","full_name":"foliant-docs/foliantcontrib.utils.combined_options","owner":"foliant-docs","description":"[DEPRECATED]","archived":false,"fork":false,"pushed_at":"2021-07-21T08:43:31.000Z","size":32,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-25T16:49:59.955Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/foliant-docs.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-02-14T09:18:42.000Z","updated_at":"2021-07-21T08:43:34.000Z","dependencies_parsed_at":"2022-09-02T10:52:43.994Z","dependency_job_id":null,"html_url":"https://github.com/foliant-docs/foliantcontrib.utils.combined_options","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/foliant-docs/foliantcontrib.utils.combined_options","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foliant-docs%2Ffoliantcontrib.utils.combined_options","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foliant-docs%2Ffoliantcontrib.utils.combined_options/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foliant-docs%2Ffoliantcontrib.utils.combined_options/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foliant-docs%2Ffoliantcontrib.utils.combined_options/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/foliant-docs","download_url":"https://codeload.github.com/foliant-docs/foliantcontrib.utils.combined_options/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foliant-docs%2Ffoliantcontrib.utils.combined_options/sbom","scorecard":{"id":406113,"data":{"date":"2025-08-11","repo":{"name":"github.com/foliant-docs/foliantcontrib.utils.combined_options","commit":"09016143754cc58912b61664af394f38d32cc109"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/20 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-18T21:18:03.540Z","repository_id":57431796,"created_at":"2025-08-18T21:18:03.540Z","updated_at":"2025-08-18T21:18:03.540Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28794642,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T21:49:50.245Z","status":"ssl_error","status_checked_at":"2026-01-26T21:48:29.455Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-27T01:06:54.081Z","updated_at":"2026-01-27T01:06:55.079Z","avatar_url":"https://github.com/foliant-docs.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![](https://img.shields.io/pypi/v/foliantcontrib.utils.combined_options.svg)](https://pypi.org/project/foliantcontrib.utils.combined-options/) [![](https://img.shields.io/github/v/tag/foliant-docs/foliantcontrib.utils.combined_options.svg?label=GitHub)](https://github.com/foliant-docs/foliantcontrib.utils.combined_options)\n\n**This package is deprecated and will be removed in future. Combined options moved to the [main utils package](https://github.com/foliant-docs/foliantcontrib.utils).**\n\n# Overview\n\ncombined_options is a module which helps you cope with the options from foliant.yml and tag options.\n\nModule has two classes:\n- **Options** which extends functionality of an options dictionary,\n- **CombinedOptions** which allows to combine config and tag options into one dictionary-like object.\n\n# Usage\n\nTo use functions and classes from this module, install it with command\n\n```bash\npip3 install foliantcontrib.utils.combined_options\n```\n\nThen in your preprocessor module import the Options or CombinedOptions class and wrap your options dictionaries in them:\n\n```python\nfrom foliant.preprocessors.utils.combined_options import CombinedOptions\n\n...\n\noptions = CombinedOptions({'main': main_options,\n                           'tag': tag_options},\n                          priority='tag')\nif 'caption' in options:\n    self._caption = options['caption']\n```\n\nOptions and CombinedOptions act like a dictionary. For detailed description of the functions, please refer to the rest of the documentation.\n\n## Options class\n\nOptions class wraps around the options dictionary, for example from your foliant.yaml file, and gives it some extra functionality.\n\n**Init parameters**\n\n- `options` (dict, required) — the pure dictionary with options.\n- `defaults` (dict, optional) — dictionary with default values, usually declared at the top of the preprocessor class.\n- `convertors` (dict, optional) — dictionary with key = option name, value = convertor function which will be applied to the value of an option with such name before storing in class.\n- `validators` (dict, optional) — dictionary with key = option name, value = validator function which will be applied to the value of this option. Function should check for validity and raise ValidationError if the check fails.\n- `required` (list, optional) — a list of required parameters or a list of combinations of required parameters.\n\nLet's say you have such options in your config:\n\n```yaml\npreprocessors:\n    - MyAwesomePreprocessor:\n        config: config.xml\n        articles:\n            - a1\n            - a2\n            - a3\n        store_log: true\n```\n\nFoliant will parse this config into a dictionary which will look like this:\n\n```python\n\u003e\u003e\u003e config_options = {'config': 'config.xml', 'articles': ['a1','a2','a3'], 'store_log': True}\n\n```\n\nLet's say you have a `defaults` dictionary in your preprocessor source code looking like this:\n\n```python\n\u003e\u003e\u003e defaults = {'config': 'config.xml', 'articles': []}\n\n```\n\nLet's import the `Options` class to look at some of its functions:\n\n```python\n\u003e\u003e\u003e from foliant.preprocessors.utils.combined_options import Options\n\n```\n\nTo use the class we need to supply our options dictionary and the dictionary with default values to the class constructor:\n\n```python\n\u003e\u003e\u003e options = Options(config_options, defaults)\n\n```\n\n\u003e Note that supplying the dictionary with defaults is not required, it is needed only for the work of `is_default` class method\n\nThe resulting object acts just like a dictionary:\n\n```python\n\u003e\u003e\u003e options['config']\n'config.xml'\n\u003e\u003e\u003e 'articles' in options\nTrue\n\u003e\u003e\u003e options.get('missing', 'value')\n'value'\n\n```\n\nBut now, since we've given it a dictionary with default values, we can check if the value set in options differs from its default:\n\n```python\n\u003e\u003e\u003e options.is_default('config')\nTrue\n\u003e\u003e\u003e options.is_default('articles')\nFalse\n\u003e\u003e\u003e options.is_default('store_log')\nFalse\n\n```\n\nAnother function of this class is that it can validate option values and convert them.\n\nValidators and convertors are functions which you'll have to create yourself. A few of them are already available in the module though, check the source code.\n\n**Validators**\n\nValidator is a function that takes option value as parameter and raises `ValidationError` it the value is wrong in some way.\n\nFor example, if you want to be sure that type the option user supplied is a string you can write a validator like this:\n\n```python\n\u003e\u003e\u003e from foliant.preprocessors.utils.combined_options import ValidationError\n\u003e\u003e\u003e def validate_is_str(option):\n...     if type(option) is not str:\n...         raise ValidationError('Value should be string!')\n\n```\n\nTo add validator to your options object, supply it in the constructor:\n\n```python\n\u003e\u003e\u003e config_options = {'check': 123}\n\u003e\u003e\u003e options = Options (config_options, validators={'check': validate_is_str})\nTraceback (most recent call last):\n  ...\nfoliant.preprocessors.utils.combined_options.ValidationError: Error in option \"check\": Value should be string!\n\n```\n\nYou see, it even didn't allow us to create an options object because the value of the parameter is wrong. You should handle this error on your own.\n\n**Convertors**\n\nSometimes you have to convert the value of the option that user provided before using it. Convertors are functions that are applied to certain options and replace their value in the Options object with the converted result of this function.\n\nFor example, if we need a comma-separated string has to be converted into a list, we can write this kind of convertor:\n\n```python\n\u003e\u003e\u003e def convert_to_list(option):\n...     if type(option) is str:\n...         return option.split(',')\n...     else:\n...         return option\n\n```\n\nSo now let's attach our convertor to an option object:\n\n```python\n\u003e\u003e\u003e config_options = {'names': 'Sam,Ben,Dan'}\n\u003e\u003e\u003e options = Options(config_options, convertors={'names': convert_to_list})\n\u003e\u003e\u003e options['names']\n['Sam', 'Ben', 'Dan']\n\n```\n\n**Required options**\n\nOptions class may check if all of the required options are defined. To use this feature supply a list of required param names in the `required` key:\n\n```python\n\u003e\u003e\u003e config_options = {'title': 'My article', 'id': 335}\n\u003e\u003e\u003e options = Options(config_options, required=['title', 'space'])\nTraceback (most recent call last):\n  ...\nfoliant.preprocessors.utils.combined_options.RequiredParamsMissingError: Not all required params are supplied: ['title', 'space']\n\n```\n\nWe've forgot to define the required parameter `space` and Options object alerted us right away.\n\nThere are situations when you have not just required parameters but combination of required parameters. For example you can define the page you are editing by id, or by title and space. Both ways are possible, but one of them has to be satisfied.\n\nIn this case you can supply a list with all possible combination in the `required` key like this:\n\n```python\n\u003e\u003e\u003e config_options = {'title': 'My article', 'id': 335}\n\u003e\u003e\u003e options = Options(config_options, required=[['title', 'space'], ['id']])\n\n```\n\n## CombinedOptions class\n\nCombinedOptions is designed to merge several options dictionaries into one object. It is a common task when you have some global options set in foliant.yml but they can be overriden by tag options in Markdown source. The result is a dictionary-like CombinedOptions object which has all options from config and from the tag.\n\nWhen options overlap, the priority would be given to the option from the dictionary, which defined it first. If you want to override this behavior (or make things more verbose) you can utilize the `priority` parameter.\n\nCombinedOptions is inherited from Options class and repeats all its functionality.\n\n**Init parameters**\n\n- `options` (dict, required) — dictionary where key = priority, value = option dictionary.\n- `priority` (str) — initial priority (if not set = first key from options dict).\n\nRemaining parameters are the same as in Options class:\n\n- `defaults` (dict, optional) — dictionary with default values, usually declared at the top of the preprocessor class.\n- `convertors` (dict, optional) — dictionary with key = option name, value = convertor function which will be applied to the value of this option before storing in class.\n- `validators` (dict, optional) — dictionary with key = option name, value = validator function which will be applied to the value of this option. Function should check for validity and raise ValidationError if the check fails.\n\nTo illustrate CombinedOptions' handiness let's assume that you have two option dictionaries, one came from foliant.yml and the other one — from the tag you are currently processing:\n\n```python\n\u003e\u003e\u003e config_options = {'config': 'config.xml', 'dpi': 300}\n\u003e\u003e\u003e tag_options = {'dpi': 500, 'caption': 'Main screen'}\n\n```\n\nLet's combine these two options in one object. To do this we will have to pack them into a single dictionary under arbitrary keys (keys are explained later):\n\n```python\n\u003e\u003e\u003e from foliant.preprocessors.utils.combined_options import CombinedOptions\n\u003e\u003e\u003e options = CombinedOptions({'tag': tag_options, 'config': config_options})\n\n```\n\nAs you have noticed, we have parameter `'dpi'` defined in both option dictionaries. Let's look at the values we will be getting:\n\n```python\n\u003e\u003e\u003e options['config']  # we have option from config_options\n'config.xml'\n\u003e\u003e\u003e options['caption']  # we also have an option from tag_options\n'Main screen'\n\u003e\u003e\u003e options['dpi']  # when we ask option which occurs in both, we get one from tag_options\n500\n\n```\n\nIn the CombinedOptions object we have options from both `config` and `tag` dictionaries. The conflicting options (like `dpi` in our example) are picked from the first dictionary defined. (If you are using Python 3.5 and older, supply options in `OrderedDict`)\n\nIf you wish to change the priority of the dictionaries (or make it more verbose), use the `priority` parameter, which may be a single string, o list of strings in priority order:\n\n```python\n\u003e\u003e\u003e options = CombinedOptions({'tag': tag_options, 'config': config_options}, priority='config')\n\u003e\u003e\u003e options['dpi']  # now we should get the value from 'config' dictionary\n300\n\n```\n\n\nYou can also change the priority on fly. To do this just give a new value to the `priority` attribute:\n\n```python\n\u003e\u003e\u003e options.priority = 'tag'\n\u003e\u003e\u003e options['dpi']\n500\n\n```\n\n# Predefined convertors and validators\n\nThere are some convertors and validators already predefined in combined_options module.\n\n**Validators**\n\n`validate_in` — factory that returns a validator which checks if specified value is in the list.\n\nTo use this validator, first get one from the factory, supplying the list of correct values for option:\n\n```python\n\u003e\u003e\u003e from foliant.preprocessors.utils.combined_options import validate_in\n\u003e\u003e\u003e correct = ['spam', 'eggs', 'bacon']\n\u003e\u003e\u003e validator = validate_in(correct)\n\u003e\u003e\u003e options = Options({'dish': 'chicken'}, validators={'dish': validator})\nTraceback (most recent call last):\n  ...\nfoliant.preprocessors.utils.combined_options.ValidationError: Error in option \"dish\": Unsupported option value chicken. Should be one of: spam, eggs, bacon\n\n```\n\n***\n\n`val_type` — factory that returns validator, which checks if specified value is of correct type.\n\n```python\n\u003e\u003e\u003e from foliant.preprocessors.utils.combined_options import val_type\n\u003e\u003e\u003e validator = val_type(str)\n\u003e\u003e\u003e options = Options({'name': 998}, validators={'name': validator})\nTraceback (most recent call last):\n  ...\nfoliant.preprocessors.utils.combined_options.ValidationError: Error in option \"name\": Unsupported option value 998. Must be of type \u003cclass 'str'\u003e\n\n```\n\nYou can also specify a list of supported types:\n\n```python\n\u003e\u003e\u003e correct = [str, int, None]\n\u003e\u003e\u003e validator = val_type(correct)\n\u003e\u003e\u003e options = Options({'name': None}, validators={'name': validator})\n\u003e\u003e\u003e options = Options({'name': ['Bob', 'Alice']}, validators={'name': validator})\nTraceback (most recent call last):\n  ...\nfoliant.preprocessors.utils.combined_options.ValidationError: Error in option \"name\": Unsupported option value ['Bob', 'Alice']. Must be of type \u003cclass 'str'\u003e, \u003cclass 'int'\u003e, None\n\n```\n\n***\n\n`validate_exists` — checks if specified path exists in file system.\n\n```python\n\u003e\u003e\u003e from foliant.preprocessors.utils.combined_options import validate_exists\n\u003e\u003e\u003e options = Options({'fp': '/'}, validators={'fp': validate_exists})\n\u003e\u003e\u003e options = Options({'fp': '/wrong'}, validators={'fp': validate_exists})\nTraceback (most recent call last):\n  ...\nfoliant.preprocessors.utils.combined_options.ValidationError: Error in option \"fp\": Path /wrong does not exist.\n\n```\n\n**Convertors**\n\n`yaml_to_dict_convertor` — converts yaml-string to python dict. If value is a dict already — just returns it. **DEPRECATED:** since Foliant 1.0.9 all tag option values are treated as YAML.\n\n***\n\n`boolean_convertor` — converts strings and integers into Boolean according to the table\n\nvalue | result\n----- | ------\n`1` | `True`\n`0` | `False`\n`'1'` | `True`\n`'0'` | `False`\n`'y'` | `True`\n`'n'` | `False`\n`'yes'` | `True`\n`'no'` | `False`\n`'true'` | `True`\n`'false'` | `False`\n`\u003cother\u003e` | `True`\n\n***\n\n`rel_path_convertor` —  Convertor factory which makes path, supplied in option value, relative to parent_path, set during the convertor initialization. Returns `PosixPath` object.\n\n```python\n\u003e\u003e\u003e from foliant.preprocessors.utils.combined_options import rel_path_convertor\n\u003e\u003e\u003e my_conv = rel_path_convertor('/usr/src/app')\n\u003e\u003e\u003e options = Options({'index': 'src/index.md'}, convertors={'index': my_conv})\n\u003e\u003e\u003e options['index']\nPosixPath('/usr/src/app/src/index.md')\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoliant-docs%2Ffoliantcontrib.utils.combined_options","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffoliant-docs%2Ffoliantcontrib.utils.combined_options","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoliant-docs%2Ffoliantcontrib.utils.combined_options/lists"}