Skip to content

Commit

Permalink
refactorchange yamlencode/yamldecode to yaml_encode/yaml_decode and a…
Browse files Browse the repository at this point in the history
…dd same hooks to yaml, toml, and ini providers
  • Loading branch information
robcxyz committed Dec 24, 2023
1 parent 71e243e commit 9939c0a
Show file tree
Hide file tree
Showing 16 changed files with 152 additions and 107 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -e .
pip install -r docs/requirements.txt
pip install -r requirements-docs.txt
pip install -e .[all]
- name: Build docs
Expand Down
50 changes: 50 additions & 0 deletions providers/ini/hooks/ini.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import configparser
import os
from io import StringIO
from typing import Union

from tackle import BaseHook, Field
Expand Down Expand Up @@ -53,3 +54,52 @@ def exec(self) -> Union[dict, str, list]:
output[section][option] = config.get(section, option)

return output


class IniEncodeHook(BaseHook):
"""Hook for converting a dict to an ini encoded string."""

hook_name: str = 'ini_encode'
data: dict | str = Field(
...,
description="Map or renderable string to data to convert to ini string.",
render_by_default=True,
)
args: list = ['data']

def exec(self) -> str:
if not isinstance(self.data, dict):
raise ValueError("INI serialization requires a dictionary input")

config = configparser.ConfigParser()
# https://stackoverflow.com/a/19359720/12642712
config.optionxform = str

for section, values in self.data.items():
config[section] = values

with StringIO() as string_stream:
config.write(string_stream)
return string_stream.getvalue()


class IniDecodeHook(BaseHook):
"""Hook for decoding an ini string to a dict."""

hook_name: str = 'ini_decode'
data: str = Field(
...,
description="Yaml string to convert to dict.",
)
args: list = ['data']

def exec(self) -> dict:
config = configparser.ConfigParser()
# https://stackoverflow.com/a/19359720/12642712
config.optionxform = str
string_stream = StringIO(self.data)
config.read_file(string_stream)

# Convert to a regular dictionary
output = {section: dict(config.items(section)) for section in config.sections()}
return output
25 changes: 25 additions & 0 deletions providers/ini/tests/test_provider_ini.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest

from tackle.main import tackle
from tackle.utils.hooks import get_hook


@pytest.fixture()
Expand All @@ -22,3 +23,27 @@ def test_provider_ini_read(clean_outputs):
assert os.path.exists('output.ini')
assert output['read']['section']['stuff'] == 'things'
assert output['read']['another']['foo'] is None


def test_provider_ini_encode():
Hook = get_hook('ini_encode')
output = Hook(data={"Section1": {"keyA": "valueA", "keyB": "valueB"}}).exec()
assert (
output
== """[Section1]
keyA = valueA
keyB = valueB
"""
)


def test_provider_ini_decode():
Hook = get_hook('ini_decode')
output = Hook(
data="""[Section1]
keyA = valueA
keyB = valueB
"""
).exec()
assert output == {"Section1": {"keyA": "valueA", "keyB": "valueB"}}
36 changes: 16 additions & 20 deletions providers/json/hooks/jsons.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,27 @@ def exec(self) -> Union[dict, str]:
return data


class JsonifyHook(BaseHook):
"""
Hook for reading and writing json. Hook reads from `path` if no `data` field is
provided, otherwise it writes the `data` to `path`.
"""
class JsonEncodeHook(BaseHook):
"""Hook for converting a dict to a JSON encoded string."""

hook_name: str = 'jsonify'
hook_name: str = 'json_encode'
data: Union[dict, list, str] = Field(
...,
description="Map/list or renderable string to a map/list key to write.",
description="Map/list or renderable string to data to convert to JSON string.",
render_by_default=True,
)
args: list = ['data']

