Setting Up Python

Ma Mahalakshmi V Updated 13 Sep 2026
20 min read ·Lesson 2 of 10

The Python Version Requirement

The OpenAI Python SDK requires Python 3.10 or higher. This is not a soft recommendation — the package metadata enforces it, and pip will refuse to install on an older interpreter.

Check what you have:

python3 --version

You should see something like Python 3.12.4. If you see Python 3.9.x or lower, you need a newer interpreter before anything else in this course will work.

Why does a version floor exist at all? Libraries adopt language features and drop support for old versions to reduce maintenance burden. The SDK is heavily typed and uses modern syntax — union types written as int | None, structural pattern matching, and typing features that landed in 3.10 — so supporting 3.9 would mean maintaining a second, uglier code path. The practical effect for you is simple: newer is fine, older is not.

Which Python should you install if you need one?

  • macOS: the system Python is not meant for your projects and may be old or missing. Install from python.org or use Homebrew (brew install python@3.12).
  • Windows: install from python.org and tick "Add python.exe to PATH" on the first screen of the installer. Skipping that box is the single most common cause of python: command not found on Windows.
  • Linux: your distribution's package manager usually has a recent enough version (sudo apt install python3 python3-venv). Note that on Debian and Ubuntu, python3-venv is a separate package and virtual environment creation fails without it.

A note on command names. On macOS and Linux, python3 and pip3 refer to Python 3, while a bare python may not exist or may point somewhere unexpected. On Windows, python is usually correct, and the launcher py -3.12 lets you pick a specific version when several are installed. Throughout this lesson, use whichever of these works on your machine — the concepts do not change.

What a Virtual Environment Is, and Why You Cannot Skip It

A virtual environment is a self-contained directory holding its own Python interpreter link and its own site-packages folder where installed libraries live.

That is the mechanism. The reason it exists is dependency conflict.

Python installs packages globally by default — one shared site-packages for every project on the machine. That works fine until two projects disagree. Project A was written against openai 2.30 and depends on httpx being installed. Project B needs openai 3.x, which uses HTTPX2 instead. There is exactly one global site-packages, so only one version of openai can exist there. Installing what B needs silently breaks A, and you find out weeks later when A throws an import error you cannot explain.

A virtual environment gives each project its own site-packages. Project A gets openai 2.30 in its own folder; project B gets 3.x in its own folder; neither can see the other. The isolation is complete and it costs you two commands.

How the isolation actually works. When you activate a virtual environment, two things change in your shell session:

  1. The environment's bin (or Scripts on Windows) directory is prepended to your PATH, so python and pip now resolve to the environment's copies rather than the global ones.
  2. That interpreter has its own sys.prefix, which makes it look for packages in the environment's site-packages instead of the global one.

Nothing magical, nothing permanent, and nothing that modifies your system Python. This is also why deleting the folder is a complete, clean uninstall — there is no registry of environments to clean up.

When would you skip a virtual environment? Essentially never for project work. The narrow exceptions are throwaway one-liners in a container that exists for thirty seconds, and command-line tools you want available everywhere (which have a better answer: pipx). For anything you will run twice, use one.

Creating and Activating the Environment

Make a project folder and create the environment inside it:

mkdir openai-course
cd openai-course
python3 -m venv .venv

Breaking that last command down, because every part is doing something:

  • python3 -m venv runs the standard-library venv module as a script. Using -m rather than a venv command guarantees the environment is built from the interpreter you just named — which is exactly what you want when several Pythons are installed.
  • .venv is the directory to create. The name is arbitrary, but .venv is the near-universal convention: the leading dot hides it in directory listings, and editors like VS Code and PyCharm detect it automatically.

Keeping the environment inside the project folder is deliberate. It makes the association between project and dependencies obvious, and it means deleting the project deletes its dependencies too.

Now activate it. The command differs by shell:

# macOS / Linux (bash, zsh)
source .venv/bin/activate

# Windows PowerShell
.venv\Scripts\Activate.ps1

# Windows Command Prompt
.venv\Scripts\activate.bat

Your prompt should now be prefixed with (.venv). That prefix is the activation script telling you which environment is live — it is the fastest way to check, and glancing at it before running pip install will save you a lot of confusion.

Verify properly rather than trusting the prompt:

