mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-06 07:29:24 +00:00
* Require explicit CrewAI project definitions
JSON crews and declarative flows now resolve from `[tool.crewai]`
metadata instead of implicit filename discovery. This makes project type
selection deterministic, prevents stray `crew.json(c)` files from changing
CLI behavior, and centralizes definition path validation for run, install,
deploy validation, plotting, and memory reset paths.
`[tool.crewai].definition` must be a project-local file path. Absolute
paths, `~`, missing files, directories, and paths escaping the project root
are rejected so deploy and runtime commands use the same contract.
Breaking changes and migration paths:
* JSON crew projects are no longer discovered from `crew.json` or
`crew.jsonc` alone. Add explicit metadata:
```toml
[tool.crewai]
type = "crew"
definition = "crew.jsonc"
```
* Declarative flow projects must use a valid project-local definition path:
```toml
[tool.crewai]
type = "flow"
definition = "flows/research.yaml"
```
* `Flow.from_definition(definition)` is removed. Use:
```python
Flow.from_declaration(contents=definition)
```
* `FlowDefinition.to_json()` and `FlowDefinition.to_yaml()` are removed.
Use `FlowDefinition.to_dict()` and serialize with the caller's JSON or
YAML library.
* `FlowDefinition.from_dict()` is removed. Use:
```python
FlowDefinition.from_declaration(contents=data)
```
* `FlowDefinition.json_schema()` is removed. Use Pydantic's schema API only
where schema generation is intentionally needed:
```python
FlowDefinition.model_json_schema(by_alias=True)
```
* `crewai_cli.run_crew.find_crew_json_file()` and `_has_json_crew()` are
removed. Use `configured_project_json_crew()` or the shared
`crewai_core.project.configured_project_definition("crew")` helper.
* `crewai reset-memories` now only loads JSON crews declared through
`[tool.crewai].definition`, and invalid declared JSON crew definitions
fail instead of silently falling back to classic crew discovery.
* Address code review comments
209 lines
6.2 KiB
Python
209 lines
6.2 KiB
Python
"""Smoke tests for the crewai-core leaf modules."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from crewai_core import (
|
|
constants,
|
|
lock_store,
|
|
paths,
|
|
printer,
|
|
project,
|
|
user_data,
|
|
version,
|
|
)
|
|
from opentelemetry.sdk.trace import TracerProvider
|
|
import pytest
|
|
|
|
|
|
def test_version_returns_string() -> None:
|
|
v = version.get_crewai_version()
|
|
assert isinstance(v, str) and v
|
|
|
|
|
|
def test_paths_creates_storage_dir(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("CREWAI_STORAGE_DIR", str(tmp_path / "store"))
|
|
monkeypatch.setattr(
|
|
"crewai_core.paths.appdirs.user_data_dir",
|
|
lambda app, author: str(tmp_path / app),
|
|
)
|
|
out = paths.db_storage_path()
|
|
assert Path(out).exists()
|
|
|
|
|
|
def test_constants_exposes_env_keys() -> None:
|
|
assert constants.CREWAI_TRAINED_AGENTS_FILE_ENV == "CREWAI_TRAINED_AGENTS_FILE"
|
|
|
|
|
|
def test_printer_emits_when_not_suppressed(capsys: pytest.CaptureFixture[str]) -> None:
|
|
printer.PRINTER.print("hello", color="green")
|
|
out = capsys.readouterr().out
|
|
assert "hello" in out
|
|
|
|
|
|
def test_printer_respects_suppression(capsys: pytest.CaptureFixture[str]) -> None:
|
|
token = printer.set_suppress_console_output(True)
|
|
try:
|
|
printer.PRINTER.print("hidden")
|
|
finally:
|
|
printer._suppress_console_output.reset(token) # type: ignore[arg-type]
|
|
assert "hidden" not in capsys.readouterr().out
|
|
|
|
|
|
def test_lock_acquires_and_releases() -> None:
|
|
with lock_store.lock("crewai_core.tests.smoke", timeout=5):
|
|
pass
|
|
|
|
|
|
def test_user_data_round_trip(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("CREWAI_STORAGE_DIR", "crewai_core_test_user_data")
|
|
monkeypatch.setattr(
|
|
"crewai_core.paths.appdirs.user_data_dir",
|
|
lambda app, author: str(tmp_path / app),
|
|
)
|
|
user_data.update_user_data({"trace_consent": True, "first_execution_done": True})
|
|
data = user_data._load_user_data()
|
|
assert data == {"trace_consent": True, "first_execution_done": True}
|
|
assert user_data.has_user_declined_tracing() is False
|
|
monkeypatch.setenv("CREWAI_TRACING_ENABLED", "true")
|
|
assert user_data.is_tracing_enabled() is True
|
|
monkeypatch.delenv("CREWAI_TRACING_ENABLED", raising=False)
|
|
assert (
|
|
user_data.is_tracing_enabled() is True
|
|
) # consent alone enables (matches runtime)
|
|
|
|
|
|
def test_user_data_decline_blocks(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("CREWAI_STORAGE_DIR", "crewai_core_test_decline")
|
|
monkeypatch.setattr(
|
|
"crewai_core.paths.appdirs.user_data_dir",
|
|
lambda app, author: str(tmp_path / app),
|
|
)
|
|
user_data.update_user_data({"trace_consent": False, "first_execution_done": True})
|
|
assert user_data.has_user_declined_tracing() is True
|
|
monkeypatch.delenv("CREWAI_TRACING_ENABLED", raising=False)
|
|
assert user_data.is_tracing_enabled() is False
|
|
monkeypatch.setenv("CREWAI_TRACING_ENABLED", "true")
|
|
assert user_data.is_tracing_enabled() is True # env-var override (matches runtime)
|
|
|
|
|
|
def test_unused_var_warning_silenced() -> None:
|
|
# Touch os to keep the import (used by env-var fixtures above)
|
|
assert os.environ is not None
|
|
|
|
|
|
def test_configured_project_definition_resolves_project_local_file(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
definition = tmp_path / "crew.jsonc"
|
|
definition.write_text("{}\n")
|
|
|
|
resolved = project.configured_project_definition(
|
|
"crew",
|
|
pyproject_data={
|
|
"tool": {
|
|
"crewai": {
|
|
"type": "crew",
|
|
"definition": " crew.jsonc ",
|
|
}
|
|
}
|
|
},
|
|
project_root=tmp_path,
|
|
)
|
|
|
|
assert resolved == definition.resolve()
|
|
|
|
|
|
def test_configured_project_definition_rejects_project_escape(tmp_path: Path) -> None:
|
|
outside = tmp_path.parent / f"{tmp_path.name}-outside-crew.jsonc"
|
|
outside.write_text("{}\n")
|
|
|
|
with pytest.raises(project.ProjectDefinitionError):
|
|
project.configured_project_definition(
|
|
"crew",
|
|
pyproject_data={
|
|
"tool": {
|
|
"crewai": {
|
|
"type": "crew",
|
|
"definition": "../outside-crew.jsonc",
|
|
}
|
|
}
|
|
},
|
|
project_root=tmp_path,
|
|
)
|
|
|
|
|
|
def test_configured_project_definition_rejects_non_string_definition(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
with pytest.raises(project.ProjectDefinitionError, match="must be a string"):
|
|
project.configured_project_definition(
|
|
"crew",
|
|
pyproject_data={
|
|
"tool": {
|
|
"crewai": {
|
|
"type": "crew",
|
|
"definition": ["crew.jsonc"],
|
|
}
|
|
}
|
|
},
|
|
project_root=tmp_path,
|
|
)
|
|
|
|
|
|
def test_configured_project_definition_rejects_empty_definition(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
with pytest.raises(project.ProjectDefinitionError, match="non-empty"):
|
|
project.configured_project_definition(
|
|
"crew",
|
|
pyproject_data={
|
|
"tool": {
|
|
"crewai": {
|
|
"type": "crew",
|
|
"definition": " ",
|
|
}
|
|
}
|
|
},
|
|
project_root=tmp_path,
|
|
)
|
|
|
|
|
|
def test_core_telemetry_skips_duplicate_tracer_provider(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
from crewai_core.telemetry import Telemetry
|
|
|
|
Telemetry._instance = None
|
|
monkeypatch.delenv("OTEL_SDK_DISABLED", raising=False)
|
|
monkeypatch.delenv("CREWAI_DISABLE_TELEMETRY", raising=False)
|
|
monkeypatch.delenv("CREWAI_DISABLE_TRACKING", raising=False)
|
|
|
|
monkeypatch.setattr(
|
|
"crewai_core.telemetry.trace.get_tracer_provider",
|
|
lambda: TracerProvider(),
|
|
)
|
|
|
|
called = False
|
|
|
|
def fail_if_called(provider: object) -> None:
|
|
nonlocal called
|
|
called = True
|
|
|
|
monkeypatch.setattr(
|
|
"crewai_core.telemetry.trace.set_tracer_provider",
|
|
fail_if_called,
|
|
)
|
|
|
|
telemetry = Telemetry()
|
|
telemetry.set_tracer()
|
|
|
|
assert called is False
|
|
assert telemetry.trace_set is True
|