def exec(self) -> Union[dict, str]:
def exec(self) -> str:
return json.dumps(self.data)
# if self.path:
# self.path = os.path.abspath(
# os.path.expanduser(os.path.expandvars(self.path))
# )
# # Make path if it does not exist
# if not os.path.exists(os.path.dirname(self.path)) and self.data:
# os.makedirs(os.path.dirname(self.path))
# with open(self.path, 'w') as f:
# json.dump(self.data, f)
# return self.path
# else:
# return json.dumps(self.data)


class JsonDecodeHook(BaseHook):
"""Hook for decoding a JSON string to a dict."""

hook_name: str = 'json_decode'
data: str = Field(..., description="JSON string to convert to dict.")
args: list = ['data']

def exec(self) -> Union[dict, list]:
return json.loads(self.data)
13 changes: 13 additions & 0 deletions providers/json/tests/test_provider_json.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
from tackle import tackle
from tackle.utils.hooks import get_hook


def test_provider_json():
output = tackle('json.yaml')
assert output


def test_provider_json_encode():
Hook = get_hook('json_encode')
output = Hook(data={"Section1": {"keyA": "valueA", "keyB": "valueB"}}).exec()
assert output == '{"Section1": {"keyA": "valueA", "keyB": "valueB"}}'


def test_provider_json_decode():
Hook = get_hook('json_decode')
output = Hook(data='{"Section1": {"keyA": "valueA", "keyB": "valueB"}}').exec()
assert output == {"Section1": {"keyA": "valueA", "keyB": "valueB"}}
13 changes: 2 additions & 11 deletions providers/toml/.tackle.meta.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@

name: TOML Provider

description: Reading and writing toml files.
description: |
Reading toml files. For writing toml you will need a third party provider since this wraps python's native toml library which only supports reads.
examples: []

Expand All @@ -14,13 +15,3 @@ hook_examples:
->: toml
path: path/to/toml/file.toml
compact->: toml path/to/toml/file.toml
- name: Write toml
description: Write a toml file from a key
content: |
stuff:
and: things
expanded:
->: toml
path: path/to/toml/file.toml
contents: "{{ stuff }}"
compact->: toml path/to/toml/file.toml "{{ this }}"
49 changes: 23 additions & 26 deletions providers/toml/hooks/tomls.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
try:
import tomli as toml
except ModuleNotFoundError:
import tomllib as toml

import os
from typing import MutableMapping, Union
from typing import Union

import tomli as toml

from tackle import BaseHook, Field


class TomlHook(BaseHook):
"""Hook for reading toml. Does not support write which needs another provider"""
"""
Hook for reading TOML. Wraps python's native toml library which does not support
writing toml, only reading.
"""

hook_name: str = 'toml'
path: str = Field(..., description="The file path to put read or write to.")
data: Union[dict, list, str] = Field(
None,
description="Map/list or renderable string to a map/list key to write.",
render_by_default=True,
)
args: list = ['path', 'data']

def exec(self) -> Union[MutableMapping, str]:
path: str = Field(..., description="The file path to read or write to.")

args: list = ['path']

def exec(self) -> Union[dict, str]:
self.path = os.path.abspath(os.path.expanduser(os.path.expandvars(self.path)))
# Make path if it does not exist
# if not os.path.exists(os.path.dirname(self.path)) and self.data:
# os.makedirs(os.path.dirname(self.path))

# if self.data:
# with open(self.path, 'w') as f:
# toml.dump(self.data, f)
# return self.path
#
# else:
with open(self.path, 'rb') as f:
data = toml.load(f)
return data


class TomlDecodeHook(BaseHook):
"""Hook for decoding a TOML string to a dict."""

hook_name: str = 'toml_decode'
data: str = Field(..., description="TOML string to convert to dict.")
args: list = ['data']

def exec(self) -> dict:
return toml.loads(self.data)
1 change: 0 additions & 1 deletion providers/toml/requirements.txt

This file was deleted.

16 changes: 5 additions & 11 deletions providers/toml/tests/test_provider_toml.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
from tackle.main import tackle
from tackle.utils.hooks import get_hook