# macOS / Linux
which python
# Expected: /path/to/openai-course/.venv/bin/python

# Windows PowerShell
Get-Command python

If that path does not point inside your project's .venv, activation did not take effect and anything you install next goes to the wrong place.

If PowerShell refuses to run the activation script with an error about execution policies, Windows is blocking unsigned scripts. Allow them for your user account only:

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

This permits locally-created scripts while still requiring downloaded ones to be signed. It is a per-user setting and does not weaken the machine's policy.

Activation is per shell session. Open a new terminal tab and you are back to the global Python until you activate again. This is not a bug; it is the point. Nothing about your system is permanently altered.

To leave the environment, run deactivate.

Installing the SDK

With the environment active:

python -m pip install --upgrade pip
python -m pip install openai

Two details in those commands matter.

Why python -m pip instead of pip? Because python -m pip provably uses the pip belonging to the interpreter you are running. A bare pip is a separate executable found on PATH, and if activation did not work, or if you have several Pythons installed, pip can easily belong to a different interpreter than python does. The failure mode is maddening: pip install openai reports success, then import openai raises ModuleNotFoundError, because they were talking about different Pythons. Using python -m pip makes that mismatch impossible.

Why upgrade pip first? Older pip versions resolve dependencies less reliably and sometimes cannot read modern package metadata, producing confusing errors on packages that install fine with a current pip. It costs one command.

Verify the install:

python -c "import openai; print(openai.__version__)"

You should see a version number printed — at the time of writing, 3.0.0 or later. If you get ModuleNotFoundError, the near-certain cause is the interpreter mismatch described above.

What Actually Got Installed

pip install openai pulls in the SDK plus its dependencies. Inspect them:

python -m pip list

You will see, alongside openai itself, packages including:

  • httpx2 — the HTTP client the SDK uses to make requests. As of openai 3.0.0 this replaced the older httpx.
  • pydantic — the data-validation library that turns JSON responses into typed Python objects. It is why response.output_text is an attribute your editor can autocomplete rather than a dictionary lookup.
  • typing-extensions, anyio, sniffio, distro, jiter — supporting libraries for typing, async support, environment detection and fast JSON parsing.

Knowing these exist matters for two reasons. First, when a stack trace points into httpx2 or pydantic, you now know why the SDK is calling them. Second, these are real dependencies with their own versions, and they are the usual source of conflicts when the SDK is installed alongside other libraries in the same environment.

Pinning the Version — And Why This Lesson Insists On It

Installing with pip install openai gives you whatever the latest version is on the day you run it. That is fine today and a liability in three months, when a teammate clones your project, installs "openai", gets a newer major version, and the code no longer runs.

Record your dependencies in a file:

python -m pip freeze > requirements.txt

pip freeze writes every installed package with its exact version:

anyio==4.14.0
httpx2==2.1.3
jiter==0.13.0
openai==3.0.0
pydantic==2.14.1
...

Anyone can then reproduce your environment exactly:

python -m pip install -r requirements.txt

The alternative: pin a range in a hand-written file. pip freeze output is exhaustive and noisy — it includes transitive dependencies you never chose. Many projects instead keep a short, hand-written requirements.txt listing only direct dependencies with deliberate constraints:

openai>=3.0,<4.0
python-dotenv>=1.0,<2.0

Read >=3.0,<4.0 as "any 3.x, but never 4.x." That constraint is meaningful because the SDK follows semantic versioning:

ChangeVersion bumpWhat it means for you
Bug fix3.0.03.0.1Safe. Install it.
New feature, backwards-compatible3.0.03.1.0Safe. Existing code keeps working.
Breaking change3.0.04.0.0Unsafe. Read the changelog before upgrading.

So >=3.0,<4.0 says: take every fix and feature, but never cross a boundary where the maintainers have explicitly told you something broke.

Which style should you use? For a course project or an application you deploy, use pip freeze — total reproducibility is worth the noise, and a deployment that installs a different transitive dependency than the one you tested is a real class of bug. For a library other people will install alongside their own packages, use ranges — hard pins in a library force conflicts on everyone downstream.

