Skip to content

Commit

Permalink
Fix too many things at once...
Browse files Browse the repository at this point in the history
  • Loading branch information
luismedel committed Nov 18, 2024
1 parent 4ae6d14 commit 2031788
Show file tree
Hide file tree
Showing 14 changed files with 115 additions and 89 deletions.
2 changes: 1 addition & 1 deletion .bluish/bluish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:

- name: Build project
run: |
python -m pip install --upgrade build
python -m pip install --upgrade pip build
python -m build
- name: Prepare credentials
Expand Down
12 changes: 1 addition & 11 deletions src/bluish/actions/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,7 @@
import bluish.process
from bluish.contexts import Definition
from bluish.logging import debug
from bluish.schemas import KV, validate_schema


class RequiredInputError(Exception):
def __init__(self, param: str):
super().__init__(f"Missing required input parameter: {param}")


class RequiredAttributeError(Exception):
def __init__(self, param: str):
super().__init__(f"Missing required attribute: {param}")
from bluish.schemas import validate_schema


def _key_exists(key: str, attrs: Definition) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion src/bluish/actions/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class RunCommand(bluish.actions.base.Action):
SCHEMA = {
"type": dict,
"properties": {
"run": [str, None],
"run": str,
"shell": [str, None],
},
}
Expand Down
3 changes: 2 additions & 1 deletion src/bluish/actions/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import bluish.contexts.step
import bluish.process
from bluish.logging import debug, error, info, warning
from bluish.schemas import STR_LIST
from bluish.utils import decorate_for_log


