-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Expand file tree
/
Copy pathserver_main.py
More file actions
executable file
·168 lines (141 loc) · 4.93 KB
/
server_main.py
File metadata and controls
executable file
·168 lines (141 loc) · 4.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import argparse
import logging
from pathlib import Path
from runtime.bootstrap.schema import ensure_schema_registry_populated
from server.app import app
ensure_schema_registry_populated()
# Directories containing the server's Python sources. When --reload is
# enabled, only these are watched so that agent-generated files under
# WareHouse/, logs/, etc. never trigger a StatReload restart mid-workflow
# (issue #569).
RELOAD_SOURCE_DIRS = [
"check",
"entity",
"functions",
"mcp_example",
"runtime",
"schema_registry",
"server",
"tools",
"utils",
"workflow",
]
# Directory names whose contents must never trigger a reload. These are
# expanded into multi-depth glob patterns below so nested files (e.g.
# ``WareHouse/demo/foo.py``) are also excluded: uvicorn applies these via
# ``Path.match``, which on Python < 3.13 does not understand ``**`` and
# matches a pattern of N components only against the last N path parts.
_RELOAD_EXCLUDE_DIRS = ("WareHouse", "logs", "data", "temp", "node_modules")
_RELOAD_EXCLUDE_MAX_DEPTH = 10
# Glob patterns excluded from reload watching. Only honoured when
# ``watchfiles`` is installed; StatReload (the pure-Python fallback that
# ships with uvicorn core) ignores exclude patterns entirely, so the
# primary defence is the reload_dirs restriction to RELOAD_SOURCE_DIRS.
RELOAD_EXCLUDES = [
f"{d}{'/*' * (depth + 1)}"
for d in _RELOAD_EXCLUDE_DIRS
for depth in range(_RELOAD_EXCLUDE_MAX_DEPTH)
]
def _watchfiles_available() -> bool:
"""Return ``True`` when the ``watchfiles`` package is importable.
Split out so tests can patch it without touching ``sys.modules``.
"""
import importlib.util
return importlib.util.find_spec("watchfiles") is not None
def build_reload_kwargs(args: argparse.Namespace) -> dict:
"""Build the reload-related kwargs passed to ``uvicorn.run``.
Extracted so the configuration is unit-testable without spinning up
a real server. When ``--reload`` is off the return value is empty.
"""
if not args.reload:
return {}
return {
"reload_dirs": list(args.reload_dir) if args.reload_dir else list(RELOAD_SOURCE_DIRS),
"reload_excludes": list(args.reload_exclude) if args.reload_exclude else list(RELOAD_EXCLUDES),
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="DevAll Workflow Server")
parser.add_argument(
"--host",
type=str,
default="0.0.0.0",
help="Server host (default: 0.0.0.0)"
)
parser.add_argument(
"--port",
type=int,
default=8000,
help="Server port (default: 8000)"
)
parser.add_argument(
"--log-level",
choices=["debug", "info", "warning", "error", "critical"],
default="info",
help="Log level (default: info)"
)
parser.add_argument(
"--reload",
action="store_true",
help="Enable auto-reload for development"
)
parser.add_argument(
"--reload-dir",
action="append",
default=None,
metavar="DIR",
help=(
"Directory to watch when --reload is active (repeatable). "
"Defaults to the server's Python source folders, which excludes "
"WareHouse/ and other output dirs."
),
)
parser.add_argument(
"--reload-exclude",
action="append",
default=None,
metavar="GLOB",
help=(
"Glob pattern excluded from reload watching (repeatable). "
"Requires watchfiles to take effect."
),
)
return parser
def main():
import uvicorn
args = build_parser().parse_args()
# Configure structured logging
import os
os.environ['LOG_LEVEL'] = args.log_level.upper()
# Ensure log directory exists
log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)
# Configure logging
logging.basicConfig(
level=getattr(logging, args.log_level.upper()),
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(log_dir / "server.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
logger.info(f"Starting DevAll Workflow Server on {args.host}:{args.port}")
if args.reload and not _watchfiles_available():
logger.warning(
"--reload is active but 'watchfiles' is not installed; uvicorn will "
"fall back to StatReload, which ignores --reload-exclude patterns "
"(including the WareHouse/ defaults). Install watchfiles (or "
"`pip install uvicorn[standard]`) to enable exclude filtering."
)
# Launch the server
uvicorn.run(
"server.app:app",
host=args.host,
port=args.port,
reload=args.reload,
log_level=args.log_level,
ws="wsproto",
**build_reload_kwargs(args),
)
if __name__ == "__main__":
main()