Concrete evidence that this matters: version 3.0.0 of this very SDK, released in August 2026, replaced httpx with httpx2 as the default HTTP client and stopped installing httpx automatically. Code that passed a custom httpx.Client to OpenAI(http_client=...) stopped working on upgrade. That is a textbook major-version break — and a project pinned to <4.0 but unpinned below would have sailed straight into it if the break had come one version later. Pin, read changelogs, upgrade deliberately.

The HTTPX2 Trust Store Trap

One consequence of the 3.0.0 change is worth knowing before it bites you, because the symptom is confusing.

HTTPX2 verifies TLS certificates against the operating system's trust store, whereas the previous client used the certifi CA bundle shipped inside Python packages. On a normal laptop these agree and you notice nothing. They diverge in three situations:

  • Minimal containers. A slim Docker base image may have no system CA certificates at all, so every HTTPS request fails certificate verification. Fix: install the certificates package in the image (ca-certificates on Debian-based images).
  • Corporate networks with TLS inspection. A proxy that re-signs traffic with an internal CA works only if that CA is in the store the client consults. Fix: point the client at the right bundle with the SSL_CERT_FILE or SSL_CERT_DIR environment variables.
  • Custom certificate bundles. Same fix as above.

The symptom is an SSL or certificate-verification error on your very first API call, in an environment where other tools work fine. If you see one, this is almost certainly why — and it is an environment problem, not a code problem.

If you genuinely need the legacy client for an existing integration, there is a documented escape hatch:

python -m pip install openai httpx
from typing import Any, cast
import httpx
from openai import OpenAI

client = OpenAI(http_client=cast(Any, httpx.Client()))

Note the cast — the SDK's type annotations now expect HTTPX2 objects, so this is a runtime-only workaround that your type checker will not endorse. The maintainers describe it as temporary and subject to removal. Use it to unblock a migration, not as a permanent design.

Keeping the Environment Out of Version Control

Create a .gitignore in your project root before your first commit:

# Virtual environment
.venv/
venv/

# Secrets
.env

# Python bytecode
__pycache__/
*.py[cod]

# Editors
.vscode/
.idea/

Committing .venv/ is a mistake with three separate costs: it adds thousands of files and tens of megabytes to the repository, it is platform-specific (a macOS environment is useless to a Windows colleague), and it contains absolute paths from your machine that will not resolve on anyone else's.

The correct thing to commit is requirements.txt — a few lines that describe how to rebuild the environment anywhere. The .env line is there because Lesson 3 puts your API key in that file, and it must never reach a repository.

Modern Alternatives to venv + pip

venv and pip ship with Python, work everywhere, and are what every tutorial and error message assumes. That makes them the right thing to learn first. But two alternatives are common enough in real projects that you should recognise them.

uv is a much faster installer and environment manager that replaces both tools:

uv venv                    # create .venv
uv pip install openai      # install into it
uv add openai              # add to pyproject.toml and install

Installs that take tens of seconds with pip often take under a second with uv, which matters when you rebuild environments frequently or in CI. It understands requirements.txt, so adopting it does not require restructuring a project.

Poetry and PDM manage dependencies through pyproject.toml and a lock file, handling environment creation, dependency resolution, and packaging as one workflow. They are heavier than pip and worth the weight when you are publishing a package or managing a large dependency graph.

ToolStrengthUse when
venv + pipBuilt in, universally understoodLearning; simple projects; anywhere you cannot install extra tools
uvVery fast; drop-in for pip commandsYou rebuild environments often; CI pipelines
Poetry / PDMLock files, packaging, dependency groupsPublishing a library; large team projects

For this course, venv and pip are entirely sufficient, and every error message you might search for will assume them.

Pointing Your Editor at the Right Interpreter

Creating an environment in the terminal does not tell your editor about it. If your editor is still using the global Python, you get red underlines under from openai import OpenAI even though the code runs fine, and the debugger launches with the wrong interpreter.

VS Code: open the Command Palette (Ctrl+Shift+P / Cmd+Shift+P), run Python: Select Interpreter, and choose the one whose path contains your project's .venv. The selected interpreter appears in the status bar. VS Code usually offers it automatically when a .venv folder exists in the workspace root — another reason for that naming convention.

PyCharm: Settings → Project → Python Interpreter → add an existing environment and point it at .venv/bin/python (or .venv\Scripts\python.exe).

