diff --git a/README.md b/README.md index 1759d94cb..dae55f326 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ [![Documentation](https://img.shields.io/badge/docs-docs.codegen.com-purple?style=flat-square)](https://docs.codegen.com) [![Slack Community](https://img.shields.io/badge/slack-community-4A154B?logo=slack&style=flat-square)](https://community.codegen.com) [![License](https://img.shields.io/badge/Code%20License-Apache%202.0-gray?&color=gray)](https://github.com/codegen-sh/codegen-sdk/tree/develop?tab=Apache-2.0-1-ov-file) -[![Follow on X](https://img.shields.io/twitter/follow/codegen?style=social)](https://x.com/codegen) +[![Follow on X](https://img.shields.io/twitter/follow/codegen?style=social)](https://x.com/codegen) diff --git a/docs/introduction/getting-started.mdx b/docs/introduction/getting-started.mdx index 7c0463f37..cdb805452 100644 --- a/docs/introduction/getting-started.mdx +++ b/docs/introduction/getting-started.mdx @@ -9,7 +9,7 @@ A quick tour of Codegen in a Jupyter notebook. ## Installation -Install [`codegen` on pypi](https://pypi.org/project/codegen/) via [uv](https://github.com/astral-sh/uv): +Install [codegen](https://pypi.org/project/codegen/) on Pypi via [uv](https://github.com/astral-sh/uv): ```bash uv tool install codegen @@ -34,8 +34,8 @@ The [codgen notebook](/cli/notebook) command creates a virtual environment and o # Navigate to your repository cd path/to/git/repository -# Initialize codegen and launch Jupyter -codegen notebook +# Initialize codegen and launch Jupyter with a demo notebook +codegen notebook --demo ``` This will: @@ -47,7 +47,7 @@ This will: 2. Launch Jupyter Lab with a pre-configured notebook - The notebook comes pre-configured to load your codebase, so you can start + The `notebook --demo` comes pre-configured to load [FastAPI](https://github.com/fastapi/fastapi)'s codebase , so you can start exploring right away! @@ -58,13 +58,17 @@ Instantiating a [Codebase](/api-reference/core/Codebase) will automatically pars ```python from codegen import Codebase -# Parse a codebase -codebase = Codebase("./") +# Initialize FastAPI codebase +print('Cloning and parsing FastAPI to /tmp/codegen/fastapi...') +codebase = Codebase.from_repo('fastapi/fastapi') + +# To initialize an existing local codebase, use this constructor +# codebase = Codebase("path/to/git/repo") ``` This will automatically infer the programming language of the codebase and - parse all files in the codebase. + parse all files in the codebase. Learn more about [parsing codebases here](/building-with-codegen/parsing-codebases) @@ -81,8 +85,10 @@ Here are some common patterns for code navigation in Codegen: - Iterate over all [Functions](/api-reference/core/Function) with [Codebase.functions](/api-reference/core/Codebase#functions) - View class inheritance with [Class.superclasses](/api-reference/core/Class#superclasses) -- View function call-sites with [Function.call_sites](/api-reference/core/Function#call-sites) - View function usages with [Function.usages](/api-reference/core/Function#usages) +- View inheritance hierarchies with [inheritance APIs](https://docs.codegen.com/building-with-codegen/class-api#working-with-inheritance) +- Identify recursive functions by looking at [FunctionCalls](https://docs.codegen.com/building-with-codegen/function-calls-and-callsites) +- View function call-sites with [Function.call_sites](/api-reference/core/Function#call-sites) ```python # Print overall stats @@ -108,7 +114,7 @@ if recursive: print(f" - {func.name}") ``` -### Analyzing Tests +## Analyzing Tests Let's specifically drill into large test files, which can be cumbersome to manage. @@ -135,38 +141,52 @@ for file, num_tests in file_test_counts.most_common()[:5]: print(f" ๐Ÿ’ก Functions: {len(file.functions)}") ``` -### Splitting Up Large Test Files +## Splitting Up Large Test Files + +Lets split up the largest test files into separate modules for better organization. + +This uses Codegen's [codebase.move_to_file(...)](/building-with-codegen/moving-symbols), which will: +- update all imports +- (optionally) move dependencies +- do so very fast โšก๏ธ -Lets split up the largest test files into separate modules for better organization: +While maintaining correctness. ```python -print("\n๐Ÿ“ฆ Splitting Test Files") +filename = 'tests/test_path.py' +print(f"๐Ÿ“ฆ Splitting Test File: {filename}") print("=" * 50) -# Process top 5 largest test files -for file, num_tests in file_test_counts.most_common()[:5]: - # Create a new directory based on the file name - base_name = file.path.replace('.py', '') - print(f"\n๐Ÿ”„ Processing: {file.filepath}") - print(f" ๐Ÿ“Š {num_tests} test classes to split") - - # Move each test class to its own file - for test_class in file.classes: - if test_class.name.startswith('Test'): - # Create descriptive filename from test class name - new_file = f"{base_name}/{test_class.name.lower()}.py" - print(f" ๐Ÿ“ Moving {test_class.name} -> {new_file}") - - # Codegen handles all the complexity: - # - Creates directories if needed - # - Updates all imports automatically - # - Maintains test dependencies - # - Preserves decorators and docstrings - test_class.move_to_file(new_file) +# Grab a file +file = codebase.get_file(filename) +base_name = filename.replace('.py', '') + +# Group tests by subpath +test_groups = {} +for test_function in file.functions: + if test_function.name.startswith('test_'): + test_subpath = '_'.join(test_function.name.split('_')[:3]) + if test_subpath not in test_groups: + test_groups[test_subpath] = [] + test_groups[test_subpath].append(test_function) + +# Print and process each group +for subpath, tests in test_groups.items(): + print(f"\\n{subpath}/") + new_filename = f"{base_name}/{subpath}.py" + + # Create file if it doesn't exist + if not codebase.has_file(new_filename): + new_file = codebase.create_file(new_filename) + file = codebase.get_file(new_filename) + + # Move each test in the group + for test_function in tests: + print(f" - {test_function.name}") + test_function.move_to_file(new_file, strategy="add_back_edge") # Commit changes to disk codebase.commit() - ``` @@ -299,14 +319,15 @@ if base_class: Understand key concepts like working with files, functions, imports, and the call graph to effectively manipulate code. + + Iterate locally with your favorite IDE, work with a debugger and build sophisticated codemods + Learn how to use Codegen with Cursor, Devin, Windsurf, and more. - - Explore the complete API documentation for all Codegen classes and methods. - + diff --git a/src/codegen/cli/commands/notebook/main.py b/src/codegen/cli/commands/notebook/main.py index bff4a9d01..d00447288 100644 --- a/src/codegen/cli/commands/notebook/main.py +++ b/src/codegen/cli/commands/notebook/main.py @@ -20,9 +20,11 @@ def create_jupyter_dir() -> Path: @click.command(name="notebook") +@click.option("--background", is_flag=True, help="Run Jupyter Lab in the background") +@click.option("--demo", is_flag=True, help="Create a demo notebook with FastAPI example code") @requires_init -def notebook_command(session: CodegenSession): - """Open a Jupyter notebook with the current codebase loaded.""" +def notebook_command(session: CodegenSession, background: bool, demo: bool): + """Launch Jupyter Lab with a pre-configured notebook for exploring your codebase.""" with create_spinner("Setting up Jupyter environment...") as status: venv = VenvManager() @@ -30,7 +32,7 @@ def notebook_command(session: CodegenSession): venv.ensure_jupyter() jupyter_dir = create_jupyter_dir() - notebook_path = create_notebook(jupyter_dir) + notebook_path = create_notebook(jupyter_dir, demo=demo) status.update("Running Jupyter Lab...") diff --git a/src/codegen/cli/utils/notebooks.py b/src/codegen/cli/utils/notebooks.py index c4b047ebf..2414eeff7 100644 --- a/src/codegen/cli/utils/notebooks.py +++ b/src/codegen/cli/utils/notebooks.py @@ -1,7 +1,11 @@ import json from pathlib import Path +from typing import Any -DEFAULT_NOTEBOOK_CONTENT = """from codegen import Codebase +DEFAULT_CELLS = [ + { + "cell_type": "code", + "source": """from codegen import Codebase # Initialize codebase codebase = Codebase('../../') @@ -11,31 +15,199 @@ print("=" * 50) print(f"๐Ÿ“š Total Files: {len(codebase.files)}") print(f"โšก Total Functions: {len(codebase.functions)}") +print(f"๐Ÿ”„ Total Imports: {len(codebase.imports)}")""".strip(), + } +] + +DEMO_CELLS = [ + ##### [ CODGEN DEMO ] ##### + { + "cell_type": "markdown", + "source": """# Codegen Demo: FastAPI + +Welcome to [Codegen](https://docs.codegen.com)! + +This demo notebook will walk you through some features of Codegen applied to [FastAPI](https://github.com/fastapi/fastapi). + +See the [getting started](https://docs.codegen.com/introduction/getting-started) guide to learn more.""".strip(), + }, + { + "cell_type": "code", + "source": """from codegen import Codebase + +# Initialize FastAPI codebase +print('Cloning and parsing FastAPI to /tmp/codegen/fastapi...') +codebase = Codebase.from_repo('fastapi/fastapi', commit="eab0653a346196bff6928710410890a300aee4ae") + +# To initialize a local codebase, use this constructor +# codebase = Codebase("path/to/git/repo")""".strip(), + }, + ##### [ CODEBASE ANALYSIS ] ##### + { + "cell_type": "markdown", + "source": """# Codebase Analysis + +Let's do a quick codebase analysis! + +- Grab codebase content with [codebase.functions](https://docs.codegen.com/building-with-codegen/symbol-api) et al. +- View inheritance hierarchies with [inhertance APIs](https://docs.codegen.com/building-with-codegen/class-api#working-with-inheritance) +- Identify recursive functions by looking at [FunctionCalls](https://docs.codegen.com/building-with-codegen/function-calls-and-callsites)""".strip(), + }, + { + "cell_type": "code", + "source": """# Print overall stats +print("๐Ÿ” FastAPI Analysis") +print("=" * 50) +print(f"๐Ÿ“š Total Classes: {len(codebase.classes)}") +print(f"โšก Total Functions: {len(codebase.functions)}") print(f"๐Ÿ”„ Total Imports: {len(codebase.imports)}") -""".strip() + +# Find class with most inheritance +if codebase.classes: + deepest_class = max(codebase.classes, key=lambda x: len(x.superclasses)) + print(f"\\n๐ŸŒณ Class with most inheritance: {deepest_class.name}") + print(f" ๐Ÿ“Š Chain Depth: {len(deepest_class.superclasses)}") + print(f" โ›“๏ธ Chain: {' -> '.join(s.name for s in deepest_class.superclasses)}") + +# Find first 5 recursive functions +recursive = [f for f in codebase.functions + if any(call.name == f.name for call in f.function_calls)][:5] +if recursive: + print(f"\\n๐Ÿ”„ Recursive functions:") + for func in recursive: + print(f" - {func.name} ({func.file.filepath})")""".strip(), + }, + ##### [ TEST DRILL DOWN ] ##### + { + "cell_type": "markdown", + "source": """# Drilling Down on Tests + +Let's specifically drill into large test files, which can be cumbersome to manage:""".strip(), + }, + { + "cell_type": "code", + "source": """from collections import Counter + +# Filter to all test functions and classes +test_functions = [x for x in codebase.functions if x.name.startswith('test_')] + +print("๐Ÿงช Test Analysis") +print("=" * 50) +print(f"๐Ÿ“ Total Test Functions: {len(test_functions)}") +print(f"๐Ÿ“Š Tests per File: {len(test_functions) / len(codebase.files):.1f}") + +# Find files with the most tests +print("\\n๐Ÿ“š Top Test Files by Count") +print("-" * 50) +file_test_counts = Counter([x.file for x in test_functions]) +for file, num_tests in file_test_counts.most_common()[:5]: + print(f"๐Ÿ” {num_tests} test functions: {file.filepath}") + print(f" ๐Ÿ“ File Length: {len(file.source.split('\\n'))} lines") + print(f" ๐Ÿ’ก Functions: {len(file.functions)}")""".strip(), + }, + ##### [ TEST SPLITTING ] ##### + { + "cell_type": "markdown", + "source": """# Splitting Up Large Test Files + +Lets split up the largest test files into separate modules for better organization. + +This uses Codegen's [codebase.move_to_file(...)](https://docs.codegen.com/building-with-codegen/moving-symbols), which will: +- update all imports +- (optionally) move depenencies +- do so very fast โšก๏ธ + +While maintaining correctness.""", + }, + ##### [ TEST SPLITTING ] ##### + { + "cell_type": "code", + "source": """filename = 'tests/test_path.py' +print(f"๐Ÿ“ฆ Splitting Test File: {filename}") +print("=" * 50) + +# Grab a file +file = codebase.get_file(filename) +base_name = filename.replace('.py', '') + +# Group tests by subpath +test_groups = {} +for test_function in file.functions: + if test_function.name.startswith('test_'): + test_subpath = '_'.join(test_function.name.split('_')[:3]) + if test_subpath not in test_groups: + test_groups[test_subpath] = [] + test_groups[test_subpath].append(test_function) + +# Print and process each group +for subpath, tests in test_groups.items(): + print(f"\\n{subpath}/") + new_filename = f"{base_name}/{subpath}.py" + + # Create file if it doesn't exist + if not codebase.has_file(new_filename): + new_file = codebase.create_file(new_filename) + file = codebase.get_file(new_filename) + + # Move each test in the group + for test_function in tests: + print(f" - {test_function.name}") + test_function.move_to_file(new_file, strategy="add_back_edge") + +# Commit changes to disk +codebase.commit()""".strip(), + }, + ##### [ RESET ] ##### + { + "cell_type": "markdown", + "source": """## View Changes + +You can now view changes by `cd /tmp/codegen/fastapi && git diff` + +Enjoy! + +# Reset + +Reset your codebase to it's initial state, discarding all changes + +Learn more in [commit and reset](https://docs.codegen.com/building-with-codegen/commit-and-reset).""".strip(), + }, + { + "cell_type": "code", + "source": """codebase.reset()""".strip(), + }, +] + + +def create_cells(cells_data: list[dict[str, str]]) -> list[dict[str, Any]]: + """Convert cell data into Jupyter notebook cell format.""" + return [ + { + "cell_type": cell["cell_type"], + "source": cell["source"], + "metadata": {}, + "execution_count": None, + "outputs": [] if cell["cell_type"] == "code" else None, + } + for cell in cells_data + ] -def create_notebook(jupyter_dir: Path) -> Path: +def create_notebook(jupyter_dir: Path, demo: bool = False) -> Path: """Create a new Jupyter notebook if it doesn't exist. Args: jupyter_dir: Directory where the notebook should be created + demo: Whether to create a demo notebook with FastAPI example code Returns: Path to the created or existing notebook """ - notebook_path = jupyter_dir / "tmp.ipynb" + notebook_path = jupyter_dir / ("demo.ipynb" if demo else "tmp.ipynb") if not notebook_path.exists(): + cells = create_cells(DEMO_CELLS if demo else DEFAULT_CELLS) notebook_content = { - "cells": [ - { - "cell_type": "code", - "execution_count": None, - "metadata": {}, - "outputs": [], - "source": DEFAULT_NOTEBOOK_CONTENT, - } - ], + "cells": cells, "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}}, "nbformat": 4, "nbformat_minor": 4,