hammurabi.rules package

Submodules

hammurabi.rules.abstract module

This module contains the definition of the AbstractRule which describes what is shared between Rules and Preconditions.

class hammurabi.rules.abstract.AbstractRule(name: str, param: Any)[source]

Bases: abc.ABC

Abstract class which describes the common behaviour for any kind of rule even it is a hammurabi.rules.base.Rule or hammurabi.rules.base.Precondition

Parameters
  • name (str) – Name of the rule which will be used for printing

  • param (Any) – Input parameter of the rule will be used as self.param

property description

Return the description of the hammurabi.rules.base.Rule.task() based on its docstring.

Returns

Stripped description of hammurabi.rules.base.Rule.task()

Return type

str

Note

As of this property returns the docstring of hammurabi.rules.base.Rule.task() method, it worth to take care of its description when initialized.

property documentation

Return the documentation of the rule based on its name, docstring and the description of its task.

Returns

Concatenation of the rule’s name, docstring, and task description

Return type

str

Note

As of this method returns the name and docstring of the rule it worth to take care of its name and description when initialized.

made_changes
name
param
post_task_hook()[source]

Run code after the hammurabi.rules.base.Rule.task() has been performed. To access the parameter passed to the rule, always use self.param for hammurabi.rules.base.Rule.post_task_hook().

Note

This method can be used for execution of git commands like git add, or double checking a modification made.

Warning

This method is not called in dry run mode.

pre_task_hook() → None[source]

Run code before performing the hammurabi.rules.base.Rule.task(). To access the parameter passed to the rule, always use self.param for hammurabi.rules.base.Rule.pre_task_hook().

Warning

This method is not called in dry run mode.

abstract task() → Any[source]

Abstract method representing how a hammurabi.rules.base.Rule.task() or hammurabi.preconditions.base.Precondition.task() must be parameterized. Any difference in the parameters will result in pylint/mypy errors.

To be able to use the power of pipe and children, return something which can be generally used for other rules as in input.

Returns

Returns an output which can be used as an input for other rules

Return type

Any (usually same as self.param’s type)

Note

Although it is a good practice to return the same type for the output that the input has, but this is not the case for “Boolean Rules”. “Boolean Rules” should return True (or truthy) or False (or falsy) values.

static validate(val: Any, cast_to: Optional[Any] = None, required=False) → Any[source]

Validate and/or cast the given value to another type. In case the existence of the value is required or casting failed an exception will be raised corresponding to the failure.

Parameters
  • val (Any) – Value to validate

  • cast_to (Any) – Type in which the value should be returned

  • required (bool) – Check that the value is not falsy

Raise

ValueError if the given value is required but falsy

Returns

Returns the value in its original or casted type

Return type

Any

Example usage:

>>> from typing import Optional
>>> from pathlib import Path
>>> from hammurabi import Rule
>>>
>>> class MyAwesomeRule(Rule):
>>>     def __init__(self, name: str, param: Optional[Path] = None):
>>>         self.param = self.validate(param, required=True)
>>>
>>>     # Other method definitions ...
>>>

hammurabi.rules.attributes module

Attributes module contains file and directory attribute manipulation rules which can be handy after creating new files or directories or even when adding execute permissions for a script in the project.

class hammurabi.rules.attributes.ModeChanged(name: str, path: Optional[pathlib.Path] = None, new_value: Optional[int] = None, **kwargs)[source]

Bases: hammurabi.rules.attributes.SingleAttributeRule

Change the mode of a file or directory.

Supported modes:

Config option

Description

stat.S_ISUID

Set user ID on execution.

stat.S_ISGID

Set group ID on execution.

stat.S_ENFMT

Record locking enforced.

stat.S_ISVTX

Save text image after execution.

stat.S_IREAD

Read by owner.

stat.S_IWRITE

Write by owner.

stat.S_IEXEC

Execute by owner.

stat.S_IRWXU

Read, write, and execute by owner.

stat.S_IRUSR

Read by owner.

stat.S_IWUSR

Write by owner.

stat.S_IXUSR

Execute by owner.

stat.S_IRWXG

Read, write, and execute by group.

stat.S_IRGRP

Read by group.

stat.S_IWGRP

Write by group.

stat.S_IXGRP

Execute by group.

stat.S_IRWXO

Read, write, and execute by others.

stat.S_IROTH

Read by others.

stat.S_IWOTH

Write by others.

stat.S_IXOTH

Execute by others.

Example usage:

>>> import stat
>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, ModeChanged
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         ModeChanged(
>>>             name="Update script must be executable",
>>>             path=Path("./scripts/update.sh"),
>>>             new_value=stat.S_IXGRP | stat.S_IXGRP | stat.S_IXOTH
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
task() → pathlib.Path[source]

Change the mode of the given file or directory.

Returns

Return the input path as an output

Return type

Path

class hammurabi.rules.attributes.OwnerChanged(name: str, path: Optional[pathlib.Path] = None, new_value: Optional[str] = None, **kwargs)[source]

Bases: hammurabi.rules.attributes.SingleAttributeRule

Change the ownership of a file or directory.

The new ownership of a file or directory can be set in three ways. To set only the user use new_value="username". To set only the group use new_value=":group_name" (please note the colon :). It is also possible to set both username and group at the same time by using new_value="username:group_name".

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, OwnerChanged
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         OwnerChanged(
>>>             name="Change ownership of nginx config",
>>>             path=Path("./nginx.conf"),
>>>             new_value="www:web_admin"
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
task() → pathlib.Path[source]

Change the ownership of the given file or directory. None of the new username or group name can contain colons, otherwise only the first two colon separated values will be used as username and group name.

Returns

Return the input path as an output

Return type

Path

class hammurabi.rules.attributes.SingleAttributeRule(name: str, path: Optional[pathlib.Path] = None, new_value: Optional[str] = None, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Extend hammurabi.rules.base.Rule to handle attributes of a single file or directory.

made_changes
name
param
post_task_hook()[source]

Run code after the hammurabi.rules.base.Rule.task() has been performed. To access the parameter passed to the rule, always use self.param for hammurabi.rules.base.Rule.post_task_hook().

Note

This method can be used for execution of git commands like git add, or double checking a modification made.

Warning

This method is not called in dry run mode.

abstract task() → Any[source]

Abstract method representing how a hammurabi.rules.base.Rule.task() must be parameterized. Any difference in the parameters will result in pylint/mypy errors.

For more details please check hammurabi.rules.base.Rule.task().

hammurabi.rules.base module

This module contains the definition of Rule which describes what to do with the received parameter and does the necessary changes.

The Rule is an abstract class which describes all the required methods and parameters, but it can be extended and customized easily by inheriting from it. A good example for this kind of customization is hammurabi.rules.text.LineExists which adds more parameters to hammurabi.rules.files.SingleFileRule which inherits from hammurabi.rules.base.Rule.

class hammurabi.rules.base.Rule(name: str, param: Any, preconditions: Iterable[hammurabi.preconditions.base.Precondition] = (), pipe: Optional[Rule] = None, children: Iterable[Rule] = ())[source]

Bases: hammurabi.rules.abstract.AbstractRule, abc.ABC

Abstract class which describes the bare minimum and helper functions for Rules. A rule defines what and how should be executed. Since a rule can have piped and children rules, the “parent” rule is responsible for those executions. This kind of abstraction allows to run both piped and children rules sequentially in a given order.

Example usage:

>>> from typing import Optional
>>> from pathlib import Path
>>> from hammurabi import Rule
>>> from hammurabi.mixins import GitMixin
>>>
>>> class SingleFileRule(Rule, GitMixin):
>>>     def __init__(self, name: str, path: Optional[Path] = None, **kwargs) -> None:
>>>         super().__init__(name, path, **kwargs)
>>>
>>>     def post_task_hook(self):
>>>         self.git_add(self.param)
>>>
>>>     @abstractmethod
>>>     def task(self) -> Path:
>>>         pass
Parameters
  • name (str) – Name of the rule which will be used for printing

  • param (Any) – Input parameter of the rule will be used as self.param

  • preconditions (Iterable["Rule"]) – “Boolean Rules” which returns a truthy or falsy value

  • pipe (Optional["Rule"]) – Pipe will be called when the rule is executed successfully

  • children (Iterable["Rule"]) – Children will be executed after the piped rule if there is any

Warning

Preconditions can be used in several ways. The most common way is to run “Boolean Rules” which takes a parameter and returns a truthy or falsy value. In case of a falsy return, the precondition will fail and the rule will not be executed.

If any modification is done by any of the rules which are used as a precondition, those changes will be committed.

property can_proceed

Evaluate if a rule can continue its execution. In case the execution is called with dry_run config option set to true, this method will always return False to make sure not performing any changes. If preconditions are set, those will be evaluated by this method.

Returns

Return with the result of evaluation

Return type

bool

Warning

hammurabi.rules.base.Rule.can_proceed() checks the result of self.preconditions, which means the preconditions are executed. Make sure that you are not doing any modifications within rules used as preconditions, otherwise take extra attention for those rules.

execute(param: Optional[Any] = None)[source]

Execute the rule’s task, its piped and children rules as well.

The execution order of task, piped rule and children rules described in but not by hammurabi.rules.base.Rule.get_rule_chain().

Parameters

param (Optional[Any]) – Input parameter of the rule given by the user

Raise

AssertionError

Returns

None

Note

The input parameter can be optional because of the piped and children rules which are receiving the output of its parent. In this case the user is not able to set the param manually, since it is calculated.

Warning

If self.can_proceed returns False the whole execution will be stopped immediately and AssertionError will be raised.

get_execution_order() → List[Union[Rule, hammurabi.preconditions.base.Precondition]][source]

Same as hammurabi.rules.base.Rule.get_rule_chain() but for the root rule.

get_rule_chain(rule: Rule) → List[Union[Rule, hammurabi.preconditions.base.Precondition]][source]

Get the execution chain of the given rule. The execution order is the following:

Parameters

rule (hammurabi.rules.base.Rule) – The rule which execution chain should be returned

Returns

Returns the list of rules in the order above

Return type

List[Rule]

made_changes
name
param
abstract task() → Any[source]

See the documentation of hammurabi.rules.base.AbstractRule.task()

hammurabi.rules.common module

class hammurabi.rules.common.MultiplePathRule(name: str, paths: Optional[Iterable[pathlib.Path]] = (), **kwargs)[source]

Bases: hammurabi.rules.base.Rule, hammurabi.mixins.GitMixin

Abstract class which extends hammurabi.rules.base.Rule to handle operations on multiple files.

made_changes
name
param
post_task_hook()[source]

Run code after the hammurabi.rules.base.Rule.task() has been performed. To access the parameter passed to the rule, always use self.param for hammurabi.rules.base.Rule.post_task_hook().

Note

This method can be used for execution of git commands like git add, or double checking a modification made.

Warning

This method is not called in dry run mode.

abstract task() → Any[source]

Abstract method representing how a hammurabi.rules.base.Rule.task() must be parameterized. Any difference in the parameters will result in pylint/mypy errors.

For more details please check hammurabi.rules.base.Rule.task().

class hammurabi.rules.common.SinglePathRule(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Bases: hammurabi.rules.base.Rule, hammurabi.mixins.GitMixin

Abstract class which extends hammurabi.rules.base.Rule to handle operations on a single directory.

made_changes
name
param
post_task_hook()[source]

Run code after the hammurabi.rules.base.Rule.task() has been performed. To access the parameter passed to the rule, always use self.param for hammurabi.rules.base.Rule.post_task_hook().

Note

This method can be used for execution of git commands like git add, or double checking a modification made.

Warning

This method is not called in dry run mode.

abstract task() → Any[source]

Abstract method representing how a hammurabi.rules.base.Rule.task() must be parameterized. Any difference in the parameters will result in pylint/mypy errors.

For more details please check hammurabi.rules.base.Rule.task().

hammurabi.rules.dictionaries module

Extend hammurabi.rules.base.Rule to handle parsed content manipulations dictionaries. Standalone these rules are not useful, but they are very handy when files should be manipulated like YAML or JSON which will be parsed as dict.

These rules are intentionally not exported directly through hammurabi as it is done for YAML or JSON rules. The reason, as it is mentioned above, these rules are not standalone rules. Also, it is intentional that these rules are not represented in the documentation’s Rules section.

class hammurabi.rules.dictionaries.DictKeyExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[None, list, dict, str, int, float] = None, **kwargs)[source]

Bases: hammurabi.rules.dictionaries.SinglePathDictParsedRule, abc.ABC

Ensure that the given key exists. If needed, the rule will create a key with the given name, and optionally the specified value. In case the value is set, the value will be assigned to the key. If no value is set, the key will be created with an empty value.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar
>>> from hammurabi.rules.dictionaries import DictKeyExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         DictKeyExists(
>>>             name="Ensure service descriptor has stack",
>>>             path=Path("./service.dictionary"),
>>>             key="stack",
>>>             value="my-awesome-stack",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

Compared to hammurabi.rules.text.LineExists, this rule is NOT able to add a key before or after a target.

made_changes
name
param
task() → pathlib.Path[source]

Ensure that the given key exists in the parsed file. If needed, create the key with the given name, and optionally the specified value.

Returns

Return the input path as an output

Return type

Path

class hammurabi.rules.dictionaries.DictKeyNotExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', loader: Callable[[Any], Dict[str, Any]] = <class 'dict'>, **kwargs)[source]

Bases: hammurabi.rules.dictionaries.SinglePathDictParsedRule, abc.ABC

Ensure that the given key not exists. If needed, the rule will remove a key with the given name, including its value.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar
>>> from hammurabi.rules.dictionaries import DictKeyNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         DictKeyNotExists(
>>>             name="Ensure outdated_key is removed",
>>>             path=Path("./service.dictionary"),
>>>             key="outdated_key",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
task() → pathlib.Path[source]

Ensure that the given key does not exists in the parsed file.

Returns

Return the input path as an output

Return type

Path

class hammurabi.rules.dictionaries.DictKeyRenamed(name: str, path: Optional[pathlib.Path] = None, key: str = '', new_name: str = '', **kwargs)[source]

Bases: hammurabi.rules.dictionaries.SinglePathDictParsedRule, abc.ABC

Ensure that the given key is renamed. In case the key can not be found, a LookupError exception will be raised to stop the execution. The execution must be stopped at this point, because if other rules depending on the rename they will fail otherwise.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar
>>> from hammurabi.rules.dictionaries import DictKeyRenamed
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         DictKeyRenamed(
>>>             name="Ensure service descriptor has dependencies",
>>>             path=Path("./service.dictionary"),
>>>             key="development.depends_on",
>>>             value="dependencies",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
task() → pathlib.Path[source]

Ensure that the given key is renamed. In case the key can not be found, a LookupError exception will be raised to stop the execution. The execution must be stopped at this point, because if other rules depending on the rename they will fail otherwise.

Raises

LookupError raised if no key can be renamed or both the new and old keys are in the config file

Returns

Return the input path as an output

Return type

Path

class hammurabi.rules.dictionaries.DictValueExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[None, list, dict, str, int, float] = None, **kwargs)[source]

Bases: hammurabi.rules.dictionaries.SinglePathDictParsedRule, abc.ABC

Ensure that the given key has the expected value(s). In case the key cannot be found, a LookupError exception will be raised to stop the execution.

This rule is special in the way that the value can be almost anything. For more information please read the warning below.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar
>>> from hammurabi.rules.dictionaries import DictValueExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         DictValueExists(
>>>             name="Ensure service descriptor has dependencies",
>>>             path=Path("./service.dictionary"),
>>>             key="development.dependencies",
>>>             value=["service1", "service2", "service3"],
>>>         ),
>>>         # Or
>>>         DictValueExists(
>>>             name="Add infra alerting to existing alerting components",
>>>             path=Path("./service.dictionary"),
>>>             key="development.alerting",
>>>             value={"infra": "#slack-channel-2"},
>>>         ),
>>>         # Or
>>>         DictValueExists(
>>>             name="Add support info",
>>>             path=Path("./service.dictionary"),
>>>             key="development.supported",
>>>             value=True,
>>>         ),
>>>         # Or even
>>>         DictValueExists(
>>>             name="Make sure that no development branch is set",
>>>             path=Path("./service.dictionary"),
>>>             key="development.branch",
>>>             value=None,
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

Since the value can be anything from None to a list of lists, and rule piping passes the 1st argument (path) to the next rule the value parameter can not be defined in __init__ before the path. Hence the value parameter must have a default value. The default value is set to None, which translates to the following:

Using the DictValueExists rule and not assigning value to value parameter will set the matching key’s value to None` by default in the document.

made_changes
name
param
task() → pathlib.Path[source]

Ensure that the given key has the expected value(s). In case the key cannot be found, a LookupError exception will be raised to stop the execution.

Warning

Since the value can be anything from None to a list of lists, and rule piping passes the 1st argument (path) to the next rule the value parameter can not be defined in __init__ before the path. Hence the value parameter must have a default value. The default value is set to None, which translates to the following:

Using the DictValueExists rule and not assigning value to value parameter will set the matching key’s value to None` by default in the document.

Raises

LookupError raised if no key can be renamed or both the new and old keys are in the config file

Returns

Return the input path as an output

Return type

Path

class hammurabi.rules.dictionaries.DictValueNotExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[str, int, float] = None, **kwargs)[source]

Bases: hammurabi.rules.dictionaries.SinglePathDictParsedRule, abc.ABC

Ensure that the key has no value given. In case the key cannot be found, a LookupError exception will be raised to stop the execution.

Compared to hammurabi.rules.dictionaries.DictValueExists, this rule can only accept simple value for its value parameter. No list, dict, or None can be used.

Based on the key’s value’s type if the value contains (or equals for simple types) value provided in the value parameter the value is:

  1. Set to None (if the key’s value’s type is not a dict or list)

  2. Removed from the list (if the key’s value’s type is a list)

  3. Removed from the dict (if the key’s value’s type is a dict)

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar
>>> from hammurabi.rules.dictionaries import DictValueNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         DictValueNotExists(
>>>             name="Remove decommissioned service from dependencies",
>>>             path=Path("./service.dictionary"),
>>>             key="development.dependencies",
>>>             value="service4",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
task() → pathlib.Path[source]

Ensure that the key has no value given. In case the key cannot be found, a LookupError exception will be raised to stop the execution.

Based on the key’s value’s type if the value contains (or equals for simple types) value provided in the value parameter the value is: 1. Set to None (if the key’s value’s type is not a dict or list) 2. Removed from the list (if the key’s value’s type is a list) 3. Removed from the dict (if the key’s value’s type is a dict)

Returns

Return the input path as an output

Return type

Path

class hammurabi.rules.dictionaries.SinglePathDictParsedRule(name: str, path: Optional[pathlib.Path] = None, key: str = '', loader: Callable[[Any], Dict[str, Any]] = <class 'dict'>, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule, hammurabi.rules.mixins.SelectorMixin

Extend hammurabi.rules.base.Rule to handle parsed content manipulations dictionaries. Standalone this rule is not useful, but it is very handy when files should be manipulated like YAML or JSON which will be parsed as dict. This rule ensures that the implementation will be the same for these rules, so the maintenance cost and effort is reduced.

Although this rule is not that powerful on its own, we would not like to make it an abstract class like hammurabi.rules.base.Rule because it can easily happen that at some point this rule will be a standalone rule.

made_changes
name
param
pre_task_hook() → None[source]

Parse the file for later use.

abstract task() → pathlib.Path[source]

Abstract method representing how a hammurabi.rules.base.Rule.task() must be parameterized. Any difference in the parameters will result in pylint/mypy errors.

For more details please check hammurabi.rules.base.Rule.task().

hammurabi.rules.directories module

Directories module contains directory specific manipulation rules. Please note that those rules which can be used for files and directories are located in other modules like hammurabi.rules.operations or hammurabi.rules.attributes.

class hammurabi.rules.directories.DirectoryEmptied(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Ensure that the given directory’s content is removed. Please note the difference between emptying a directory and recreating it. The latter results in lost ACLs, permissions and modes.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, DirectoryEmptied
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         DirectoryEmptied(
>>>             name="Empty results directory",
>>>             path=Path("./test-results")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
task() → pathlib.Path[source]

Iterate through the entries of the given directory and remove them. If an entry is a file simply remove it, otherwise remove the whole subdirectory and its content.

Returns

Return the input path as an output

Return type

Path

class hammurabi.rules.directories.DirectoryExists(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Ensure that a directory exists. If the directory does not exists, make sure the directory is created.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, DirectoryExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         DirectoryExists(
>>>             name="Create secrets directory",
>>>             path=Path("./secrets")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
task() → pathlib.Path[source]

Create the given directory if not exists.

Returns

Return the input path as an output

Return type

Path

class hammurabi.rules.directories.DirectoryNotExists(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Ensure that the given directory does not exists.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, DirectoryNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         DirectoryNotExists(
>>>             name="Remove unnecessary directory",
>>>             path=Path("./temp")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
post_task_hook()[source]

Remove the given directory from git index.

task() → pathlib.Path[source]

Remove the given directory.

Returns

Return the input path as an output

Return type

Path

hammurabi.rules.files module

Files module contains file specific manipulation rules. Please note that those rules which can be used for files and directories are located in other modules like hammurabi.rules.operations or hammurabi.rules.attributes.

class hammurabi.rules.files.FileEmptied(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Remove the content of the given file, but keep the file. Please note the difference between emptying a file and recreating it. The latter results in lost ACLs, permissions and modes.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, FileEmptied
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         FileEmptied(
>>>             name="Empty the check log file",
>>>             path=Path("/var/log/service/check.log")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
task() → pathlib.Path[source]

Remove the content of the given file. If the file does not exists this rule will create the file without content.

Returns

Return the emptied/created file’s path

Return type

Path

class hammurabi.rules.files.FileExists(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Ensure that a file exists. If the file does not exists, make sure the file is created.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, FileExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         FileExists(
>>>             name="Create service descriptor",
>>>             path=Path("./service.yaml")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
task() → pathlib.Path[source]

If the target file not exists, create the file to make sure we can manipulate it.

Returns

The created/existing file’s path

Return type

Path

class hammurabi.rules.files.FileNotExists(name: str, path: Optional[pathlib.Path] = None, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Ensure that the given file does not exists. If the file exists remove it, otherwise do nothing and return the original path.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, FileNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         FileNotExists(
>>>             name="Remove unused file",
>>>             path=Path("./debug.yaml")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
post_task_hook()[source]

Remove the given file from git index.

task() → pathlib.Path[source]

Remove the given file if exists, otherwise do nothing and return the original path.

Returns

Return the removed file’s path

Return type

Path

class hammurabi.rules.files.FilesExist(name: str, paths: Optional[Iterable[pathlib.Path]] = (), **kwargs)[source]

Bases: hammurabi.rules.common.MultiplePathRule

Ensure that all files exists. If the files does not exists, make sure the files are created.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, FilesExist
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         FilesExist(
>>>             name="Create test files",
>>>             paths=[
>>>                 Path("./file_1"),
>>>                 Path("./file_2"),
>>>                 Path("./file_3"),
>>>             ]
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
task() → Iterable[pathlib.Path][source]

If the target files not exist, create the files to make sure we can manipulate them.

Returns

The created/existing files’ path

Return type

Iterable[Path]

class hammurabi.rules.files.FilesNotExist(name: str, paths: Optional[Iterable[pathlib.Path]] = (), **kwargs)[source]

Bases: hammurabi.rules.common.MultiplePathRule

Ensure that the given files does not exist. If the files exist remove them, otherwise do nothing and return the original paths.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, FilesNotExist
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         FilesNotExist(
>>>             name="Remove several files",
>>>             paths=[
>>>                 Path("./file_1"),
>>>                 Path("./file_2"),
>>>                 Path("./file_3"),
>>>             ]
>>>         ),
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
post_task_hook()[source]

Remove the given files from git index.

task() → Iterable[pathlib.Path][source]

Remove all existing files.

Returns

Return the removed files’ paths

Return type

Iterable[Path]

hammurabi.rules.ini module

hammurabi.rules.json module

This module adds JSON file support. JSON module is an extension for text rules tailor made for .json files. The main difference lies in the way it works. First, the .json file is parsed, then the modifications are made on the already parsed file.

class hammurabi.rules.json.JSONKeyExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[None, list, dict, str, int, float] = None, **kwargs)[source]

Bases: hammurabi.rules.dictionaries.DictKeyExists, hammurabi.rules.json.SingleJSONFileRule

Ensure that the given key exists. If needed, the rule will create a key with the given name, and optionally the specified value. In case the value is set, the value will be assigned to the key. If no value is set, the key will be created with an empty value.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, JSONKeyExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         JSONKeyExists(
>>>             name="Ensure service descriptor has stack",
>>>             path=Path("./service.json"),
>>>             key="stack",
>>>             value="my-awesome-stack",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

Compared to hammurabi.rules.text.LineExists, this rule is NOT able to add a key before or after a target.

made_changes
name
param
class hammurabi.rules.json.JSONKeyNotExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', **kwargs)[source]

Bases: hammurabi.rules.dictionaries.DictKeyNotExists, hammurabi.rules.json.SingleJSONFileRule

Ensure that the given key not exists. If needed, the rule will remove a key with the given name, including its value.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, JSONKeyNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         JSONKeyNotExists(
>>>             name="Ensure outdated_key is removed",
>>>             path=Path("./service.json"),
>>>             key="outdated_key",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
class hammurabi.rules.json.JSONKeyRenamed(name: str, path: Optional[pathlib.Path] = None, key: str = '', new_name: str = '', **kwargs)[source]

Bases: hammurabi.rules.dictionaries.DictKeyRenamed, hammurabi.rules.json.SingleJSONFileRule

Ensure that the given key is renamed. In case the key can not be found, a LookupError exception will be raised to stop the execution. The execution must be stopped at this point, because if other rules depending on the rename they will fail otherwise.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, JSONKeyRenamed
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         JSONKeyRenamed(
>>>             name="Ensure service descriptor has dependencies",
>>>             path=Path("./service.json"),
>>>             key="development.depends_on",
>>>             value="dependencies",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
class hammurabi.rules.json.JSONValueExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[None, list, dict, str, int, float] = None, **kwargs)[source]

Bases: hammurabi.rules.dictionaries.DictValueExists, hammurabi.rules.json.SingleJSONFileRule

Ensure that the given key has the expected value(s). In case the key cannot be found, a LookupError exception will be raised to stop the execution.

This rule is special in the way that the value can be almost anything. For more information please read the warning below.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, JSONValueExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         JSONValueExists(
>>>             name="Ensure service descriptor has dependencies",
>>>             path=Path("./service.json"),
>>>             key="development.dependencies",
>>>             value=["service1", "service2", "service3"],
>>>         ),
>>>         # Or
>>>         JSONValueExists(
>>>             name="Add infra alerting to existing alerting components",
>>>             path=Path("./service.json"),
>>>             key="development.alerting",
>>>             value={"infra": "#slack-channel-2"},
>>>         ),
>>>         # Or
>>>         JSONValueExists(
>>>             name="Add support info",
>>>             path=Path("./service.json"),
>>>             key="development.supported",
>>>             value=True,
>>>         ),
>>>         # Or even
>>>         JSONValueExists(
>>>             name="Make sure that no development branch is set",
>>>             path=Path("./service.json"),
>>>             key="development.branch",
>>>             value=None,
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

Since the value can be anything from None to a list of lists, and rule piping passes the 1st argument (path) to the next rule the value parameter can not be defined in __init__ before the path. Hence the value parameter must have a default value. The default value is set to None, which translates to the following:

Using the JSONValueExists rule and not assigning value to value parameter will set the matching key’s value to None` by default in the document.

made_changes
name
param
class hammurabi.rules.json.JSONValueNotExists(name: str, path: Optional[pathlib.Path] = None, key: str = '', value: Union[str, int, float] = None, **kwargs)[source]

Bases: hammurabi.rules.dictionaries.DictValueNotExists, hammurabi.rules.json.SingleJSONFileRule

Ensure that the key has no value given. In case the key cannot be found, a LookupError exception will be raised to stop the execution.

Compared to hammurabi.rules.json.JSONValueExists, this rule can only accept simple value for its value parameter. No list, dict, or None can be used.

Based on the key’s value’s type if the value contains (or equals for simple types) value provided in the value parameter the value is:

  1. Set to None (if the key’s value’s type is not a dict or list)

  2. Removed from the list (if the key’s value’s type is a list)

  3. Removed from the dict (if the key’s value’s type is a dict)

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, JSONValueNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         JSONValueNotExists(
>>>             name="Remove decommissioned service from dependencies",
>>>             path=Path("./service.json"),
>>>             key="development.dependencies",
>>>             value="service4",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
class hammurabi.rules.json.SingleJSONFileRule(name: str, path: Optional[pathlib.Path] = None, key: str = '', **kwargs)[source]

Bases: hammurabi.rules.dictionaries.SinglePathDictParsedRule

Extend hammurabi.rules.dictionaries.SinglePathDictParsedRule to handle parsed content manipulations on a single JSON file.

made_changes
name
param
abstract task() → pathlib.Path[source]

Abstract method representing how a hammurabi.rules.base.Rule.task() must be parameterized. Any difference in the parameters will result in pylint/mypy errors.

For more details please check hammurabi.rules.base.Rule.task().

hammurabi.rules.mixins module

class hammurabi.rules.mixins.SelectorMixin[source]

Bases: object

This mixin contains the helper function to get a value from dict by a css selector like selector path. (.example.path.to.key)

get_by_selector(data: Any, key_path: Union[str, List[str]]) → Dict[str, Any][source]

Get a key’s value by a selector and traverse the path.

Parameters
  • data (hammurabi.rules.mixins.Any) – The loaded YAML data into dict

  • key_path (Union[str, List[str]]) – Path to the key in a selector format (.path.to.the.key or ["path", "to", "the", "key"])

Returns

Return the value belonging to the selector

Return type

hammurabi.rules.mixins.Any

set_by_selector(loaded_data: Any, key_path: Union[str, List[str]], value: Union[None, list, dict, str, int, float], delete: bool = False) → Any[source]

Set a value by the key selector and traverse the path.

Parameters
  • loaded_data (hammurabi.rules.mixins.Any) – The loaded YAML data into dict

  • key_path (Union[str, List[str]]) – Path to the key in a selector format (.path.to.the.key or ["path", "to", "the", "key"])

  • value (Union[None, list, dict, str, int, float]) – The value set for the key

  • delete (bool) – Indicate if the key should be deleted

Returns

The modified YAML data

Return type

hammurabi.rules.mixins.Any

hammurabi.rules.operations module

Operations module contains common file/directory operation which can be handy when need to move, rename or copy files.

class hammurabi.rules.operations.Copied(name: str, path: Optional[pathlib.Path] = None, destination: Optional[pathlib.Path] = None, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Ensure that the given file or directory is copied to the new path.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, Copied
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         Copied(
>>>             name="Create backup file",
>>>             path=Path("./service.yaml"),
>>>             destination=Path("./service.bkp.yaml")
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
post_task_hook()[source]

Add the destination and not the original path.

task() → pathlib.Path[source]

Copy the given file or directory to a new place.

Returns

Returns the path of the copied file/directory

Return type

Path

class hammurabi.rules.operations.Moved(name: str, path: Optional[pathlib.Path] = None, destination: Optional[pathlib.Path] = None, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Move a file or directory from “A” to “B”.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, Moved
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         Moved(
>>>             name="Move pyproject.toml to its place",
>>>             path=Path("/tmp/generated/pyproject.toml.template"),
>>>             destination=Path("./pyproject.toml"),  # Notice the rename!
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
post_task_hook()[source]

Add both the new and old git objects.

task() → pathlib.Path[source]

Move the given path to the destination. In case the file got a new name when destination is provided, the file/directory will be moved to its new place with its new name.

Returns

Returns the new destination of the file/directory

Return type

Path

class hammurabi.rules.operations.Renamed(name: str, path: Optional[pathlib.Path] = None, new_name: Optional[str] = None, **kwargs)[source]

Bases: hammurabi.rules.operations.Moved

This rule is a shortcut for hammurabi.rules.operations.Moved. Instead of destination path a new name is required.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, Renamed
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         Renamed(
>>>             name="Rename pyproject.toml.bkp",
>>>             path=Path("/tmp/generated/pyproject.toml.bkp"),
>>>             new_name="pyproject.toml",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param

hammurabi.rules.templates module

Templates module contains rules which are capable to create a new file based on a Jinja2 template by rendering it.

class hammurabi.rules.templates.TemplateRendered(name: str, template: Optional[pathlib.Path] = None, destination: Optional[pathlib.Path] = None, context: Optional[Dict[str, Any]] = None, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Render a file from a Jinja2 template. In case the destination file not exists, this rule will create it, otherwise the file will be overridden.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, TemplateRendered
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         TemplateRendered(
>>>             name="Create gunicorn config from template",
>>>             template=Path("/tmp/templates/gunicorn.conf.py"),
>>>             destination=Path("./gunicorn.conf.py"),
>>>             context={
>>>                 "keepalive": 65
>>>             },
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Warning

This rule requires the templating extra to be installed.

made_changes
name
param
post_task_hook()[source]

Add the destination and not the original path.

task() → pathlib.Path[source]

Render a file from a Jinja2 template. In case the destination file not exists, this rule will create it, otherwise the file will be overridden.

Returns

Returns the path of the rendered file

Return type

Path

hammurabi.rules.text module

Text module contains simple but powerful general file content manipulations. Combined with other simple rules like hammurabi.rules.files.FileExists or hammurabi.rules.attributes.ModeChanged almost anything can be achieved. Although any file’s content can be changed using these rules, for common file formats like ini, yaml or json dedicated rules are created.

class hammurabi.rules.text.LineExists(name: str, path: Optional[pathlib.Path] = None, text: Optional[str] = None, target: Optional[str] = None, position: int = 1, respect_indentation: bool = True, ensure_trailing_newline: bool = False, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Make sure that the given file contains the required line. This rule is capable for inserting the expected text before or after the unique target text respecting the indentation of its context.

The default behaviour is to insert the required text exactly after the target line, and respect its indentation. Please note that text``and ``target parameters are required.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, LineExists, IsLineNotExists
>>>
>>> gunicorn_config = Path("./gunicorn.conf.py")
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         LineExists(
>>>             name="Extend gunicorn config",
>>>             path=gunicorn_config,
>>>             text="keepalive = 65",
>>>             target=r"^bind.*",
>>>             preconditions=[
>>>                 IsLineNotExists(path=gunicorn_config, criteria=r"^keepalive.*")
>>>             ]
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Note

The indentation of the target text will be extracted by a simple regular expression. If a more complex regexp is required, please inherit from this class.

made_changes
name
param
task() → pathlib.Path[source]

Make sure that the given file contains the required line. This rule is capable for inserting the expected rule before or after the unique target text respecting the indentation of its context.

Raises

LookupError

Returns

Returns the path of the modified file

Return type

Path

class hammurabi.rules.text.LineNotExists(name: str, path: Optional[pathlib.Path] = None, text: Optional[str] = None, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Make sure that the given file not contains the specified line.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, LineNotExists
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         LineNotExists(
>>>             name="Remove keepalive",
>>>             path=Path("./gunicorn.conf.py"),
>>>             text="keepalive = 65",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)
made_changes
name
param
task() → pathlib.Path[source]

Make sure that the given file not contains the specified line.

Returns

Returns the path of the modified file

Return type

Path

class hammurabi.rules.text.LineReplaced(name: str, path: Optional[pathlib.Path] = None, text: Optional[str] = None, target: Optional[str] = None, respect_indentation: bool = True, **kwargs)[source]

Bases: hammurabi.rules.common.SinglePathRule

Make sure that the given text is replaced in the given file.

The default behaviour is to replace the required text with the exact same indentation that the target line has. This behaviour can be turned off by setting the respect_indentation parameter to False. Please note that text and target parameters are required.

Example usage:

>>> from pathlib import Path
>>> from hammurabi import Law, Pillar, LineReplaced
>>>
>>> example_law = Law(
>>>     name="Name of the law",
>>>     description="Well detailed description what this law does.",
>>>     rules=(
>>>         LineReplaced(
>>>             name="Replace typo using regex",
>>>             path=Path("./gunicorn.conf.py"),
>>>             text="keepalive = 65",
>>>             target=r"^kepalive.*",
>>>         ),
>>>     )
>>> )
>>>
>>> pillar = Pillar()
>>> pillar.register(example_law)

Note

The indentation of the target text will be extracted by a simple regular expression. If a more complex regexp is required, please inherit from this class.

Warning

This rule will replace all the matching lines in the given file. Make sure the given target regular expression is tested before the rule used against production code.

made_changes
name
param
task() → pathlib.Path[source]

Make sure that the given text is replaced in the given file.

Raises

LookupError if we can not decide or can not find what should be replaced

Returns

Returns the path of the modified file

Return type

Path

hammurabi.rules.yaml module

Module contents