A useful diagnostic when imports resolve in the terminal but not in the editor: run this from inside the editor, not the terminal, and compare the path to the one which python gave you.

import sys
print(sys.executable)

Two different paths means two different environments, and the editor is looking at the wrong one.

Verifying the Whole Setup

Before moving on, confirm the environment end to end. Create check_setup.py:

import sys
import openai

print("Python executable:", sys.executable)
print("Python version:   ", sys.version.split()[0])
print("openai version:   ", openai.__version__)

assert sys.version_info >= (3, 10), "Python 3.10 or higher is required"
print("Setup looks correct.")

Run it with python check_setup.py. Three checks are happening:

  • sys.executable must point inside your project's .venv. If it points at /usr/bin/python3 or C:\Python312\python.exe, your environment is not active.
  • openai.__version__ confirms the package is importable by this interpreter — the thing pip list alone does not prove.
  • The assert fails loudly on an old interpreter rather than letting you discover the problem through an obscure syntax error later.

There is no API call here, so this costs nothing and needs no key. Getting a clean run before you add credentials means that when Lesson 4 fails, you know the failure is about the key or the request, not the installation.

Common Mistakes

Installing before activating. You create .venv, forget to activate, run pip install openai, and the package lands in your global Python. Then you activate and import openai fails. Why it happens: nothing stops you from running pip outside an environment. The tell: your prompt has no (.venv) prefix. The fix: activate, then reinstall. Prevention: check the prompt, or use python -m pip so the interpreter and the installer can never disagree.

Naming a file openai.py. You write a test script called openai.py, and import openai imports your file instead of the library — because Python searches the current directory first. The error is bewildering (AttributeError: module 'openai' has no attribute 'OpenAI') because the import succeeded, just not the one you wanted. The same trap applies to any package name. The fix: rename the file, and delete any openai.pyc or __pycache__ left behind. Prevention: never name a script after a package you import.

Using sudo pip install. On macOS and Linux, a permission error tempts people into sudo pip install openai. This writes into the system Python, which the operating system itself depends on, and can break system tooling. It is also unnecessary: a permission error almost always means you forgot to activate your environment, because installing into an active .venv never needs elevated privileges. If you see a permission error, the answer is "activate the environment," not "add sudo."

The externally-managed-environment error. Newer Linux distributions and Homebrew refuse global pip install outright with this message. It is not a bug — it is the packaging system protecting itself, and it is telling you to use a virtual environment. Do that rather than reaching for --break-system-packages.

Activating in one terminal, running in another. Activation is per shell session. A second tab, a new editor terminal, or a restarted machine all start deactivated. If a script that worked yesterday now says ModuleNotFoundError, check the prompt before you change any code.

Committing .venv to Git. Covered above; the giveaway is a first commit with several thousand files.

Assuming pip list proves the import will work. It proves the package is installed for whichever pip you invoked. If pip and python resolve to different interpreters, pip list shows the package and import still fails. python -c "import openai" is the only check that settles it.

Upgrading blindly. pip install --upgrade openai can cross a major version boundary and break working code — exactly the httpxhttpx2 situation described earlier. Upgrade deliberately, read the changelog for the versions you are crossing, and re-run your code afterwards.

Upgrading Safely

When you do want a newer version:

# See what you have and what is available
python -m pip index versions openai
python -m pip show openai

# Upgrade
python -m pip install --upgrade openai

# Record the new state
python -m pip freeze > requirements.txt

Before upgrading across a major version, read the project's CHANGELOG.md for every version you are crossing, not just the target. The changelog is where breaking changes are documented, and it is the difference between a five-minute upgrade and an afternoon of debugging.

If an upgrade goes wrong, downgrading is one command:

python -m pip install "openai==3.0.0"

And if the environment gets into a state you cannot reason about, the nuclear option is fast and safe, because nothing outside the folder is affected:

deactivate
rm -rf .venv                    # Windows: rmdir /s .venv
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt

That sequence — delete, recreate, reinstall from the requirements file — is why keeping requirements.txt current is worth the discipline. It turns "my environment is broken" from a debugging session into a thirty-second reset.

The Project Layout You Will Use for This Course

By the end of this lesson your project should look like this:

