diff --git a/docs/_scripts/third_party_page/__init__.py b/docs/_scripts/third_party_page/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/_scripts/third_party_page/create_third_party_page.py b/docs/_scripts/third_party_page/create_third_party_page.py new file mode 100755 index 000000000..15038f5a3 --- /dev/null +++ b/docs/_scripts/third_party_page/create_third_party_page.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +"""Create the third party page for the documentation.""" + +import argparse +from typing import List +from typing import TypedDict + +import yaml + +MARKDOWN = """\ +# 🚀 Third-party Libraries + +A collection of third-party libraries that extend LangGraph's functionality. + +## 📚 Available Libraries + +{library_list} + +## ✨ Contributing Your Library + +If you'd like to add your library to this list, please open a pull request on the {langgraph_url}. + +Thanks for contributing! 🚀 +""" + + +class ResolvedPackage(TypedDict): + name: str + """The name of the package.""" + repo: str + """Repository ID within github. Format is: [orgname]/[repo_name].""" + weekly_downloads: int | None + """The weekly download count of the package.""" + description: str + """A brief description of what the package does.""" + + +def generate_markdown(resolved_packages: List[ResolvedPackage], language: str) -> str: + """Generate the markdown content for the third party page. + + Args: + resolved_packages: A list of resolved package information. + language: str + + Returns: + The markdown content as a string. + """ + # Update the URL to the actual file once the initial version is merged + if language == "python": + langgraph_url = "https://github.com/langchain-ai/langgraph/pulls" + elif language == "js": + langgraph_url = "https://github.com/langchain-ai/langgraphjs/pulls" + else: + raise ValueError(f"Invalid language '{language}'. Expected 'python' or 'js'.") + + sorted_packages = sorted( + resolved_packages, key=lambda p: p["weekly_downloads"] or 0, reverse=True + ) + rows = [ + "| Name | GitHub URL | Description | Downloads |", + "| --- | --- | --- | --- |", + ] + for package in sorted_packages: + name = f"**{package['name']}**" + repo_url = f"[{package['repo']}](https://github.com/{package['repo']})" + downloads = package["weekly_downloads"] or 0 + row = f"| {name} | {repo_url} | {package['description']} | {downloads} |" + rows.append(row) + markdown_content = MARKDOWN.format( + library_list="\n".join(rows), langgraph_url=langgraph_url + ) + return markdown_content + + +def main(input_file: str, output_file: str, language: str) -> None: + """Main function to create the third party page. + + Args: + input_file: Path to the input YAML file containing resolved package information. + output_file: Path to the output file for the third party page. + language: The language for which to generate the third party page. + """ + # Parse the input YAML file + with open(input_file, "r") as f: + resolved_packages: List[ResolvedPackage] = yaml.safe_load(f) + + markdown_content = generate_markdown(resolved_packages, language) + + # Write the markdown content to the output file + with open(output_file, "w", encoding="utf-8") as f: + f.write(markdown_content) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Create the third party page.") + parser.add_argument( + "input_file", + help="Path to the input YAML file containing resolved package information.", + ) + parser.add_argument( + "output_file", help="Path to the output file for the third party page." + ) + parser.add_argument( + "--language", + choices=["python", "js"], + default="python", + help="The language for which to generate the third party page. Defaults to 'python'.", + ) + args = parser.parse_args() + + main(args.input_file, args.output_file, args.language) diff --git a/docs/_scripts/third_party_page/get_download_stats.py b/docs/_scripts/third_party_page/get_download_stats.py new file mode 100755 index 000000000..1bd027341 --- /dev/null +++ b/docs/_scripts/third_party_page/get_download_stats.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +"""Retrieve download count for a list of Python packages from PyPI.""" + +import argparse +from datetime import datetime +from typing import TypedDict +import pathlib + +import requests +import yaml + + +class Package(TypedDict): + """A TypedDict representing a package""" + + name: str + """The name of the package.""" + repo: str + """Repository ID within github. Format is: [orgname]/[repo_name].""" + description: str + """A brief description of what the package does.""" + + +class ResolvedPackage(Package): + weekly_downloads: int | None + + +HERE = pathlib.Path(__file__).parent +PACKAGES_FILE = HERE / "packages.yml" +PACKAGES = yaml.safe_load(PACKAGES_FILE.read_text())['packages'] + + +def _get_weekly_downloads(packages: list[Package]) -> list[ResolvedPackage]: + """Retrieve the monthly download count for a list of packages from PyPIStats.""" + resolved_packages: list[ResolvedPackage] = [] + + for package in packages: + url = f"https://pypistats.org/api/packages/{package['name']}/overall" + + response = requests.get(url) + response.raise_for_status() + data = response.json() + + sorted_data = sorted( + data["data"], + key=lambda x: datetime.strptime(x["date"], "%Y-%m-%d"), + reverse=True, + ) + + # Sum the last 7 days of downloads + num_downloads = sum(entry["downloads"] for entry in sorted_data[:7]) + + resolved_packages.append( + { + "name": package["name"], + "repo": package["repo"], + "weekly_downloads": num_downloads, + "description": package["description"], + } + ) + + return resolved_packages + + + +def main(output_file: str) -> None: + """Main function to generate package download information. + + Args: + output_file: Path to the output YAML file. + """ + resolved_packages: list[ResolvedPackage] = _get_weekly_downloads(PACKAGES) + + if not output_file.endswith(".yml"): + raise ValueError("Output file must have a .yml extension") + + with open(output_file, "w") as f: + f.write("# This file is auto-generated. Do not edit.\n") + yaml.dump(resolved_packages, f) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate package download information." + ) + parser.add_argument( + "output_file", + help=( + "Path to the output YAML file. Example: python generate_downloads.py " + "downloads.yml" + ), + ) + args = parser.parse_args() + + main(args.output_file) diff --git a/docs/_scripts/third_party_page/packages.yml b/docs/_scripts/third_party_page/packages.yml new file mode 100644 index 000000000..480b20f26 --- /dev/null +++ b/docs/_scripts/third_party_page/packages.yml @@ -0,0 +1,5 @@ +#A list of third-party packages to surface on the third-party page. +packages: + - name: "trustcall" + repo: "hinthornw/trustcall" + description: "Tenacious tool calling built on LangGraph"