Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhancement: add new backend to load and dump python code as data #161

Merged
merged 5 commits into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/api/anyconfig.backend.pickle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
:special-members:
:private-members:
:undoc-members:
:show-inherita
:show-inheritance:

.. toctree::

Expand Down
9 changes: 9 additions & 0 deletions docs/api/anyconfig.backend.python.builtin.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
:mod:`anyconfig.backend.python.builtin`
==============================================

.. automodule:: anyconfig.backend.python.builtin
:members:
:special-members:
:private-members:
:undoc-members:
:show-inheritance:
9 changes: 9 additions & 0 deletions docs/api/anyconfig.backend.python.dumper.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
:mod:`anyconfig.backend.python.dumper`
==============================================

.. automodule:: anyconfig.backend.python.dumper
:members:
:special-members:
:private-members:
:undoc-members:
:show-inheritance:
9 changes: 9 additions & 0 deletions docs/api/anyconfig.backend.python.loader.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
:mod:`anyconfig.backend.python.loader`
==============================================

.. automodule:: anyconfig.backend.python.loader
:members:
:special-members:
:private-members:
:undoc-members:
:show-inheritance:
15 changes: 15 additions & 0 deletions docs/api/anyconfig.backend.python.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
:mod:`anyconfig.backend.python`
=====================================

.. automodule:: anyconfig.backend.python
:members:
:special-members:
:private-members:
:undoc-members:
:show-inheritance:

.. toctree::

anyconfig.backend.python.builtin
anyconfig.backend.python.loader
anyconfig.backend.python.dumper
2 changes: 1 addition & 1 deletion docs/api/anyconfig.backend.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
anyconfig.backend.json
anyconfig.backend.pickle
anyconfig.backend.properties
anyconfig.backend.python
anyconfig.backend.sh
anyconfig.backend.toml
anyconfig.backend.yaml
anyconfig.backend.xml

5 changes: 3 additions & 2 deletions src/anyconfig/backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
json,
pickle,
properties,
python,
sh,
toml,
yaml,
Expand All @@ -23,8 +24,8 @@


PARSERS: ParserClssT = [
*ini.PARSERS, *pickle.PARSERS, *properties.PARSERS,
*sh.PARSERS, *xml.PARSERS, *json.PARSERS
*ini.PARSERS, *json.PARSERS, *pickle.PARSERS, *properties.PARSERS,
*python.PARSERS, *sh.PARSERS, *xml.PARSERS,
]


Expand Down
6 changes: 3 additions & 3 deletions src/anyconfig/backend/base/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#
# Copyright (C) 2021 Satoru SATOH <satoru.satoh@gmail.com>
# Copyright (C) 2021 - 2024 Satoru SATOH <satoru.satoh @ gmail.com>
# SPDX-License-Identifier: MIT
#
"""Backend basic classes, functions and constants."""
import typing