openai-course/
├── .venv/                 # environment (git-ignored)
├── .gitignore
├── requirements.txt       # pinned dependencies
└── check_setup.py         # the verification script

Lesson 3 adds two more files — a .env holding your API key, and an example file showing its shape without the secret:

├── .env                   # your real key (git-ignored)
└── .env.example           # placeholder values (committed)

That structure scales further than it looks. Real projects add source folders and tests, but the four foundations — an isolated environment, a pinned dependency list, secrets in a git-ignored file, and a .gitignore that enforces both — stay exactly the same.

Best Practices Worth Adopting Now

One environment per project, always. The cost is two commands; the cost of not doing it is a class of bug that appears weeks later and is very hard to attribute.

Always python -m pip, never bare pip. It removes an entire failure mode at no cost.

Commit requirements.txt; never commit .venv. Describe the environment, do not ship it.

Pin an upper major bound. openai>=3.0,<4.0 accepts fixes and features while guaranteeing you cross a breaking change only on purpose.

Verify with python -c "import openai; print(openai.__version__)" after every install or upgrade. It is one line and it catches the interpreter-mismatch problem immediately, rather than three files into your next script.

Check sys.executable when something is inexplicable. Most "impossible" import problems are two interpreters wearing the same name.

Where to Run the Examples in This Course

Three environments will all execute the code in the coming lessons, and each suits a different moment.

Script files (python script.py) are the default and what every example assumes. Each run starts fresh, which makes results reproducible and makes it obvious when a variable you relied on came from somewhere you forgot. Use these for anything you will run more than once.

The interactive REPL (python with no arguments) is for poking at an object you just received. When a response comes back and you want to know what is actually on it, the REPL is where dir(response), type(response.output[0]) and response.model_dump() belong. It keeps state between statements, which is exactly what you want for exploration and exactly what you do not want for a program.

Jupyter notebooks sit between the two and are popular for API work because you can inspect a response cell by cell without re-sending the request. If you use one, install it inside the environment and register a kernel for it:

python -m pip install ipykernel
python -m ipykernel install --user --name openai-course

Then select the openai-course kernel in the notebook. Skipping the kernel step is the notebook version of the interpreter-mismatch problem: the notebook runs against a different Python than your terminal, import openai fails in a cell while working perfectly in the shell, and nothing about the error hints at why.

Notebooks have one trap worth naming, because it costs money here specifically: out-of-order cell execution. Re-running a cell that makes an API call sends a new billed request, and a cell that loops can send many. Keep API calls in cells you run deliberately, and never leave a request inside a cell you are re-running to fix a formatting bug.

Quick Troubleshooting Reference

SymptomMost likely causeFix
ModuleNotFoundError: No module named 'openai'Installed outside the active environment, or pip and python are different interpretersActivate, then python -m pip install openai
AttributeError: module 'openai' has no attribute 'OpenAI'A local file named openai.py is shadowing the packageRename the file; delete __pycache__
python: command not found (Windows)PATH entry skipped during installationReinstall with "Add python.exe to PATH", or use py
cannot be loaded because running scripts is disabled (PowerShell)Execution policy blocks the activation scriptSet-ExecutionPolicy -Scope CurrentUser RemoteSigned
No module named venv (Debian/Ubuntu)python3-venv is a separate system packagesudo apt install python3-venv
error: externally-managed-environmentInstalling globally on a protected system PythonCreate and activate a virtual environment
Permission denied during pip installInstalling globally without an active environmentActivate the environment — do not use sudo
SSL / certificate verification error on the first API callHTTPX2 uses the OS trust store; container or proxy lacks the right CAInstall ca-certificates, or set SSL_CERT_FILE
Imports underlined in the editor but the script runsEditor is pointed at a different interpreterSelect the .venv interpreter in the editor
Code worked yesterday, ModuleNotFoundError todayNew shell session, environment not activatedActivate it

The pattern across most of these rows is worth naming explicitly: the majority of setup failures are not about the SDK at all — they are about which Python is running. When something inexplicable happens, print sys.executable first. It answers the question faster than any amount of reading the error message.

0 Comments

Reviewed before they appear

No comments yet.

OpenAI SDK
Ask about this post
AI Ask about this post

Ask questions about Setting Up Python and get answers drawn from it.

Signed-in readers only.