fmartingr
/
jeeves
Archived
1
0
Fork 0

Added action: Script (library/script)

This commit is contained in:
Felipe Martín 2019-10-06 11:27:15 +00:00
parent 51d9296206
commit 8e482bd787
26 changed files with 446 additions and 131 deletions

View File

@ -1,32 +1,43 @@
image: python:3 image: python:3
stages:
- test
- deploy
# Change pip's cache directory to be inside the project directory since we can # Change pip's cache directory to be inside the project directory since we can
# only cache local items. # only cache local items.
variables: variables:
POETRY_CACHE_DIR: "$CI_PROJECT_DIR/.cache/poetry"
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
# Pip's cache doesn't store the python packages
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
#
# If you want to also cache the installed packages, you have to install
# them in a virtualenv and cache it as well.
cache: cache:
key: ${CI_COMMIT_REF_SLUG}
paths: paths:
- .cache/pypoetry - .cache/pip
- .cache/poetry
before_script: before_script:
- python -V - python -V
- pip install poetry - pip install poetry
- "poetry config settings.virtualenvs.path ${POETRY_CACHE_DIR}"
- poetry install - poetry install
test: test:
stage: test
script: script:
- poetry run pytest . - poetry run pytest . --cov=jeeves.core --cov-report xml --cov-report term
artifacts:
paths:
- coverage.xml
pages: pages:
stage: deploy
dependencies:
- test
script: script:
- poetry run docs/build.sh - poetry run docs/build.sh
- mv docs/_build/html/ public - mv docs/_build/html/ public
- coverage html --directory public/htmlcov
artifacts: artifacts:
paths: paths:
- public - public

View File

@ -12,9 +12,6 @@ BUILDDIR = _build
help: help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
apidoc:
sphinx-apidoc -o api ../jeeves/core
.PHONY: help Makefile .PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new # Catch-all target: route all unknown targets to Sphinx using the new

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
cd docs || exit 1 cd docs || exit 1
make apidoc sphinx-apidoc -o api ../jeeves/core
make html make html

View File

