GitHub Copilot with PyCharm
Python is dynamically typed, which changes what Copilot can do for you. In Java,
a signature tells the model almost everything. In Python, a function taking
data and returning something could be doing anything at all — and the model
will guess, plausibly and often wrongly.
This lesson is about closing that gap: giving Copilot the constraints Python does not supply by default, and using PyCharm’s environment awareness to keep answers tied to the packages you actually have.
Copilot support
Copilot in PyCharm (JetBrains plugin)
One plugin across the whole JetBrains family. Core features are supported; much of the customisation layer is still in preview.
- Code completionSupported
- ChatSupported
- Agent modeSupported
- Custom instructionsPreview
- MCPSupported
- Copilot code reviewSupported
Key takeaways
- The plugin is the standard JetBrains one — see the JetBrains lesson for setup and feature support.
- Type hints are the highest-leverage habit in Python. They constrain suggestions the way declarations do in typed languages.
- Copilot does not know which virtual environment is active. Keep dependency files open and name your versions.
- PyCharm Community Edition is supported, so Copilot does not require Professional.
- Generated
pytesttests need reviewing for assertions that would pass regardless of correctness. - Django and FastAPI answers drift towards the most common tutorial patterns unless you pin your version and structure.
Setup, briefly
Install GitHub Copilot from the Marketplace inside PyCharm, restart, then Tools → GitHub Copilot → Login to GitHub and complete the device-code flow in your browser.
Type hints do the work
The single most useful thing you can do for Copilot in Python is annotate.
Consider a function that takes records and returns something. Without hints,
the model has to infer from the name, the body and whatever surrounding code it
can see. With hints:
def summarise_by_region(
orders: list[Order],
*,
include_cancelled: bool = False,
) -> dict[str, Decimal]:it now knows the input is a list of a specific domain type, that there is a keyword-only flag with a default, and that the return is a mapping of strings to decimals rather than floats. The space of plausible completions has collapsed dramatically, and — importantly — a suggestion that returns floats is now visibly wrong rather than subtly wrong.
Three further habits follow from this:
- Annotate before asking, not after. Adding hints to a function you are about to have Copilot fill in costs seconds and improves what comes back.
- Use domain types.
CustomerIdcommunicates more thanstr, even as aNewType. - Say what
Optionalmeans.str | Nonein a signature tells the model that absence is expected and must be handled rather than assumed away.
The interpreter problem
Copilot does not know which Python interpreter your project uses, which virtual environment is active, or which packages are installed. PyCharm knows all three; the model does not.
The consequence is familiar: suggestions that import a package you do not have,
or that use an API from a version other than yours. Python makes this worse than
most ecosystems because the same library name can behave very differently across
major versions — Pydantic 1 and 2, SQLAlchemy 1.4 and 2.0, and the whole
requests-versus-httpx question are all live in training data simultaneously.
Two mitigations, both cheap.
Keep your dependency file open. pyproject.toml, requirements.txt or
environment.yml — whichever your project uses. It states the packages and the
pinned versions.
Name the version in the prompt when it matters.
Write this model using Pydantic v2 — field validators in the v2 style, not v1. Use only packages already listed in my pyproject.toml. If something is missing, tell me rather than importing it.
Generating pytest tests
/tests produces reasonable pytest output, with the same condition as
elsewhere: give it an existing test module to imitate, or you will get whichever
style is most common rather than yours.
The specific things worth checking in generated Python tests:
Fixtures. If your project uses conftest.py fixtures, generated tests should
use them rather than constructing objects inline. Have conftest.py open.
Parametrisation. @pytest.mark.parametrize is the idiomatic way to cover
cases in pytest, and generated tests often write three near-identical functions
instead. Ask for it explicitly.
Assertions that cannot fail. The dangerous pattern is a test that asserts a mock was called rather than that the result was right. It passes whether or not the code works.
Write pytest tests for this function, using the fixtures in the conftest.py I have open. Use @pytest.mark.parametrize for the input variations. Every assertion must check a value that would be different if the function were wrong — do not assert only that a mock was called.
Run them in PyCharm’s test runner and read the results. Then do the check that matters: break the function deliberately and confirm the tests fail. A test suite that passes against broken code is worse than none, and this is the fastest way to find out.
Django
Django’s structure is highly conventional, which cuts both ways. The model has seen an enormous amount of Django and produces plausible code readily. It has also seen every Django version since 1.x, and tutorials outnumber production code in that corpus.
Three practical points.
Pin the version. “Django 5.2” in your repository instructions prevents patterns retired several releases ago.
Open the model when asking about a view, and the view when asking about a template. Django’s layers reference each other by name, and a file that does not show the related layer leaves the model to guess field names.
Migrations are not a Copilot task. Generate them with makemigrations and
read them. Copilot can usefully explain what a migration will do to existing
data, which is a genuinely valuable question:
Explain what this migration does to existing rows. Identify anything that would fail or lose data on a table that already has several million rows, and say whether it takes a lock that would block writes.
That is a question about consequences, which is where the tool earns its place — as opposed to generating the migration itself, which Django does correctly and Copilot does approximately.
FastAPI and typed frameworks
FastAPI suits Copilot better than most Python frameworks for a structural reason: it is built on type hints, so the constraints are already in the code. A route declaring its Pydantic request and response models has told the model nearly everything about what the handler must do.
The habit that follows is to write the models first, then the route signature, then let completion fill the body. By the time you reach the body, the shape is so constrained that the suggestion is usually close.
Where it still goes wrong: async correctness. A blocking database call inside an
async def handler will work in development and fall over under load. The model
writes both patterns fluently and cannot tell which library you are using is
async. Check every I/O call in an async handler.
Notebooks
PyCharm Professional supports Jupyter notebooks, and Copilot works in them, with a caveat worth stating.
Notebook state is invisible. A cell’s behaviour depends on what ran before it, and — because cells can be executed out of order — on the order in which they ran. Copilot sees the code, not the kernel state, so a suggestion referencing a variable can be right in the notebook you intended and wrong in the kernel you have.
Notebooks also accumulate patterns that would not survive review in a module: mutation of globals, re-defined names, cells that only work once. Generated code inherits the surrounding style. If notebook code is destined for a module, that is a rewrite rather than a copy, and Copilot is useful for it:
Convert this notebook cell into a module-level function with type hints and no dependence on globals. Every input becomes a parameter. Tell me which values it was relying on from earlier cells.
Data work: pandas, NumPy and the plausible-wrong problem
A large share of Python work is data manipulation, and it is the area where Copilot is simultaneously most impressive and most dangerous.
Impressive, because pandas has a wide API with many ways to express the same operation, and the model knows all of them. Asking for a grouped aggregation with a rolling window produces working code faster than looking up the syntax.
Dangerous, because pandas operations fail quietly. A merge that should have been
an inner join done as a left join produces a DataFrame with nulls rather than an
error. A groupby that drops NaN keys silently loses rows. Chained assignment
may or may not write back. None of this raises; all of it changes your numbers.
The habit that addresses it is to check shape at every step rather than reading the code and deciding it looks right:
Write this aggregation. Then list, separately, the ways it could silently produce wrong output rather than raising: rows dropped by the join, NaN handling in the grouping keys, and any operation whose result depends on the index. I want the failure modes, not reassurance.
Two supporting habits. Assert the row count you expect after any join, as a line of code rather than an intention — it costs nothing and catches the most common error immediately. And compare aggregates against a known-good figure before trusting a pipeline, whether or not Copilot wrote it.
Scripts, packaging and imports
Python’s import system is a recurring source of generated code that is subtly wrong for your project layout.
The model does not know whether you run your code as a script, as a module with
-m, or as an installed package — and the correct import style differs across
those. Relative imports that work in a package fail in a script; sys.path
manipulation appears in training data far more often than it should.
State the layout when it matters:
This project is an installed package run with python -m myapp.cli. Use absolute
imports rooted at the package name. Do not manipulate sys.path and do not use
relative imports beyond a single level.
PyCharm helps here in a way that is easy to overlook: it marks source roots, so unresolved imports are flagged immediately against your actual layout rather than against an assumed one. That is another reason the gutter-check habit matters more in Python than in languages where the compiler would object.
Repository instructions for a Python project
Custom instructions are in public preview in JetBrains. Keep them factual:
# Copilot instructions
## Stack
- Python 3.12, FastAPI 0.115, SQLAlchemy 2.0, Pydantic v2.
- Dependencies are managed with uv; declared in pyproject.toml.
- Tests are pytest with pytest-asyncio. Not unittest.
## Conventions
- Every function has type hints, including the return type.
- Money is Decimal, never float.
- No bare `except:`. Catch the specific exception.
- Async handlers use async database calls only — no blocking I/O.
## Tests
- Use fixtures from conftest.py; do not construct models inline.
- Use @pytest.mark.parametrize for input variations.Each line is checkable in the output. “Write Pythonic code” is not.
A realistic session
A short, concrete example of the whole thing working together.
You are adding an export endpoint to a FastAPI service: given a date range, it returns a CSV of orders. Nothing conceptually hard, and several places to get it wrong.
Write the models first. A Pydantic request model with the date range and a response model — or, since this returns a file, a documented response type. This is the step that makes everything after it easier, because it states the contract in code the model can read.
Declare the route signature. Path, method, dependencies, return type. At this point the handler body is heavily constrained.
Let completion draft the body, then check the I/O. The specific thing to look
for is a blocking database call in an async def handler. It will work when you
test it and degrade badly under concurrency, and the model writes both styles
fluently.
Ask about the part that is genuinely hard.
This endpoint may return a large result set. Explain what happens to memory if a date range covers a year of orders, and show how to stream the CSV instead of building it in memory — using the streaming approach FastAPI supports rather than a custom generator wrapped around a list.
That is the question that separates a working endpoint from one that survives production, and it is exactly the kind of thing worth asking rather than discovering.
Generate tests with conftest.py open, then break the handler on purpose and
confirm they fail.
Read the diff before committing. In PyCharm’s Git tool window, where you see what changed rather than what you meant to change.
Copilot did three useful things there — drafted a constrained body, explained a scaling consequence, and wrote tests. The decisions about contract, layering and correctness stayed with you, which is the arrangement that holds up.
Security
Frequently asked questions
Does Copilot work in PyCharm Community Edition? Yes. Community is on GitHub’s compatibility list.
Why does it suggest packages I have not installed?
It cannot see your virtual environment. Keep pyproject.toml or
requirements.txt open and PyCharm will flag the unresolved import immediately.
Why is it writing Pydantic v1 code? Because both versions are heavily represented in training data. Name the version in the prompt and in your repository instructions.
Do type hints really change the output? Substantially. They are the closest Python equivalent to the declarations that make typed languages easier for the model, and they also give PyCharm something to check the result against.
Should I let it write migrations? No — generate them with your framework’s tooling. Do ask it to explain what a migration will do to existing data.
Is the plugin different from IntelliJ’s? No, it is the same plugin. The Python-specific parts are habits, not features.
Next steps
Android Studio covers the JetBrains IDE with the most unusual situation, and IntelliJ IDEA covers the same plugin in a Java and Kotlin context.
For the plugin itself — installation, features, preview status — see GitHub Copilot with JetBrains IDEs.
Sources
Every version-sensitive claim on this page was checked against first-party documentation. Only sources actually used are listed.
Your progress
Saved in this browser only. No account, no server, and nothing leaves your device. Clearing site data resets it.
Was this lesson helpful?
Your answer is stored in this browser and is not sent anywhere.