Skip to content
Open
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
5 changes: 5 additions & 0 deletions src/viur/core/bones/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
UniqueValue,
)
from .boolean import BooleanBone
from .code import CodeBone, JinjaBone, LogicsBone, PythonBone
from .captcha import CaptchaBone
from .color import ColorBone
from .credential import CredentialBone
Expand Down Expand Up @@ -51,6 +52,10 @@
"BaseBone",
"BooleanBone",
"CaptchaBone",
"CodeBone",
"JinjaBone",
"LogicsBone",
"PythonBone",
"CloneBehavior",
"CloneStrategy",
"ColorBone",
Expand Down
95 changes: 95 additions & 0 deletions src/viur/core/bones/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import ast
import jinja2
import logics
from viur.core.bones.raw import RawBone


class CodeBone(RawBone):
"""
Stores source code with optional language-specific syntax validation.

The ``syntax`` parameter sets the type suffix used by the frontend for syntax
highlighting, e.g. ``syntax="python"`` yields ``type = "raw.code.python"``.
``type_suffix`` can override this to an arbitrary suffix.

Neither ``multiple`` nor ``languages`` are supported.
Setting ``validate=False`` disables any syntax validation in subclasses.
"""

type = "raw.code"

def __init__(
self,
*,
indexed: bool = False,
languages=None,
multiple: bool = False,
syntax: str | None = None,
type_suffix: str = "",
validate: bool = True,
**kwargs,
):
assert not multiple, "CodeBone does not support multiple values"
assert not languages, "CodeBone does not support language variants"
self.syntax = syntax
self.validate = validate
if self.syntax:
type_suffix = type_suffix or syntax
super().__init__(indexed=indexed, type_suffix=type_suffix, **kwargs)


class LogicsBone(CodeBone):
"""
Validates its value as a Logics expression (https://github.com/viur-framework/logics).
Uses Python syntax highlighting in the frontend.
"""

def __init__(self, *, syntax: str = "logics", **kwargs):
super().__init__(syntax=syntax, type_suffix="python", **kwargs)

def singleValueFromClient(self, value, skel, bone_name, client_data):
if value := str(value or "").strip():
value += "\n"
return super().singleValueFromClient(value, skel, bone_name, client_data)

def isInvalid(self, value):
if self.validate and value:
try:
logics.Logics(value)
except logics.ParseException as e:
return str(e).replace("&eof", "end-of-expression")


class JinjaBone(CodeBone):
"""
Validates its value as a Jinja2 template.
"""

def __init__(self, *, syntax: str = "jinja2", **kwargs):
super().__init__(syntax=syntax, **kwargs)

def isInvalid(self, value):
if self.validate and value:
env = jinja2.Environment()
try:
env.parse(value)
except jinja2.TemplateSyntaxError as e:
return f"Syntax error in line {e.lineno}: {e.message}"
except jinja2.TemplateError as e:
return f"General error: {e}"


class PythonBone(CodeBone):
"""
Validates its value as Python source code.
"""

def __init__(self, *, syntax: str = "python", **kwargs):
super().__init__(syntax=syntax, **kwargs)

def isInvalid(self, value):
if self.validate and value:
try:
ast.parse(value)
except SyntaxError as e:
return f"Syntax error in line {e.lineno}: {e.msg}"
4 changes: 1 addition & 3 deletions src/viur/core/modules/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,8 @@ class ScriptLeafSkel(BaseScriptAbstractSkel):
else "Filename is invalid or doesn't have a '.py'-suffix",
)

script = RawBone(
script = PythonBone(
descr="Code",
type_suffix="code.python",
indexed=False,
)

access = SelectBone(
Expand Down
10 changes: 2 additions & 8 deletions src/viur/core/skeleton/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from .utils import skeletonByKind, listKnownSkeletons
from .relskel import RelSkel

from ..bones.raw import RawBone
from ..bones.code import LogicsBone
from ..bones.record import RecordBone
from ..bones.relational import RelationalBone, RelationalConsistency, RelationalUpdateLevel
from ..bones.select import SelectBone
Expand Down Expand Up @@ -221,9 +221,8 @@ class FilterRowUsingSkel(RelSkel):
format="$(name)$(op)=$(value)",
)

condition = RawBone(
condition = LogicsBone(
descr="Condition",
type_suffix="code.python", # Logics expression
required=True,
defaultValue="False # fused: by default, doesn't affect anything.\n",
params={
Expand All @@ -232,11 +231,6 @@ class FilterRowUsingSkel(RelSkel):
)

def execute(self, task, kinds, filters, condition):
try:
logics.Logics(condition)
except logics.ParseException as e:
raise errors.BadRequest(f"Error parsing condition {e}")

notify = current.user.get()["name"]

for kind in kinds:
Expand Down
Loading