Skip to content

Commit aa65f54

Browse files
Merge branch 'develop' into api-reference-v0
2 parents be74e76 + 2d224ab commit aa65f54

File tree

10 files changed

+112
-15
lines changed

10 files changed

+112
-15
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ repos:
8282
hooks:
8383
- id: renovate-config-validator
8484
- repo: https://github.com/astral-sh/uv-pre-commit
85-
rev: "0.5.23"
85+
rev: "0.5.24"
8686
hooks:
8787
- id: uv-lock
8888
entry: bash -c "uv lock --frozen"

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Graph sitter
22

33
[![Documentation](https://img.shields.io/badge/docs-docs.codegen.com-blue)](https://docs.codegen.com)
4-
[![Unit Tests](https://github.com/codegen-sh/codegen-sdk/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/codegen-sh/codegen-sdk/actions/workflows/unit-tests.yml)
54

65
[Codegen](https://docs.codegen.com) is a python library for manipulating codebases.
76

@@ -42,7 +41,7 @@ We built Codegen backwards from real-world refactors performed on enterprise cod
4241

4342
- **Natural mental model**: Write transforms that read like your thought process - "move this function", "rename this variable", "add this parameter". No more wrestling with ASTs or manual import management.
4443

45-
- **Battle-tested on complex codebases**: Handle Python, TypeScript, and React codebases with millions of lines of code. Built and validated on refactors at companies like [Ramp](https://ramp.com).
44+
- **Battle-tested on complex codebases**: Handle Python, TypeScript, and React codebases with millions of lines of code.
4645

4746
- **Built for advanced intelligences**: As AI developers become more sophisticated, they need expressive yet precise tools to manipulate code. Codegen provides a programmatic interface that both humans and AI can use to express complex transformations through code itself.
4847

docs/building-with-codegen/files-and-directories.mdx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,32 @@ dir = file.directory
5050
exists = codebase.has_directory("path/to/dir")
5151
```
5252

53+
## Working with Non-Code Files (README, JSON, etc.)
54+
55+
By default, Codegen focuses on source code files (Python, TypeScript, etc). However, you can access all files in your codebase, including documentation, configuration, and other non-code files like README.md, package.json, or .env:
56+
57+
```python
58+
# Get all files in the codebase (including README, docs, config files)
59+
files = codebase.files(extensions="*")
60+
61+
# Print files that are not source code (documentation, config, etc)
62+
for file in files:
63+
if not file.filepath.endswith(('.py', '.ts', '.js')):
64+
print(f"📄 Non-code file: {file.filepath}")
65+
```
66+
67+
You can also filter for specific file types:
68+
69+
```python
70+
# Get only markdown documentation files
71+
docs = codebase.files(extensions=[".md", ".mdx"])
72+
73+
# Get configuration files
74+
config_files = codebase.files(extensions=[".json", ".yaml", ".toml"])
75+
```
76+
77+
These APIs are similar for [`Directory`](../api-reference/core/Directory), which provides similar methods for accessing files and subdirectories.
78+
5379
## Raw Content and Metadata
5480

5581
```python

docs/prism.css

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
1+
/* Override font variables */
2+
:root {
3+
--font-jetbrains-mono: Menlo, Monaco, "Courier New", monospace !important;
4+
/* --font-jetbrains-mono: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace !important; */
5+
}
6+
7+
pre[class*="language-"],
8+
code[class*="language-"],
9+
pre,
10+
code,
11+
kbd,
12+
samp {
13+
font-family: Menlo, Monaco, "Andale Mono", "Ubuntu Mono", monospace !important;
14+
--font-jetbrains-mono: Consolas, Monaco, "Andale Mono", "Ubuntu Mono",
15+
monospace !important;
16+
font-weight: 500 !important;
17+
}
18+
119
pre[class*="language-"] {
2-
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
20+
font-family: Menlo, Monaco, "Andale Mono", "Ubuntu Mono", monospace !important;
21+
font-weight: 500 !important;
322
text-align: left;
423
white-space: pre;
524
word-spacing: normal;
@@ -52,7 +71,7 @@ pre[class*="language-"] {
5271

5372
.language-python .language-bash {
5473
background-color: #15141b !important;
55-
font-family: Menlo, Monaco, Consolas, "Courier New", monospace !important;
74+
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace !important;
5675
font-weight: 600 !important;
5776
font-size: 16px !important;
5877
}

src/codegen/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from codegen.cli.sdk.decorator import function
2+
from codegen.cli.sdk.functions import Function
3+
from codegen.sdk.core.codebase import Codebase
4+
5+
__all__ = ["Codebase", "Function", "function"]

src/codegen/git/repo_operator/remote_repo_operator.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@
2424
class RemoteRepoOperator(RepoOperator):
2525
"""A wrapper around GitPython to make it easier to interact with a cloned lowside repo."""
2626

27+
# __init__ attributes
2728
repo_config: RepoConfig
2829
base_dir: str
30+
github_type: GithubType
31+
32+
# lazy attributes
2933
_remote_git_repo: GitRepoClient | None = None
3034
_codeowners_parser: CodeOwnersParser | None = None
3135
_default_branch: str | None = None
32-
bot_commit: bool = True
3336

3437
# TODO: allow setting the access scope level of the lowside repo (currently it's always WRITE)
3538
def __init__(
@@ -38,9 +41,10 @@ def __init__(
3841
base_dir: str = "/tmp",
3942
setup_option: SetupOption = SetupOption.PULL_OR_CLONE,
4043
shallow: bool = True,
41-
bot_commit: bool = True,
44+
github_type: GithubType = GithubType.GithubEnterprise,
4245
) -> None:
43-
super().__init__(repo_config=repo_config, base_dir=base_dir, bot_commit=bot_commit)
46+
super().__init__(repo_config=repo_config, base_dir=base_dir)
47+
self.github_type = github_type
4448
self.setup_repo_dir(setup_option=setup_option, shallow=shallow)
4549

4650
####################################################################################################################
@@ -50,8 +54,7 @@ def __init__(
5054
@property
5155
def remote_git_repo(self) -> GitRepoClient:
5256
if not self._remote_git_repo:
53-
# NOTE: local repo operator by default points at lowside (i.e. origin remote is lowside remote)
54-
self._remote_git_repo = GitRepoClient(self.repo_config, github_type=GithubType.GithubEnterprise, access_scope=GithubScope.WRITE)
57+
self._remote_git_repo = GitRepoClient(self.repo_config, github_type=self.github_type, access_scope=GithubScope.WRITE)
5558
return self._remote_git_repo
5659

5760
@property
@@ -69,27 +72,31 @@ def codeowners_parser(self) -> CodeOwnersParser | None:
6972
####################################################################################################################
7073
# SET UP
7174
####################################################################################################################
75+
7276
@override
7377
def pull_repo(self) -> None:
7478
"""Pull the latest commit down to an existing local repo"""
75-
pull_repo(repo=self.repo_config, path=self.base_dir)
79+
pull_repo(repo=self.repo_config, path=self.base_dir, github_type=self.github_type)
80+
81+
def clone_repo(self, shallow: bool = True) -> None:
82+
clone_repo(repo=self.repo_config, path=self.base_dir, shallow=shallow, github_type=self.github_type)
7683

77-
def clone_or_pull_repo(self) -> None:
84+
def clone_or_pull_repo(self, shallow: bool = True) -> None:
7885
"""If repo exists, pulls changes. otherwise, clones the repo."""
7986
# TODO(CG-7804): if repo is not valid we should delete it and re-clone. maybe we can create a pull_repo util + use the existing clone_repo util
8087
if self.repo_exists():
8188
self.clean_repo()
82-
clone_or_pull_repo(self.repo_config, path=self.base_dir, shallow=True)
89+
clone_or_pull_repo(self.repo_config, path=self.base_dir, shallow=shallow, github_type=self.github_type)
8390

8491
def setup_repo_dir(self, setup_option: SetupOption = SetupOption.PULL_OR_CLONE, shallow: bool = True) -> None:
8592
os.makedirs(self.base_dir, exist_ok=True)
8693
os.chdir(self.base_dir)
8794
if setup_option is SetupOption.CLONE:
8895
# if repo exists delete, then clone, else clone
89-
clone_repo(repo=self.repo_config, path=self.base_dir, shallow=shallow)
96+
clone_repo(shallow=shallow)
9097
elif setup_option is SetupOption.PULL_OR_CLONE:
9198
# if repo exists, pull changes, else clone
92-
self.clone_or_pull_repo()
99+
self.clone_or_pull_repo(shallow=shallow)
93100
elif setup_option is SetupOption.SKIP:
94101
if not self.repo_exists():
95102
logger.warning(f"Valid git repo does not exist at {self.repo_path}. Cannot skip setup with SetupOption.SKIP.")

src/codegen/sdk/core/autocommit/decorators.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import TYPE_CHECKING, ParamSpec, TypeVar, Union, overload
44

55
import wrapt
6+
67
from codegen.sdk.core.autocommit.constants import AutoCommitState, enabled
78
from codegen.sdk.core.node_id_factory import NodeId
89

src/codegen/sdk/core/codebase.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ def files(self, *, extensions: list[str] | Literal["*"] | None = None) -> list[T
224224
if extensions is None:
225225
# Return all source files
226226
files = self.G.get_nodes(NodeType.FILE)
227+
elif isinstance(extensions, str) and extensions != "*":
228+
raise ValueError("extensions must be a list of extensions or '*'")
227229
else:
228230
files = []
229231
# Get all files with the specified extensions

tests/unit/codebase/file/test_file.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,28 @@ def test_codebase_files(tmpdir) -> None:
5959
assert {f for f in codebase.files(extensions=[".bin"])} == {file3}
6060

6161

62+
@pytest.mark.skip("MDX editing is broken")
63+
def test_codebase_edit_mdx(tmpdir) -> None:
64+
"""Editing MDx seems broken currently - it will just prepend to the file"""
65+
with get_codebase_session(tmpdir=tmpdir, files={"file1.mdx": "# Header", "file2.tsx": "console.log('hello, world!')"}) as codebase:
66+
file = codebase.get_file("file1.mdx")
67+
file.edit("NEW TEXT")
68+
codebase.commit()
69+
file = codebase.get_file("file1.mdx")
70+
assert file.content == "NEW TEXT"
71+
72+
73+
@pytest.mark.skip("MDX replacing is broken")
74+
def test_codebase_replace_mdx(tmpdir) -> None:
75+
"""Editing MDx seems broken currently - it will just prepend to the file"""
76+
with get_codebase_session(tmpdir=tmpdir, files={"file1.mdx": "# Header"}) as codebase:
77+
file = codebase.get_file("file1.mdx")
78+
file.replace("# Header", "NEW TEXT")
79+
codebase.commit()
80+
file = codebase.get_file("file1.mdx")
81+
assert file.content == "NEW TEXT"
82+
83+
6284
@pytest.mark.skipif(sys.platform == "darwin", reason="macOS is case-insensitive")
6385
def test_file_extensions_ignore_case(tmpdir) -> None:
6486
with get_codebase_session(tmpdir=tmpdir, files={"file1.py": "print(123)", "file2.py": "print(456)", "file3.bin": b"\x89PNG", "file4": "Hello world!"}) as codebase:

tests/unit/test_imports.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import codegen
2+
from codegen import Codebase
3+
4+
5+
def test_codegen_imports():
6+
# Test decorated function
7+
@codegen.function(name="sample_codemod")
8+
def run(codebase):
9+
pass
10+
11+
# Test class
12+
cls = codegen.Function
13+
assert cls is not None
14+
15+
codebase = Codebase("./")
16+
assert codebase is not None

0 commit comments

Comments
 (0)