quantum-framework 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +417 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +527 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +547 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2611 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +148 -0
- quantum/core/expressions.py +591 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_fetching/src/__init__.py +21 -0
- quantum/core/features/data_fetching/src/ast_node.py +312 -0
- quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
- quantum/core/features/data_fetching/src/html_adapter.py +474 -0
- quantum/core/features/data_fetching/src/parser.py +225 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +291 -0
- quantum/core/features/data_import/src/runtime.py +538 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +146 -0
- quantum/core/features/invocation/src/runtime.py +327 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/database_service.py +322 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +228 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
- quantum/core/features/ui_engine/src/parser.py +1106 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1235 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +68 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +89 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +217 -0
- quantum/core/parsers/data/invoke_parser.py +116 -0
- quantum/core/parsers/data/query_parser.py +188 -0
- quantum/core/parsers/data/transaction_parser.py +77 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +54 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +137 -0
- quantum/core/parsers/functions/param_parser.py +66 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/persistence/__init__.py +9 -0
- quantum/core/parsers/persistence/persist_parser.py +64 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +82 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +28 -0
- quantum/runtime/action_handler.py +443 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/api_server.py +86 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +990 -0
- quantum/runtime/component_composer.py +319 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/database_service.py +598 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +286 -0
- quantum/runtime/executor_registry.py +171 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +262 -0
- quantum/runtime/executors/control_flow/__init__.py +11 -0
- quantum/runtime/executors/control_flow/if_executor.py +93 -0
- quantum/runtime/executors/control_flow/loop_executor.py +307 -0
- quantum/runtime/executors/control_flow/set_executor.py +412 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +145 -0
- quantum/runtime/executors/data/invoke_executor.py +176 -0
- quantum/runtime/executors/data/query_executor.py +256 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +500 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/logging_setup.py +140 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +184 -0
- quantum/runtime/pypy_compat.py +315 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +733 -0
- quantum/runtime/service_container.py +444 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +188 -0
- quantum/runtime/ui_desktop_adapter.py +1730 -0
- quantum/runtime/ui_desktop_templates.py +307 -0
- quantum/runtime/ui_html_adapter.py +2691 -0
- quantum/runtime/ui_html_templates.py +2297 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +1766 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/utils/__init__.py +1 -0
- quantum_framework-0.9.0.dist-info/METADATA +244 -0
- quantum_framework-0.9.0.dist-info/RECORD +262 -0
- quantum_framework-0.9.0.dist-info/WHEEL +5 -0
- quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Make an unresolved `{expression}` say something.
|
|
3
|
+
|
|
4
|
+
FRAMEWORK_PLAN.md Fase 2.2. Both databinding paths catch every exception and
|
|
5
|
+
hand back the literal placeholder, which is the right thing to RENDER — a page
|
|
6
|
+
should not 500 because one field is missing — but it threw the diagnosis away.
|
|
7
|
+
The evaluator already knew that `p` was not defined, or that `projects` is a
|
|
8
|
+
list with no `recordCount`; nobody was told.
|
|
9
|
+
|
|
10
|
+
That silence was the single biggest cost measured in Fase 4: six of the eleven
|
|
11
|
+
frictions found while writing one real screen were the same disease, and three
|
|
12
|
+
of the four bugs produced no message at all. The time went into discovering
|
|
13
|
+
what was wrong, not into fixing it. See DOGFOOD_NOTES.md.
|
|
14
|
+
|
|
15
|
+
Two things this has to get right to be worth having:
|
|
16
|
+
|
|
17
|
+
- **Not flood.** A `q:loop` over 1,000 rows evaluates the same broken
|
|
18
|
+
expression 1,000 times. Each distinct problem is reported once.
|
|
19
|
+
- **Not cry wolf.** A missing `session.`/`form.` variable is a documented
|
|
20
|
+
contract, not a mistake — templates render before login on purpose. Those
|
|
21
|
+
stay quiet, or the log becomes noise nobody reads.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
import re
|
|
26
|
+
import threading
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger('quantum.databinding')
|
|
29
|
+
|
|
30
|
+
# Scopes whose absence is normal: a template renders before login, before a
|
|
31
|
+
# form is posted, before a request has query parameters.
|
|
32
|
+
QUIET_SCOPES = ('session', 'application', 'request', 'cookie', 'form', 'query')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def root_scope(expression: str) -> str:
|
|
36
|
+
"""The leading scope name of an expression, if it has one."""
|
|
37
|
+
return expression.split('.')[0].split('[')[0].strip()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def is_absent_scope(expression: str) -> bool:
|
|
41
|
+
"""Whether an unresolved expression is an ABSENT SCOPED VARIABLE.
|
|
42
|
+
|
|
43
|
+
Those resolve to '' rather than failing — a template renders before login
|
|
44
|
+
on purpose. Both databinding paths have to agree on this or the same
|
|
45
|
+
{form.email} renders as '' in one pass and as literal text in the other,
|
|
46
|
+
which is exactly what happened: the runtime honoured the contract and the
|
|
47
|
+
renderer leaked the placeholder onto the page.
|
|
48
|
+
"""
|
|
49
|
+
return root_scope(expression) in QUIET_SCOPES
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# `{"product": "Laptop", "price": 999}` — a JSON object written inside a
|
|
53
|
+
# value, not an expression that failed. Quantum has no escape for a brace, so
|
|
54
|
+
# a JSON literal in a .q file is read as databinding, complained about once
|
|
55
|
+
# per object, and then left alone (which is why it still works). Ten objects
|
|
56
|
+
# meant ten warnings pointing at nothing the author can fix.
|
|
57
|
+
_JSON_OBJECT_BODY = re.compile(r'^\s*(["\'])(?:(?!\1).)*\1\s*:', re.DOTALL)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def looks_like_json_object(expression: str) -> bool:
|
|
61
|
+
"""Whether this is the inside of a JSON object, not an expression."""
|
|
62
|
+
return bool(_JSON_OBJECT_BODY.match(expression))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
_seen = set()
|
|
66
|
+
_lock = threading.Lock()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def report_unresolved(expression: str, error: BaseException) -> None:
|
|
70
|
+
"""Log why a `{expression}` did not resolve, once per distinct problem."""
|
|
71
|
+
if is_absent_scope(expression) or looks_like_json_object(expression):
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
reason = str(error) or type(error).__name__
|
|
75
|
+
key = (expression, reason)
|
|
76
|
+
with _lock:
|
|
77
|
+
if key in _seen:
|
|
78
|
+
return
|
|
79
|
+
_seen.add(key)
|
|
80
|
+
|
|
81
|
+
logger.warning("{%s} did not resolve: %s", expression, reason)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def reset() -> None:
|
|
85
|
+
"""Forget what has been reported. For tests, and for a reloading server."""
|
|
86
|
+
with _lock:
|
|
87
|
+
_seen.clear()
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standard functions available inside Quantum `{...}` expressions.
|
|
3
|
+
|
|
4
|
+
Deliberately small. Every entry here is a function that was either already
|
|
5
|
+
needed by real .q files or whose absence caused a concrete bug:
|
|
6
|
+
|
|
7
|
+
- `now()` / `dateAdd()` / `dateFormat()` — their absence is why
|
|
8
|
+
`session.sessionExpiry` had to be hardcoded to make login work at all
|
|
9
|
+
(AUDIT_FIX_PLAN.md Fase 1). A framework that cannot express "24 hours from
|
|
10
|
+
now" in its own language forces every app to hardcode a date.
|
|
11
|
+
- `len` / `first` / `last` — `.length` already worked on arrays via attribute
|
|
12
|
+
access; these make the same thing available as a call.
|
|
13
|
+
|
|
14
|
+
Functions are looked up by name from expressions, so anything added here
|
|
15
|
+
becomes language surface. That is the reason to keep it short.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from datetime import datetime, timedelta
|
|
19
|
+
from typing import Any, Iterable, Optional
|
|
20
|
+
|
|
21
|
+
_UNITS = {
|
|
22
|
+
's': 'seconds', 'sec': 'seconds', 'second': 'seconds', 'seconds': 'seconds',
|
|
23
|
+
'n': 'minutes', 'min': 'minutes', 'minute': 'minutes', 'minutes': 'minutes',
|
|
24
|
+
'h': 'hours', 'hour': 'hours', 'hours': 'hours',
|
|
25
|
+
'd': 'days', 'day': 'days', 'days': 'days',
|
|
26
|
+
'w': 'weeks', 'week': 'weeks', 'weeks': 'weeks',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# -- dates -----------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
def now() -> datetime:
|
|
33
|
+
"""Current local datetime."""
|
|
34
|
+
return datetime.now()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def date_add(unit: str, amount: int, start: Optional[Any] = None) -> datetime:
|
|
38
|
+
"""Add an interval to a datetime (defaults to now).
|
|
39
|
+
|
|
40
|
+
dateAdd('h', 24) -> 24 hours from now
|
|
41
|
+
"""
|
|
42
|
+
key = _UNITS.get(str(unit).lower())
|
|
43
|
+
if key is None:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"unknown date unit {unit!r}; use s/n/h/d/w "
|
|
46
|
+
f"(seconds/minutes/hours/days/weeks)"
|
|
47
|
+
)
|
|
48
|
+
base = _as_datetime(start) if start is not None else datetime.now()
|
|
49
|
+
return base + timedelta(**{key: int(amount)})
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def date_format(value: Any, fmt: str = "%Y-%m-%d %H:%M:%S") -> str:
|
|
53
|
+
"""Format a datetime (or ISO string) with a strftime pattern."""
|
|
54
|
+
return _as_datetime(value).strftime(fmt)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def date_diff(unit: str, start: Any, end: Any) -> int:
|
|
58
|
+
"""Whole units between two datetimes."""
|
|
59
|
+
key = _UNITS.get(str(unit).lower())
|
|
60
|
+
if key is None:
|
|
61
|
+
raise ValueError(f"unknown date unit {unit!r}")
|
|
62
|
+
delta = _as_datetime(end) - _as_datetime(start)
|
|
63
|
+
seconds = delta.total_seconds()
|
|
64
|
+
per = {'seconds': 1, 'minutes': 60, 'hours': 3600,
|
|
65
|
+
'days': 86400, 'weeks': 604800}[key]
|
|
66
|
+
return int(seconds // per)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _as_datetime(value: Any) -> datetime:
|
|
70
|
+
if isinstance(value, datetime):
|
|
71
|
+
return value
|
|
72
|
+
if isinstance(value, str):
|
|
73
|
+
return datetime.fromisoformat(value)
|
|
74
|
+
raise ValueError(f"cannot read {value!r} as a datetime")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# -- strings ---------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
def _upper(v: Any) -> str: return str(v).upper()
|
|
80
|
+
def _lower(v: Any) -> str: return str(v).lower()
|
|
81
|
+
def _trim(v: Any) -> str: return str(v).strip()
|
|
82
|
+
def _replace(v: Any, old: str, new: str) -> str: return str(v).replace(old, new)
|
|
83
|
+
def _split(v: Any, sep: str = ",") -> list: return str(v).split(sep)
|
|
84
|
+
def _contains(v: Any, needle: Any) -> bool: return str(needle) in str(v)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# -- collections -----------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
def _len(v: Any) -> int:
|
|
90
|
+
try:
|
|
91
|
+
return len(v)
|
|
92
|
+
except TypeError:
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _first(v: Iterable) -> Any:
|
|
97
|
+
items = list(v)
|
|
98
|
+
return items[0] if items else None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _last(v: Iterable) -> Any:
|
|
102
|
+
items = list(v)
|
|
103
|
+
return items[-1] if items else None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _join(v: Iterable, sep: str = ", ") -> str:
|
|
107
|
+
return sep.join(str(i) for i in v)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _sort(v: Iterable, reverse: bool = False) -> list:
|
|
111
|
+
return sorted(v, reverse=reverse)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# -- numbers ---------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
def _round(v: Any, digits: int = 0) -> Any:
|
|
117
|
+
result = round(float(v), int(digits))
|
|
118
|
+
return int(result) if digits == 0 else result
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
STDLIB = {
|
|
122
|
+
# dates
|
|
123
|
+
'now': now,
|
|
124
|
+
'dateAdd': date_add,
|
|
125
|
+
'dateFormat': date_format,
|
|
126
|
+
'dateDiff': date_diff,
|
|
127
|
+
# strings
|
|
128
|
+
'upper': _upper,
|
|
129
|
+
'lower': _lower,
|
|
130
|
+
'trim': _trim,
|
|
131
|
+
'replace': _replace,
|
|
132
|
+
'split': _split,
|
|
133
|
+
'contains': _contains,
|
|
134
|
+
# collections
|
|
135
|
+
'len': _len,
|
|
136
|
+
'first': _first,
|
|
137
|
+
'last': _last,
|
|
138
|
+
'join': _join,
|
|
139
|
+
'sort': _sort,
|
|
140
|
+
# numbers
|
|
141
|
+
'round': _round,
|
|
142
|
+
'abs': abs,
|
|
143
|
+
'min': min,
|
|
144
|
+
'max': max,
|
|
145
|
+
'int': int,
|
|
146
|
+
'float': float,
|
|
147
|
+
'str': str,
|
|
148
|
+
}
|