Skip to content

Commit 46c0d75

Browse files
committed
.
1 parent 7b97809 commit 46c0d75

File tree

2 files changed

+195
-0
lines changed

2 files changed

+195
-0
lines changed

examples/dict_to_schema/README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Dict to Schema
2+
3+
This example demonstrates how to automatically convert Python dictionary literals into Pydantic models. The codemod makes this process simple by handling all the tedious manual updates automatically.
4+
5+
## How the Conversion Script Works
6+
7+
The script (`run.py`) automates the entire conversion process in a few key steps:
8+
9+
1. **Codebase Loading**
10+
```python
11+
codebase = Codebase.from_repo("modal-labs/modal-client")
12+
```
13+
- Loads your codebase into Codegen's intelligent code analysis engine
14+
- Provides a simple SDK for making codebase-wide changes
15+
- Supports any Git repository as input
16+
17+
2. **Dictionary Detection**
18+
```python
19+
if "{" in global_var.source and "}" in global_var.source:
20+
dict_content = global_var.value.source.strip("{}")
21+
```
22+
- Automatically identifies dictionary literals in your code
23+
- Processes both global variables and class attributes
24+
- Skips empty dictionaries to avoid unnecessary conversions
25+
26+
3. **Schema Creation**
27+
```python
28+
class_name = global_var.name.title() + "Schema"
29+
model_def = f"""class {class_name}(BaseModel):
30+
{dict_content.replace(",", "\n ")}"""
31+
```
32+
- Generates meaningful model names based on variable names
33+
- Converts dictionary key-value pairs to class attributes
34+
- Maintains proper Python indentation
35+
36+
4. **Code Updates**
37+
```python
38+
global_var.insert_before(model_def + "\n\n")
39+
global_var.set_value(f"{class_name}(**{global_var.value.source})")
40+
```
41+
- Inserts new Pydantic models in appropriate locations
42+
- Updates dictionary assignments to use the new models
43+
- Automatically adds required Pydantic imports
44+
45+
46+
## Common Conversion Patterns
47+
48+
### Global Variables
49+
```python
50+
# Before
51+
config = {"host": "localhost", "port": 8080}
52+
53+
# After
54+
class ConfigSchema(BaseModel):
55+
host: str = "localhost"
56+
port: int = 8080
57+
58+
config = ConfigSchema(**{"host": "localhost", "port": 8080})
59+
```
60+
61+
### Class Attributes
62+
```python
63+
# Before
64+
class Service:
65+
defaults = {"timeout": 30, "retries": 3}
66+
67+
# After
68+
class DefaultsSchema(BaseModel):
69+
timeout: int = 30
70+
retries: int = 3
71+
72+
class Service:
73+
defaults = DefaultsSchema(**{"timeout": 30, "retries": 3})
74+
```
75+
76+
## Running the Conversion
77+
78+
```bash
79+
# Install Codegen
80+
pip install codegen
81+
82+
# Run the conversion
83+
python run.py
84+
```
85+
86+
## Learn More
87+
88+
- [Pydantic Documentation](https://docs.pydantic.dev/)
89+
- [Codegen Documentation](https://docs.codegen.com)
90+
91+
## Contributing
92+
93+
Feel free to submit issues and enhancement requests!

examples/dict_to_schema/run.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import codegen
2+
from codegen import Codebase
3+
4+
5+
@codegen.function("dict-to-pydantic-schema")
6+
def run(codebase: Codebase):
7+
"""Convert dictionary literals to Pydantic models in a Python codebase.
8+
9+
This codemod:
10+
1. Finds all dictionary literals in global variables and class attributes
11+
2. Creates corresponding Pydantic models
12+
3. Updates the assignments to use the new models
13+
4. Adds necessary Pydantic imports
14+
"""
15+
# Track statistics
16+
files_modified = 0
17+
models_created = 0
18+
19+
# Iterate through all files in the codebase
20+
for file in codebase.files:
21+
needs_imports = False
22+
file_modified = False
23+
24+
# Look for dictionary assignments in global variables
25+
for global_var in file.global_vars:
26+
try:
27+
if "{" in global_var.source and "}" in global_var.source:
28+
dict_content = global_var.value.source.strip("{}")
29+
if not dict_content.strip():
30+
continue
31+
32+
# Convert dict to Pydantic model
33+
class_name = global_var.name.title() + "Schema"
34+
model_def = f"""class {class_name}(BaseModel):
35+
{dict_content.replace(",", "\n ")}"""
36+
37+
print(f"\nConverting '{global_var.name}' to schema")
38+
print("\nOriginal code:")
39+
print(global_var.source)
40+
print("\nNew code:")
41+
print(model_def)
42+
print(f"{class_name}(**{global_var.value.source})")
43+
print("-" * 50)
44+
45+
# Insert model and update assignment
46+
global_var.insert_before(model_def + "\n\n")
47+
global_var.set_value(f"{class_name}(**{global_var.value.source})")
48+
needs_imports = True
49+
models_created += 1
50+
file_modified = True
51+
except Exception as e:
52+
print(f"Error processing global variable {global_var.name}: {str(e)}")
53+
54+
# Look for dictionary assignments in class attributes
55+
for cls in file.classes:
56+
for attr in cls.attributes:
57+
try:
58+
if "{" in attr.source and "}" in attr.source:
59+
dict_content = attr.value.source.strip("{}")
60+
if not dict_content.strip():
61+
continue
62+
63+
# Convert dict to Pydantic model
64+
class_name = attr.name.title() + "Schema"
65+
model_def = f"""class {class_name}(BaseModel):
66+
{dict_content.replace(",", "\n ")}"""
67+
68+
print(f"\nConverting'{attr.name}' to schema")
69+
print("\nOriginal code:")
70+
print(attr.source)
71+
print("\nNew code:")
72+
print(model_def)
73+
print(f"{class_name}(**{attr.value.source})")
74+
print("-" * 50)
75+
76+
# Insert model and update attribute
77+
cls.insert_before(model_def + "\n\n")
78+
attr.set_value(f"{class_name}(**{attr.value.source})")
79+
needs_imports = True
80+
models_created += 1
81+
file_modified = True
82+
except Exception as e:
83+
print(f"Error processing attribute {attr.name} in class {cls.name}: {str(e)}")
84+
85+
# Add imports if needed
86+
if needs_imports:
87+
file.add_import_from_import_string("from pydantic import BaseModel")
88+
89+
if file_modified:
90+
files_modified += 1
91+
92+
print("\nModification complete:")
93+
print(f"Files modified: {files_modified}")
94+
print(f"Schemas created: {models_created}")
95+
96+
97+
if __name__ == "__main__":
98+
print("Initializing codebase...")
99+
codebase = Codebase.from_repo("modal-labs/modal-client")
100+
101+
print("Running codemod...")
102+
run(codebase)

0 commit comments

Comments
 (0)