@ -15,7 +15,7 @@ for example. given a test flow:
"tasks": [ "tasks": [
{ {
"name": "Say hello", "name": "Say hello",
"type": "jeeves.core.tasks.shell:ShellTask", "type": "jeeves.core.actions.shell:ScriptAction",
"parameters": { "parameters": {
"script": "#!/bin/bash\necho HELLO WORLD!" "script": "#!/bin/bash\necho HELLO WORLD!"
} }

View File

@ -0,0 +1 @@
PROVIDED_ACTIONS = ["jeeves.core.actions.shell:ScriptAction"]

View File

@ -3,7 +3,7 @@ import logging
import pydantic import pydantic
class Task: class Action:
id = "" id = ""
class Parameters(pydantic.BaseModel): class Parameters(pydantic.BaseModel):

View File

@ -0,0 +1,69 @@
import os
import tempfile
import subprocess
from typing import Text
import pydantic
from jeeves.core.objects import Result
from .base import Action
class ScriptAction(Action):
"""
Executes the provided script direcly on the system.
The script is written into a temporary file that is then executed.
If no shebang is provided, a default of ``#!/bin/bash -e`` will be used, if
the provided shebang interpreter is not found on the system the action will fail.
.. automethod:: _get_script
"""
DEFAULT_SHEBANG = "#!/bin/bash"
id = "contrib/script"
verbose_name = "Execute script"
class Parameters(pydantic.BaseModel):
"""
+----------------+------+-----------+---------------------------+
| Parameter name | Type | Mandatory | Description |
+================+======+===========+===========================+
| ``script`` | text | yes | The script to be executed |
+----------------+------+-----------+---------------------------+
"""
script: Text
def _get_script(self):
"""
Returns the script defined in the parameters, checking for a shebang.
If no shebang is defined, :any:`ScriptAction.DEFAULT_SHEBANG` with be used.
"""
if not self.parameters.script.startswith("#!"):
return f"{self.DEFAULT_SHEBANG}{os.linesep}{self.parameters.script}"
return self.parameters.script
def execute(self):
script = self._get_script()
# Write the script to a temporary file
script_file = tempfile.NamedTemporaryFile(mode="w", delete=False)
with script_file as handler:
handler.write(script)
# Read/Execute only for owner
os.chmod(script_file.name, mode=0o500)
process = subprocess.run(
script_file.name, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
os.unlink(script_file.name)
return Result(
success=process.returncode == 0, output=process.stdout.decode("utf-8")
)

View File

@ -1,48 +1,48 @@
""" """
This is a collection of Tasks provided for testing purposes only. This is a collection of Actions provided for testing purposes only.
""" """
from typing import Text, Optional from typing import Text, Optional
from jeeves.core.objects import Result from jeeves.core.objects import Result
from .base import Task from .base import Action
class StubSuccessTask(Task): class StubSuccessAction(Action):
id = "stub/success" id = "stub/success"
def execute(self, message=None): def execute(self, message=None):
return Result(success=True) return Result(success=True)
class StubNonSuccessTask(Task): class StubNonSuccessAction(Action):
id = "stub/non-success" id = "stub/non-success"
def execute(self): def execute(self):
return Result(output="error!", success=False) return Result(output="error!", success=False)
class StubUncaughtExceptionTask(Task): class StubUncaughtExceptionAction(Action):
id = "stub/uncaught-exception" id = "stub/uncaught-exception"
def execute(self): def execute(self):
raise Exception("Oh god...") raise Exception("Oh god...")
class StubNoParametersTask(StubSuccessTask): class StubNoParametersAction(StubSuccessAction):
""" """
An empty task that provides no configurable parameters. An empty Action that provides no configurable parameters.
""" """
id = "stub/no-parameters" id = "stub/no-parameters"
class StubParametersTask(StubSuccessTask): class StubParametersAction(StubSuccessAction):
""" """
An empty task that provide two configurable parameters. An empty Action that provide two configurable parameters.
""" """
id = "sub/parameters" id = "sub/parameters"
class Parameters(Task.Parameters): class Parameters(Action.Parameters):
mandatory: Text mandatory: Text
non_mandatory: Optional[Text] = None non_mandatory: Optional[Text] = None

View File

@ -14,13 +14,13 @@ class Executor:
for step in self._execution.steps: for step in self._execution.steps:
yield step yield step
def _get_steps(self, flow): def _get_steps(self, flow: Flow):
for task in flow.tasks: for task in flow.tasks:
yield ExecutionStep(task=task, result=Result()) yield ExecutionStep(task=task, result=Result())
def execute_step(self, step): def execute_step(self, step: ExecutionStep):
try: try:
step.result = step.task.runner.execute() step.result = step.task.action.execute()
except Exception as error: except Exception as error:
# Catch unhandled exceptions, mark the result as unsuccessful # Catch unhandled exceptions, mark the result as unsuccessful
# and append the error as output. # and append the error as output.

View File

@ -3,7 +3,7 @@ from dataclasses import field
from pydantic import BaseModel from pydantic import BaseModel
from jeeves.core.registry import TaskRegistry from jeeves.core.registry import ActionRegistry
class BaseObject(BaseModel): class BaseObject(BaseModel):
@ -21,8 +21,11 @@ class Task(BaseObject):
parameters: Dict[Any, Any] parameters: Dict[Any, Any]
@property @property
def runner(self): def action(self):
return TaskRegistry.get_task_cls(self.type)(parameters=self.parameters) """
Returns the instanced :any:`jeeves.core.actions.base.Action` defined in ``type`` for this ``Task``.
"""
return ActionRegistry.get_action_cls(self.type)(parameters=self.parameters)
class Flow(BaseObject): class Flow(BaseObject):

View File

@ -1,4 +1,4 @@
from typing import Dict, Text from typing import Any, Text, MutableMapping
import toml import toml
@ -6,14 +6,14 @@ from jeeves.core.objects import Flow, BaseObject
class ObjectParser: class ObjectParser:
object = None object: BaseObject = None
@classmethod @classmethod
def from_json(cls, serialized: Text) -> BaseObject: def from_json(cls, serialized: Text) -> BaseObject:
return cls.object.parse_raw(serialized) return cls.object.parse_raw(serialized)
@classmethod @classmethod
def from_dict(cls, serialized: Dict) -> BaseObject: def from_dict(cls, serialized: MutableMapping[str, Any]) -> BaseObject:
return cls.object.parse_obj(serialized) return cls.object.parse_obj(serialized)
@classmethod @classmethod

View File

@ -1,6 +1,8 @@
import logging import logging
from typing import Any, Dict, Text, Type, Optional
from jeeves.core.tasks import PROVIDED_TASKS from jeeves.core.actions import PROVIDED_ACTIONS
from jeeves.core.actions.base import Action
class Singleton(type): class Singleton(type):
@ -14,53 +16,62 @@ class Singleton(type):
return cls._instance return cls._instance
class TaskRegistry(metaclass=Singleton): class ActionRegistry(metaclass=Singleton):
tasks = {} actions: Dict[str, str] = {}
def __init__(self): def __init__(self):
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
@classmethod @classmethod
def autodiscover(cls): def autodiscover(cls):
"""Loads all provided tasks.""" """Loads all provided actions."""
# TODO: Third party plugins # TODO: Third party plugins
registry = cls() registry = cls()
for task_namespace in PROVIDED_TASKS: for action_namespace in PROVIDED_ACTIONS:
registry.register_task(cls.get_task_cls(task_namespace)) registry.register_action(cls.get_action_cls(action_namespace))
@classmethod @classmethod
def register_task(cls, task_cls): def register_action(cls, action_cls):
registry = cls() registry = cls()
# namespace = task_cls.id # namespace = action_cls.id
namespace = f"{task_cls.__module__}:{task_cls.__name__}" namespace = f"{action_cls.__module__}:{action_cls.__name__}"
if namespace in registry.tasks: if namespace in registry.actions:
raise cls.TaskNamespaceConflict( raise cls.ActionNamespaceConflict(
f"Namespace {namespace} is already registered" f"Namespace {namespace} is already registered"
) )
registry.tasks[namespace] = task_cls registry.actions[namespace] = action_cls
@classmethod @classmethod
def get_task_cls(cls, namespace, **kwargs): def get_action_cls(cls, namespace) -> Type[Action]:
# Right now tasks are being imported and returned dinamically because it's easier, """Returns the class for the provided action namespace"""
# but we will need a way of autodiscover all tasks (or register them manually) and # Right now actions are being imported and returned dinamically because it's easier,
# referencing them on a list so the user knows which tasks are available. # but we will need a way of autodiscover all (or register them manually) and
# referencing them on a list so the user knows which actions are available.
modulename, clsname = namespace.split(":") modulename, clsname = namespace.split(":")
try: try:
module = __import__(f"{modulename}", fromlist=(clsname,), level=0) module = __import__(f"{modulename}", fromlist=(clsname,), level=0)
task_cls = getattr(module, clsname) action_cls = getattr(module, clsname)
return task_cls return action_cls
except ModuleNotFoundError as error: except ModuleNotFoundError as error:
raise cls.TaskDoesNotExist(f"Error importing task {namespace}: {error.msg}") raise cls.ActionDoesNotExist(f"Error importing action {namespace}: {error}")
class TaskNamespaceConflict(Exception): @classmethod
pass def get_action(
cls, namespace: Text, parameters: Optional[Dict[Any, Any]] = None
) -> Action:
"""Returns the instanced action for the provided namespace"""
action_cls: Type[Action] = cls.get_action_cls(namespace)
return action_cls(parameters=parameters or {})
class TaskDoesNotExist(Exception): class ActionNamespaceConflict(Exception):
""" """Raised when an action is defined with an already registered namespace"""
Used when there's a problem retrieving a task. More info will be available on the message.
""" pass
class ActionDoesNotExist(Exception):
"""Raised when there's a problem retrieving an action. More info will be available on the message."""
pass pass

View File

@ -1 +0,0 @@
PROVIDED_TASKS = ["jeeves.core.tasks.shell:ShellTask"]

View File

@ -1,26 +0,0 @@
import subprocess
from typing import Text
import pydantic
from jeeves.core.objects import Result
from .base import Task
class ShellTask(Task):
id = "library/shell"
verbose_name = "Execute Shell script"
class Parameters(pydantic.BaseModel):
script: Text
def execute(self):
process = subprocess.run(
self.parameters.script,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
return Result(
success=process.returncode == 0, output=process.stdout.decode("utf-8")
)

View File

@ -1,8 +1,8 @@
import pytest import pytest
from jeeves.core.registry import TaskRegistry from jeeves.core.registry import ActionRegistry
@pytest.fixture(scope="session", autouse=True) @pytest.fixture(scope="session", autouse=True)
def autoregister_tasks(): def autoregister_actions():
TaskRegistry.autodiscover() ActionRegistry.autodiscover()

View File

@ -1,15 +1,12 @@
import factory import factory
from faker import Faker
from jeeves.core.objects import Flow, Task from jeeves.core.objects import Flow, Task
from jeeves.core.registry import TaskRegistry from jeeves.core.registry import ActionRegistry
fake = Faker()
class TaskFactory(factory.Factory): class TaskFactory(factory.Factory):
name = fake.sentence() name = factory.Faker("name")
type = factory.Iterator(TaskRegistry.tasks) type = factory.Iterator(ActionRegistry.actions)
parameters = {"script": "#!/bin/bash\necho test"} parameters = {"script": "#!/bin/bash\necho test"}
class Meta: class Meta:
@ -17,7 +14,7 @@ class TaskFactory(factory.Factory):
class FlowFactory(factory.Factory): class FlowFactory(factory.Factory):
name = fake.name() name = factory.Faker("name")
tasks = factory.LazyFunction(lambda: [TaskFactory() for _ in range(0, 2)]) tasks = factory.LazyFunction(lambda: [TaskFactory() for _ in range(0, 2)])
class Meta: class Meta:

View File

@ -0,0 +1,3 @@
"""
This module contains tests excluve to the tasks provided bundled with Jeeves
"""

View File

@ -0,0 +1,110 @@
import sys
import os.path
import tempfile
from unittest import mock
from subprocess import CompletedProcess
from jeeves.core.objects import Task
MOCK_DEFINITION = {
"type": f"jeeves.core.actions.shell:ScriptAction",
"name": "Say hello world in bash",
"parameters": {"script": "#!/bin/bash\necho Hello World"},
}
def get_completed_process(returncode=0, stdout=b"", **kwargs):
"""Mocks a :class:`subprocess.CompletedProcess` object"""
return CompletedProcess("", returncode=returncode, stdout=stdout, **kwargs)
@mock.patch("subprocess.run", mock.MagicMock(return_value=get_completed_process()))
def test_script_bash_task_ok():
task = Task.parse_obj(MOCK_DEFINITION).action
result = task.execute()
assert result.success
@mock.patch(
"subprocess.run", mock.MagicMock(return_value=get_completed_process(returncode=1))
)
def test_script_bash_task_ko():
task = Task.parse_obj(MOCK_DEFINITION).action
result = task.execute()
assert not result.success
@mock.patch("subprocess.run", mock.MagicMock(return_value=get_completed_process()))
def test_script_no_shebang_defaults_to_bash_ok():
definition = MOCK_DEFINITION.copy()
definition["parameters"]["script"] = definition["parameters"]["script"].strip(
"#!/bin/bash"
)
task = Task.parse_obj(definition).action
assert task._get_script().startswith(task.DEFAULT_SHEBANG)
result = task.execute()
assert result.success
@mock.patch("subprocess.run", mock.MagicMock(return_value=get_completed_process()))
def test_script_with_other_shebang_ok():
py_interpreter = sys.executable
expected_output = "Hello world! (from python)"
definition = MOCK_DEFINITION.copy()
py_script = f"#!{py_interpreter}\nprint('{expected_output}')"
definition["parameters"]["script"] = py_script
task = Task.parse_obj(definition).action
assert task._get_script().startswith(f"#!{py_interpreter}")
result = task.execute()
assert result.success
def test_script_stdout_and_stderr_is_sent_to_result_ok():
"""
..warning:: This test actually calls ``subprocess.run``.
Not 100% sure this one is needed since we are just testing that subprocess.STDOUT works.
I'm leaving it for now since it's important to ensure we get the entire stdout/err in the
:any:`Result` object.
"""
script = "\n".join(
[
f"#!{sys.executable}",
"import sys",
"sys.stdout.write('Hello')",
"sys.stderr.write('World')",
]
)
definition = MOCK_DEFINITION.copy()
definition["parameters"]["script"] = script
task = Task.parse_obj(definition).action
result = task.execute()
assert "Hello" in result.output
assert "World" in result.output
@mock.patch("subprocess.run", mock.MagicMock(return_value=get_completed_process()))
def test_script_task_cleans_tempfile_ok():
"""Make sure that the script is removed from the system after execution"""
task = Task.parse_obj(MOCK_DEFINITION).action
temp = tempfile.NamedTemporaryFile(mode="w", delete=False)
with mock.patch(
"tempfile.NamedTemporaryFile", mock.MagicMock(return_value=temp)
) as mocked:
task.execute()
assert not os.path.isfile(mocked.return_value.name)
@mock.patch("subprocess.run", mock.MagicMock(return_value=get_completed_process()))
def test_script_task_sets_permissions_for_owner_only_ok():
"""Make sure that the script have only read and execution permissions for owner"""
task = Task.parse_obj(MOCK_DEFINITION).action
temp = tempfile.NamedTemporaryFile(mode="w", delete=False)
with mock.patch(
"tempfile.NamedTemporaryFile", mock.MagicMock(return_value=temp)
) as mocked:
with mock.patch("os.unlink"):
task.execute()
stat = os.stat(mocked.return_value.name)
assert oct(stat.st_mode).endswith("500")
os.unlink(mocked.return_value.name)

View File

@ -3,7 +3,7 @@ from .factories import FlowFactory, TaskFactory
def test_executor_success_task_ok(): def test_executor_success_task_ok():
task = TaskFactory(type="jeeves.core.tasks.stub:StubSuccessTask") task = TaskFactory(type="jeeves.core.actions.stub:StubSuccessAction")
flow = FlowFactory(tasks=[task]) flow = FlowFactory(tasks=[task])
runner = Executor(flow) runner = Executor(flow)
runner.start() runner.start()
@ -13,7 +13,7 @@ def test_executor_success_task_ok():
def test_executor_non_success_task_ok(): def test_executor_non_success_task_ok():
task = TaskFactory(type="jeeves.core.tasks.stub:StubNonSuccessTask") task = TaskFactory(type="jeeves.core.actions.stub:StubNonSuccessAction")
flow = FlowFactory(tasks=[task]) flow = FlowFactory(tasks=[task])
runner = Executor(flow) runner = Executor(flow)
runner.start() runner.start()
@ -23,7 +23,7 @@ def test_executor_non_success_task_ok():
def test_executor_uncaught_exception_in_task_ok(): def test_executor_uncaught_exception_in_task_ok():
task = TaskFactory(type="jeeves.core.tasks.stub:StubUncaughtExceptionTask") task = TaskFactory(type="jeeves.core.actions.stub:StubUncaughtExceptionAction")
flow = FlowFactory(tasks=[task]) flow = FlowFactory(tasks=[task])
runner = Executor(flow) runner = Executor(flow)
runner.start() runner.start()

View File

@ -6,8 +6,8 @@ EXPORTED_FLOW = {
"name": "Test", "name": "Test",
"tasks": [ "tasks": [
{ {
"type": "jeeves.core.tasks.stub:StubSuccessfulTask", "type": "jeeves.core.actions.stub:StubSuccessfulAction",
"name": "Test task", "name": "Test Action",
"parameters": {}, "parameters": {},
} }
], ],

View File

@ -1,14 +1,27 @@
import pytest import pytest
from jeeves.core.registry import TaskRegistry from jeeves.core.registry import ActionRegistry
from jeeves.core.tasks.base import Task from jeeves.core.actions.base import Action
from jeeves.core.actions.stub import StubSuccessAction
def test_registry_get_task_cls_ok(): def test_registry_get_action_cls_ok():
task = TaskRegistry.get_task_cls("jeeves.core.tasks.stub:StubSuccessTask") action = ActionRegistry.get_action_cls("jeeves.core.actions.stub:StubSuccessAction")
assert issubclass(task, Task) and not isinstance(task, Task) assert issubclass(action, Action) and not isinstance(action, Action)
def test_registry_get_task_ko(): def test_registry_get_action_cls_ko():
with pytest.raises(TaskRegistry.TaskDoesNotExist): with pytest.raises(ActionRegistry.ActionDoesNotExist):
TaskRegistry.get_task_cls("non.existant:task") ActionRegistry.get_action_cls("non.existant:action")
def test_registry_get_action_ok():
action = ActionRegistry.get_action("jeeves.core.actions.stub:StubSuccessAction")
assert issubclass(action.__class__, Action) and isinstance(action, Action)
def test_registry_namespace_conflict_ok():
ActionRegistry.register_action(StubSuccessAction)
with pytest.raises(ActionRegistry.ActionNamespaceConflict):
ActionRegistry.register_action(StubSuccessAction)

View File

@ -1,22 +1,28 @@
import pytest import pytest
import pydantic import pydantic
from jeeves.core.registry import TaskRegistry from jeeves.core.registry import ActionRegistry
def test_task_with_empty_parameters_ok(): def test_action_with_empty_parameters_ok():
task = TaskRegistry.get_task_cls("jeeves.core.tasks.stub:StubNoParametersTask") action = ActionRegistry.get_action_cls(
task() "jeeves.core.actions.stub:StubNoParametersAction"
task(parameters=None) )
task(parameters={}) action()
action(parameters=None)
action(parameters={})
def test_task_with_parameters_ok(): def test_action_with_parameters_ok():
task = TaskRegistry.get_task_cls("jeeves.core.tasks.stub:StubParametersTask") action = ActionRegistry.get_action_cls(
task(parameters=dict(mandatory="text", non_mandatory="text")) "jeeves.core.actions.stub:StubParametersAction"
)
action(parameters=dict(mandatory="text", non_mandatory="text"))
def test_task_with_parameters_ko(): def test_action_with_parameters_ko():
task = TaskRegistry.get_task_cls("jeeves.core.tasks.stub:StubParametersTask") action = ActionRegistry.get_action_cls(
"jeeves.core.actions.stub:StubParametersAction"
)
with pytest.raises(pydantic.ValidationError): with pytest.raises(pydantic.ValidationError):
task(parameters=dict(thisshould="fail")) action(parameters=dict(thisshould="fail"))

View File

@ -27,7 +27,7 @@
<div class="control is-fullwidth select {% if form.type.errors %}has-icons-right{% endif %}"> <div class="control is-fullwidth select {% if form.type.errors %}has-icons-right{% endif %}">
<select class="input {% if form.type.errors %}is-danger{% endif %}" name="type" placeholder="Task type"> <select class="input {% if form.type.errors %}is-danger{% endif %}" name="type" placeholder="Task type">
{% for task_type in task_types %} {% for task_type in task_types %}
<option value="jeeves.tasks.shell:ShellTask" {% if task_type == form.type.value %}selected{% endif %}>{{ task_type }}</option> <option value="jeeves.tasks.shell:ScriptTask" {% if task_type == form.type.value %}selected{% endif %}>{{ task_type }}</option>
{% endfor %} {% endfor %}
</select> </select>
{% if form.type.errors %} {% if form.type.errors %}

137
poetry.lock generated
View File

@ -34,6 +34,23 @@ version = "1.3.0"
[package.dependencies] [package.dependencies]
pyyaml = "*" pyyaml = "*"
[[package]]
category = "dev"
description = "An abstract syntax tree for Python with inference support."
name = "astroid"
optional = false
python-versions = ">=3.5.*"
version = "2.3.1"
[package.dependencies]
lazy-object-proxy = ">=1.4.0,<1.5.0"
six = "1.12"
wrapt = ">=1.11.0,<1.12.0"
[package.dependencies.typed-ast]
python = "<3.8"
version = ">=1.4.0,<1.5"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Atomic file writes." description = "Atomic file writes."
@ -128,6 +145,14 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.4.1" version = "0.4.1"
[[package]]
category = "dev"
description = "Code coverage measurement for Python"
name = "coverage"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
version = "4.5.4"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Better living through Python with decorators" description = "Better living through Python with decorators"
@ -362,6 +387,14 @@ version = "2.10.1"
[package.dependencies] [package.dependencies]
MarkupSafe = ">=0.23" MarkupSafe = ">=0.23"
[[package]]
category = "dev"
description = "A fast and thorough lazy object proxy."
name = "lazy-object-proxy"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.4.2"
[[package]] [[package]]
category = "main" category = "main"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
@ -386,6 +419,27 @@ optional = false
python-versions = ">=3.4" python-versions = ">=3.4"
version = "7.2.0" version = "7.2.0"
[[package]]
category = "dev"
description = "Optional static typing for Python"
name = "mypy"
optional = false
python-versions = ">=3.5"
version = "0.730"
[package.dependencies]
mypy-extensions = ">=0.4.0,<0.5.0"
typed-ast = ">=1.4.0,<1.5.0"
typing-extensions = ">=3.7.4"
[[package]]
category = "dev"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
name = "mypy-extensions"
optional = false
python-versions = "*"
version = "0.4.2"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Node.js virtual environment builder" description = "Node.js virtual environment builder"
@ -546,6 +600,20 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.4.2" version = "2.4.2"
[[package]]
category = "dev"
description = "python code static checker"
name = "pylint"
optional = false
python-versions = ">=3.5.*"
version = "2.4.2"
[package.dependencies]
astroid = ">=2.3.0,<2.4"
colorama = "*"
isort = ">=4.2.5,<5"
mccabe = ">=0.6,<0.7"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Python parsing module" description = "Python parsing module"
@ -576,6 +644,18 @@ wcwidth = "*"
python = "<3.8" python = "<3.8"
version = ">=0.12" version = ">=0.12"
[[package]]
category = "dev"
description = "Pytest plugin for measuring coverage."
name = "pytest-cov"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.7.1"
[package.dependencies]
coverage = ">=4.4"
pytest = ">=3.6"
[[package]] [[package]]
category = "dev" category = "dev"
description = "A Django plugin for pytest." description = "A Django plugin for pytest."
@ -710,14 +790,6 @@ sphinxcontrib-jsmath = "*"
sphinxcontrib-qthelp = "*" sphinxcontrib-qthelp = "*"
sphinxcontrib-serializinghtml = "*" sphinxcontrib-serializinghtml = "*"
[[package]]
category = "dev"
description = "GLPI theme for Sphinx"
name = "sphinx-glpi-theme"
optional = false
python-versions = "*"
version = "0.3"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Read the Docs theme for Sphinx" description = "Read the Docs theme for Sphinx"
@ -827,6 +899,33 @@ decorator = "*"
ipython-genutils = "*" ipython-genutils = "*"
six = "*" six = "*"
[[package]]
category = "dev"
description = "a fork of Python 2 and 3 ast modules with type comment support"
name = "typed-ast"
optional = false
python-versions = "*"
version = "1.4.0"
[[package]]
category = "dev"
description = "Type Hints for Python"
name = "typing"
optional = false
python-versions = "*"
version = "3.7.4.1"
[[package]]
category = "dev"
description = "Backported and Experimental Type Hints for Python 3.5+"
name = "typing-extensions"
optional = false
python-versions = "*"
version = "3.7.4"
[package.dependencies]
typing = ">=3.7.4"
[[package]] [[package]]
category = "dev" category = "dev"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
@ -851,6 +950,14 @@ optional = false
python-versions = "*" python-versions = "*"
version = "0.1.7" version = "0.1.7"
[[package]]
category = "dev"
description = "Module for decorators, wrappers and monkey patching."
name = "wrapt"
optional = false
python-versions = "*"
version = "1.11.2"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Backport of pathlib-compatible object wrapper for zip files" description = "Backport of pathlib-compatible object wrapper for zip files"
@ -863,7 +970,7 @@ version = "0.6.0"
more-itertools = "*" more-itertools = "*"
[metadata] [metadata]
content-hash = "50511f4767d8112f485d30bc7a85814ec7ae997416532bcf90ef5e3cc73503c0" content-hash = "f4d87d4fe9e9f6dd2357037fca409b0942685ba69ab5f7e8674f02d462dc21d9"
python-versions = "^3.7" python-versions = "^3.7"
[metadata.hashes] [metadata.hashes]
@ -871,6 +978,7 @@ alabaster = ["446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"]
appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"] appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"]
"aspy.yaml" = ["463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc", "e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"] "aspy.yaml" = ["463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc", "e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"]
astroid = ["98c665ad84d10b18318c5ab7c3d203fe11714cbad2a4aef4f44651f415392754", "b7546ffdedbf7abcfbff93cd1de9e9980b1ef744852689decc5aeada324238c6"]
atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"]
attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"]
babel = ["af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", "e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"] babel = ["af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", "e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"]
@ -881,6 +989,7 @@ cfgv = ["edb387943b665bf9c434f717bf630fa78aecd53d5900d2e05da6ad6048553144", "fbd
chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"]
coverage = ["08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", "0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", "141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", "19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", "23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", "245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", "331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", "386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", "3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", "60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", "63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", "6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", "6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", "7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", "826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", "93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", "9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", "af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", "bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", "bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", "c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", "dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", "df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", "e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", "e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", "e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", "eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", "eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", "ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", "efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", "fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", "ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"]
decorator = ["86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", "f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"] decorator = ["86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", "f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"]
django = ["148a4a2d1a85b23883b0a4e99ab7718f518a83675e4485e44dc0c1d36988c5fa", "deb70aa038e59b58593673b15e9a711d1e5ccd941b5973b30750d5d026abfd56"] django = ["148a4a2d1a85b23883b0a4e99ab7718f518a83675e4485e44dc0c1d36988c5fa", "deb70aa038e59b58593673b15e9a711d1e5ccd941b5973b30750d5d026abfd56"]
django-dramatiq = ["492ef6e216ca6ce1c5b09447b2be554ad299207d625649f2e934d28f96af4db9", "5f4f11027413cb3043254a5fa91f0e79db7482a3a1e4b80e55fcdb3f7206689e"] django-dramatiq = ["492ef6e216ca6ce1c5b09447b2be554ad299207d625649f2e934d28f96af4db9", "5f4f11027413cb3043254a5fa91f0e79db7482a3a1e4b80e55fcdb3f7206689e"]
@ -902,9 +1011,12 @@ ipython-genutils = ["72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c6
isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"] isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"]
jedi = ["786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", "ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e"] jedi = ["786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", "ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e"]
jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"] jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"]
lazy-object-proxy = ["02b260c8deb80db09325b99edf62ae344ce9bc64d68b7a634410b8e9a568edbf", "18f9c401083a4ba6e162355873f906315332ea7035803d0fd8166051e3d402e3", "1f2c6209a8917c525c1e2b55a716135ca4658a3042b5122d4e3413a4030c26ce", "2f06d97f0ca0f414f6b707c974aaf8829c2292c1c497642f63824119d770226f", "616c94f8176808f4018b39f9638080ed86f96b55370b5a9463b2ee5c926f6c5f", "63b91e30ef47ef68a30f0c3c278fbfe9822319c15f34b7538a829515b84ca2a0", "77b454f03860b844f758c5d5c6e5f18d27de899a3db367f4af06bec2e6013a8e", "83fe27ba321e4cfac466178606147d3c0aa18e8087507caec78ed5a966a64905", "84742532d39f72df959d237912344d8a1764c2d03fe58beba96a87bfa11a76d8", "874ebf3caaf55a020aeb08acead813baf5a305927a71ce88c9377970fe7ad3c2", "9f5caf2c7436d44f3cec97c2fa7791f8a675170badbfa86e1992ca1b84c37009", "a0c8758d01fcdfe7ae8e4b4017b13552efa7f1197dd7358dc9da0576f9d0328a", "a4def978d9d28cda2d960c279318d46b327632686d82b4917516c36d4c274512", "ad4f4be843dace866af5fc142509e9b9817ca0c59342fdb176ab6ad552c927f5", "ae33dd198f772f714420c5ab698ff05ff900150486c648d29951e9c70694338e", "b4a2b782b8a8c5522ad35c93e04d60e2ba7f7dcb9271ec8e8c3e08239be6c7b4", "c462eb33f6abca3b34cdedbe84d761f31a60b814e173b98ede3c81bb48967c4f", "fd135b8d35dfdcdb984828c84d695937e58cc5f49e1c854eb311c4d6aa03f4f1"]
markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"]
mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"]
more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"] more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"]
mypy = ["1d98fd818ad3128a5408148c9e4a5edce6ed6b58cc314283e631dd5d9216527b", "22ee018e8fc212fe601aba65d3699689dd29a26410ef0d2cc1943de7bec7e3ac", "3a24f80776edc706ec8d05329e854d5b9e464cd332e25cde10c8da2da0a0db6c", "42a78944e80770f21609f504ca6c8173f7768043205b5ac51c9144e057dcf879", "4b2b20106973548975f0c0b1112eceb4d77ed0cafe0a231a1318f3b3a22fc795", "591a9625b4d285f3ba69f541c84c0ad9e7bffa7794da3fa0585ef13cf95cb021", "5b4b70da3d8bae73b908a90bb2c387b977e59d484d22c604a2131f6f4397c1a3", "84edda1ffeda0941b2ab38ecf49302326df79947fa33d98cdcfbf8ca9cf0bb23", "b2b83d29babd61b876ae375786960a5374bba0e4aba3c293328ca6ca5dc448dd", "cc4502f84c37223a1a5ab700649b5ab1b5e4d2bf2d426907161f20672a21930b", "e29e24dd6e7f39f200a5bb55dcaa645d38a397dd5a6674f6042ef02df5795046"]
mypy-extensions = ["a161e3b917053de87dbe469987e173e49fb454eca10ef28b48b384538cc11458"]
nodeenv = ["ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a"] nodeenv = ["ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a"]
packaging = ["28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"] packaging = ["28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"]
parso = ["63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", "666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c"] parso = ["63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", "666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c"]
@ -921,8 +1033,10 @@ pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56
pydantic = ["18598557f0d9ab46173045910ed50458c4fb4d16153c23346b504d7a5b679f77", "6a9335c968e13295430a208487e74d69fef40168b72dea8d975765d14e2da660", "6f5eb88fe4c21380aa064b7d249763fc6306f0b001d7e7d52d80866d1afc9ed3", "bc6c6a78647d7a65a493e1107572d993f26a652c49183201e3c7d23924bf7311", "e1a63b4e6bf8820833cb6fa239ffbe8eec57ccdd7d66359eff20e68a83c1deeb", "ede2d65ae33788d4e26e12b330b4a32c53cb14131c65bca3a59f037c73f6ee7a"] pydantic = ["18598557f0d9ab46173045910ed50458c4fb4d16153c23346b504d7a5b679f77", "6a9335c968e13295430a208487e74d69fef40168b72dea8d975765d14e2da660", "6f5eb88fe4c21380aa064b7d249763fc6306f0b001d7e7d52d80866d1afc9ed3", "bc6c6a78647d7a65a493e1107572d993f26a652c49183201e3c7d23924bf7311", "e1a63b4e6bf8820833cb6fa239ffbe8eec57ccdd7d66359eff20e68a83c1deeb", "ede2d65ae33788d4e26e12b330b4a32c53cb14131c65bca3a59f037c73f6ee7a"]
pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"]
pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"]
pylint = ["7edbae11476c2182708063ac387a8f97c760d9cfe36a5ede0ca996f90cf346c8", "844ce067788028c1a35086a5c66bc5e599ddd851841c41d6ee1623b36774d9f2"]
pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"] pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"]
pytest = ["13c1c9b22127a77fc684eee24791efafcef343335d855e3573791c68588fe1a5", "d8ba7be9466f55ef96ba203fc0f90d0cf212f2f927e69186e1353e30bc7f62e5"] pytest = ["13c1c9b22127a77fc684eee24791efafcef343335d855e3573791c68588fe1a5", "d8ba7be9466f55ef96ba203fc0f90d0cf212f2f927e69186e1353e30bc7f62e5"]
pytest-cov = ["2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", "e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a"]
pytest-django = ["264fb4c506db5d48a6364c311a0b00b7b48a52715bad8839b2d8bee9b99ed6bb", "4adfe5fb3ed47f0ba55506dd3daf688b1f74d5e69148c10ad2dd2f79f40c0d62"] pytest-django = ["264fb4c506db5d48a6364c311a0b00b7b48a52715bad8839b2d8bee9b99ed6bb", "4adfe5fb3ed47f0ba55506dd3daf688b1f74d5e69148c10ad2dd2f79f40c0d62"]
pytest-tldr = ["008b7d53cabbce9d49ee7a92754ed4adafc056bc49ae01b257c2ffb1f5ce2408", "dca4a464a002f389677f4c42f5b9c815aae43219d73ecfe6b7fffe2d190e38eb"] pytest-tldr = ["008b7d53cabbce9d49ee7a92754ed4adafc056bc49ae01b257c2ffb1f5ce2408", "dca4a464a002f389677f4c42f5b9c815aae43219d73ecfe6b7fffe2d190e38eb"]
python-dateutil = ["7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"] python-dateutil = ["7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"]
@ -935,7 +1049,6 @@ rope = ["6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969", "c5c
six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
snowballstemmer = ["209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", "df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"] snowballstemmer = ["209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", "df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"]
sphinx = ["0d586b0f8c2fc3cc6559c5e8fd6124628110514fda0e5d7c82e682d749d2e845", "839a3ed6f6b092bb60f492024489cc9e6991360fb9f52ed6361acd510d261069"] sphinx = ["0d586b0f8c2fc3cc6559c5e8fd6124628110514fda0e5d7c82e682d749d2e845", "839a3ed6f6b092bb60f492024489cc9e6991360fb9f52ed6361acd510d261069"]
sphinx-glpi-theme = ["5acf9a8bd942e8478d987c6da5caccc1e9459d7cbaafbf95eca4f2178b1de29c", "896c8630af2f2995f3b384ea598e891b7dea092b592a8708e6db7a179be26060"]
sphinx-rtd-theme = ["00cf895504a7895ee433807c62094cf1e95f065843bf3acd17037c3e9a2becd4", "728607e34d60456d736cc7991fd236afb828b21b82f956c5ea75f94c8414040a"] sphinx-rtd-theme = ["00cf895504a7895ee433807c62094cf1e95f065843bf3acd17037c3e9a2becd4", "728607e34d60456d736cc7991fd236afb828b21b82f956c5ea75f94c8414040a"]
sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"] sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"]
sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"] sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"]
@ -948,7 +1061,11 @@ stevedore = ["01d9f4beecf0fbd070ddb18e5efb10567801ba7ef3ddab0074f54e3cd4e91730",
text-unidecode = ["1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", "bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"] text-unidecode = ["1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", "bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"]
toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
traitlets = ["9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", "c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"] traitlets = ["9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", "c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"]
typed-ast = ["18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"]
typing = ["91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23", "c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36", "f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714"]
typing-extensions = ["2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", "b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", "d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed"]
urllib3 = ["3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", "9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"] urllib3 = ["3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", "9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"]
virtualenv = ["680af46846662bb38c5504b78bad9ed9e4f3ba2d54f54ba42494fdf94337fe30", "f78d81b62d3147396ac33fc9d77579ddc42cc2a98dd9ea38886f616b33bc7fb2"] virtualenv = ["680af46846662bb38c5504b78bad9ed9e4f3ba2d54f54ba42494fdf94337fe30", "f78d81b62d3147396ac33fc9d77579ddc42cc2a98dd9ea38886f616b33bc7fb2"]
wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"]
wrapt = ["565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"]
zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"] zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"]

View File

@ -33,6 +33,10 @@ factory-boy = "^2.12"
sphinx = "^2.2" sphinx = "^2.2"
doc8 = "^0.8.0" doc8 = "^0.8.0"
sphinx-rtd-theme = "^0.4.3" sphinx-rtd-theme = "^0.4.3"
coverage = "^4.5"
pytest-cov = "^2.7"
pylint = "^2.4"
mypy = "^0.730.0"
[build-system] [build-system]
requires = ["poetry>=0.12"] requires = ["poetry>=0.12"]

View File

@ -11,6 +11,6 @@ include_trailing_comma = True
length_sort = 1 length_sort = 1
lines_between_types = 0 lines_between_types = 0
line_length = 88 line_length = 88
known_third_party = click,django,dramatiq,environ,factory,faker,pydantic,pytest,toml known_third_party = click,django,dramatiq,environ,factory,pydantic,pytest,toml
sections = FUTURE, STDLIB, DJANGO, THIRDPARTY, FIRSTPARTY, LOCALFOLDER sections = FUTURE, STDLIB, DJANGO, THIRDPARTY, FIRSTPARTY, LOCALFOLDER
no_lines_before = LOCALFOLDER no_lines_before = LOCALFOLDER