planar 0.12.0__py3-none-any.whl → 0.13.2__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.
- planar/ai/__init__.py +2 -0
- planar/ai/agent.py +2 -2
- planar/ai/agent_base.py +19 -18
- planar/ai/{state.py → tool_context.py} +3 -3
- planar/cli.py +17 -1
- planar/config.py +110 -82
- planar/data/connection.py +51 -0
- planar/human/__init__.py +11 -2
- planar/human/models.py +2 -3
- planar/logging/attributes.py +1 -0
- planar/scaffold_templates/pyproject.toml.j2 +11 -3
- planar/sse/proxy.py +7 -1
- planar/workflows/notifications.py +5 -1
- {planar-0.12.0.dist-info → planar-0.13.2.dist-info}/METADATA +2 -1
- {planar-0.12.0.dist-info → planar-0.13.2.dist-info}/RECORD +17 -17
- {planar-0.12.0.dist-info → planar-0.13.2.dist-info}/WHEEL +1 -1
- {planar-0.12.0.dist-info → planar-0.13.2.dist-info}/entry_points.txt +0 -0
planar/ai/__init__.py
CHANGED
planar/ai/agent.py
CHANGED
@@ -50,8 +50,8 @@ class AgentWorkflowNotifier(AgentEventEmitter):
|
|
50
50
|
class Agent[
|
51
51
|
TInput: BaseModel | str,
|
52
52
|
TOutput: BaseModel | str,
|
53
|
-
|
54
|
-
](AgentBase[TInput, TOutput,
|
53
|
+
TToolContext,
|
54
|
+
](AgentBase[TInput, TOutput, TToolContext]):
|
55
55
|
model: models.KnownModelName | models.Model = "openai:gpt-4o"
|
56
56
|
|
57
57
|
async def run_step(
|
planar/ai/agent_base.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
1
|
import abc
|
4
2
|
from dataclasses import dataclass, field
|
5
3
|
from typing import (
|
@@ -15,7 +13,7 @@ from pydantic import BaseModel
|
|
15
13
|
from pydantic_ai.settings import ModelSettings
|
16
14
|
|
17
15
|
from planar.ai.models import AgentConfig, AgentEventEmitter, AgentRunResult
|
18
|
-
from planar.ai.
|
16
|
+
from planar.ai.tool_context import clear_tool_context, set_tool_context
|
19
17
|
from planar.logging import get_logger
|
20
18
|
from planar.modeling.field_helpers import JsonSchema
|
21
19
|
from planar.utils import P, R, T, U
|
@@ -30,7 +28,7 @@ class AgentBase[
|
|
30
28
|
# TODO: add `= str` default when we upgrade to 3.13
|
31
29
|
TInput: BaseModel | str,
|
32
30
|
TOutput: BaseModel | str,
|
33
|
-
|
31
|
+
TToolContext,
|
34
32
|
](abc.ABC):
|
35
33
|
"""An LLM-powered agent that can be called directly within workflows."""
|
36
34
|
|
@@ -47,7 +45,7 @@ class AgentBase[
|
|
47
45
|
)
|
48
46
|
event_emitter: AgentEventEmitter | None = None
|
49
47
|
durable: bool = True
|
50
|
-
|
48
|
+
tool_context_type: Type[TToolContext] | None = None
|
51
49
|
|
52
50
|
# TODO: move here to serialize to frontend
|
53
51
|
#
|
@@ -94,16 +92,16 @@ class AgentBase[
|
|
94
92
|
|
95
93
|
@overload
|
96
94
|
async def __call__(
|
97
|
-
self: "AgentBase[TInput, str,
|
95
|
+
self: "AgentBase[TInput, str, TToolContext]",
|
98
96
|
input_value: TInput,
|
99
|
-
|
97
|
+
tool_context: TToolContext | None = None,
|
100
98
|
) -> AgentRunResult[str]: ...
|
101
99
|
|
102
100
|
@overload
|
103
101
|
async def __call__(
|
104
|
-
self: "AgentBase[TInput, TOutput,
|
102
|
+
self: "AgentBase[TInput, TOutput, TToolContext]",
|
105
103
|
input_value: TInput,
|
106
|
-
|
104
|
+
tool_context: TToolContext | None = None,
|
107
105
|
) -> AgentRunResult[TOutput]: ...
|
108
106
|
|
109
107
|
def as_step_if_durable(
|
@@ -125,7 +123,7 @@ class AgentBase[
|
|
125
123
|
async def __call__(
|
126
124
|
self,
|
127
125
|
input_value: TInput,
|
128
|
-
|
126
|
+
tool_context: TToolContext | None = None,
|
129
127
|
) -> AgentRunResult[Any]:
|
130
128
|
if self.input_type is not None and not isinstance(input_value, self.input_type):
|
131
129
|
raise ValueError(
|
@@ -153,22 +151,25 @@ class AgentBase[
|
|
153
151
|
return_type=AgentRunResult[self.output_type],
|
154
152
|
)
|
155
153
|
|
156
|
-
if
|
157
|
-
if self.
|
158
|
-
raise ValueError(
|
159
|
-
|
154
|
+
if tool_context is not None:
|
155
|
+
if self.tool_context_type is None:
|
156
|
+
raise ValueError(
|
157
|
+
"tool_context cannot be provided when tool_context_type is not set"
|
158
|
+
)
|
159
|
+
if not isinstance(tool_context, self.tool_context_type):
|
160
160
|
raise ValueError(
|
161
|
-
f"
|
161
|
+
f"tool_context must be of type {self.tool_context_type}, "
|
162
|
+
f"but got {type(tool_context)}"
|
162
163
|
)
|
163
|
-
|
164
|
+
set_tool_context(cast(TToolContext, tool_context))
|
164
165
|
|
165
166
|
try:
|
166
167
|
result = await run_step(input_value=input_value)
|
167
168
|
# Cast the result to ensure type compatibility
|
168
169
|
return cast(AgentRunResult[TOutput], result)
|
169
170
|
finally:
|
170
|
-
if
|
171
|
-
|
171
|
+
if tool_context is not None:
|
172
|
+
clear_tool_context()
|
172
173
|
|
173
174
|
@abc.abstractmethod
|
174
175
|
async def run_step(
|
@@ -5,13 +5,13 @@ from planar.task_local import TaskLocal
|
|
5
5
|
data: TaskLocal[Any] = TaskLocal()
|
6
6
|
|
7
7
|
|
8
|
-
def
|
8
|
+
def set_tool_context(ctx: Any):
|
9
9
|
return data.set(ctx)
|
10
10
|
|
11
11
|
|
12
|
-
def
|
12
|
+
def get_tool_context[T](_: Type[T]) -> T:
|
13
13
|
return cast(T, data.get())
|
14
14
|
|
15
15
|
|
16
|
-
def
|
16
|
+
def clear_tool_context():
|
17
17
|
return data.clear()
|
planar/cli.py
CHANGED
@@ -262,6 +262,11 @@ def run_command(
|
|
262
262
|
def scaffold_project(
|
263
263
|
name: str = typer.Option(None, "--name", help="Name of the new project"),
|
264
264
|
directory: Path = typer.Option(Path("."), "--directory", help="Target directory"),
|
265
|
+
use_local_source: bool = typer.Option(
|
266
|
+
False,
|
267
|
+
"--use-local-source",
|
268
|
+
help="Use local planar source instead of published package (for development)",
|
269
|
+
),
|
265
270
|
):
|
266
271
|
"""
|
267
272
|
Creates a new Planar project with a basic structure and example workflow.
|
@@ -278,8 +283,19 @@ def scaffold_project(
|
|
278
283
|
template_dir = Path(__file__).parent / "scaffold_templates"
|
279
284
|
jinja_env = JinjaEnvironment(loader=FileSystemLoader(template_dir))
|
280
285
|
|
286
|
+
planar_source_path = None
|
287
|
+
if use_local_source:
|
288
|
+
# Assume we're running from the planar package itself
|
289
|
+
# Go up from planar/cli.py -> planar/ -> planar_repo/
|
290
|
+
planar_source_path = Path(__file__).parent.parent.resolve()
|
291
|
+
typer.echo(f"Using local planar source: {planar_source_path}")
|
292
|
+
|
281
293
|
# Template context
|
282
|
-
context = {
|
294
|
+
context = {
|
295
|
+
"name": name,
|
296
|
+
"local_source": use_local_source,
|
297
|
+
"planar_source_path": planar_source_path,
|
298
|
+
}
|
283
299
|
|
284
300
|
# Create project structure
|
285
301
|
try:
|
planar/config.py
CHANGED
@@ -3,6 +3,7 @@ import logging
|
|
3
3
|
import logging.config
|
4
4
|
import os
|
5
5
|
import sys
|
6
|
+
from contextlib import contextmanager
|
6
7
|
from enum import Enum
|
7
8
|
from pathlib import Path
|
8
9
|
from typing import Annotated, Any, Dict, Literal
|
@@ -24,6 +25,7 @@ from sqlalchemy import URL, make_url
|
|
24
25
|
from planar.data.config import DataConfig
|
25
26
|
from planar.files.storage.config import LocalDirectoryConfig, StorageConfig
|
26
27
|
from planar.logging import get_logger
|
28
|
+
from planar.logging.formatter import StructuredFormatter
|
27
29
|
|
28
30
|
logger = get_logger(__name__)
|
29
31
|
|
@@ -144,12 +146,12 @@ class CorsConfig(BaseModel):
|
|
144
146
|
allow_headers: list[str]
|
145
147
|
|
146
148
|
@model_validator(mode="after")
|
147
|
-
def validate_allow_origins(
|
148
|
-
if
|
149
|
+
def validate_allow_origins(self):
|
150
|
+
if self.allow_credentials and "*" in self.allow_origins:
|
149
151
|
raise ValueError(
|
150
152
|
"allow_credentials cannot be True if allow_origins includes '*'. Must explicitly specify allowed origins."
|
151
153
|
)
|
152
|
-
return
|
154
|
+
return self
|
153
155
|
|
154
156
|
|
155
157
|
LOCAL_CORS_CONFIG = CorsConfig(
|
@@ -173,10 +175,10 @@ class JWTConfig(BaseModel):
|
|
173
175
|
additional_exclusion_paths: list[str] | None = Field(default_factory=list)
|
174
176
|
|
175
177
|
@model_validator(mode="after")
|
176
|
-
def validate_client_id(
|
177
|
-
if not
|
178
|
+
def validate_client_id(self):
|
179
|
+
if not self.client_id or not self.org_id:
|
178
180
|
raise ValueError("Both client_id and org_id required to enable JWT")
|
179
|
-
return
|
181
|
+
return self
|
180
182
|
|
181
183
|
|
182
184
|
# Coplane ORG JWT config
|
@@ -235,12 +237,12 @@ class PlanarConfig(BaseModel):
|
|
235
237
|
model_config = ConfigDict(extra="forbid")
|
236
238
|
|
237
239
|
@model_validator(mode="after")
|
238
|
-
def validate_db_connection_reference(
|
239
|
-
if
|
240
|
+
def validate_db_connection_reference(self):
|
241
|
+
if self.app.db_connection not in self.db_connections:
|
240
242
|
raise ValueError(
|
241
|
-
f"Invalid db_connection reference: {
|
243
|
+
f"Invalid db_connection reference: {self.app.db_connection}"
|
242
244
|
)
|
243
|
-
return
|
245
|
+
return self
|
244
246
|
|
245
247
|
def connection_url(self) -> URL:
|
246
248
|
connection = self.db_connections[self.app.db_connection]
|
@@ -460,6 +462,28 @@ def load_environment_aware_env_vars() -> None:
|
|
460
462
|
return
|
461
463
|
|
462
464
|
|
465
|
+
@contextmanager
|
466
|
+
def _temporary_config_logging():
|
467
|
+
"""Install a console handler so config loading logs are visible before configuration."""
|
468
|
+
root_logger = logging.getLogger()
|
469
|
+
handler = logging.StreamHandler(sys.stderr)
|
470
|
+
handler.setFormatter(StructuredFormatter(use_colors=sys.stderr.isatty()))
|
471
|
+
handler.setLevel(logging.NOTSET)
|
472
|
+
|
473
|
+
previous_level = root_logger.level
|
474
|
+
if root_logger.getEffectiveLevel() > logging.INFO:
|
475
|
+
root_logger.setLevel(logging.INFO)
|
476
|
+
|
477
|
+
root_logger.addHandler(handler)
|
478
|
+
|
479
|
+
try:
|
480
|
+
yield
|
481
|
+
finally:
|
482
|
+
root_logger.removeHandler(handler)
|
483
|
+
handler.close()
|
484
|
+
root_logger.setLevel(previous_level)
|
485
|
+
|
486
|
+
|
463
487
|
def load_environment_aware_config[ConfigClass]() -> PlanarConfig:
|
464
488
|
"""
|
465
489
|
Load configuration based on environment settings, using environment variables
|
@@ -476,83 +500,87 @@ def load_environment_aware_config[ConfigClass]() -> PlanarConfig:
|
|
476
500
|
Raises:
|
477
501
|
InvalidConfigurationError: If configuration loading or validation fails.
|
478
502
|
"""
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
503
|
+
with _temporary_config_logging():
|
504
|
+
load_environment_aware_env_vars()
|
505
|
+
env = get_environment()
|
506
|
+
|
507
|
+
if env == "dev":
|
508
|
+
base_config = sqlite_config(db_path="planar_dev.db")
|
509
|
+
base_config.security = SecurityConfig(cors=LOCAL_CORS_CONFIG)
|
510
|
+
base_config.environment = Environment.DEV
|
511
|
+
else:
|
512
|
+
base_config = sqlite_config(db_path="planar.db")
|
513
|
+
base_config.environment = Environment.PROD
|
514
|
+
base_config.security = SecurityConfig(
|
515
|
+
cors=PROD_CORS_CONFIG, jwt=JWT_COPLANE_CONFIG
|
516
|
+
)
|
492
517
|
|
493
|
-
|
494
|
-
|
495
|
-
|
518
|
+
# Convert base config to dict for merging
|
519
|
+
# Use by_alias=False to work with Python field names before validation
|
520
|
+
base_dict = base_config.model_dump(mode="python", by_alias=False)
|
496
521
|
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
522
|
+
override_config_path = get_config_path()
|
523
|
+
if override_config_path:
|
524
|
+
if not override_config_path.exists():
|
525
|
+
raise InvalidConfigurationError(
|
526
|
+
f"Configuration file not found: {override_config_path}"
|
527
|
+
)
|
528
|
+
else:
|
529
|
+
paths_to_check = []
|
530
|
+
if os.environ.get("PLANAR_ENTRY_POINT"):
|
531
|
+
# Extract the directory from the entry point path
|
532
|
+
entry_point_dir = Path(os.environ["PLANAR_ENTRY_POINT"]).parent
|
533
|
+
paths_to_check = [
|
534
|
+
entry_point_dir / f"planar.{env}.yaml",
|
535
|
+
entry_point_dir / "planar.yaml",
|
536
|
+
]
|
537
|
+
paths_to_check.append(Path(f"planar.{env}.yaml"))
|
538
|
+
paths_to_check.append(Path("planar.yaml"))
|
539
|
+
|
540
|
+
override_config_path = next(
|
541
|
+
(path for path in paths_to_check if path.exists()), None
|
502
542
|
)
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
override_config_path = next(
|
516
|
-
(path for path in paths_to_check if path.exists()), None
|
517
|
-
)
|
518
|
-
if override_config_path is None:
|
519
|
-
logger.warning(
|
520
|
-
"no override config file found, using default config",
|
521
|
-
search_paths=[str(p) for p in paths_to_check],
|
522
|
-
env=env,
|
543
|
+
if override_config_path is None:
|
544
|
+
logger.warning(
|
545
|
+
"no override config file found, using default config",
|
546
|
+
search_paths=[str(p) for p in paths_to_check],
|
547
|
+
env=env,
|
548
|
+
)
|
549
|
+
|
550
|
+
merged_dict = base_dict
|
551
|
+
if override_config_path and override_config_path.exists():
|
552
|
+
logger.info(
|
553
|
+
"using override config file", override_config_path=override_config_path
|
523
554
|
)
|
555
|
+
try:
|
556
|
+
# We can't use load_config_from_file here because we expect
|
557
|
+
# the override config to not be a fully validated PlanarConfig object,
|
558
|
+
# and we need to merge it onto the base default config.
|
559
|
+
with open(override_config_path, "r") as f:
|
560
|
+
override_yaml_str = f.read()
|
561
|
+
|
562
|
+
# Expand environment variables in the YAML string
|
563
|
+
processed_yaml_str = os.path.expandvars(override_yaml_str)
|
564
|
+
logger.debug(
|
565
|
+
"processed override yaml string",
|
566
|
+
processed_yaml_str=processed_yaml_str,
|
567
|
+
)
|
568
|
+
|
569
|
+
override_dict = yaml.safe_load(processed_yaml_str) or {}
|
570
|
+
logger.debug("loaded override config", override_dict=override_dict)
|
571
|
+
|
572
|
+
# Deep merge the override onto the base dictionary
|
573
|
+
merged_dict = deep_merge_dicts(override_dict, base_dict)
|
574
|
+
logger.debug("merged config dict", merged_dict=merged_dict)
|
575
|
+
except yaml.YAMLError as e:
|
576
|
+
raise InvalidConfigurationError(
|
577
|
+
f"Error parsing override configuration file {override_config_path}: {e}"
|
578
|
+
) from e
|
524
579
|
|
525
|
-
merged_dict = base_dict
|
526
|
-
if override_config_path and override_config_path.exists():
|
527
|
-
logger.info(
|
528
|
-
"using override config file", override_config_path=override_config_path
|
529
|
-
)
|
530
580
|
try:
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
with open(override_config_path, "r") as f:
|
535
|
-
override_yaml_str = f.read()
|
536
|
-
|
537
|
-
# Expand environment variables in the YAML string
|
538
|
-
processed_yaml_str = os.path.expandvars(override_yaml_str)
|
539
|
-
logger.debug(
|
540
|
-
"processed override yaml string", processed_yaml_str=processed_yaml_str
|
541
|
-
)
|
542
|
-
|
543
|
-
override_dict = yaml.safe_load(processed_yaml_str) or {}
|
544
|
-
logger.debug("loaded override config", override_dict=override_dict)
|
545
|
-
|
546
|
-
# Deep merge the override onto the base dictionary
|
547
|
-
merged_dict = deep_merge_dicts(override_dict, base_dict)
|
548
|
-
logger.debug("merged config dict", merged_dict=merged_dict)
|
549
|
-
except yaml.YAMLError as e:
|
581
|
+
final_config = PlanarConfig.model_validate(merged_dict)
|
582
|
+
return final_config
|
583
|
+
except ValidationError as e:
|
550
584
|
raise InvalidConfigurationError(
|
551
|
-
f"
|
585
|
+
f"Configuration validation error: {e}"
|
552
586
|
) from e
|
553
|
-
|
554
|
-
try:
|
555
|
-
final_config = PlanarConfig.model_validate(merged_dict)
|
556
|
-
return final_config
|
557
|
-
except ValidationError as e:
|
558
|
-
raise InvalidConfigurationError(f"Configuration validation error: {e}") from e
|
planar/data/connection.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import asyncio
|
2
2
|
from dataclasses import dataclass
|
3
|
+
from urllib.parse import urlparse
|
3
4
|
|
4
5
|
import ibis
|
5
6
|
from ibis.backends.duckdb import Backend as DuckDBBackend
|
@@ -55,6 +56,48 @@ async def _close_backend(connection: DuckDBBackend) -> None:
|
|
55
56
|
logger.warning("failed to close DuckDB connection", error=str(exc))
|
56
57
|
|
57
58
|
|
59
|
+
def _make_aws_s3_secret_query(config: S3Config) -> str:
|
60
|
+
"""
|
61
|
+
https://duckdb.org/docs/stable/core_extensions/httpfs/s3api
|
62
|
+
"""
|
63
|
+
columns = ["TYPE s3"]
|
64
|
+
|
65
|
+
if config.region:
|
66
|
+
columns.append(
|
67
|
+
f"REGION '{config.region}'",
|
68
|
+
)
|
69
|
+
|
70
|
+
if config.endpoint_url:
|
71
|
+
parsed_url = urlparse(config.endpoint_url)
|
72
|
+
endpoint_host = parsed_url.hostname
|
73
|
+
endpoint_port = f":{parsed_url.port}" if parsed_url.port else ""
|
74
|
+
endpoint = f"{endpoint_host}{endpoint_port}"
|
75
|
+
|
76
|
+
columns.append(f"ENDPOINT '{endpoint}'")
|
77
|
+
|
78
|
+
if config.access_key and config.secret_key:
|
79
|
+
columns.extend(
|
80
|
+
[
|
81
|
+
"PROVIDER config",
|
82
|
+
f"KEY_ID '{config.access_key}'",
|
83
|
+
f"SECRET '{config.secret_key}'",
|
84
|
+
]
|
85
|
+
)
|
86
|
+
else:
|
87
|
+
columns.extend(
|
88
|
+
[
|
89
|
+
"PROVIDER credential_chain",
|
90
|
+
"CHAIN 'env;sts;instance'",
|
91
|
+
]
|
92
|
+
)
|
93
|
+
|
94
|
+
return f"""
|
95
|
+
CREATE OR REPLACE SECRET secret (
|
96
|
+
{", ".join(columns)}
|
97
|
+
);
|
98
|
+
"""
|
99
|
+
|
100
|
+
|
58
101
|
async def _create_connection(config: PlanarConfig) -> DuckDBBackend:
|
59
102
|
"""Create Ibis DuckDB connection with Ducklake."""
|
60
103
|
data_config = config.data
|
@@ -105,6 +148,14 @@ async def _create_connection(config: PlanarConfig) -> DuckDBBackend:
|
|
105
148
|
if isinstance(storage, LocalDirectoryConfig):
|
106
149
|
data_path = storage.directory
|
107
150
|
elif isinstance(storage, S3Config):
|
151
|
+
await asyncio.to_thread(con.raw_sql, "INSTALL httpfs;")
|
152
|
+
await asyncio.to_thread(con.raw_sql, "LOAD httpfs;")
|
153
|
+
|
154
|
+
await asyncio.to_thread(
|
155
|
+
con.raw_sql,
|
156
|
+
_make_aws_s3_secret_query(storage),
|
157
|
+
)
|
158
|
+
|
108
159
|
data_path = f"s3://{storage.bucket_name}/"
|
109
160
|
else:
|
110
161
|
# Generic fallback
|
planar/human/__init__.py
CHANGED
@@ -1,2 +1,11 @@
|
|
1
|
-
from .human import Human, Timeout, complete_human_task, get_human_tasks
|
2
|
-
from .models import HumanTask, HumanTaskResult
|
1
|
+
from .human import Human, Timeout, complete_human_task, get_human_tasks
|
2
|
+
from .models import HumanTask, HumanTaskResult
|
3
|
+
|
4
|
+
__all__ = [
|
5
|
+
"Human",
|
6
|
+
"Timeout",
|
7
|
+
"complete_human_task",
|
8
|
+
"get_human_tasks",
|
9
|
+
"HumanTask",
|
10
|
+
"HumanTaskResult",
|
11
|
+
]
|
planar/human/models.py
CHANGED
@@ -7,7 +7,6 @@ from pydantic import BaseModel
|
|
7
7
|
from sqlmodel import JSON, Column, Field
|
8
8
|
|
9
9
|
from planar.db import PlanarInternalBase
|
10
|
-
from planar.modeling.field_helpers import JsonSchema
|
11
10
|
from planar.modeling.mixins import TimestampMixin
|
12
11
|
from planar.modeling.mixins.auditable import AuditableMixin
|
13
12
|
from planar.modeling.mixins.uuid_primary_key import UUIDPrimaryKeyMixin
|
@@ -48,12 +47,12 @@ class HumanTask(
|
|
48
47
|
workflow_name: str
|
49
48
|
|
50
49
|
# Input data for context
|
51
|
-
input_schema: Optional[
|
50
|
+
input_schema: Optional[dict[str, Any]] = Field(default=None, sa_column=Column(JSON))
|
52
51
|
input_data: Optional[dict[str, Any]] = Field(default=None, sa_column=Column(JSON))
|
53
52
|
message: Optional[str] = Field(default=None)
|
54
53
|
|
55
54
|
# Schema for expected output
|
56
|
-
output_schema:
|
55
|
+
output_schema: dict[str, Any] = Field(sa_column=Column(JSON))
|
57
56
|
output_data: Optional[dict[str, Any]] = Field(default=None, sa_column=Column(JSON))
|
58
57
|
|
59
58
|
# Suggested data for the form (optional)
|
planar/logging/attributes.py
CHANGED
@@ -3,8 +3,16 @@ name = "{{ name }}"
|
|
3
3
|
version = "0.1.0"
|
4
4
|
requires-python = ">=3.12"
|
5
5
|
dependencies = [
|
6
|
-
|
6
|
+
{%- if local_source %}
|
7
|
+
"planar",
|
8
|
+
{%- else %}
|
9
|
+
"planar>=0.10.0",
|
10
|
+
{%- endif %}
|
7
11
|
]
|
8
12
|
|
9
|
-
|
10
|
-
|
13
|
+
{%- if local_source %}
|
14
|
+
|
15
|
+
[tool.uv.sources]
|
16
|
+
planar = { path = "{{ planar_source_path }}", editable = true }
|
17
|
+
|
18
|
+
{%- endif %}
|
planar/sse/proxy.py
CHANGED
@@ -178,7 +178,13 @@ class SSEProxy:
|
|
178
178
|
# builtin hub. That's why we check for self.builtin_process instead of
|
179
179
|
# self.enable_builtin_hub. Also helps with type checking.
|
180
180
|
logger.info("terminating builtin sse hub process")
|
181
|
-
|
181
|
+
try:
|
182
|
+
self.builtin_process.terminate()
|
183
|
+
except AttributeError:
|
184
|
+
# Race condition: _popen may be None if process already terminated.
|
185
|
+
# This can happen when stop() is called multiple times during shutdown.
|
186
|
+
logger.debug("process already terminated or cleaned up")
|
187
|
+
self.builtin_process = None
|
182
188
|
|
183
189
|
def setup_proxy_endpoint(self):
|
184
190
|
@self.router.get("/")
|
@@ -104,11 +104,15 @@ def agent_notify(kind: Notification, data: str):
|
|
104
104
|
if callback is not None:
|
105
105
|
context = get_context()
|
106
106
|
logger.debug("notifying agent event", kind=kind)
|
107
|
+
if context.step_stack:
|
108
|
+
step_id = context.step_stack[-1].step_id
|
109
|
+
else:
|
110
|
+
step_id = -1
|
107
111
|
callback(
|
108
112
|
WorkflowNotification(
|
109
113
|
kind=kind,
|
110
114
|
workflow_id=context.workflow_id,
|
111
|
-
data=AgentEventData(data=data, step_id=
|
115
|
+
data=AgentEventData(data=data, step_id=step_id),
|
112
116
|
)
|
113
117
|
)
|
114
118
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: planar
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.13.2
|
4
4
|
Summary: Batteries-included framework for building durable agentic workflows and business applications.
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
6
6
|
Requires-Dist: aiofiles>=24.1.0
|
@@ -25,6 +25,7 @@ Requires-Dist: zen-engine>=0.40.0
|
|
25
25
|
Requires-Dist: azure-storage-blob>=12.19.0 ; extra == 'azure'
|
26
26
|
Requires-Dist: azure-identity>=1.15.0 ; extra == 'azure'
|
27
27
|
Requires-Dist: aiohttp>=3.8.0 ; extra == 'azure'
|
28
|
+
Requires-Dist: duckdb>=1.3,<1.4 ; extra == 'data'
|
28
29
|
Requires-Dist: ducklake>=0.1.1 ; extra == 'data'
|
29
30
|
Requires-Dist: ibis-framework[duckdb]>=10.8.0 ; extra == 'data'
|
30
31
|
Requires-Dist: polars>=1.31.0 ; extra == 'data'
|
@@ -1,18 +1,18 @@
|
|
1
1
|
planar/__init__.py,sha256=FAYRGjuJOH2Y_XYFA0-BrRFjuKdPzIShNbaYwJbtu6A,499
|
2
|
-
planar/ai/__init__.py,sha256=
|
3
|
-
planar/ai/agent.py,sha256=
|
4
|
-
planar/ai/agent_base.py,sha256=
|
2
|
+
planar/ai/__init__.py,sha256=lgGmY3ffmKqt35JncZmJOK5jWZ0bJRAksR54hYqV8is,298
|
3
|
+
planar/ai/agent.py,sha256=xNJiQV-FC1BVkIBb9zpIy8NKN7jQl1j21TMiACyxBZg,12513
|
4
|
+
planar/ai/agent_base.py,sha256=0P4vjTvka8MERP9z360J4nGd-TAfeafK9zczbhcvU9k,6410
|
5
5
|
planar/ai/agent_utils.py,sha256=MYNerdAm2TPVbDSKAmBCUlGmR56NAc8seZmDAFOWvUA,4199
|
6
6
|
planar/ai/models.py,sha256=bZd4MoBBJMqzXJqsmsbMdZtOaRrNeX438CHAqOvmpfw,4598
|
7
7
|
planar/ai/pydantic_ai.py,sha256=FpD0pE7wWNYwmEUZ90D7_J8gbAoqKmWtrLr2fhAd7rg,23503
|
8
|
-
planar/ai/
|
8
|
+
planar/ai/tool_context.py,sha256=9W7VFrHPhNeieEvlJlv-OrFPVzySItdUjKCutkXpYFU,298
|
9
9
|
planar/ai/utils.py,sha256=WVBW0TGaoKytC4bNd_a9lXrBf5QsDRut4GBcA53U2Ww,3116
|
10
10
|
planar/app.py,sha256=2ijbrORCfjKReDieLbwgey_9poJJwEfWkvCNKcVYcdk,19484
|
11
|
-
planar/cli.py,sha256=
|
12
|
-
planar/config.py,sha256=
|
11
|
+
planar/cli.py,sha256=GONw7Dpyxy_7Vf48rLmQXRQnC_IyWENRULQF0DwiCuY,11108
|
12
|
+
planar/config.py,sha256=54N-qPaxihEn6QUQvXsdtbEN-pTTrd_5NG8WFYb0CRI,18879
|
13
13
|
planar/data/__init__.py,sha256=eqSREzJ58HM05DXpR_00M6RDFxtHSIh-OEuqXBPVsVc,362
|
14
14
|
planar/data/config.py,sha256=oaLiwN3OUt3i85MS0zxpdMNG5VjaM5xB6VtPc1f4MFU,1484
|
15
|
-
planar/data/connection.py,sha256=
|
15
|
+
planar/data/connection.py,sha256=QPcbomoWDdttu28cfeQAtnUdc_ptayQTaqwcC8Gq4vc,8056
|
16
16
|
planar/data/dataset.py,sha256=f-x9cOuGyQQldC98mCwpFVOXMiDNN6LQDQ9vtljXVRo,6095
|
17
17
|
planar/data/exceptions.py,sha256=AlhGQ_TReyEzfPSlqoXCjoZ1206Ut7dS4lrukVfGHaw,358
|
18
18
|
planar/data/utils.py,sha256=NhxJFSxPLNX1ddSANS3-bVhMr1Q_-25qDRRXxGug4Vc,6312
|
@@ -32,11 +32,11 @@ planar/files/storage/config.py,sha256=lzODgufhZliwQDikqMQCX3KKiap0CL6Cc9qbIdeUMC
|
|
32
32
|
planar/files/storage/context.py,sha256=Qadlz4T-FQ1A0TdGsOfuNmM679ohayU7oaexUpT8AY0,361
|
33
33
|
planar/files/storage/local_directory.py,sha256=1SsEfr0Ud9TvSQJneDl_M-D7AFYixLE9t-bVIiY3aSI,7395
|
34
34
|
planar/files/storage/s3.py,sha256=1861rSw3kplXtugUWD7mdSD_EnPSHME1mGc82V69r5g,8234
|
35
|
-
planar/human/__init__.py,sha256=
|
35
|
+
planar/human/__init__.py,sha256=SOdDlIvf4QOz_wGhJ_w-7v6b1daJHbO785TsJUVAzOc,252
|
36
36
|
planar/human/human.py,sha256=-oRtN_8bCtSV7Sxku7yG4rof7T5pr4j18Cfm3u4Z3PM,14925
|
37
|
-
planar/human/models.py,sha256=
|
37
|
+
planar/human/models.py,sha256=uYqH79qgwH2HM6BO7dR54WFcvleVCfpZOYRSNJZ9P2o,2272
|
38
38
|
planar/logging/__init__.py,sha256=BW101gskQS3C2agKSDiJEy4L-j5z6BP1AwEfd2JMXHM,254
|
39
|
-
planar/logging/attributes.py,sha256=
|
39
|
+
planar/logging/attributes.py,sha256=4JCmncF46MbGt6q4wD60DZss8eBWDdKDvxS8w8lE9fc,1580
|
40
40
|
planar/logging/context.py,sha256=mGVpL-276VWpRUIGlkGwbn9gkATqFS7lcFn1aM0v-eQ,384
|
41
41
|
planar/logging/formatter.py,sha256=NMDhpo7ezsfGO7cPwOWbClqODviEgSjqHCoqrCCIu2c,3560
|
42
42
|
planar/logging/logger.py,sha256=vj3-z2ea0J1ppgDpJBsrueA3jg5kjk9ceIo-4tWarXk,2650
|
@@ -81,7 +81,7 @@ planar/scaffold_templates/app/flows/process_invoice.py.j2,sha256=R3EII_O2DHV1kvf
|
|
81
81
|
planar/scaffold_templates/main.py.j2,sha256=zrqsuv3Fp4lcknvB37RrRHy11msdFB1yDguYmTLLPhw,398
|
82
82
|
planar/scaffold_templates/planar.dev.yaml.j2,sha256=I5-IqX7GJm6qA91WtUMw43L4hKACqgnER_H2racim4c,998
|
83
83
|
planar/scaffold_templates/planar.prod.yaml.j2,sha256=FahJ2atDtvVH7IUCatGq6h9hmyF8meeiWC8RLfWphOQ,867
|
84
|
-
planar/scaffold_templates/pyproject.toml.j2,sha256=
|
84
|
+
planar/scaffold_templates/pyproject.toml.j2,sha256=XdOf3B_fkTDwEzQsneNdmC43rptmZoqwFRj7QpaL7OI,299
|
85
85
|
planar/security/auth_context.py,sha256=i63JkHQ3oXNlTis7GIKRkZJbkcvZhD2jVDuO7blgbSc,5068
|
86
86
|
planar/security/auth_middleware.py,sha256=Grrm0i2bstWZ83ukrNZsHvFbNzffN0rvbbCcb2OxRY0,5746
|
87
87
|
planar/security/authorization.py,sha256=zTh5rLmVJZnGDq540_1WYljJ7Hws-BY_P6VqUCONqLE,12760
|
@@ -92,7 +92,7 @@ planar/sse/constants.py,sha256=jE3SooTEWPuuL_Bi6DisJYMR9pKOiHVfboU2h5QTJRg,22
|
|
92
92
|
planar/sse/example.html,sha256=SgTJbdJ3B1F1DxLC2YWuX2F1XVwKcTjX34CbJCXoCTM,4144
|
93
93
|
planar/sse/hub.py,sha256=5jhfk7zdCivau3TT1MxU2qtvETSskhqEiXzt-t0sRpE,6859
|
94
94
|
planar/sse/model.py,sha256=fU_Fx9LS2ouS6-Dj1TIF-PLGul9YratKWafoWfZR1gc,123
|
95
|
-
planar/sse/proxy.py,sha256=
|
95
|
+
planar/sse/proxy.py,sha256=LgfnCjZHtnDquV5HpdoSru43pxk-T5aY9NCjQLNrrcQ,9518
|
96
96
|
planar/task_local.py,sha256=pyvT0bdzAn15HL2yQUs9YrU5MVXh9njQt9MH51AGljs,1102
|
97
97
|
planar/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
98
98
|
planar/testing/fixtures.py,sha256=z0VkKTqAg9z_1L_ojB0pTzKSaLPcDeNxRq_RB36rUU8,9740
|
@@ -112,7 +112,7 @@ planar/workflows/execution.py,sha256=8c4a2L1qRMPQrCEJ8-sEk-gJi_xKq5gYKDSWSbSspVI
|
|
112
112
|
planar/workflows/lock.py,sha256=QU5_y_n8RHOC7ppLicH7yWX-Ni7N93hlB14D2zXOQ8A,8773
|
113
113
|
planar/workflows/misc.py,sha256=g3XVRMeU9mgDRi_6RgFdydLEqvTAg49wbIGlmC7kOu8,140
|
114
114
|
planar/workflows/models.py,sha256=SKBJTGhd4nVWxtlDkaQrU2RvRTtoj07PJhLT73cF_ac,5591
|
115
|
-
planar/workflows/notifications.py,sha256=
|
115
|
+
planar/workflows/notifications.py,sha256=APAUKk1-Y7R5E1LHA4RWQK5Z6vh2lIvf5tHYWwhD5WM,3895
|
116
116
|
planar/workflows/orchestrator.py,sha256=rneB1yOPDZiJcHFbD6UDZ4juU77iSBK1eu1gOFm58vM,15480
|
117
117
|
planar/workflows/query.py,sha256=38B5SLwXf3AlA_1ChR5DicFWdcUqzpQzMkuAUCNHafI,8838
|
118
118
|
planar/workflows/serialization.py,sha256=v3eqUS0odUFS7PnIrKyKUrK-feIv0ssxEp2VxjkddyI,13733
|
@@ -122,7 +122,7 @@ planar/workflows/step_testing_utils.py,sha256=WiTwxB4mM2y6dW7CJ3PlIR1BkBodSxQV7-
|
|
122
122
|
planar/workflows/sub_workflow_runner.py,sha256=EpS7DhhXRbC6ABm-Sho6Uyxh2TqCjcTPDYvcTQN4FjY,8313
|
123
123
|
planar/workflows/tracing.py,sha256=E7E_kj2VBQisDqrllviIshbvOmB9QcEeRwMapunqio4,2732
|
124
124
|
planar/workflows/wrappers.py,sha256=dY_3NqkzGMG4jgX2lkAqvHTYFA1lBzhkQCw7N5CyaQM,1174
|
125
|
-
planar-0.
|
126
|
-
planar-0.
|
127
|
-
planar-0.
|
128
|
-
planar-0.
|
125
|
+
planar-0.13.2.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
126
|
+
planar-0.13.2.dist-info/entry_points.txt,sha256=L3T0w9u2UPKWXv6JbXFWKU1d5xyEAq1xVWbpYS6mLNg,96
|
127
|
+
planar-0.13.2.dist-info/METADATA,sha256=hHuWQx_u5HHzw4onoBPARIX8wVjU_PwW_ia6X5g7oPU,7335
|
128
|
+
planar-0.13.2.dist-info/RECORD,,
|
File without changes
|