Skip to content

Commit 7881c6b

Browse files
Merge branch 'main' into dependabot/uv/cryptography-48.0.1
2 parents 917daea + f855616 commit 7881c6b

29 files changed

Lines changed: 6489 additions & 91 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# =============================================================================
2+
# publish-api-docs.yml — bedrock-agentcore-sdk-python
3+
# =============================================================================
4+
# Auto-generates the Python SDK API reference as AsciiDoc (.adoc) on every
5+
# release and uploads it as a build artifact for the docs team to publish.
6+
#
7+
# Pipeline: install released SDK -> extract doc-model JSON (inspect + docstrings)
8+
# -> render .adoc -> upload artifact.
9+
#
10+
# NOTE: this generates and publishes the docs as an artifact only; landing them
11+
# in the docs site is a separate step for now and may be automated later.
12+
# =============================================================================
13+
14+
name: Publish API docs
15+
16+
on:
17+
release:
18+
types: [published]
19+
workflow_dispatch: {}
20+
21+
permissions:
22+
contents: read
23+
24+
env:
25+
ADOC_PREFIX: python-sdk
26+
27+
jobs:
28+
generate:
29+
runs-on: ubuntu-latest
30+
steps:
31+
- name: Checkout
32+
uses: actions/checkout@v4
33+
34+
- name: Set up Python
35+
uses: actions/setup-python@v5
36+
with:
37+
python-version: "3.12"
38+
39+
- name: Install the published SDK
40+
# Install the EXACT released version, not the newest on PyPI.
41+
# `release: [published]` can fire for pre-releases too, so pin to the
42+
# release tag when present; fall back to newest for manual runs.
43+
run: |
44+
pip install --upgrade pip
45+
if [ -n "$RELEASE_TAG" ]; then
46+
pip install "bedrock-agentcore==${RELEASE_TAG#v}"
47+
else
48+
pip install bedrock-agentcore
49+
fi
50+
env:
51+
RELEASE_TAG: ${{ github.event.release.tag_name }}
52+
53+
- name: Extract doc-model JSON
54+
run: python scripts/extract_api_model.py --out doc-model.json
55+
56+
- name: Render .adoc
57+
run: |
58+
python scripts/render_adoc.py \
59+
--model doc-model.json \
60+
--out-dir out/adoc \
61+
--prefix "${ADOC_PREFIX}"
62+
63+
- name: Upload API-docs artifact
64+
uses: actions/upload-artifact@v4
65+
with:
66+
name: api-docs-adoc
67+
path: out/adoc
68+
if-no-files-found: error

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [1.17.0] - 2026-07-02
4+
5+
### Other Changes
6+
- fix(runtime): prevent streaming-bridge deadlock on client disconnect (#482) (#563) (2bfabb3)
7+
- Revert "Create poc-caller.yml (#561)" (#562) (8bbfe18)
8+
- Create poc-caller.yml (#561) (df244a2)
9+
310
## [1.16.0] - 2026-06-30
411

512
### Other Changes

pyproject.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "bedrock-agentcore"
7-
version = "1.16.0"
7+
version = "1.17.0"
88
description = "An SDK for using Bedrock AgentCore"
99
readme = "README.md"
1010
requires-python = ">=3.10"
@@ -151,6 +151,9 @@ dev = [
151151
"wheel>=0.45.1",
152152
"strands-agents>=1.20.0",
153153
"strands-agents-evals>=0.1.0",
154+
"langchain>=1.0.0",
155+
"langgraph>=1.0.0",
156+
"langchain-mcp-adapters>=0.1.0",
154157
"a2a-sdk[http-server]>=0.3,<1.0",
155158
"ag-ui-protocol>=0.1.10",
156159
"mcp-proxy-for-aws>=0.1.0",
@@ -163,6 +166,12 @@ strands-agents = [
163166
"strands-agents>=1.20.0",
164167
"mcp>=1.23.0,<2.0.0",
165168
]
169+
langgraph = [
170+
"langchain>=1.0.0",
171+
"langgraph>=1.0.0",
172+
"langchain-mcp-adapters>=0.1.0",
173+
"httpx>=0.27.0",
174+
]
166175
strands-agents-evals = [
167176
"strands-agents-evals>=0.1.0"
168177
]

scripts/extract_api_model.py

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
#!/usr/bin/env python3
2+
# =============================================================================
3+
# extract_api_model.py — Python SDK -> doc-model JSON
4+
# =============================================================================
5+
# Introspects the installed `bedrock-agentcore` package and emits the shared
6+
# doc-model JSON (schema v1, see _shared/render_adoc.py) that the shared
7+
# renderer turns into .adoc.
8+
#
9+
# Runs on a GitHub-hosted runner AFTER `pip install bedrock-agentcore`, so the
10+
# import below resolves against the real published wheel.
11+
#
12+
# NOTE: if these workflows are later consolidated into a single shared reusable
13+
# workflow, this script is vendored there and selected via a `language: python`
14+
# input. It lives here now only so the draft is self-contained and runnable in
15+
# isolation.
16+
#
17+
# Docstring style: the SDK uses Google-style docstrings. We do a light parse
18+
# (Args/Returns/Raises/Example sections). We intentionally keep the parser
19+
# small; anything we can't classify falls through into `description` verbatim.
20+
# =============================================================================
21+
22+
import argparse
23+
import importlib
24+
import inspect
25+
import json
26+
import re
27+
import sys
28+
29+
PACKAGE = "bedrock_agentcore"
30+
31+
# All public modules to document (decision: include ALL of them).
32+
# Each maps to one group == one .adoc file.
33+
GROUPS = [
34+
("runtime", "Runtime", "bedrock_agentcore.runtime"),
35+
("memory", "Memory", "bedrock_agentcore.memory"),
36+
("identity", "Identity", "bedrock_agentcore.identity"),
37+
("tools", "Built-in Tools", "bedrock_agentcore.tools"),
38+
("gateway", "Gateway", "bedrock_agentcore.gateway"),
39+
("policy", "Policy", "bedrock_agentcore.policy"),
40+
("evaluation", "Evaluation", "bedrock_agentcore.evaluation"),
41+
("config-bundle", "Configuration Bundles", "bedrock_agentcore.config_bundle"),
42+
("payments", "Payments", "bedrock_agentcore.payments"),
43+
("knowledge-base", "Knowledge Base", "bedrock_agentcore.knowledge_base"),
44+
]
45+
46+
_SECTION_RE = re.compile(r"^\s*(Args|Arguments|Returns|Raises|Example|Examples):\s*$")
47+
_ARG_RE = re.compile(r"^\s+(\w+)\s*(?:\(([^)]+)\))?:\s*(.*)$")
48+
49+
# The SDK mixes Google-style ("Args:") and reST-style (":param x:") docstrings,
50+
# so we also recognize the reST field forms and pull them out of the prose.
51+
_REST_PARAM_RE = re.compile(r"^\s*:param\s+(\w+):\s*(.*)$")
52+
_REST_RETURNS_RE = re.compile(r"^\s*:returns?:\s*(.*)$")
53+
_REST_RAISES_RE = re.compile(r"^\s*:raises?\s+([\w.]+):\s*(.*)$")
54+
55+
56+
def extract_rest_fields(lines, result):
57+
"""Pull reST field lines (:param:/:returns:/:raises:) out of `lines`.
58+
59+
Returns the remaining (non-field) lines so they can form the description.
60+
Mutates `result` in place, matching the Google parser's output shape.
61+
"""
62+
kept = []
63+
for line in lines:
64+
m = _REST_PARAM_RE.match(line)
65+
if m:
66+
result["params"].append(
67+
{
68+
"name": m.group(1),
69+
"type": None,
70+
"required": True,
71+
"description": m.group(2).strip(),
72+
}
73+
)
74+
continue
75+
m = _REST_RETURNS_RE.match(line)
76+
if m:
77+
result["returns"] = {"type": None, "description": m.group(1).strip()}
78+
continue
79+
m = _REST_RAISES_RE.match(line)
80+
if m:
81+
result["raises"].append({"type": m.group(1), "description": m.group(2).strip()})
82+
continue
83+
kept.append(line)
84+
return kept
85+
86+
87+
def parse_google_docstring(doc):
88+
"""Very small Google-style docstring parser -> structured dict."""
89+
result = {"summary": "", "description": "", "params": [], "returns": None, "raises": [], "examples": []}
90+
if not doc:
91+
return result
92+
lines = inspect.cleandoc(doc).splitlines()
93+
94+
# summary = first paragraph
95+
i = 0
96+
summary = []
97+
while i < len(lines) and lines[i].strip():
98+
summary.append(lines[i].strip())
99+
i += 1
100+
result["summary"] = " ".join(summary)
101+
102+
section = "description"
103+
desc, example_buf = [], []
104+
while i < len(lines):
105+
line = lines[i]
106+
m = _SECTION_RE.match(line)
107+
if m:
108+
name = m.group(1).lower()
109+
section = {
110+
"args": "args",
111+
"arguments": "args",
112+
"returns": "returns",
113+
"raises": "raises",
114+
"example": "example",
115+
"examples": "example",
116+
}[name]
117+
i += 1
118+
continue
119+
if section == "description":
120+
desc.append(line)
121+
elif section == "args":
122+
am = _ARG_RE.match(line)
123+
if am:
124+
result["params"].append(
125+
{
126+
"name": am.group(1),
127+
"type": (am.group(2) or "").strip() or None,
128+
"required": "optional" not in (am.group(2) or "").lower(),
129+
"description": am.group(3).strip(),
130+
}
131+
)
132+
elif result["params"] and line.strip():
133+
result["params"][-1]["description"] += " " + line.strip()
134+
elif section == "returns":
135+
if line.strip():
136+
if result["returns"] is None:
137+
result["returns"] = {"type": None, "description": line.strip()}
138+
else:
139+
result["returns"]["description"] += " " + line.strip()
140+
elif section == "raises":
141+
am = _ARG_RE.match(line)
142+
if am:
143+
result["raises"].append({"type": am.group(1), "description": am.group(3).strip()})
144+
elif section == "example":
145+
example_buf.append(line)
146+
i += 1 # always advance — non-header branches above don't, else infinite loop
147+
148+
# Second pass: some docstrings use reST fields (:param:/:returns:/:raises:)
149+
# instead of, or mixed with, Google sections. Pull those out of the prose.
150+
desc = extract_rest_fields(desc, result)
151+
result["description"] = "\n".join(desc).strip()
152+
if example_buf:
153+
code = "\n".join(example_buf).strip()
154+
# strip a leading ```python fence if the docstring used one
155+
code = re.sub(r"^```\w*\n?|\n?```$", "", code).strip()
156+
if code:
157+
result["examples"].append({"lang": "python", "code": code})
158+
return result
159+
160+
161+
def _own_docstring(obj):
162+
"""Return obj's docstring, but suppress ones merely inherited from `object`.
163+
164+
Classes/methods that don't define their own docstring inherit boilerplate
165+
like "Initialize self. See help(type(self))..." from object.__init__ /
166+
object.__new__ — noise we don't want in the reference.
167+
"""
168+
doc = inspect.getdoc(obj)
169+
if not doc:
170+
return None
171+
for base in (object.__init__, object.__new__, object):
172+
if doc == inspect.getdoc(base):
173+
return None
174+
return doc
175+
176+
177+
def entry_from_object(name, obj):
178+
"""Build a doc-model entry for a class or function."""
179+
doc = parse_google_docstring(_own_docstring(obj))
180+
try:
181+
signature = inspect.signature(obj)
182+
# Drop the implicit `self`/`cls` receiver from method signatures.
183+
params = [p for p in signature.parameters.values() if p.name not in ("self", "cls")]
184+
signature = signature.replace(parameters=params)
185+
sig = f"{name}{signature}"
186+
except (ValueError, TypeError):
187+
sig = name
188+
189+
kind = "class" if inspect.isclass(obj) else "function"
190+
entry = {
191+
"kind": kind,
192+
"name": name,
193+
"signature": sig,
194+
"summary": doc["summary"],
195+
"description": doc["description"],
196+
"params": doc["params"],
197+
"returns": doc["returns"],
198+
"raises": doc["raises"],
199+
"examples": doc["examples"],
200+
"members": [],
201+
}
202+
203+
if kind == "class":
204+
for mname, mobj in inspect.getmembers(obj, predicate=inspect.isfunction):
205+
if mname.startswith("_") and mname != "__init__":
206+
continue
207+
if mobj.__qualname__.split(".")[0] != obj.__name__:
208+
continue # skip inherited members
209+
entry["members"].append(entry_from_object(mname, mobj))
210+
return entry
211+
212+
213+
def collect_public_names(module):
214+
"""Public API of a module = its __all__, else non-underscore attrs."""
215+
names = getattr(module, "__all__", None)
216+
if names:
217+
return list(names)
218+
return [n for n in dir(module) if not n.startswith("_")]
219+
220+
221+
def build_group(gid, title, modname):
222+
try:
223+
module = importlib.import_module(modname)
224+
except Exception as e: # noqa: BLE001 — module may not exist in a given version
225+
print(f" skip {modname}: {e}", file=sys.stderr)
226+
return None
227+
228+
summary = (inspect.getdoc(module) or "").split("\n\n")[0]
229+
entries = []
230+
for name in collect_public_names(module):
231+
# Some modules (e.g. evaluation) expose symbols via a lazy __getattr__
232+
# that RAISES ImportError for optional extras rather than returning a
233+
# default — so we can't rely on getattr's default and must catch.
234+
try:
235+
obj = getattr(module, name, None)
236+
except Exception as e: # noqa: BLE001
237+
print(f" skip {modname}.{name}: {type(e).__name__}", file=sys.stderr)
238+
continue
239+
if inspect.isclass(obj) or inspect.isfunction(obj):
240+
# only document objects actually defined in this package
241+
if getattr(obj, "__module__", "").startswith(PACKAGE):
242+
entries.append(entry_from_object(name, obj))
243+
if not entries:
244+
return None
245+
return {"id": gid, "title": title, "summary": summary, "entries": entries}
246+
247+
248+
def main():
249+
ap = argparse.ArgumentParser()
250+
ap.add_argument("--out", required=True, help="output doc-model JSON path")
251+
args = ap.parse_args()
252+
253+
importlib.import_module(PACKAGE)
254+
# The package exposes its version via distribution metadata, not a
255+
# __version__ attribute, so read it from there (fall back gracefully).
256+
try:
257+
from importlib.metadata import version as _dist_version
258+
259+
version = _dist_version("bedrock-agentcore")
260+
except Exception: # noqa: BLE001
261+
version = "unknown"
262+
263+
groups = []
264+
for gid, title, modname in GROUPS:
265+
g = build_group(gid, title, modname)
266+
if g:
267+
groups.append(g)
268+
269+
model = {
270+
"source": "python-sdk",
271+
"package": "bedrock-agentcore",
272+
"version": version,
273+
"language": "python",
274+
"groups": groups,
275+
}
276+
with open(args.out, "w") as f:
277+
json.dump(model, f, indent=2)
278+
print(f"Wrote doc-model: {len(groups)} groups, version {version}", file=sys.stderr)
279+
280+
281+
if __name__ == "__main__":
282+
main()

0 commit comments

Comments
 (0)