Compare commits

...
Author SHA1 Message Date
Ben Burns b9f26d28ea add missing build step 2025-02-13 23:03:58 -08:00
Ben Burns 06d4ba7fa0 bump prebuilt.md 2025-02-13 22:57:15 -08:00
Ben Burns 2dbdb36743 fix cassette hashing 2025-02-13 22:56:16 -08:00
Ben Burns 2b72fbd5de state-reducers.md WIP 2025-02-13 21:57:45 -08:00
Ben Burns c681545c97 make CodeQL happy, fix Makefile 2025-02-13 21:57:44 -08:00
Ben Burns 5ebdefba63 ts exec works - back out half-baked TS code snippet for now 2025-02-13 21:57:44 -08:00
Ben Burns 441923282c docs: get ts execution working in build pipeline 2025-02-13 21:57:37 -08:00
21 changed files with 6284 additions and 583 deletions
+7
View File
@@ -56,6 +56,12 @@ jobs:
with:
fetch-depth: 0
- uses: actions/checkout@v4
with:
repository: langchain-ai/langchainjs
token: ${{ secrets.MKDOCS_GITHUB_TOKEN }}
path: docs/langchainjs
- name: Set up Python + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
@@ -72,6 +78,7 @@ jobs:
- name: Install dependencies
run: |
cd langchainjs && yarn && yarn build && cd ..
yarn
poetry install --with test --with docs --no-root
poetry run pip install -U \
+873
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.5.1.cjs
+17 -3
View File
@@ -13,7 +13,22 @@ build-prebuilt:
poetry run python -m _scripts.third_party_page.get_download_stats stats.yml
poetry run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/prebuilt.md --language python
build-docs: build-typedoc build-prebuilt
grab-langgraphjs:
if [ -d "langgraphjs" ]; then \
if [ ! -d "langgraphjs/.git" ]; then \
rm -rf langgraphjs; \
fi \
fi
if [ ! -d "langgraphjs" ]; then \
git clone https://github.com/langchain-ai/langgraphjs.git; \
else \
cd langgraphjs && git checkout main && git pull; \
fi
cd langgraphjs && yarn
cd langgraphjs && yarn build
yarn
build-docs: build-typedoc build-prebuilt grab-langgraphjs
poetry run python -m mkdocs build --clean -f mkdocs.yml --strict
llms-text:
@@ -32,7 +47,6 @@ install-vercel-deps:
poetry run tslab install --python=python3
poetry run jupyter kernelspec list
tests:
# RUn unit tests
poetry run pytest tests/unit_tests
@@ -45,7 +59,7 @@ vercel-build-docs: install-vercel-deps
serve-clean-docs: clean-docs
poetry run python -m mkdocs serve -c -f mkdocs.yml --strict -w ../libs/langgraph
serve-docs: build-typedoc
serve-docs: build-typedoc grab-langgraphjs
poetry run python -m mkdocs serve -f mkdocs.yml -w ../libs/langgraph -w ../libs/checkpoint -w ../libs/sdk-py --dirty
clean-docs:
+5 -3
View File
@@ -24,14 +24,16 @@ function decompressData(compressedString: string): NockCassetteData {
return msgpack.decode(decompressed) as NockCassetteData;
}
// deno-lint-ignore no-unused-vars
class HashedCassette {
hash: string;
private recording = true;
constructor(
private readonly cassettePath: string,
private readonly hash: string
) {}
hash: string
) {
this.hash = hash;
}
async enter() {
try {
+2 -2
View File
@@ -63,7 +63,7 @@ class HashedCassette:
content = f.read()
try:
cassette_data = serializer.deserialize(content)
except Exception as e:
except Exception:
os.remove(self.cassette_path)
else:
existing_hash = cassette_data.get("cassette_hash")
@@ -96,7 +96,7 @@ class HashedCassette:
content = f.read()
try:
cassette_data = serializer.deserialize(content)
except Exception as e:
except Exception:
return result
# Update the cassette data with the expected hash.
if cassette_data.get("cassette_hash") != self.hash_value:
+4
View File
@@ -0,0 +1,4 @@
hook_state = {
"document_filename": "__UNKNOWN__",
"document_content": "__UNKNOWN__",
}
+27 -52
View File
@@ -6,14 +6,17 @@ import traceback
from typing import Any, Callable, Dict
from markdown import Markdown
from markdown_exec.hooks import SessionHistoryEntry
from mkdocs.structure.files import Files, File
from mkdocs.structure.pages import Page
from pymdownx.superfences import SuperFencesException
from _scripts.hook_state import hook_state
from markdown_exec.hooks import SessionHistoryEntry
from _scripts.generate_api_reference_links import update_markdown_with_imports
from _scripts.notebook_convert import convert_notebook
from _scripts.setup_vcr import load_postamble, load_preamble, _hash_string
from _scripts.setup_vcr import get_hash_for_session, load_postamble, load_preamble, _hash_string
logger = logging.getLogger(__name__)
logging.basicConfig()
@@ -62,29 +65,6 @@ def on_files(files: Files, **kwargs: Dict[str, Any]):
return new_files
def _add_path_to_code_blocks(markdown: str, page: Page) -> str:
"""Add the path to the code blocks."""
code_block_pattern = re.compile(
r"(?P<indent>[ \t]*)```(?P<language>\w+)[ ]*(?P<attributes>[^\n]*)\n"
r"(?P<code>((?:.*\n)*?))" # Capture the code inside the block using named group
r"(?P=indent)```" # Match closing backticks with the same indentation
)
def replace_code_block_header(match: re.Match) -> str:
indent = match.group("indent")
language = match.group("language")
attributes = match.group("attributes").rstrip()
if 'exec="on"' not in attributes:
# Return original code block
return match.group(0)
code = match.group("code")
return f'{indent}```{language} {attributes} path="{page.file.src_path}"\n{code}{indent}```'
return code_block_pattern.sub(replace_code_block_header, markdown)
def _highlight_code_blocks(markdown: str) -> str:
"""Find code blocks with highlight comments and add hl_lines attribute.
@@ -162,7 +142,6 @@ def _highlight_code_blocks(markdown: str) -> str:
markdown = code_block_pattern.sub(replace_highlight_comments, markdown)
return markdown
def handle_vcr_setup(
*,
formatter: Callable,
@@ -174,26 +153,25 @@ def handle_vcr_setup(
**kwargs: Dict[str, Any],
) -> Dict[str, Any]:
"""Handle VCR setup in markdown content if necessary."""
logger.info(f"handle_vcr_setup: {hook_state['document_filename']}")
try:
if kwargs.get("extra", None) is None:
if hook_state['document_filename'] == '__UNKNOWN__':
raise SuperFencesException(
f"error while processing {language} block: extra dict is required"
f"error while processing {language} block: document filename hasn't been set yet"
)
if kwargs["extra"].get("path", None) is None:
if hook_state['document_content'] == '__UNKNOWN__':
raise SuperFencesException(
f"error while processing {language} block: path is required"
f"error while processing {language} block: document content hasn't been set yet"
)
document_filename = kwargs["extra"]["path"]
if session is None or session == "" and id is None or id == "":
id = _hash_string(code)
if session is not None and session != "":
logger.info(f"new {language} session {session} on page {document_filename}")
logger.info(f"new {language} session {session} on page {hook_state['document_filename']}")
cassette_prefix = document_filename.replace(".md", "").replace(os.path.sep, "_")
cassette_prefix = hook_state['document_filename'].replace(".md", "").replace(os.path.sep, "_")
cassette_dir = os.path.abspath(
os.path.join(os.path.dirname(os.path.dirname(__file__)), "cassettes")
@@ -208,14 +186,15 @@ def handle_vcr_setup(
# Add context manager at start with explicit __enter__ and __exit__ calls
hash_ = get_hash_for_session(language, session, hook_state['document_content'])
wrapped_lines = [
load_preamble(language, code, cassette_name),
load_preamble(language, hash_, cassette_name),
code,
]
if session is None or session == "":
logger.info(
f"no session, adding postamble for {language} in {document_filename}"
f"no session, adding postamble for {language} in {hook_state['document_filename']}"
)
wrapped_lines.append(load_postamble(language))
@@ -234,7 +213,7 @@ def handle_vcr_setup(
return dict(
transform_source=lambda code: (transformed_source, code),
id=id,
extra=keep_extras,
extra={ **keep_extras, "path": hook_state['document_filename'] },
)
except Exception as e:
raise SuperFencesException(traceback.format_exc()) from e
@@ -247,24 +226,17 @@ def handle_vcr_teardown(
session: str,
history: list[SessionHistoryEntry],
):
last_inputs = dict(history[-1].inputs)
code = load_postamble(language)
md = last_inputs["md"]
html = False
update_toc = False
document_filename = last_inputs.get("extra", {}).get("path", None)
if document_filename is None:
logger.warning(f"no document filename found while tearing down {session}!")
else:
logger.info(f"tearing down {language} {session} on {document_filename}")
logger.info(f"tearing down {language} {session} on {hook_state['document_filename']}")
kwargs = dict(
code=code,
session=session,
id=f"{id}_vcr_end",
md=md,
md=None, # md is unused by the formatter, but it's a required argument
html=html,
update_toc=update_toc,
extra={},
@@ -296,11 +268,6 @@ def _on_page_markdown_with_config(
# Apply highlight comments to code blocks
markdown = _highlight_code_blocks(markdown)
# Add file path as an attribute to code blocks that are executable.
# This file path is used to associate fixtures with the executable code
# which can be used in CI to test the docs without making network requests.
markdown = _add_path_to_code_blocks(markdown, page)
if remove_base64_images:
# Remove base64 encoded images from markdown
markdown = re.sub(r"!\[.*?\]\(data:image/+;base64,[^\)]+\)", "", markdown)
@@ -309,6 +276,8 @@ def _on_page_markdown_with_config(
def on_page_markdown(markdown: str, page: Page, **kwargs: Dict[str, Any]):
logger.info(f"on_page_markdown: {page.file.src_path}")
hook_state['document_filename'] = page.file.src_path
return _on_page_markdown_with_config(
markdown,
page,
@@ -370,3 +339,9 @@ def on_post_build(config):
+ suffix
)
write_html(config["site_dir"], old_html_path, new_html_path)
def on_pre_page(page: Page, **kwargs: Dict[str, Any]):
logger.info(f"on_pre_page: {page.file.src_path}")
hook_state['document_filename'] = page.file.src_path
hook_state['document_content'] = page.file.content_string
return page
+67 -4
View File
@@ -1,7 +1,12 @@
# A list of patterns that, if found in a code block, will cause us to leave that block unchanged.
import hashlib
import json
import os
from textwrap import dedent
import re
from textwrap import dedent, indent
from mistune import BlockParser, BlockState, Markdown, create_markdown
from mistune.renderers.markdown import MarkdownRenderer
preambles = {
"python": "vcr_setup_preamble.py",
@@ -50,21 +55,19 @@ preamble_cleanups = {
}
def load_preamble(language: str, code: str, cassette_name: str) -> str:
def load_preamble(language: str, hash_: str, cassette_name: str) -> str:
"""Load the source code for the preamble for a given language."""
_assets_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets")
preamble_path = os.path.join(_assets_dir, preambles[language])
with open(preamble_path, "r") as f:
lines = f.readlines()
hash_ = _hash_string(code)
lines.append(preamble_inits[language](cassette_name, hash_))
return "\n".join(lines).strip()
def load_postamble(language: str) -> str:
"""Load the source code for the postamble for a given language."""
return preamble_cleanups[language]()
@@ -75,3 +78,63 @@ def _hash_string(input_string: str) -> str:
sha256_hash = hashlib.sha256(encoded_string)
# Get the hexadecimal digest of the hash
return sha256_hash.hexdigest()
def extract_code_blocks_for_session(language: str, session: str, content: str) -> str:
code_blocks_for_session = []
TAB_REGEX = r"^===!? \"(?P<title>[^\"]+)\"\n(?P<content>(?:(?P<indent> )+[^\n]*\n)+)"
def parse_tabs(block: BlockParser, m: re.Match, state: BlockState) -> str:
state.append_token(
{
"raw": m.group(0),
"type": "block_tab",
"attrs": {
"title": m.group("title"),
"level": len(m.group("indent")) // 4,
"content": dedent(m.group("content")).strip(),
},
}
)
return m.end()
def render_tabs(self, token: dict, state: BlockState):
recursive_transformer = create_markdown(renderer=DocumentRenderer())
recursive_transformer.block.register("block_tab", TAB_REGEX, parse_tabs, before='list')
recursive_transformer.renderer.register("block_tab", render_tabs)
return (
f'=== "{token["attrs"]["title"]}"\n'
f'{indent(recursive_transformer(token["attrs"]["content"]), " " * token["attrs"]["level"])}\n'
)
class DocumentRenderer(MarkdownRenderer):
def block_code(self, token: dict, state: BlockState):
if token["style"] == "fenced":
if token["attrs"]["info"]:
attributes = {}
block_language = token["attrs"]["info"].split()[0]
for match in re.finditer(r'(?P<key>\w+)=(?:(?P<value>(?:[\w]+))|"(?P<value_quoted>(?:[^"\s]+))")', token["attrs"]["info"]):
attributes[match.group("key")] = match.group("value") or match.group("value_quoted")
if block_language == language and "session" in attributes and attributes["session"] == session:
code_blocks_for_session.append(token["raw"].rstrip())
return super().block_code(token, state)
transformer: Markdown = create_markdown(renderer=DocumentRenderer())
transformer.block.register("block_tab", TAB_REGEX, parse_tabs, before='list')
transformer.renderer.register("block_tab", render_tabs)
# Parses the page content, which causes the code blocks to be added to the code_blocks_for_session list.
# There's probably some way to do this by using the renderer as a filter, but I would've had to NO-OP
# all of the default behavior, and this was easier.
transformer(content)
return code_blocks_for_session
def get_hash_for_session(language: str, session: str, content: str) -> str:
# include the preamble in the hash so we invalidate if it changes
preamble_hash = _hash_string(load_preamble(language, session, "test"))
code_blocks_for_session = [preamble_hash, *extract_code_blocks_for_session(language, session, content)]
return _hash_string("\n".join(code_blocks_for_session))
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -12,9 +12,9 @@ below. These libraries can extend LangGraph's functionality in various ways.
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
| Name | GitHub URL | Description | Weekly Downloads |
| --- | --- | --- | --- |
| **trustcall** | [hinthornw/trustcall](https://github.com/hinthornw/trustcall) | Tenacious tool calling built on LangGraph | 8803 |
| **langgraph-supervisor** | [langchain-ai/langgraph-supervisor](https://github.com/langchain-ai/langgraph-supervisor) | Build supervisor multi-agent systems with LangGraph | 636 |
| **breeze-agent** | [andrestorres123/breeze-agent](https://github.com/andrestorres123/breeze-agent) | A streamlined research system built inspired on STORM and built on LangGraph | 184 |
| **trustcall** | [hinthornw/trustcall](https://github.com/hinthornw/trustcall) | Tenacious tool calling built on LangGraph | 11189 |
| **langgraph-supervisor** | [langchain-ai/langgraph-supervisor](https://github.com/langchain-ai/langgraph-supervisor) | Build supervisor multi-agent systems with LangGraph | 1291 |
| **breeze-agent** | [andrestorres123/breeze-agent](https://github.com/andrestorres123/breeze-agent) | A streamlined research system built inspired on STORM and built on LangGraph | 226 |
## ✨ Contributing Your Library
+8 -1
View File
@@ -2,11 +2,18 @@
"name": "docs",
"version": "1.0.0",
"license": "MIT",
"packageManager": "yarn@3.5.1",
"scripts": {
"build": "echo 'export OPENAI_API_KEY=\"sk-proj-1234567890\"' >> ~/.bashrc && echo 'export ANTHROPIC_API_KEY=\"sk-ant-api03-1234567890\"' >> ~/.bashrc && echo 'export PATH=$PATH:/vercel/.local/bin:$PATH' >> ~/.bashrc && source ~/.bashrc && make vercel-build-docs"
"build": "make build-docs"
},
"dependencies": {
"@langchain/core": "^0.3.38",
"@langchain/langgraph": "portal:./langgraphjs/libs/langgraph",
"@langchain/langgraph-checkpoint": "portal:./langgraphjs/libs/checkpoint",
"@langchain/langgraph-checkpoint-mongodb": "portal:./langgraphjs/libs/checkpoint-mongodb",
"@langchain/langgraph-checkpoint-postgres": "portal:./langgraphjs/libs/checkpoint-postgres",
"@langchain/langgraph-checkpoint-sqlite": "portal:./langgraphjs/libs/checkpoint-sqlite",
"@langchain/langgraph-checkpoint-validation": "portal:./langgraphjs/libs/checkpoint-validation",
"@langchain/openai": "^0.4.2",
"msgpack-lite": "^0.1.26",
"nock": "^14.0.1"
+60 -6
View File
@@ -1016,12 +1016,12 @@ version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["docs", "test"]
groups = ["main", "docs", "test"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
markers = {docs = "python_version <= \"3.11\" or python_version >= \"3.12\"", test = "(platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\") and (python_version <= \"3.11\" or python_version >= \"3.12\")"}
markers = {main = "sys_platform == \"win32\" and (python_version <= \"3.11\" or python_version >= \"3.12\")", docs = "python_version <= \"3.11\" or python_version >= \"3.12\"", test = "(platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\") and (python_version <= \"3.11\" or python_version >= \"3.12\")"}
[[package]]
name = "coloredlogs"
@@ -1416,7 +1416,7 @@ version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
groups = ["docs", "test"]
groups = ["main", "docs", "test"]
markers = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
@@ -2462,6 +2462,19 @@ enabler = ["pytest-enabler (>=2.2)"]
test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"]
type = ["pytest-mypy"]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "ipykernel"
version = "6.29.5"
@@ -5234,7 +5247,7 @@ version = "24.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
groups = ["docs", "test"]
groups = ["main", "docs", "test"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
@@ -5522,6 +5535,23 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-a
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
type = ["mypy (>=1.11.2)"]
[[package]]
name = "pluggy"
version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "posthog"
version = "3.12.1"
@@ -6382,6 +6412,30 @@ files = [
[package.extras]
dev = ["build", "flake8", "mypy", "pytest", "twine"]
[[package]]
name = "pytest"
version = "8.3.4"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
{file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.5,<2"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
@@ -7755,7 +7809,7 @@ version = "2.2.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
groups = ["docs", "test"]
groups = ["main", "docs", "test"]
markers = "python_version < \"3.11\""
files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
@@ -8634,4 +8688,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.1"
python-versions = "^3.10"
content-hash = "06debb82135affdb2baf1fdcc028c062c236121508d787588cd0de1db2da11e4"
content-hash = "cbcc30bb9bdead3545070eedf47fb83fe971d4914613e2188323fb3e91035e76"
+1
View File
@@ -10,6 +10,7 @@ readme = "README.md"
python = "^3.10"
aiohappyeyeballs = "2.4.3"
hub = "^3.0.1"
pytest = "^8.3.4"
[tool.poetry.group.docs.dependencies]
langgraph = { path = "../libs/langgraph/", develop = true }
+117
View File
@@ -0,0 +1,117 @@
import re
from textwrap import dedent
import pytest
from _scripts.hook_state import hook_state
from _scripts.notebook_hooks import handle_vcr_setup
from _scripts.setup_vcr import extract_code_blocks_for_session, get_hash_for_session
INITIAL_DOCUMENT_CONTENT = dedent(
"""
Blah blah blah
```python exec="on" source="above" session="1" result="ansi"
print("FIRST_CODE_BLOCK")
```
Blah blah blah!
```python exec="on" source="above" session="1" result="ansi"
print("SECOND_CODE_BLOCK")
```
more blah blah blah
"""
)
@pytest.mark.parametrize(
"replace_string",
[
"FIRST_CODE_BLOCK",
"SECOND_CODE_BLOCK",
],
)
def test_changing_block_in_session_invalidates_hash(replace_string: str):
hook_state['document_filename'] = 'test.md'
hook_state['document_content'] = INITIAL_DOCUMENT_CONTENT
code = "print('Hello, world!')"
result1 = handle_vcr_setup(
formatter=lambda **kwargs: None,
language="python",
session="1",
id="test",
code=code,
md=None,
extra={},
)
cassette_init_expr = re.compile(r"^_cassette = HashedCassette\('[^']+', '(?P<hash>[^']+)'\)$")
assert result1['transform_source']
execute_source, display_source = result1['transform_source'](code)
assert display_source == code
cassette_init_line = [line for line in execute_source.splitlines() if line.startswith("_cassette = HashedCassette(")][0]
assert cassette_init_line
match = cassette_init_expr.match(cassette_init_line)
assert match
hash_ = str(match.group('hash'))
# change the content of the second block of code
hook_state['document_content'] = INITIAL_DOCUMENT_CONTENT.replace(replace_string, "world")
assert hook_state['document_content'] != INITIAL_DOCUMENT_CONTENT
result2 = handle_vcr_setup(
formatter=lambda **kwargs: None,
language="python",
session="1",
id="test",
code=code,
md=None,
extra={},
)
assert result2['transform_source']
execute_source, display_source = result2['transform_source'](code)
assert display_source == code
cassette_init_line = [line for line in execute_source.splitlines() if line.startswith("_cassette = HashedCassette(")][0]
assert cassette_init_line
match = cassette_init_expr.match(cassette_init_line)
assert match
# this is the important part
assert str(match.group('hash')) != hash_
@pytest.mark.parametrize(
"replace_string",
[
"FIRST_CODE_BLOCK",
"SECOND_CODE_BLOCK",
],
)
def test_get_hash_for_session(replace_string: str):
hash_ = get_hash_for_session(
language="python",
session="1",
content=INITIAL_DOCUMENT_CONTENT,
)
content = INITIAL_DOCUMENT_CONTENT.replace(replace_string, "world")
assert content != INITIAL_DOCUMENT_CONTENT
assert get_hash_for_session(
language="python",
session="1",
content=INITIAL_DOCUMENT_CONTENT.replace(replace_string, "world"),
) != hash_
def test_get_code_blocks_for_session():
code_blocks = extract_code_blocks_for_session(
language="python",
session="1",
content=INITIAL_DOCUMENT_CONTENT,
)
assert code_blocks
assert len(code_blocks) == 2
assert code_blocks[0] == 'print("FIRST_CODE_BLOCK")'
assert code_blocks[1] == 'print("SECOND_CODE_BLOCK")'
+1 -1
View File
@@ -111,7 +111,7 @@ print("Hello, World!")
END_TO_END_INPUT_HIGHLIGHT_1_EXPECT = """\
```python exec="on" source="below" hl_lines="2" path="dummy.md"
```python exec="on" source="below" hl_lines="2"
print("Hello, World!")
print("Hello, World!")
```
+3 -2
View File
@@ -1,4 +1,5 @@
{
"buildCommand": "yarn build",
"outputDirectory": "site"
"buildCommand": "echo 'export OPENAI_API_KEY=\"sk-proj-1234567890\"' >> ~/.bashrc && echo 'export ANTHROPIC_API_KEY=\"sk-ant-api03-1234567890\"' >> ~/.bashrc && echo 'export PATH=$PATH:/vercel/.local/bin:$PATH' >> ~/.bashrc && source ~/.bashrc && make vercel-build-docs",
"outputDirectory": "site",
"installCommand": "echo done"
}
+4823 -408
View File
File diff suppressed because it is too large Load Diff