Expand Down Expand Up @@ -106,7 +107,7 @@ class Build(bluish.actions.base.Action):
"type": dict,
"properties": {
"dockerfile": [str, None],
"tags": [str, list[str]],
"tags": [str, STR_LIST],
"context": [str, None],
},
}
Expand Down
10 changes: 5 additions & 5 deletions src/bluish/contexts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import bluish.core
import bluish.process
from bluish.logging import info
from bluish.redacted_string import RedactedString
from bluish.safe_string import SafeString
from bluish.schemas import (
JOB_SCHEMA,
STEP_SCHEMA,
Expand Down Expand Up @@ -274,7 +274,7 @@ def prepare_value(value: Any) -> Any:
wf = cast(bluish.contexts.workflow.WorkflowContext, _workflow(ctx))
if varname in wf.secrets:
return prepare_value(
RedactedString(cast(str, wf.secrets[varname]), "********")
SafeString(cast(str, wf.secrets[varname]), "********")
)
elif root == "jobs":
wf = cast(bluish.contexts.workflow.WorkflowContext, _workflow(ctx))
Expand All @@ -288,7 +288,7 @@ def prepare_value(value: Any) -> Any:
elif root == "steps":
job = cast(bluish.contexts.job.JobContext, _job(ctx))
step_id, varname = varname.split(".", maxsplit=1)
step = job.steps.get(step_id)
step = next((step for step in job.steps if step.id == step_id), None)
if not step:
raise ValueError(f"Step {step_id} not found")
return _try_get_value(step, varname, raw)
Expand All @@ -302,7 +302,7 @@ def prepare_value(value: Any) -> Any:
node = _step_or_job(ctx)
if varname in node.inputs:
if varname in node.sensitive_inputs:
return prepare_value(RedactedString(node.inputs[varname], "********"))
return prepare_value(SafeString(node.inputs[varname], "********"))
else:
return prepare_value(node.inputs[varname])
elif root == "outputs":
Expand Down Expand Up @@ -346,7 +346,7 @@ def _try_set_value(ctx: "ContextNode", name: str, value: str) -> bool:
elif root == "steps":
job = cast(bluish.contexts.job.JobContext, _job(ctx))
step_id, varname = varname.split(".", maxsplit=1)
step = job.steps.get(step_id)
step = next((step for step in job.steps if step.id == step_id), None)
if not step:
raise ValueError(f"Step {step_id} not found")
return _try_set_value(step, varname, value)
Expand Down
6 changes: 3 additions & 3 deletions src/bluish/contexts/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ def __init__(
**self.attrs.var,
}

self.steps: dict[str, bluish.contexts.step.StepContext] = {}
self.steps: list[bluish.contexts.step.StepContext] = []
for i, step_dict in enumerate(self.attrs.steps):
step_def = contexts.StepDefinition(step_dict)
step_def.ensure_property("id", f"step_{i+1}")
step = bluish.contexts.step.StepContext(self, step_def)
self.steps[step_id] = step
self.steps.append(step)

def dispatch(self) -> bluish.process.ProcessResult | None:
self.status = bluish.core.ExecutionStatus.RUNNING
Expand All @@ -71,7 +71,7 @@ def dispatch(self) -> bluish.process.ProcessResult | None:
info("Job skipped")
return None

for step in self.steps.values():
for step in self.steps:
result = step.dispatch()
if not result:
continue
Expand Down
12 changes: 6 additions & 6 deletions src/bluish/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import lark

import bluish.contexts
from bluish.redacted_string import RedactedString
from bluish.safe_string import SafeString

SENSITIVE_LITERALS = ("password", "secret", "token")

Expand Down Expand Up @@ -91,12 +91,12 @@ def concat(a: Any, b: Any) -> Any:
if b is None:
return a

result = RedactedString(str(a) + str(b))
result = SafeString(str(a) + str(b))
result.redacted_value = (
a.redacted_value if isinstance(a, RedactedString) else str(a)
a.redacted_value if isinstance(a, SafeString) else str(a)
)
result.redacted_value += (
b.redacted_value if isinstance(b, RedactedString) else str(b)
b.redacted_value if isinstance(b, SafeString) else str(b)
)
return result

Expand All @@ -111,8 +111,8 @@ def number(self, value: str) -> int | float:
return to_number(value)

def str(self, value: str) -> str:
if isinstance(value, RedactedString):
return RedactedString(value[1:-1], value.redacted_value[1:-1])
if isinstance(value, SafeString):
return SafeString(value[1:-1], value.redacted_value[1:-1])
elif isinstance(value, str):
return value[1:-1]
else:
Expand Down
16 changes: 8 additions & 8 deletions src/bluish/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,39 @@
from logging import warning as logging_warning
from typing import Any

from bluish.redacted_string import RedactedString
from bluish.safe_string import SafeString


def info(message: str, *args: Any, **kwargs: Any) -> None:
msg = message.redacted_value if isinstance(message, RedactedString) else message
msg = message.redacted_value if isinstance(message, SafeString) else message
logging_info(msg, *args, **kwargs)


def error(message: str, *args: Any, **kwargs: Any) -> None:
msg = message.redacted_value if isinstance(message, RedactedString) else message
msg = message.redacted_value if isinstance(message, SafeString) else message
logging_error(msg, *args, **kwargs)


def warning(message: str, *args: Any, **kwargs: Any) -> None:
msg = message.redacted_value if isinstance(message, RedactedString) else message
msg = message.redacted_value if isinstance(message, SafeString) else message
logging_warning(msg, *args, **kwargs)


def debug(message: str, *args: Any, **kwargs: Any) -> None:
msg = message.redacted_value if isinstance(message, RedactedString) else message
msg = message.redacted_value if isinstance(message, SafeString) else message
logging_debug(msg, *args, **kwargs)


def critical(message: str, *args: Any, **kwargs: Any) -> None:
msg = message.redacted_value if isinstance(message, RedactedString) else message
msg = message.redacted_value if isinstance(message, SafeString) else message
logging_critical(msg, *args, **kwargs)


def exception(message: str, *args: Any, **kwargs: Any) -> None:
msg = message.redacted_value if isinstance(message, RedactedString) else message
msg = message.redacted_value if isinstance(message, SafeString) else message
logging_exception(msg, *args, **kwargs)


def log(level: int, message: str, *args: Any, **kwargs: Any) -> None:
msg = message.redacted_value if isinstance(message, RedactedString) else message
msg = message.redacted_value if isinstance(message, SafeString) else message
logging_log(level, msg, *args, **kwargs)
6 changes: 3 additions & 3 deletions src/bluish/redacted_string.py → src/bluish/safe_string.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from typing import cast


class RedactedString(str):
class SafeString(str):
"""A string with two values: one for logging and one for the actual value."""

def __new__(cls, value: str, redacted_value: str | None = None) -> "RedactedString":
result = cast(RedactedString, super().__new__(cls, value))
def __new__(cls, value: str, redacted_value: str | None = None) -> "SafeString":
result = cast(SafeString, super().__new__(cls, value))
result.redacted_value = redacted_value or value
return result

Expand Down
82 changes: 59 additions & 23 deletions src/bluish/schemas.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
from typing import Any, Union
from typing import Any, Iterable, Union

from bluish.safe_string import SafeString


class InvalidTypeError(Exception):
def __init__(self, value: Any, types: str):
super().__init__(f"{value} is not any of the allowed types: {types}")


class RequiredAttributeError(Exception):
def __init__(self, param: str):
super().__init__(f"Missing required attribute: {param}")


class UnexpectedAttributesError(Exception):
def __init__(self, attrs: Iterable[str]):
super().__init__(f"Unexpected attributes: {attrs}")


KV = {
"type": dict,
"key_schema": {"type": str},
"value_schema": {"type": str},
"key_schema": str,
"value_schema": [str, bool, int, float, None],
}

STR_KV = {
"type": dict,
"key_schema": str,
"value_schema": str,
}

STR_LIST = {
Expand All @@ -17,7 +41,7 @@
"name": [str, None],
"env": [KV, None],
"var": [KV, None],
"secrets": [KV, None],
"secrets": [STR_KV, None],
"secrets_file": [str, None],
"env_file": [str, None],
"uses": [str, None],
Expand All @@ -36,7 +60,7 @@
"name": [str, None],
"env": [KV, None],
"var": [KV, None],
"secrets": [KV, None],
"secrets": [STR_KV, None],
"secrets_file": [str, None],
"env_file": [str, None],
"runs_on": [str, None],
Expand All @@ -56,7 +80,7 @@
"name": [str, None],
"env": [KV, None],
"var": [KV, None],
"secrets": [KV, None],
"secrets": [STR_KV, None],
"secrets_file": [str, None],
"env_file": [str, None],
"runs_on": [str, None],
Expand Down Expand Up @@ -84,21 +108,32 @@ def _get_type_repr(t: type_def | None) -> str:
return f"{t}"


def _find_type(value: Any, t: type_def | None) -> dict | type | None:
if value is None or t is None:
def _find_type(value: Any, _def: type_def | None) -> dict | type | None:
def get_origin(t):
if t is None:
return None
if isinstance(t, SafeString):
return str
return get_origin(t.__origin__) if "__origin__" in t.__dict__ else t

if value is None or _def is None:
return None
elif isinstance(t, list):
return next((tt for tt in t if _find_type(value, tt)), None) # type: ignore
elif isinstance(t, dict):
if "type" not in t:
elif isinstance(_def, list):
for tt in _def:
if _find_type(value, tt):
return tt
return None
elif isinstance(_def, dict):
_t = _def.get("type")
if _t is None:
return None
if t["type"] is Any:
return t
return t if type(value) is t["type"] else None
if _t is Any:
return _def
return _def if get_origin(type(value)) is get_origin(_t) else None
else:
if t is Any:
return t # type: ignore
return t if type(value) is t else None
if _def is Any:
return _def # type: ignore
return _def if get_origin(type(value)) is get_origin(_def) else None


def _is_required(t: type_def | None) -> bool:
Expand Down Expand Up @@ -138,12 +173,13 @@ def validate_schema(
...
ValueError: 42 is not any of the allowed types: <class 'str'>
"""

if data is None and not _is_required(schema):
return

type_def = _find_type(data, schema)
if type_def is None:
raise ValueError(
f"{data} is not any of the allowed types: {_get_type_repr(schema)}"
)
raise InvalidTypeError(data, _get_type_repr(schema))

if isinstance(type_def, type) or type_def is Any:
return
Expand All @@ -164,13 +200,13 @@ def validate_schema(
if data.get(prop) is None:
if not _is_required(tdef):
continue
raise ValueError(f"Missing required key: {prop}")
raise RequiredAttributeError(f"Missing required key: {prop}")
validate_schema(tdef, data[prop])

if reject_extra_keys and "properties" in type_def:
extra_keys = set(data.keys()) - set(type_def["properties"].keys())
if extra_keys:
raise ValueError(f"Extra keys: {extra_keys}")
raise UnexpectedAttributesError(extra_keys)
elif type_def["type"] == list:
assert isinstance(data, list)
item_schema = type_def["item_schema"]
Expand Down
12 changes: 6 additions & 6 deletions src/bluish/utils.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# The dreaded "utils" module, where lazy programmers put all the miscellaneous functions.


from bluish.redacted_string import RedactedString
from bluish.safe_string import SafeString


def safe_string(value: str | RedactedString) -> str:
def safe_string(value: str | SafeString) -> str:
"""Returns the string value of a RedactedString."""
if isinstance(value, RedactedString):
if isinstance(value, SafeString):
return value.redacted_value
else:
return value


def decorate_for_log(value: str | RedactedString, decoration: str = " ") -> str:
def decorate_for_log(value: str | SafeString, decoration: str = " ") -> str:
"""Decorates a multiline string for pretty logging."""

def decorate(value: str, decoration: str) -> str:
Expand All @@ -25,8 +25,8 @@ def decorate(value: str, decoration: str) -> str:
lines = value.rstrip().splitlines(keepends=True)
return "\n" + "".join(f"{decoration}{line}" for line in lines)

if isinstance(value, RedactedString):
result = RedactedString(value)
if isinstance(value, SafeString):
result = SafeString(value)
result.redacted_value = decorate(value.redacted_value, decoration)
return result
else:
Expand Down
Loading

0 comments on commit 2031788

Please sign in to comment.