fraq 0.2.2__tar.gz → 0.2.3__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.
- {fraq-0.2.2 → fraq-0.2.3}/CHANGELOG.md +23 -0
- {fraq-0.2.2 → fraq-0.2.3}/PKG-INFO +1 -1
- fraq-0.2.3/examples/cli-docker/run.py +63 -0
- fraq-0.2.3/examples/fastapi-docker/main.py +86 -0
- fraq-0.2.3/examples/fastapi-docker/run.py +98 -0
- fraq-0.2.3/examples/fullstack-docker/api/main.py +38 -0
- fraq-0.2.3/examples/fullstack-docker/frontend/app.py +60 -0
- fraq-0.2.3/examples/fullstack-docker/websocket/main.py +52 -0
- fraq-0.2.3/examples/websocket-docker/main.py +104 -0
- {fraq-0.2.2 → fraq-0.2.3}/fraq/__init__.py +1 -1
- {fraq-0.2.2 → fraq-0.2.3}/fraq.egg-info/SOURCES.txt +7 -0
- {fraq-0.2.2 → fraq-0.2.3}/pyproject.toml +1 -1
- {fraq-0.2.2 → fraq-0.2.3}/LICENSE +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/MANIFEST.in +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/README.md +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/examples/api_server.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/examples/app_integrations.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/examples/applications.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/examples/async_streaming.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/examples/nlp2cmd_integration.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/examples/query_examples.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/examples/text2fraq_examples.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/examples/text2fraq_files.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/fraq/adapters.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/fraq/cli.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/fraq/core.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/fraq/formats.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/fraq/generators.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/fraq/py.typed +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/fraq/query.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/fraq/schema_export.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/fraq/streaming.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/fraq/text2fraq.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/setup.cfg +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/tests/__init__.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/tests/test_adapters.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/tests/test_cli.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/tests/test_core.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/tests/test_formats.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/tests/test_generators.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/tests/test_query.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/tests/test_schema_export.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/tests/test_streaming.py +0 -0
- {fraq-0.2.2 → fraq-0.2.3}/tests/test_text2fraq.py +0 -0
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.3] - 2026-03-17
|
|
6
|
+
|
|
7
|
+
### Docs
|
|
8
|
+
- Update docs/README.md
|
|
9
|
+
- Update examples/cli-docker/README.md
|
|
10
|
+
- Update examples/fastapi-docker/README.md
|
|
11
|
+
- Update examples/fullstack-docker/README.md
|
|
12
|
+
- Update examples/websocket-docker/README.md
|
|
13
|
+
- Update project/context.md
|
|
14
|
+
|
|
15
|
+
### Other
|
|
16
|
+
- Update Dockerfile.cli
|
|
17
|
+
- Update Dockerfile.websocket
|
|
18
|
+
- Update examples/cli-docker/Dockerfile
|
|
19
|
+
- Update examples/cli-docker/docker-compose.yml
|
|
20
|
+
- Update examples/cli-docker/run.sh
|
|
21
|
+
- Update examples/fastapi-docker/Dockerfile
|
|
22
|
+
- Update examples/fastapi-docker/docker-compose.yml
|
|
23
|
+
- Update examples/fastapi-docker/main.py
|
|
24
|
+
- Update examples/fastapi-docker/run.py
|
|
25
|
+
- Update examples/fastapi-docker/run.sh
|
|
26
|
+
- ... and 21 more files
|
|
27
|
+
|
|
5
28
|
## [0.2.2] - 2026-03-17
|
|
6
29
|
|
|
7
30
|
### Docs
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CLI Docker — Wrapper do uruchamiania fraq CLI w Dockerze
|
|
4
|
+
|
|
5
|
+
Użycie:
|
|
6
|
+
python run.py files search --ext pdf --limit 10 /data
|
|
7
|
+
python run.py nl "pokaż 10 plików"
|
|
8
|
+
python run.py --local explore --depth 5 # lokalnie, bez Docker
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run_in_docker(args_list):
|
|
18
|
+
"""Uruchom fraq CLI w Docker"""
|
|
19
|
+
cmd = ["docker-compose", "run", "--rm", "fraq-cli"] + args_list
|
|
20
|
+
|
|
21
|
+
print(f"🐳 Docker: {' '.join(cmd)}")
|
|
22
|
+
print("")
|
|
23
|
+
|
|
24
|
+
result = subprocess.run(cmd)
|
|
25
|
+
return result.returncode
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_local(args_list):
|
|
29
|
+
"""Uruchom fraq CLI lokalnie"""
|
|
30
|
+
cmd = ["fraq"] + args_list
|
|
31
|
+
|
|
32
|
+
print(f"🌀 Local: {' '.join(cmd)}")
|
|
33
|
+
print("")
|
|
34
|
+
|
|
35
|
+
result = subprocess.run(cmd)
|
|
36
|
+
return result.returncode
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main():
|
|
40
|
+
# Parsuj argumenty - przekaż wszystko do fraq
|
|
41
|
+
parser = argparse.ArgumentParser(
|
|
42
|
+
description="CLI Docker wrapper",
|
|
43
|
+
add_help=False
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument("--local", action="store_true", help="Uruchom lokalnie (bez Docker)")
|
|
46
|
+
parser.add_argument("--docker", action="store_true", help="Wymuś Docker")
|
|
47
|
+
|
|
48
|
+
# Znajdź --local/--docker przed przekazaniem reszty
|
|
49
|
+
args, remaining = parser.parse_known_args()
|
|
50
|
+
|
|
51
|
+
# Jeśli nie ma argumentów, pokaż help
|
|
52
|
+
if not remaining:
|
|
53
|
+
remaining = ["--help"]
|
|
54
|
+
|
|
55
|
+
# Uruchom
|
|
56
|
+
if args.local:
|
|
57
|
+
return run_local(remaining)
|
|
58
|
+
else:
|
|
59
|
+
return run_in_docker(remaining)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
sys.exit(main())
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI + fraq Docker example
|
|
3
|
+
Minimal REST API for fractal queries and file search
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from fastapi import FastAPI, Query
|
|
7
|
+
from fastapi.responses import JSONResponse
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from fraq import FileSearchAdapter, FraqNode, FraqExecutor, FraqQuery
|
|
11
|
+
from fraq.generators import HashGenerator
|
|
12
|
+
from fraq.formats import FormatRegistry
|
|
13
|
+
|
|
14
|
+
app = FastAPI(title="fraq Docker API", version="0.2.2")
|
|
15
|
+
|
|
16
|
+
# Config from env
|
|
17
|
+
FRAQ_DIMS = int(os.getenv("FRAQ_DIMS", "3"))
|
|
18
|
+
FRAQ_SEED = int(os.getenv("FRAQ_SEED", "0"))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@app.get("/")
|
|
22
|
+
def root():
|
|
23
|
+
return {"service": "fraq-docker", "version": "0.2.2", "dims": FRAQ_DIMS}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.get("/health")
|
|
27
|
+
def health():
|
|
28
|
+
return {"status": "ok"}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.get("/explore")
|
|
32
|
+
def explore(
|
|
33
|
+
depth: int = Query(3, ge=1, le=20),
|
|
34
|
+
dims: int = Query(FRAQ_DIMS, ge=1, le=10),
|
|
35
|
+
format: str = Query("json"),
|
|
36
|
+
):
|
|
37
|
+
"""Explore fractal structure"""
|
|
38
|
+
pos = tuple(0.0 for _ in range(dims))
|
|
39
|
+
root = FraqNode(position=pos, seed=FRAQ_SEED, generator=HashGenerator())
|
|
40
|
+
node = root.zoom(steps=depth)
|
|
41
|
+
data = node.to_dict(max_depth=1)
|
|
42
|
+
return JSONResponse(content=data)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.get("/files/search")
|
|
46
|
+
def files_search(
|
|
47
|
+
path: str = Query("/host/home", description="Directory to search"),
|
|
48
|
+
ext: str | None = Query(None, description="File extension"),
|
|
49
|
+
limit: int = Query(10, ge=1, le=1000),
|
|
50
|
+
sort: str = Query("mtime", regex="^(name|mtime|size)$"),
|
|
51
|
+
):
|
|
52
|
+
"""Search files with fractal metadata"""
|
|
53
|
+
try:
|
|
54
|
+
adapter = FileSearchAdapter(base_path=path, recursive=True)
|
|
55
|
+
results = adapter.search(extension=ext, limit=limit, sort_by=sort)
|
|
56
|
+
return {"path": path, "count": len(results), "files": results}
|
|
57
|
+
except Exception as e:
|
|
58
|
+
return {"error": str(e)}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.get("/files/stat/{file_path:path}")
|
|
62
|
+
def files_stat(file_path: str):
|
|
63
|
+
"""Get file statistics with fractal coordinates"""
|
|
64
|
+
from pathlib import Path
|
|
65
|
+
from datetime import datetime
|
|
66
|
+
|
|
67
|
+
path = Path(file_path)
|
|
68
|
+
if not path.exists():
|
|
69
|
+
return {"error": "File not found"}
|
|
70
|
+
|
|
71
|
+
stat = path.stat()
|
|
72
|
+
return {
|
|
73
|
+
"filename": path.name,
|
|
74
|
+
"path": str(path.absolute()),
|
|
75
|
+
"size": stat.st_size,
|
|
76
|
+
"mtime": datetime.fromtimestamp(stat.st_mtime).isoformat(),
|
|
77
|
+
"fraq": {
|
|
78
|
+
"seed": hash(str(path)) % (2**32),
|
|
79
|
+
"value": hash(str(path)) / (2**32),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
import uvicorn
|
|
86
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
FastAPI Docker — Uruchomienie bez Docker (opcjonalnie)
|
|
4
|
+
|
|
5
|
+
Użycie:
|
|
6
|
+
python run.py # Uruchom serwer lokalnie (bez Docker)
|
|
7
|
+
python run.py --docker # Uruchom przez Docker
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_local():
|
|
17
|
+
"""Uruchom serwer lokalnie (bez Docker)"""
|
|
18
|
+
print("🌀 Uruchamianie FastAPI lokalnie...")
|
|
19
|
+
try:
|
|
20
|
+
import uvicorn
|
|
21
|
+
from main import app
|
|
22
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
23
|
+
except ImportError:
|
|
24
|
+
print("❌ Brakujące zależności. Zainstaluj:")
|
|
25
|
+
print(" pip install fraq fastapi uvicorn")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def run_docker():
|
|
30
|
+
"""Uruchom przez Docker"""
|
|
31
|
+
print("🐳 Uruchamianie przez Docker...")
|
|
32
|
+
subprocess.run(["docker-compose", "up", "--build", "-d"], check=True)
|
|
33
|
+
print("\n✅ Serwer działa na http://localhost:8000")
|
|
34
|
+
print("\nTestuj:")
|
|
35
|
+
print(" curl http://localhost:8000/health")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def stop_docker():
|
|
39
|
+
"""Zatrzymaj Docker"""
|
|
40
|
+
print("🛑 Zatrzymywanie Dockera...")
|
|
41
|
+
subprocess.run(["docker-compose", "down"], check=True)
|
|
42
|
+
print("✅ Zatrzymano")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_api():
|
|
46
|
+
"""Przetestuj API"""
|
|
47
|
+
import requests
|
|
48
|
+
import json
|
|
49
|
+
|
|
50
|
+
base = "http://localhost:8000"
|
|
51
|
+
|
|
52
|
+
print("🧪 Testowanie API...")
|
|
53
|
+
|
|
54
|
+
# Health
|
|
55
|
+
try:
|
|
56
|
+
r = requests.get(f"{base}/health", timeout=5)
|
|
57
|
+
print(f"✅ Health: {r.json()}")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"❌ Health failed: {e}")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
# Files search
|
|
63
|
+
try:
|
|
64
|
+
r = requests.get(f"{base}/files/search?ext=py&limit=3", timeout=10)
|
|
65
|
+
data = r.json()
|
|
66
|
+
print(f"✅ Files search: {data.get('count', 0)} files found")
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"❌ Files search failed: {e}")
|
|
69
|
+
|
|
70
|
+
# Explore
|
|
71
|
+
try:
|
|
72
|
+
r = requests.get(f"{base}/explore?depth=2", timeout=10)
|
|
73
|
+
data = r.json()
|
|
74
|
+
print(f"✅ Explore: fractal depth={data.get('depth', 'N/A')}")
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f"❌ Explore failed: {e}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main():
|
|
80
|
+
parser = argparse.ArgumentParser(description="FastAPI + fraq runner")
|
|
81
|
+
parser.add_argument("--docker", action="store_true", help="Użyj Docker")
|
|
82
|
+
parser.add_argument("--stop", action="store_true", help="Zatrzymaj Docker")
|
|
83
|
+
parser.add_argument("--test", action="store_true", help="Przetestuj API")
|
|
84
|
+
|
|
85
|
+
args = parser.parse_args()
|
|
86
|
+
|
|
87
|
+
if args.stop:
|
|
88
|
+
stop_docker()
|
|
89
|
+
elif args.test:
|
|
90
|
+
test_api()
|
|
91
|
+
elif args.docker:
|
|
92
|
+
run_docker()
|
|
93
|
+
else:
|
|
94
|
+
run_local()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
main()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fullstack - API service
|
|
3
|
+
"""
|
|
4
|
+
from fastapi import FastAPI, Query
|
|
5
|
+
from fastapi.responses import JSONResponse
|
|
6
|
+
|
|
7
|
+
from fraq import FileSearchAdapter, FraqNode
|
|
8
|
+
from fraq.generators import HashGenerator
|
|
9
|
+
|
|
10
|
+
app = FastAPI(title="fraq Fullstack API")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.get("/")
|
|
14
|
+
def root():
|
|
15
|
+
return {"service": "api", "version": "0.2.2"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.get("/health")
|
|
19
|
+
def health():
|
|
20
|
+
return {"status": "ok"}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.get("/explore")
|
|
24
|
+
def explore(depth: int = Query(3)):
|
|
25
|
+
root = FraqNode(position=(0.0, 0.0, 0.0), generator=HashGenerator())
|
|
26
|
+
node = root.zoom(steps=depth)
|
|
27
|
+
return JSONResponse(content=node.to_dict(max_depth=1))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.get("/files/search")
|
|
31
|
+
def files_search(
|
|
32
|
+
path: str = Query("/data"),
|
|
33
|
+
ext: str | None = Query(None),
|
|
34
|
+
limit: int = Query(10),
|
|
35
|
+
):
|
|
36
|
+
adapter = FileSearchAdapter(base_path=path, recursive=True)
|
|
37
|
+
results = adapter.search(extension=ext, limit=limit, sort_by="mtime")
|
|
38
|
+
return {"count": len(results), "files": results}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fullstack - Streamlit Frontend
|
|
3
|
+
"""
|
|
4
|
+
import streamlit as st
|
|
5
|
+
import requests
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
API_URL = os.getenv("API_URL", "http://localhost:8000")
|
|
10
|
+
WS_URL = os.getenv("WS_URL", "ws://localhost:8001")
|
|
11
|
+
|
|
12
|
+
st.set_page_config(page_title="fraq UI", layout="wide")
|
|
13
|
+
|
|
14
|
+
st.title("🌀 fraq — Fractal Query Data Library")
|
|
15
|
+
|
|
16
|
+
# Sidebar
|
|
17
|
+
st.sidebar.header("Konfiguracja")
|
|
18
|
+
path = st.sidebar.text_input("Ścieżka", "/data")
|
|
19
|
+
ext_filter = st.sidebar.text_input("Rozszerzenie", "py")
|
|
20
|
+
limit = st.sidebar.slider("Limit", 1, 100, 10)
|
|
21
|
+
|
|
22
|
+
# Tabs
|
|
23
|
+
tab1, tab2, tab3 = st.tabs(["🔍 Wyszukiwanie plików", "🌐 API REST", "ℹ️ Info"])
|
|
24
|
+
|
|
25
|
+
with tab1:
|
|
26
|
+
st.subheader("Wyszukiwanie plików")
|
|
27
|
+
if st.button("Szukaj"):
|
|
28
|
+
try:
|
|
29
|
+
resp = requests.get(f"{API_URL}/files/search", params={
|
|
30
|
+
"path": path, "ext": ext_filter or None, "limit": limit
|
|
31
|
+
})
|
|
32
|
+
data = resp.json()
|
|
33
|
+
if data.get("files"):
|
|
34
|
+
st.write(f"Znaleziono {data['count']} plików:")
|
|
35
|
+
for f in data["files"]:
|
|
36
|
+
st.code(f"{f['filename']} ({f['size']} bytes)")
|
|
37
|
+
else:
|
|
38
|
+
st.warning("Brak wyników")
|
|
39
|
+
except Exception as e:
|
|
40
|
+
st.error(f"Błąd: {e}")
|
|
41
|
+
|
|
42
|
+
with tab2:
|
|
43
|
+
st.subheader("Test API")
|
|
44
|
+
st.code(f"GET {API_URL}/health", language="bash")
|
|
45
|
+
try:
|
|
46
|
+
health = requests.get(f"{API_URL}/health").json()
|
|
47
|
+
st.json(health)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
st.error(f"API niedostępne: {e}")
|
|
50
|
+
|
|
51
|
+
with tab3:
|
|
52
|
+
st.subheader("API Endpoints")
|
|
53
|
+
st.markdown("""
|
|
54
|
+
- `GET /health` — Health check
|
|
55
|
+
- `GET /explore?depth=3` — Eksploracja fraktala
|
|
56
|
+
- `GET /files/search?ext=pdf&limit=10` — Wyszukiwanie plików
|
|
57
|
+
- `WS /ws/stream` — WebSocket streaming
|
|
58
|
+
""")
|
|
59
|
+
st.info(f"API URL: {API_URL}")
|
|
60
|
+
st.info(f"WebSocket URL: {WS_URL}")
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fullstack - WebSocket service
|
|
3
|
+
"""
|
|
4
|
+
import asyncio
|
|
5
|
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
6
|
+
|
|
7
|
+
from fraq import FileSearchAdapter, FraqNode
|
|
8
|
+
from fraq.generators import HashGenerator
|
|
9
|
+
|
|
10
|
+
app = FastAPI(title="fraq Fullstack WebSocket")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.websocket("/ws/stream")
|
|
14
|
+
async def ws_stream(websocket: WebSocket):
|
|
15
|
+
await websocket.accept()
|
|
16
|
+
try:
|
|
17
|
+
while True:
|
|
18
|
+
msg = await websocket.receive_json()
|
|
19
|
+
if msg.get("action") == "stream":
|
|
20
|
+
count = msg.get("count", 10)
|
|
21
|
+
root = FraqNode(position=(0.0, 0.0, 0.0), generator=HashGenerator())
|
|
22
|
+
cursor = root.cursor()
|
|
23
|
+
for i in range(count):
|
|
24
|
+
cursor.advance()
|
|
25
|
+
await websocket.send_json({"index": i, "value": cursor.current.value})
|
|
26
|
+
await asyncio.sleep(0.1)
|
|
27
|
+
except WebSocketDisconnect:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.websocket("/ws/files")
|
|
32
|
+
async def ws_files(websocket: WebSocket):
|
|
33
|
+
await websocket.accept()
|
|
34
|
+
try:
|
|
35
|
+
while True:
|
|
36
|
+
msg = await websocket.receive_json()
|
|
37
|
+
if msg.get("action") == "search":
|
|
38
|
+
path = msg.get("path", "/data")
|
|
39
|
+
ext = msg.get("ext")
|
|
40
|
+
limit = msg.get("limit", 10)
|
|
41
|
+
adapter = FileSearchAdapter(base_path=path, recursive=True)
|
|
42
|
+
for record in adapter.stream(extension=ext, count=limit):
|
|
43
|
+
await websocket.send_json(record)
|
|
44
|
+
await asyncio.sleep(0.01)
|
|
45
|
+
await websocket.send_json({"done": True})
|
|
46
|
+
except WebSocketDisconnect:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.get("/health")
|
|
51
|
+
def health():
|
|
52
|
+
return {"status": "ok"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WebSocket + fraq Docker example
|
|
3
|
+
Real-time streaming of fractal data and file search
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
9
|
+
|
|
10
|
+
from fraq import FileSearchAdapter, FraqNode
|
|
11
|
+
from fraq.generators import HashGenerator
|
|
12
|
+
|
|
13
|
+
app = FastAPI(title="fraq WebSocket Docker", version="0.2.2")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.websocket("/ws/stream")
|
|
17
|
+
async def ws_stream(websocket: WebSocket):
|
|
18
|
+
"""Stream fractal data"""
|
|
19
|
+
await websocket.accept()
|
|
20
|
+
try:
|
|
21
|
+
while True:
|
|
22
|
+
msg = await websocket.receive_json()
|
|
23
|
+
action = msg.get("action")
|
|
24
|
+
|
|
25
|
+
if action == "stream":
|
|
26
|
+
count = msg.get("count", 10)
|
|
27
|
+
interval = msg.get("interval", 0.1)
|
|
28
|
+
|
|
29
|
+
# Generate fractal stream
|
|
30
|
+
root = FraqNode(position=(0.0, 0.0, 0.0), generator=HashGenerator())
|
|
31
|
+
cursor = root.cursor()
|
|
32
|
+
|
|
33
|
+
for i in range(count):
|
|
34
|
+
cursor.advance()
|
|
35
|
+
await websocket.send_json({
|
|
36
|
+
"index": i,
|
|
37
|
+
"value": cursor.current.value,
|
|
38
|
+
"position": cursor.current.position,
|
|
39
|
+
})
|
|
40
|
+
await asyncio.sleep(interval)
|
|
41
|
+
|
|
42
|
+
elif action == "ping":
|
|
43
|
+
await websocket.send_json({"pong": True, "timestamp": asyncio.get_event_loop().time()})
|
|
44
|
+
|
|
45
|
+
except WebSocketDisconnect:
|
|
46
|
+
print("Client disconnected from /ws/stream")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@app.websocket("/ws/files")
|
|
50
|
+
async def ws_files(websocket: WebSocket):
|
|
51
|
+
"""Stream file search results"""
|
|
52
|
+
await websocket.accept()
|
|
53
|
+
try:
|
|
54
|
+
while True:
|
|
55
|
+
msg = await websocket.receive_json()
|
|
56
|
+
action = msg.get("action")
|
|
57
|
+
|
|
58
|
+
if action == "search":
|
|
59
|
+
path = msg.get("path", "/data")
|
|
60
|
+
ext = msg.get("ext")
|
|
61
|
+
pattern = msg.get("pattern")
|
|
62
|
+
limit = msg.get("limit", 10)
|
|
63
|
+
|
|
64
|
+
adapter = FileSearchAdapter(base_path=path, recursive=True)
|
|
65
|
+
|
|
66
|
+
# Stream results one by one
|
|
67
|
+
count = 0
|
|
68
|
+
for record in adapter.stream(extension=ext, pattern=pattern, count=limit):
|
|
69
|
+
await websocket.send_json(record)
|
|
70
|
+
count += 1
|
|
71
|
+
await asyncio.sleep(0.01)
|
|
72
|
+
|
|
73
|
+
await websocket.send_json({"done": True, "count": count})
|
|
74
|
+
|
|
75
|
+
elif action == "stat":
|
|
76
|
+
from pathlib import Path
|
|
77
|
+
from datetime import datetime
|
|
78
|
+
|
|
79
|
+
file_path = msg.get("file")
|
|
80
|
+
path = Path(file_path)
|
|
81
|
+
|
|
82
|
+
if path.exists():
|
|
83
|
+
stat = path.stat()
|
|
84
|
+
await websocket.send_json({
|
|
85
|
+
"file": str(path),
|
|
86
|
+
"size": stat.st_size,
|
|
87
|
+
"mtime": datetime.fromtimestamp(stat.st_mtime).isoformat(),
|
|
88
|
+
"fraq_seed": hash(str(path)) % (2**32),
|
|
89
|
+
})
|
|
90
|
+
else:
|
|
91
|
+
await websocket.send_json({"error": "File not found"})
|
|
92
|
+
|
|
93
|
+
except WebSocketDisconnect:
|
|
94
|
+
print("Client disconnected from /ws/files")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.get("/health")
|
|
98
|
+
def health():
|
|
99
|
+
return {"status": "ok", "websocket": True}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
import uvicorn
|
|
104
|
+
uvicorn.run(app, host="0.0.0.0", port=8001)
|
|
@@ -12,6 +12,13 @@ examples/nlp2cmd_integration.py
|
|
|
12
12
|
examples/query_examples.py
|
|
13
13
|
examples/text2fraq_examples.py
|
|
14
14
|
examples/text2fraq_files.py
|
|
15
|
+
examples/cli-docker/run.py
|
|
16
|
+
examples/fastapi-docker/main.py
|
|
17
|
+
examples/fastapi-docker/run.py
|
|
18
|
+
examples/fullstack-docker/api/main.py
|
|
19
|
+
examples/fullstack-docker/frontend/app.py
|
|
20
|
+
examples/fullstack-docker/websocket/main.py
|
|
21
|
+
examples/websocket-docker/main.py
|
|
15
22
|
fraq/__init__.py
|
|
16
23
|
fraq/adapters.py
|
|
17
24
|
fraq/cli.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|