-
Notifications
You must be signed in to change notification settings - Fork 1
162 lines (146 loc) · 7.34 KB
/
Copy pathci.yml
File metadata and controls
162 lines (146 loc) · 7.34 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
# PR / push checks for the generated SDK: it installs, imports with the expected
# config, and builds. Runs on the factory's proposal PRs so a broken generation
# is caught before merge (and therefore before release). Skips cleanly on a bare
# major branch that has no SDK yet (before the first proposal lands).
name: ci
on:
pull_request: # covers proposal PRs (sdk/* → vN)
push:
branches: ["v*"] # post-merge validation on each major line (v2, v3, …)
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
# Floor + latest. The floor is the version setup.py's `python_requires`
# promises (>=3.12): a promise nothing runs is how 1.0.0 and both release
# candidates shipped a Requires-Python they had never tested. Keep this in
# step with templates/setup.mustache in the factory.
matrix:
python-version: ["3.12", "3.14"]
steps:
- uses: actions/checkout@v5
- name: Detect package
id: detect
run: |
if [ -f pyproject.toml ]; then
echo "has_pkg=true" >> "$GITHUB_OUTPUT"
else
echo "has_pkg=false" >> "$GITHUB_OUTPUT"
echo "No SDK on this branch yet — skipping build (bootstrap branch)."
fi
- uses: actions/setup-python@v6
if: steps.detect.outputs.has_pkg == 'true'
with:
python-version: ${{ matrix.python-version }}
- name: Install
if: steps.detect.outputs.has_pkg == 'true'
run: pip install .
- name: Import + config smoke
if: steps.detect.outputs.has_pkg == 'true'
run: |
python - <<'PY'
from everos_cloud import (
ApiClient, Configuration, EverOS, KnowledgeApi, MemoryApi, StorageApi, TasksApi,
)
cfg = Configuration(access_token="sk-test")
assert cfg.host == "https://api.evermind.ai", cfg.host
assert cfg.auth_settings()["BearerAuth"]["value"] == "Bearer sk-test"
MemoryApi(ApiClient(cfg))
# generated method names — these are the operationIds, NOT the facade's
for m in ("add_memory", "search_memory", "get_memory", "delete_memory", "edit_profile",
"bind_tags", "unbind_tags", "replace_tags"):
assert hasattr(MemoryApi, m), m
for m in ("create_knowledge_base", "search_knowledge", "create_document",
"replace_document", "list_documents"):
assert hasattr(KnowledgeApi, m), m
for m in ("list_tasks", "get_task_status", "get_task_stats"):
assert hasattr(TasksApi, m), m
# ergonomic facade is exported at the top level
for m in ("add", "search", "get", "flush", "edit", "delete", "upload", "close",
"tag_bind", "tag_unbind", "tag_replace",
"kb_create", "kb_list", "kb_get", "kb_update", "kb_delete", "kb_search",
"doc_ingest", "doc_list", "doc_get",
"doc_update", "doc_delete",
"task_get", "task_list", "task_wait"):
assert hasattr(EverOS, m), m
# the facade's names must stay disjoint from the generated ones, so a reader
# never has to work out which layer a call went through
generated = set()
for cls in (KnowledgeApi, MemoryApi, StorageApi, TasksApi):
generated |= {m for m in dir(cls) if not m.startswith("_")
and not m.endswith(("_with_http_info", "_without_preload_content"))}
facade = {m for m in dir(EverOS) if not m.startswith("_")}
assert not (facade & generated), sorted(facade & generated)
# every generated client is reachable through the facade (regression: the
# knowledge / tasks handles were missing, so those surfaces were unusable)
client = EverOS("sk-test")
for attr, cls in (("memory", MemoryApi), ("storage", StorageApi),
("knowledge", KnowledgeApi), ("tasks", TasksApi)):
assert isinstance(getattr(client, attr), cls), attr
print("import + config OK")
PY
- name: Quickstart drift smoke
# quickstart.md is hand-maintained (not generated), so it can drift from the
# SDK when the API surface changes. This catches the common cases offline (no
# live API): its code must still parse, and every model/method it references
# must still exist in the installed SDK.
if: steps.detect.outputs.has_pkg == 'true' && hashFiles('quickstart.md') != ''
run: |
python - <<'PY'
import re
import everos_cloud.models as models
from everos_cloud import EverOS, KnowledgeApi, MemoryApi, StorageApi, TasksApi
CLASSES = {"EverOS": EverOS, "MemoryApi": MemoryApi, "StorageApi": StorageApi,
"KnowledgeApi": KnowledgeApi, "TasksApi": TasksApi}
text = open("quickstart.md").read()
blocks = re.findall(r"```python\n(.*?)```", text, re.S)
assert blocks, "no python code blocks found in quickstart.md"
code = "\n".join(blocks)
# 1) every snippet must still be valid Python
compile(code, "quickstart.md", "exec")
# 2) every model imported from everos_cloud.models must still exist
for paren, line in re.findall(
r"from everos_cloud\.models import (?:\(([^)]*)\)|([^\n]+))", text
):
for name in re.split(r"[,\s]+", paren or line):
name = name.strip()
if name:
assert hasattr(models, name), f"quickstart drift: missing model {name!r}"
# 3) every method called on an EverOS / MemoryApi / StorageApi instance
# must still exist (maps each instance var to its class from assignment).
var_class = {v: CLASSES[c] for v, c in
re.findall(r"(\w+)\s*=\s*(" + "|".join(CLASSES) + r")\(", code)}
for var, cls in var_class.items():
for meth in sorted(set(re.findall(rf"\b{re.escape(var)}\.(\w+)\(", code))):
assert hasattr(cls, meth), f"quickstart drift: {cls.__name__} has no {meth!r}"
print("quickstart drift smoke OK")
PY
- name: Wrapper unit tests
# Offline tests for the hand-maintained ergonomic client (everos_cloud/client.py).
if: steps.detect.outputs.has_pkg == 'true' && hashFiles('tests/test_client.py') != ''
run: |
pip install pytest
pytest -q tests/
- name: Build sdist + wheel
if: steps.detect.outputs.has_pkg == 'true'
run: |
python -m pip install --upgrade build
python -m build
# Aggregate gate. Branch protection pins required checks BY NAME, and a matrix
# job reports as `test (3.12)` / `test (3.14)` — never as a single stable name.
# Without this job the required `build` check simply never arrives and the PR
# blocks forever; with it, the matrix can grow or shrink without anyone editing
# the ruleset.
build:
needs: [test]
if: always()
runs-on: ubuntu-latest
steps:
- name: Gate on the matrix result
run: |
echo "matrix result: ${{ needs.test.result }}"
[ "${{ needs.test.result }}" = "success" ] || {
echo "::error::one or more Python versions failed"; exit 1; }