krail 0.2.1__tar.gz
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.
- krail-0.2.1/PKG-INFO +74 -0
- krail-0.2.1/README.md +38 -0
- krail-0.2.1/krail.egg-info/PKG-INFO +74 -0
- krail-0.2.1/krail.egg-info/SOURCES.txt +44 -0
- krail-0.2.1/krail.egg-info/dependency_links.txt +1 -0
- krail-0.2.1/krail.egg-info/entry_points.txt +3 -0
- krail-0.2.1/krail.egg-info/requires.txt +16 -0
- krail-0.2.1/krail.egg-info/top_level.txt +1 -0
- krail-0.2.1/pyproject.toml +50 -0
- krail-0.2.1/rail/__init__.py +53 -0
- krail-0.2.1/rail/agent.py +59 -0
- krail-0.2.1/rail/bootstrap.py +1211 -0
- krail-0.2.1/rail/cli.py +884 -0
- krail-0.2.1/rail/client.py +113 -0
- krail-0.2.1/rail/completion_gate.py +233 -0
- krail-0.2.1/rail/exceptions.py +11 -0
- krail-0.2.1/rail/integrity.py +4440 -0
- krail-0.2.1/rail/knowledge.py +3151 -0
- krail-0.2.1/rail/local.py +706 -0
- krail-0.2.1/rail/manifest.py +514 -0
- krail-0.2.1/rail/markdown_graph.py +585 -0
- krail-0.2.1/rail/models.py +18 -0
- krail-0.2.1/rail/modes.py +130 -0
- krail-0.2.1/rail/ontology.py +61 -0
- krail-0.2.1/rail/planner_sync.py +244 -0
- krail-0.2.1/rail/project.py +682 -0
- krail-0.2.1/rail/session_state.py +41 -0
- krail-0.2.1/rail/source_dependencies.py +278 -0
- krail-0.2.1/rail/vector_store.py +276 -0
- krail-0.2.1/rail/verification.py +414 -0
- krail-0.2.1/setup.cfg +4 -0
- krail-0.2.1/tests/test_bootstrap.py +190 -0
- krail-0.2.1/tests/test_cli.py +243 -0
- krail-0.2.1/tests/test_completion_gate.py +335 -0
- krail-0.2.1/tests/test_integrity.py +3279 -0
- krail-0.2.1/tests/test_issue_intake.py +66 -0
- krail-0.2.1/tests/test_knowledge_modes.py +148 -0
- krail-0.2.1/tests/test_local_hydration_alignment.py +339 -0
- krail-0.2.1/tests/test_manifest.py +280 -0
- krail-0.2.1/tests/test_markdown_graph.py +283 -0
- krail-0.2.1/tests/test_planner_sync.py +342 -0
- krail-0.2.1/tests/test_session_state.py +21 -0
- krail-0.2.1/tests/test_source_dependencies.py +61 -0
- krail-0.2.1/tests/test_think.py +192 -0
- krail-0.2.1/tests/test_verification.py +549 -0
- krail-0.2.1/tests/test_workflows.py +286 -0
krail-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: krail
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: KRAIL client for local projects and the local API runtime
|
|
5
|
+
Author: AkeBoss Tech
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/AkeBoss-tech/knowledge
|
|
8
|
+
Project-URL: Repository, https://github.com/AkeBoss-tech/knowledge
|
|
9
|
+
Project-URL: Issues, https://github.com/AkeBoss-tech/knowledge/issues
|
|
10
|
+
Keywords: knowledge,research,agents,local-first,mcp
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: pydantic>=2
|
|
25
|
+
Requires-Dist: pyyaml>=6
|
|
26
|
+
Provides-Extra: analysis
|
|
27
|
+
Requires-Dist: numpy; extra == "analysis"
|
|
28
|
+
Requires-Dist: pandas; extra == "analysis"
|
|
29
|
+
Requires-Dist: statsmodels; extra == "analysis"
|
|
30
|
+
Requires-Dist: matplotlib; extra == "analysis"
|
|
31
|
+
Provides-Extra: local
|
|
32
|
+
Requires-Dist: owlready2; extra == "local"
|
|
33
|
+
Requires-Dist: duckdb; extra == "local"
|
|
34
|
+
Provides-Extra: embeddings
|
|
35
|
+
Requires-Dist: sentence-transformers>=2.7; extra == "embeddings"
|
|
36
|
+
|
|
37
|
+
# krail
|
|
38
|
+
|
|
39
|
+
KRAIL client supporting local project mode and the local FastAPI runtime.
|
|
40
|
+
|
|
41
|
+
Install from PyPI:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install krail
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The distribution is named `krail`; the Python import namespace remains `rail`.
|
|
48
|
+
|
|
49
|
+
## Usage Examples
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import rail
|
|
53
|
+
|
|
54
|
+
# API mode
|
|
55
|
+
project = rail.connect("nj-economics")
|
|
56
|
+
|
|
57
|
+
# DataFrame queries
|
|
58
|
+
df = project.query("SELECT county_name, unemployment_rate FROM County ORDER BY unemployment_rate DESC LIMIT 10")
|
|
59
|
+
|
|
60
|
+
# Agent research
|
|
61
|
+
answer = project.agent.ask("What counties had unemployment above 10% in 2020?")
|
|
62
|
+
print(answer)
|
|
63
|
+
|
|
64
|
+
# Streaming agent
|
|
65
|
+
for event in project.agent.ask("Compare Hudson and Bergen County unemployment trends", stream=True):
|
|
66
|
+
if event["type"] == "text_delta":
|
|
67
|
+
print(event["text"], end="", flush=True)
|
|
68
|
+
|
|
69
|
+
# Direct local mode
|
|
70
|
+
project = rail.local("./nj-economics")
|
|
71
|
+
ont = project.ontology()
|
|
72
|
+
counties = ont.individuals("County")
|
|
73
|
+
print(f"Loaded {len(counties)} counties")
|
|
74
|
+
```
|
krail-0.2.1/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# krail
|
|
2
|
+
|
|
3
|
+
KRAIL client supporting local project mode and the local FastAPI runtime.
|
|
4
|
+
|
|
5
|
+
Install from PyPI:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install krail
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The distribution is named `krail`; the Python import namespace remains `rail`.
|
|
12
|
+
|
|
13
|
+
## Usage Examples
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import rail
|
|
17
|
+
|
|
18
|
+
# API mode
|
|
19
|
+
project = rail.connect("nj-economics")
|
|
20
|
+
|
|
21
|
+
# DataFrame queries
|
|
22
|
+
df = project.query("SELECT county_name, unemployment_rate FROM County ORDER BY unemployment_rate DESC LIMIT 10")
|
|
23
|
+
|
|
24
|
+
# Agent research
|
|
25
|
+
answer = project.agent.ask("What counties had unemployment above 10% in 2020?")
|
|
26
|
+
print(answer)
|
|
27
|
+
|
|
28
|
+
# Streaming agent
|
|
29
|
+
for event in project.agent.ask("Compare Hudson and Bergen County unemployment trends", stream=True):
|
|
30
|
+
if event["type"] == "text_delta":
|
|
31
|
+
print(event["text"], end="", flush=True)
|
|
32
|
+
|
|
33
|
+
# Direct local mode
|
|
34
|
+
project = rail.local("./nj-economics")
|
|
35
|
+
ont = project.ontology()
|
|
36
|
+
counties = ont.individuals("County")
|
|
37
|
+
print(f"Loaded {len(counties)} counties")
|
|
38
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: krail
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: KRAIL client for local projects and the local API runtime
|
|
5
|
+
Author: AkeBoss Tech
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/AkeBoss-tech/knowledge
|
|
8
|
+
Project-URL: Repository, https://github.com/AkeBoss-tech/knowledge
|
|
9
|
+
Project-URL: Issues, https://github.com/AkeBoss-tech/knowledge/issues
|
|
10
|
+
Keywords: knowledge,research,agents,local-first,mcp
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: pydantic>=2
|
|
25
|
+
Requires-Dist: pyyaml>=6
|
|
26
|
+
Provides-Extra: analysis
|
|
27
|
+
Requires-Dist: numpy; extra == "analysis"
|
|
28
|
+
Requires-Dist: pandas; extra == "analysis"
|
|
29
|
+
Requires-Dist: statsmodels; extra == "analysis"
|
|
30
|
+
Requires-Dist: matplotlib; extra == "analysis"
|
|
31
|
+
Provides-Extra: local
|
|
32
|
+
Requires-Dist: owlready2; extra == "local"
|
|
33
|
+
Requires-Dist: duckdb; extra == "local"
|
|
34
|
+
Provides-Extra: embeddings
|
|
35
|
+
Requires-Dist: sentence-transformers>=2.7; extra == "embeddings"
|
|
36
|
+
|
|
37
|
+
# krail
|
|
38
|
+
|
|
39
|
+
KRAIL client supporting local project mode and the local FastAPI runtime.
|
|
40
|
+
|
|
41
|
+
Install from PyPI:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install krail
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The distribution is named `krail`; the Python import namespace remains `rail`.
|
|
48
|
+
|
|
49
|
+
## Usage Examples
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import rail
|
|
53
|
+
|
|
54
|
+
# API mode
|
|
55
|
+
project = rail.connect("nj-economics")
|
|
56
|
+
|
|
57
|
+
# DataFrame queries
|
|
58
|
+
df = project.query("SELECT county_name, unemployment_rate FROM County ORDER BY unemployment_rate DESC LIMIT 10")
|
|
59
|
+
|
|
60
|
+
# Agent research
|
|
61
|
+
answer = project.agent.ask("What counties had unemployment above 10% in 2020?")
|
|
62
|
+
print(answer)
|
|
63
|
+
|
|
64
|
+
# Streaming agent
|
|
65
|
+
for event in project.agent.ask("Compare Hudson and Bergen County unemployment trends", stream=True):
|
|
66
|
+
if event["type"] == "text_delta":
|
|
67
|
+
print(event["text"], end="", flush=True)
|
|
68
|
+
|
|
69
|
+
# Direct local mode
|
|
70
|
+
project = rail.local("./nj-economics")
|
|
71
|
+
ont = project.ontology()
|
|
72
|
+
counties = ont.individuals("County")
|
|
73
|
+
print(f"Loaded {len(counties)} counties")
|
|
74
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
krail.egg-info/PKG-INFO
|
|
4
|
+
krail.egg-info/SOURCES.txt
|
|
5
|
+
krail.egg-info/dependency_links.txt
|
|
6
|
+
krail.egg-info/entry_points.txt
|
|
7
|
+
krail.egg-info/requires.txt
|
|
8
|
+
krail.egg-info/top_level.txt
|
|
9
|
+
rail/__init__.py
|
|
10
|
+
rail/agent.py
|
|
11
|
+
rail/bootstrap.py
|
|
12
|
+
rail/cli.py
|
|
13
|
+
rail/client.py
|
|
14
|
+
rail/completion_gate.py
|
|
15
|
+
rail/exceptions.py
|
|
16
|
+
rail/integrity.py
|
|
17
|
+
rail/knowledge.py
|
|
18
|
+
rail/local.py
|
|
19
|
+
rail/manifest.py
|
|
20
|
+
rail/markdown_graph.py
|
|
21
|
+
rail/models.py
|
|
22
|
+
rail/modes.py
|
|
23
|
+
rail/ontology.py
|
|
24
|
+
rail/planner_sync.py
|
|
25
|
+
rail/project.py
|
|
26
|
+
rail/session_state.py
|
|
27
|
+
rail/source_dependencies.py
|
|
28
|
+
rail/vector_store.py
|
|
29
|
+
rail/verification.py
|
|
30
|
+
tests/test_bootstrap.py
|
|
31
|
+
tests/test_cli.py
|
|
32
|
+
tests/test_completion_gate.py
|
|
33
|
+
tests/test_integrity.py
|
|
34
|
+
tests/test_issue_intake.py
|
|
35
|
+
tests/test_knowledge_modes.py
|
|
36
|
+
tests/test_local_hydration_alignment.py
|
|
37
|
+
tests/test_manifest.py
|
|
38
|
+
tests/test_markdown_graph.py
|
|
39
|
+
tests/test_planner_sync.py
|
|
40
|
+
tests/test_session_state.py
|
|
41
|
+
tests/test_source_dependencies.py
|
|
42
|
+
tests/test_think.py
|
|
43
|
+
tests/test_verification.py
|
|
44
|
+
tests/test_workflows.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rail
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "krail"
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
description = "KRAIL client for local projects and the local API runtime"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "AkeBoss Tech" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["knowledge", "research", "agents", "local-first", "mcp"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Intended Audience :: Science/Research",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"httpx>=0.27",
|
|
30
|
+
"pydantic>=2",
|
|
31
|
+
"pyyaml>=6",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/AkeBoss-tech/knowledge"
|
|
36
|
+
Repository = "https://github.com/AkeBoss-tech/knowledge"
|
|
37
|
+
Issues = "https://github.com/AkeBoss-tech/knowledge/issues"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
analysis = ["numpy", "pandas", "statsmodels", "matplotlib"]
|
|
41
|
+
local = ["owlready2", "duckdb"]
|
|
42
|
+
embeddings = ["sentence-transformers>=2.7"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
where = ["."]
|
|
46
|
+
include = ["rail*"]
|
|
47
|
+
|
|
48
|
+
[project.scripts]
|
|
49
|
+
rail = "rail.cli:main"
|
|
50
|
+
krail = "rail.cli:main"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from rail.project import Project
|
|
2
|
+
from rail.client import CloudClient
|
|
3
|
+
from rail.local import LocalEngine
|
|
4
|
+
from rail.integrity import (
|
|
5
|
+
ArtifactLineageRecord,
|
|
6
|
+
AssumptionRecord,
|
|
7
|
+
ClaimRecord,
|
|
8
|
+
IntegrityIndexes,
|
|
9
|
+
ResearchIntegrityRepo,
|
|
10
|
+
SourceRecord,
|
|
11
|
+
VerificationRunRecord,
|
|
12
|
+
)
|
|
13
|
+
from rail.manifest import (
|
|
14
|
+
ContractViolation,
|
|
15
|
+
ManifestValidationError,
|
|
16
|
+
RailManifest,
|
|
17
|
+
boot_validate_project,
|
|
18
|
+
load_and_validate_manifest,
|
|
19
|
+
load_manifest,
|
|
20
|
+
parse_manifest_content,
|
|
21
|
+
)
|
|
22
|
+
from rail.session_state import (
|
|
23
|
+
ACTIVE_STATUSES,
|
|
24
|
+
TERMINAL_STATUSES,
|
|
25
|
+
is_active_status,
|
|
26
|
+
is_terminal_status,
|
|
27
|
+
normalize_session_record,
|
|
28
|
+
normalize_session_status,
|
|
29
|
+
)
|
|
30
|
+
from rail.exceptions import RailError
|
|
31
|
+
|
|
32
|
+
__version__ = "0.2.1"
|
|
33
|
+
|
|
34
|
+
def connect(
|
|
35
|
+
slug: str,
|
|
36
|
+
api_url: str | None = None,
|
|
37
|
+
api_key: str | None = None,
|
|
38
|
+
) -> Project:
|
|
39
|
+
"""Connect to a KRAIL project via the local FastAPI runtime."""
|
|
40
|
+
import os
|
|
41
|
+
url = api_url or os.environ.get("RAIL_API_URL", "http://localhost:8000/api/v1")
|
|
42
|
+
key = api_key or os.environ.get("RAIL_API_KEY", "")
|
|
43
|
+
client = CloudClient(base_url=url, api_key=key)
|
|
44
|
+
return Project(slug=slug, backend=client)
|
|
45
|
+
|
|
46
|
+
def local(
|
|
47
|
+
path: str = ".",
|
|
48
|
+
engine_path: str | None = None,
|
|
49
|
+
) -> Project:
|
|
50
|
+
"""Load a KRAIL project from a local repo directory."""
|
|
51
|
+
engine = LocalEngine(project_path=path, engine_path=engine_path)
|
|
52
|
+
slug = engine.read_rail_yaml().project.slug
|
|
53
|
+
return Project(slug=slug, backend=engine)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import httpx
|
|
3
|
+
from typing import Generator, AsyncGenerator
|
|
4
|
+
|
|
5
|
+
class AgentClient:
|
|
6
|
+
def __init__(self, base_url: str, project_slug: str, api_key: str = ""):
|
|
7
|
+
self.base_url = base_url.rstrip("/")
|
|
8
|
+
self.project_slug = project_slug
|
|
9
|
+
self.headers = {"Authorization": f"Bearer {api_key}"} if api_key else {}
|
|
10
|
+
|
|
11
|
+
def ask(self, question: str, stream: bool = False, model: str | None = None):
|
|
12
|
+
"""Ask a question. Returns full answer text (stream=False) or event generator (stream=True)."""
|
|
13
|
+
if stream:
|
|
14
|
+
return self._stream(question, model)
|
|
15
|
+
else:
|
|
16
|
+
return self._blocking(question, model)
|
|
17
|
+
|
|
18
|
+
def _blocking(self, question: str, model: str | None) -> str:
|
|
19
|
+
"""Collect all text_delta events and return the full answer."""
|
|
20
|
+
text = []
|
|
21
|
+
for event in self._stream(question, model):
|
|
22
|
+
if event.get("type") == "text_delta":
|
|
23
|
+
text.append(event.get("text", ""))
|
|
24
|
+
return "".join(text)
|
|
25
|
+
|
|
26
|
+
def _stream(self, question: str, model: str | None) -> Generator[dict, None, None]:
|
|
27
|
+
"""Yield raw SSE event dicts."""
|
|
28
|
+
url = f"{self.base_url}/agent/chat?project={self.project_slug}"
|
|
29
|
+
payload = {"message": question, "history": []}
|
|
30
|
+
if model:
|
|
31
|
+
payload["model"] = model
|
|
32
|
+
|
|
33
|
+
with httpx.Client(timeout=300) as client:
|
|
34
|
+
with client.stream("POST", url, json=payload, headers=self.headers) as resp:
|
|
35
|
+
resp.raise_for_status()
|
|
36
|
+
for line in resp.iter_lines():
|
|
37
|
+
if line.startswith("data: "):
|
|
38
|
+
try:
|
|
39
|
+
event = json.loads(line[6:])
|
|
40
|
+
yield event
|
|
41
|
+
except json.JSONDecodeError:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
async def ask_async(self, question: str, model: str | None = None) -> AsyncGenerator[dict, None]:
|
|
45
|
+
"""Async streaming version."""
|
|
46
|
+
url = f"{self.base_url}/agent/chat?project={self.project_slug}"
|
|
47
|
+
payload = {"message": question, "history": []}
|
|
48
|
+
if model:
|
|
49
|
+
payload["model"] = model
|
|
50
|
+
|
|
51
|
+
async with httpx.AsyncClient(timeout=300) as client:
|
|
52
|
+
async with client.stream("POST", url, json=payload, headers=self.headers) as resp:
|
|
53
|
+
resp.raise_for_status()
|
|
54
|
+
async for line in resp.aiter_lines():
|
|
55
|
+
if line.startswith("data: "):
|
|
56
|
+
try:
|
|
57
|
+
yield json.loads(line[6:])
|
|
58
|
+
except json.JSONDecodeError:
|
|
59
|
+
pass
|