from .compat import BinaryFilesMixin
from .datatypes import (
GenContainerT, OptionsT,
GenContainerT, OptionsT, InDataExT, OutDataExT, IoiT
)
from .dumpers import (
ToStringDumperMixin, ToStreamDumperMixin, BinaryDumperMixin
Expand All @@ -32,7 +32,7 @@

__all__ = [
'BinaryFilesMixin',
'GenContainerT', 'OptionsT',
'GenContainerT', 'OptionsT', 'InDataExT', 'OutDataExT', 'IoiT',
'ToStringDumperMixin', 'ToStreamDumperMixin', 'BinaryDumperMixin',
'LoaderMixin',
'FromStringLoaderMixin', 'FromStreamLoaderMixin', 'BinaryLoaderMixin',
Expand Down
17 changes: 17 additions & 0 deletions src/anyconfig/backend/python/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Copyright (C) 2023, 2024 Satoru SATOH <satoru.satoh @ gmail.com>
# SPDX-License-Identifier: MIT
#
"""Backend modules to load and dump python code holding data.

- python.builtin: builtin parser [default]

Changelog:

.. versionadded:: 0.14.0
"""
from . import builtin
from ..base import ParserClssT


PARSERS: ParserClssT = [builtin.Parser]
43 changes: 43 additions & 0 deletions src/anyconfig/backend/python/builtin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#
# Copyright (C) 2023, 2024 Satoru SATOH <satoru.satoh @ gmail.com>
# SPDX-License-Identifier: MIT
#
r"""A backend module to load and dump python code conntains data.

- Format to support: Python code
- Requirements: None (built-in)
- Development Status :: 3 - Alpha
- Limitations:

- This module will load data as it is. In other words, some options like
ac_dict and ac_ordered do not affetct at all.
- Some primitive data expressions support only
- It might have some vulnerabilities for DoS and aribitary code execution
(ACE) attacks
- It's very simple and should be difficult to dump complex data using this

- Special options:

- allow_exec: bool [False]: Allow execution of the input python code on load
input files. It may cause vulnerabilities for aribitary code execution
(ACE) attacks. So you should set True only if you sure inputs are safe from
reliable sources.

Changelog:

.. versionadded:: 0.14.0

- Added builtin data loader from python code
"""
from .. import base
from . import (
loader, dumper
)


class Parser(base.Parser, loader.Loader, dumper.Dumper):
"""Parser for python code files."""

_cid = 'python.builtin'
_type = 'python'
_extensions = ['py']
39 changes: 39 additions & 0 deletions src/anyconfig/backend/python/dumper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#
# Copyright (C) 2024 Satoru SATOH <satoru.satoh @ gmail.com>
# SPDX-License-Identifier: MIT
#
r"""A backend module to dump python code conntains data.

- Format to support: Python code
- Requirements: None (built-in)
- Development Status :: 3 - Alpha
- Limitations:

- This implementaton is very simple and it should be difficult to dump
complex data using this.

- Special options: None

Changelog:

.. versionadded:: 0.14.0

- Added builtin data dumper from python code
"""
from ..base import (
InDataExT, ToStringDumperMixin
)


class Dumper(ToStringDumperMixin):
"""Dumper for objects as python code."""

def dump_to_string(self, cnf: InDataExT, **kwargs) -> str:
"""Dump config 'cnf' to a string.

:param cnf: Configuration data to dump
:param kwargs: optional keyword parameters to be sanitized :: dict

:return: string represents the configuration
"""
return repr(cnf)
115 changes: 115 additions & 0 deletions src/anyconfig/backend/python/loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#
# Copyright (C) 2023, 2024 Satoru SATOH <satoru.satoh @ gmail.com>
# SPDX-License-Identifier: MIT
#
r"""A backend module to load python code conntains data.

- Format to support: Python code
- Requirements: None (built-in)
- Development Status :: 3 - Alpha
- Limitations:

- This module will load data as it is. In other words, some options like
ac_dict and ac_ordered do not affetct at all.
- Some primitive data expressions support only
- It might have some vulnerabilities for DoS and aribitary code execution
(ACE) attacks

- Special options:

- allow_exec: bool [False]: Allow execution of the input python code on load
input files. It may cause vulnerabilities for aribitary code execution
(ACE) attacks. So you should set True only if you sure inputs are safe from
reliable sources.

Changelog:

.. versionadded:: 0.14.0

- Added builtin data loader from python code
"""
import pathlib
import tempfile
import typing

from ... import ioinfo
from ..base import (
IoiT, InDataExT, LoaderMixin
)

from . import utils


def load_from_temp_file(
content: str, **opts: typing.Dict[str, typing.Any]
) -> InDataExT:
"""Dump `content` to tempoary file and load from it.

:param content: A str to load data from
"""
with tempfile.TemporaryDirectory() as tmpdir:
path = pathlib.Path(tmpdir) / "mod.py"
path.write_text(content, encoding='utf-8')

return utils.load_from_path(
path, allow_exec=opts.get("allow_exec", False)
)


class Loader(LoaderMixin):
"""Loader for python code files."""

_allow_primitives: bool = True
_load_opts = ["allow_exec"]

def loads(self, content: str, **options) -> InDataExT:
"""Load config from given string 'content' after some checks.

:param content: Config file content
:param options:
It will be ignored at all except for 'allow_exec' opion to allow
execution of the code

:return:
dict or dict-like object holding input data or primitives
"""
allow_exec = options.get("allow_exec", False)

if allow_exec and content and utils.DATA_VAR_NAME in content:
return load_from_temp_file(content, allow_exec=allow_exec)

return utils.load_literal_data_from_string(content)

def load(self, ioi: IoiT, ac_ignore_missing: bool = False,
**options) -> InDataExT:
"""Load config from ``ioi``.

:param ioi:
'anyconfig.ioinfo.IOInfo' namedtuple object provides various info
of input object to load data from

:param ac_ignore_missing:
Ignore and just return empty result if given `ioi` object does not
exist in actual.
:param options:
options will be passed to backend specific loading functions.
please note that options have to be sanitized w/
:func:`anyconfig.utils.filter_options` later to filter out options
not in _load_opts.

:return: dict or dict-like object holding configurations
"""
allow_exec = options.get("allow_exec", False)

if not ioi:
return {}

Check warning on line 105 in src/anyconfig/backend/python/loader.py

View check run for this annotation

Codecov / codecov/patch

src/anyconfig/backend/python/loader.py#L105

Added line #L105 was not covered by tests

if ioinfo.is_stream(ioi):
return load_from_temp_file(

Check warning on line 108 in src/anyconfig/backend/python/loader.py

View check run for this annotation

Codecov / codecov/patch

src/anyconfig/backend/python/loader.py#L108

Added line #L108 was not covered by tests
typing.cast(typing.IO, ioi.src).read(),
allow_exec=allow_exec
)

return utils.load_from_path(
pathlib.Path(ioi.path), allow_exec=allow_exec
)
Loading