def test_provider_toml_hook_read():
o = tackle('read.yaml', no_input=True)
assert 'owner' in o['read'].keys()


# @pytest.fixture()
# def clean_toml():
# """Remove outputs."""
# yield
# if os.path.exists('writing.toml'):
# os.remove('writing.toml')
#
#
# def test_provider_toml_hook_write(clean_toml):
# tackle('write.yaml', no_input=True)
# assert os.path.exists('writing.toml')
def test_provider_toml_decode():
Hook = get_hook('toml_decode')
output = Hook(data='[Section1]\nkeyA = "valueA"\nkeyB = "valueB"\n').exec()
assert output == {"Section1": {"keyA": "valueA", "keyB": "valueB"}}
12 changes: 0 additions & 12 deletions providers/toml/tests/write.yaml

This file was deleted.

6 changes: 1 addition & 5 deletions providers/yaml/hooks/yaml_in_place.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
"""
TODO: https://github.com/sudoblockio/tackle/issues/100
Should change
"""
import os
import re
from typing import Any, Dict, List, Union
Expand All @@ -15,7 +11,7 @@
class YamlHook(BaseHook):
"""
Hook for modifying a yaml in place (ie read, transform, and write back to the file
in one operation). WIP -> Contributions welcome.
in one operation). WIP -> https://github.com/sudoblockio/tackle/issues/100.
"""

hook_name: str = 'yaml_in_place'
Expand Down
18 changes: 7 additions & 11 deletions providers/yaml/hooks/yamls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from io import StringIO
from typing import Union

from ruyaml import YAML
Expand Down Expand Up @@ -55,30 +56,25 @@ def exec(self) -> Union[dict, str]:
class YamlEncodeHook(BaseHook):
"""Hook for converting a dict to a yaml encoded string."""

hook_name: str = 'yamlencode'
hook_name: str = 'yaml_encode'
data: Union[dict, list, str] = Field(
...,
description="Map/list or renderable string to data to convert to yaml string.",
render_by_default=True,
)
args: list = ['data']

def exec(self) -> Union[dict, str]:
from io import StringIO

def exec(self) -> str:
yaml = YAML()
options = {}
string_stream = StringIO()
yaml.dump(self.data, string_stream, **options)
output_str = string_stream.getvalue()
string_stream.close()
return output_str
with StringIO() as string_stream:
yaml.dump(self.data, string_stream)
return string_stream.getvalue()


class YamlDecodeHook(BaseHook):
"""Hook for decoding a yaml string to a dict."""

hook_name: str = 'yamldecode'
hook_name: str = 'yaml_decode'
data: str = Field(..., description="Yaml string to convert to dict.")
args: list = ['data']

Expand Down
4 changes: 2 additions & 2 deletions providers/yaml/tests/test_provider_system_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ def test_provider_system_hook_yaml_append(clean_outputs):


def test_yaml_yamlify(clean_outputs):
output = tackle('yamlencode.yaml', no_input=True)
output = tackle('yaml_encode.yaml', no_input=True)
assert isinstance(output['out'], str)
assert 'stuff: things' in output['out']


def test_yaml_yamldecode(clean_outputs):
output = tackle('yamldecode.yaml', no_input=True)
output = tackle('yaml_decode.yaml', no_input=True)
assert isinstance(output['out'], dict)
assert output['out']['stuff'] == 'things'
6 changes: 6 additions & 0 deletions providers/yaml/tests/yaml_decode.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
in->: file read.yaml

#out_filter->: "{{in|yamldecode}}"
out_function->: "{{yaml_decode(in)['stuff']}}"

out->: yaml_decode {{in}}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
in->: yaml read.yaml

#out->: "{{in|yamlencode}}"
out->: "{{yamlencode(in)}}"
out->: "{{yaml_encode(in)}}"
6 changes: 0 additions & 6 deletions providers/yaml/tests/yamldecode.yaml

This file was deleted.

0 comments on commit 9939c0a

Please sign in to comment.