Skip to content

Commit 23d702b

Browse files
jayhackcodegen-bot
andcommitted
feat: codegen init creates + perists .codegen/.venv (#124)
# Motivation <!-- Why is this change necessary? --> # Content <!-- Please include a summary of the change --> # Testing <!-- How was the change tested? --> # Please check the following before marking your PR as ready for review - [ ] I have added tests for my changes - [ ] I have updated the documentation or added new documentation as needed - [ ] I have read and agree to the [Contributor License Agreement](../CLA.md) --------- Co-authored-by: codegen-bot <team+codegenbot@codegen.sh>
1 parent bdb3c7c commit 23d702b

File tree

10 files changed

+162
-119
lines changed

10 files changed

+162
-119
lines changed

docs/building-with-codegen/dot-codegen.mdx

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ The `.codegen` directory contains your project's Codegen configuration, codemods
1111

1212
```bash
1313
.codegen/
14-
├── config.toml # Project configuration
15-
├── codemods/ # Your codemod implementations
16-
├── jupyter/ # Jupyter notebooks for exploration
17-
├── docs/ # API documentation
18-
├── examples/ # Example code
19-
└── prompts/ # AI system prompts
14+
├── .venv/ # Python virtual environment (gitignored)
15+
├── config.toml # Project configuration
16+
├── codemods/ # Your codemod implementations
17+
├── jupyter/ # Jupyter notebooks for exploration
18+
└── codegen-system-prompt.txt # AI system prompt
2019
```
2120

2221
## Initialization
@@ -31,6 +30,20 @@ codegen init [--fetch-docs] [--repo-name NAME] [--organization-name ORG]
3130
The `--fetch-docs` flag downloads API documentation and examples specific to your project's programming language.
3231
</Note>
3332

33+
## Virtual Environment
34+
35+
Codegen maintains its own virtual environment in `.codegen/.venv/` to ensure consistent package versions and isolation from your project's dependencies. This environment is:
36+
37+
- Created using `uv` for fast, reliable package management
38+
- Initialized with Python 3.13
39+
- Automatically managed by Codegen commands
40+
- Used for running codemods and Jupyter notebooks
41+
- Gitignored to avoid committing environment-specific files
42+
43+
The environment is created during `codegen init` and used by commands like `codegen run` and `codegen notebook`.
44+
45+
<Note>To debug codemods, you will need to set the python virtual environment in your IDE to `.codegen/.venv`</Note>
46+
3447
### Configuration
3548

3649
The `config.toml` file stores your project settings:
@@ -49,13 +62,15 @@ Codegen automatically adds appropriate entries to your `.gitignore`:
4962

5063
```gitignore
5164
# Codegen
52-
.codegen/prompts/
65+
.codegen/.venv/
5366
.codegen/docs/
54-
.codegen/examples/
67+
.codegen/jupyter/
68+
.codegen/codegen-system-prompt.txt
5569
```
5670

5771
<Info>
58-
While prompts, docs, and examples are ignored, your codemods in `.codegen/codemods/` are tracked in Git.
72+
- While most directories are ignored, your codemods in `.codegen/codemods/` and `config.toml` are tracked in Git
73+
- The virtual environment and Jupyter notebooks are gitignored to avoid environment-specific issues
5974
</Info>
6075

6176
## Working with Codemods

docs/introduction/overview.mdx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ iconType: "solid"
99

1010
It provides a scriptable interface to a powerful, multi-lingual language server built on top of [Tree-sitter](https://tree-sitter.github.io/tree-sitter/).
1111

12-
export const intoSnippet = `# Codegen builds a complete graph connecting
12+
export const metaCode = `# Codegen builds a complete graph connecting
1313
# functions, classes, imports and their relationships
1414
from codegen import Codebase
1515
@@ -21,13 +21,23 @@ for function in codebase.functions:
2121
function.remove()
2222
`
2323

24+
export const code = `def foo():
25+
pass
26+
27+
def bar():
28+
foo()
29+
30+
def baz():
31+
pass
32+
`
33+
2434
<iframe
2535
width="100%"
2636
height="300px"
2737
scrolling="no"
28-
src={`https://codegen.sh/embedded/codemod/?code=${encodeURIComponent(
29-
intoSnippet
30-
)}`}
38+
src={`https://chadcode.sh/embedded/codemod/?code=${encodeURIComponent(
39+
metaCode
40+
)}&input=${encodeURIComponent(code)}`}
3141
style={{
3242
backgroundColor: "#15141b",
3343
}}

src/codegen/cli/commands/create/main.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from codegen.cli.rich.pretty_print import pretty_print_error
1313
from codegen.cli.rich.spinners import create_spinner
1414
from codegen.cli.utils.constants import ProgrammingLanguage
15+
from codegen.cli.utils.default_code import DEFAULT_CODEMOD
1516
from codegen.cli.workspace.decorators import requires_init
1617

1718

@@ -62,19 +63,6 @@ def make_relative(path: Path) -> str:
6263
return f"./{path.name}"
6364

6465

65-
def get_default_code(name: str) -> str:
66-
"""Get the default function code without using the API."""
67-
return f'''import codegen
68-
from codegen import Codebase
69-
70-
@codegen.function("{name}")
71-
def run(codebase: Codebase):
72-
"""Add a description of what this codemod does."""
73-
# Add your code here
74-
pass
75-
'''
76-
77-
7866
@click.command(name="create")
7967
@requires_init
8068
@click.argument("name", type=str)
@@ -111,7 +99,7 @@ def create_command(session: CodegenSession, name: str, path: Path, description:
11199
prompt_path.write_text(response.context)
112100
else:
113101
# Use default implementation
114-
code = get_default_code(name)
102+
code = DEFAULT_CODEMOD.format(name=name)
115103

116104
# Create the target directory if needed
117105
target_path.parent.mkdir(parents=True, exist_ok=True)

src/codegen/cli/commands/init/render.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
def get_success_message(codegen_dir: Path, docs_dir: Path, examples_dir: Path) -> str:
55
"""Get the success message to display after initialization."""
66
return """📁 .codegen configuration folder created:
7-
[dim]config.toml[/dim] Project configuration
8-
[dim]codemods/[/dim] Your codemod implementations
9-
[dim]codegen-system-prompt.txt[/dim] AI system prompt (gitignored)"""
7+
[dim]config.toml[/dim] Project configuration
8+
[dim]codemods/[/dim] Your codemod implementations
9+
[dim].venv/[/dim] Python virtual environment (gitignored)
10+
[dim]codegen-system-prompt.txt[/dim] AI system prompt (gitignored)"""

src/codegen/cli/commands/notebook/main.py

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from codegen.cli.auth.decorators import requires_auth
99
from codegen.cli.auth.session import CodegenSession
1010
from codegen.cli.rich.spinners import create_spinner
11+
from codegen.cli.utils.notebooks import create_notebook
1112
from codegen.cli.workspace.decorators import requires_init
1213
from codegen.cli.workspace.venv_manager import VenvManager
1314

@@ -19,57 +20,24 @@ def create_jupyter_dir() -> Path:
1920
return jupyter_dir
2021

2122

22-
def create_notebook(jupyter_dir: Path) -> Path:
23-
"""Create a new Jupyter notebook if it doesn't exist."""
24-
notebook_path = jupyter_dir / "tmp.ipynb"
25-
if not notebook_path.exists():
26-
notebook_content = {
27-
"cells": [
28-
{
29-
"cell_type": "code",
30-
"execution_count": None,
31-
"metadata": {},
32-
"outputs": [],
33-
"source": ["from codegen import Codebase\n", "\n", "# Initialize codebase\n", "codebase = Codebase('../../')\n"],
34-
}
35-
],
36-
"metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}},
37-
"nbformat": 4,
38-
"nbformat_minor": 4,
39-
}
40-
import json
41-
42-
notebook_path.write_text(json.dumps(notebook_content, indent=2))
43-
return notebook_path
44-
45-
4623
@click.command(name="notebook")
47-
@click.option("--background", is_flag=True, help="Run Jupyter Lab in the background")
4824
@requires_auth
4925
@requires_init
50-
def notebook_command(session: CodegenSession, background: bool = False):
26+
def notebook_command(session: CodegenSession):
5127
"""Open a Jupyter notebook with the current codebase loaded."""
5228
with create_spinner("Setting up Jupyter environment...") as status:
5329
venv = VenvManager()
5430

55-
if not venv.is_initialized():
56-
status.update("Creating virtual environment...")
57-
venv.create_venv()
58-
59-
status.update("Installing required packages...")
60-
venv.install_packages("codegen", "jupyterlab")
31+
status.update("Checking Jupyter installation...")
32+
venv.ensure_jupyter()
6133

6234
jupyter_dir = create_jupyter_dir()
6335
notebook_path = create_notebook(jupyter_dir)
6436

65-
status.update("Starting Jupyter Lab...")
37+
status.update("Running Jupyter Lab...")
6638

6739
# Prepare the environment with the virtual environment activated
6840
env = {**os.environ, "VIRTUAL_ENV": str(venv.venv_dir), "PATH": f"{venv.venv_dir}/bin:{os.environ['PATH']}"}
6941

70-
# Start Jupyter Lab
71-
if background:
72-
subprocess.Popen(["jupyter", "lab", str(notebook_path)], env=env, start_new_session=True)
73-
else:
74-
# Run in foreground
75-
subprocess.run(["jupyter", "lab", str(notebook_path)], env=env, check=True)
42+
# Run Jupyter Lab
43+
subprocess.run(["jupyter", "lab", str(notebook_path)], env=env, check=True)

src/codegen/cli/commands/run/main.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import json
2+
import os
23

34
import rich_click as click
45

56
from codegen.cli.auth.session import CodegenSession
67
from codegen.cli.utils.codemod_manager import CodemodManager
78
from codegen.cli.utils.json_schema import validate_json
89
from codegen.cli.workspace.decorators import requires_init
10+
from codegen.cli.workspace.venv_manager import VenvManager
911

1012

1113
@click.command(name="run")
@@ -22,6 +24,15 @@ def run_command(
2224
arguments: str | None = None,
2325
):
2426
"""Run a codegen function by its label."""
27+
# Ensure venv is initialized
28+
venv = VenvManager()
29+
if not venv.is_initialized():
30+
raise click.ClickException("Virtual environment not found. Please run 'codegen init' first.")
31+
32+
# Set up environment with venv
33+
os.environ["VIRTUAL_ENV"] = str(venv.venv_dir)
34+
os.environ["PATH"] = f"{venv.venv_dir}/bin:{os.environ['PATH']}"
35+
2536
# Get and validate the codemod
2637
codemod = CodemodManager.get_codemod(label)
2738

src/codegen/cli/utils/default_code.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
DEFAULT_CODEMOD = '''import codegen
2+
from codegen import Codebase
3+
4+
5+
@codegen.function("{name}")
6+
def run(codebase: Codebase):
7+
"""Add a description of what this codemod does."""
8+
# Add your code here
9+
print('Total files: ', len(codebase.files))
10+
print('Total functions: ', len(codebase.functions))
11+
print('Total imports: ', len(codebase.imports))
12+
13+
14+
if __name__ == "__main__":
15+
print('Parsing codebase...')
16+
codebase = Codebase("./")
17+
18+
print('Running...')
19+
run(codebase)
20+
'''

src/codegen/cli/utils/notebooks.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import json
2+
from pathlib import Path
3+
4+
DEFAULT_NOTEBOOK_CONTENT = """from codegen import Codebase
5+
6+
# Initialize codebase
7+
codebase = Codebase('../../')
8+
9+
# Print out stats
10+
print("🔍 Codebase Analysis")
11+
print("=" * 50)
12+
print(f"📚 Total Files: {len(codebase.files)}")
13+
print(f"⚡ Total Functions: {len(codebase.functions)}")
14+
print(f"🔄 Total Imports: {len(codebase.imports)}")
15+
""".strip()
16+
17+
18+
def create_notebook(jupyter_dir: Path) -> Path:
19+
"""Create a new Jupyter notebook if it doesn't exist.
20+
21+
Args:
22+
jupyter_dir: Directory where the notebook should be created
23+
24+
Returns:
25+
Path to the created or existing notebook
26+
"""
27+
notebook_path = jupyter_dir / "tmp.ipynb"
28+
if not notebook_path.exists():
29+
notebook_content = {
30+
"cells": [
31+
{
32+
"cell_type": "code",
33+
"execution_count": None,
34+
"metadata": {},
35+
"outputs": [],
36+
"source": DEFAULT_NOTEBOOK_CONTENT,
37+
}
38+
],
39+
"metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}},
40+
"nbformat": 4,
41+
"nbformat_minor": 4,
42+
}
43+
notebook_path.write_text(json.dumps(notebook_content, indent=2))
44+
return notebook_path

src/codegen/cli/workspace/initialize_workspace.py

Lines changed: 11 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,46 +13,10 @@
1313
from codegen.cli.git.repo import get_git_repo
1414
from codegen.cli.git.url import get_git_organization_and_repo
1515
from codegen.cli.rich.spinners import create_spinner
16+
from codegen.cli.utils.notebooks import create_notebook
1617
from codegen.cli.workspace.docs_workspace import populate_api_docs
1718
from codegen.cli.workspace.examples_workspace import populate_examples
18-
19-
DEFAULT_CODE = """
20-
from codegen import Codebase
21-
22-
# Initialize codebase
23-
codebase = Codebase('../../')
24-
25-
# Print out stats
26-
print("🔍 Codebase Analysis")
27-
print("=" * 50)
28-
print(f"📚 Total Files: {len(codebase.files)}")
29-
print(f"⚡ Total Functions: {len(codebase.functions)}")
30-
print(f"🔄 Total Imports: {len(codebase.imports)}")
31-
"""
32-
33-
34-
def create_notebook(jupyter_dir: Path) -> Path:
35-
"""Create a new Jupyter notebook if it doesn't exist."""
36-
notebook_path = jupyter_dir / "tmp.ipynb"
37-
if not notebook_path.exists():
38-
notebook_content = {
39-
"cells": [
40-
{
41-
"cell_type": "code",
42-
"execution_count": None,
43-
"metadata": {},
44-
"outputs": [],
45-
"source": [DEFAULT_CODE],
46-
}
47-
],
48-
"metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}},
49-
"nbformat": 4,
50-
"nbformat_minor": 4,
51-
}
52-
import json
53-
54-
notebook_path.write_text(json.dumps(notebook_content, indent=2))
55-
return notebook_path
19+
from codegen.cli.workspace.venv_manager import VenvManager
5620

5721

5822
def initialize_codegen(
@@ -93,6 +57,13 @@ def initialize_codegen(
9357
JUPYTER_DIR.mkdir(parents=True, exist_ok=True)
9458
CODEMODS_DIR.mkdir(parents=True, exist_ok=True)
9559

60+
# Initialize virtual environment
61+
status_obj.update(f" {'Creating' if isinstance(status, str) else 'Checking'} virtual environment...")
62+
venv = VenvManager()
63+
if not venv.is_initialized():
64+
venv.create_venv()
65+
venv.install_packages("codegen")
66+
9667
# Download system prompt
9768
try:
9869
from codegen.cli.api.endpoints import CODEGEN_SYSTEM_PROMPT_URL
@@ -164,7 +135,8 @@ def modify_gitignore(codegen_folder: Path):
164135
"examples/",
165136
"prompts/",
166137
"jupyter/",
167-
"codegen-system-prompt.txt", # Add system prompt to gitignore
138+
".venv/", # Add venv to gitignore
139+
"codegen-system-prompt.txt",
168140
"",
169141
"# Python cache files",
170142
"__pycache__/",

0 commit comments

Comments
 (0)