AI Writes Python Code, But Maintaining It Is Still Your Job


Photo by the Author
# Introduction
AI coding tools are surprisingly good at writing functional Python code. They can build entire applications and run complex algorithms in minutes. However, generated AI code is often a pain to maintain.
If you use tools like Claude Code, GitHub Copilotor Cursing agent mode, you may have encountered this. AI helps you ship working code faster, but the cost comes later. You've probably redone an inflated job just to understand how it works a few weeks after it's done.
The problem is not that AI writes bad code – although sometimes it does – it's that AI prepares to “work now” and fulfill needs at your own pace, while you need code that is readable and maintainable in the long term. This article shows you how to close this gap by focusing on Python-specific techniques.
# Avoiding the Blank Canvas Trap
The biggest mistake developers make is asking the AI to start over. AI agents work best with constraints and guidelines.
Before you write your first message, lay the foundations for the project yourself. This means choosing the architecture of your project – including your main libraries and using a few working examples – setting the tone. This may seem counterintuitive, but it helps make the AI write code that better matches what you need in your application.
Start by building a few features manually. When building an API, use a single endpoint that's packed with all the patterns you want: dependency injection, proper error handling, database access, and authentication. This becomes the reference implementation.
Say you write this first conclusion manually:
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
router = APIRouter()
# Assume get_db and User model are defined elsewhere
async def get_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
When AI sees this pattern, it understands how we handle dependencies, how we query the database, and how we handle missing records.
The same applies to your project structure. Create your directory, set up imports, and prepare your test framework. AI should not be making these architectural decisions.
# Python Type Programming Does the Heavy Lifting
Python's dynamic typing is flexible, but that flexibility becomes mandatory when AI writes your code. Make type hints important monitoring features instead of niceties to have in your application code.
Strong typing catches AI errors before they reach production. If you need type hints for all job signatures then run mypy in hard mode, the AI can't take shortcuts. It cannot return implicit types or accept parameters that may be strings or may be lists.
More importantly, strong brands force better design. For example, an AI agent trying to write a task that accepts data: dict he can make many ideas about what is in that dictionary. However, the AI agent writes the task that accepts it data: UserCreateRequest there UserCreateRequest it's a Pydantic the model has exactly one definition.
# This constrains AI to write correct code
from pydantic import BaseModel, EmailStr
class UserCreateRequest(BaseModel):
name: str
email: EmailStr
age: int
class UserResponse(BaseModel):
id: int
name: str
email: EmailStr
def process_user(data: UserCreateRequest) -> UserResponse:
pass
# Rather than this
def process_user(data: dict) -> dict:
pass
Use libraries that enforce contracts: SQLAlchemy 2.0 are type-tested models as well FastAPI with response models are the best choices. These are not just good practices; they are the obstacles that keep AI on track.
Set mypy in strict mode and make passing type checking non-negotiable. When the AI generates code that fails the type check, it will iterate until it passes. This automatic feedback loop produces better code than any amount of rapid engineering.
# Creating Documents to Guide AI
Most projects have documentation that developers don't understand. For AI agents, you need scripts that they actually use – like a README.md file with instructions. This means a single file with clear and precise rules.
Create a CLAUDE.md or AGENTS.md file in your project root. Don't make it too long. Focus on what's different about your project than standard Python best practices.
Your AI guidelines should specify:
- The structure of the project and where there are different types of code
- Which libraries will be used for common tasks
- Some patterns to follow (refer to example files)
- The forbidden patterns are obvious
- Assessment requirements
Here is an example AGENTS.md file:
# Project Guidelines
## Structure
/src/api - FastAPI routers
/src/services - business logic
/src/models - SQLAlchemy models
/src/schemas - Pydantic models
## Patterns
- All services inherit from BaseService (see src/services/base.py)
- All database access goes through repository pattern (see src/repositories/)
- Use dependency injection for all external dependencies
## Standards
- Type hints on all functions
- Docstrings using Google style
- Functions under 50 lines
- Run `mypy --strict` and `ruff check` before committing
## Never
- No bare except clauses
- No type: ignore comments
- No mutable default arguments
- No global state
The key is to be specific. Don't just say “follow best practices.” Point to the exact file that displays the pattern. Don't just “handle mistakes properly;” indicate the desired error handling pattern.
# Writing Persuasive Pointing Examples
Standard notifications generate standard code. Direct reference to your existing codebase produces maintainable code.
Instead of asking the AI to “add authenticity,” it walks through the process with clues to your patterns. Here is an example of such information that points to examples:
Use JWT authentication in src/services/auth_service.py. Follow the same structure as UserService in src/services/user_service.py. Use bcrypt to get the hashing of the password (which is already in requirements.txt).
Add validation dependencies to src/api/dependencies.py following the get_db pattern.
Create Pydantic schemas in src/schemas/auth.py similar to user.py.
Add pytest tests to tests/test_auth_service.py using the fix from conftest.py.
Notice how all the instructions point to an existing file or pattern. You don't ask AI to build architecture; you ask it to apply what you need to the new feature.
When the AI generates code, it updates it against your patterns. Does it use the same dependency injection method? Does it follow the same error handling? Does it plan imports in the same way? If not, point out the conflict and ask it to match the existing pattern.
# Planning Before Use
AI agents can move quickly, which can make them ineffective at times if speed comes at a cost to the plot. Use program mode or invoke the application before any code is written.
The planning step forces the AI to think about dependencies and structure. It also gives you the opportunity to catch architectural problems – such as circular dependencies or obsolete resources – before they are used.
Request a plan that specifies:
- Which files will be created or modified
- What dependencies exist between components
- What patterns are there to follow
- What tests are required
Review this plan as you would review a design document. Check that the AI understands your project structure. Make sure it uses the correct libraries and make sure it doesn't rewrite something that already exists.
If the program looks good, let the AI run it. If not, fix the program before any code is written. It's easier to fix a bad program than it is to fix bad code.
# Asking AI to Write Realistic Tests
AI is good and very fast in writing exams. However, AI is not good at writing useful tests unless you are specific about what “useful” means.
The default AI test behavior is to test the fun way and nothing else. You get tests that make sure the code works if everything goes well, which is exactly where you don't need tests.
State your testing needs clearly. For all aspects, you need:
- A fun way to test
- Validation error testing checks what happens with invalid input
- Edge case testing for null values, None, boundary conditions, and more
- Error handling tests for database failures, external service failures, and the like
Point AI to your existing test files as examples. If you have good test patterns already, the AI will write useful tests, too. If you don't already have good tests, write a few yourself first.
# Systematic Output Validation
After the AI generates the code, you can simply check if it works. Do it with a checklist.
Your validation checklist should include questions like the following:
- Does it bypass mypy hard mode
- Does it follow existing code patterns
- Are all functions under 50 lines
- Do tests include edge cases and errors
- Are there any kind of plans in every job
- Does it use the specified libraries correctly
Automate what you can. Set up commit yourself beforehand mypy moving hooks, The Ruffand pytest. If the AI-generated code fails this test, it is not committed.
In what you can't automate, you'll see common conflicting patterns after reviewing enough AI code – such as multitasking tasks, exception handling errors, or the concept of authentication mixed with business logic.
# Implementing Effective Workflows
Now let's summarize everything we have discussed so far.
You start a new project. You spend time planning the layout, choosing and installing libraries, and writing a few example features. You are creative CLAUDE.md with your instructions and write some Pydantic models.
Now you ask the AI to implement a new feature. Write detailed information and point to your examples. AI makes the plan. You review and approve it. AI writes code. You use testing and type testing. Everything passes. You review the code against your patterns. It's compatible. He gives himself up.
The total time from call to commit can be only 15 minutes for a feature that would take an hour to write manually. But most importantly, the code you get is easy to maintain – it follows the patterns you set up.
The next feature is faster because the AI has more examples to learn from. Code evolves over time because every new feature reinforces existing patterns.
# Wrapping up
With AI coding tools proving so useful, your job as an engineer or data scientist is changing. Now you spend less time writing code and more time on:
- Designing systems and selecting structures
- Creating reference patterns
- Writing constraints and guidelines
- It updates the AI output and maintains the quality bar
The most important skill is not to write code quickly. Instead, it designs programs that force AI to write maintainable code. Knowing which processes scale and which create technical debt. I hope you found this article useful even if you don't use Python as your programming language of choice. Let us know what else you think we can do to keep AI-generated Python code maintainable. Keep checking!
Count Priya C is an engineer and technical writer from India. He loves working at the intersection of mathematics, programming, data science, and content creation. His areas of interest and expertise include DevOps, data science, and natural language processing. She enjoys reading, writing, coding, and coffee! Currently, he works to learn and share his knowledge with the engineering community by authoring tutorials, how-to guides, ideas, and more. Bala also creates engaging resource overviews and code tutorials.



