Setting Up Python

Ma Mahalakshmi V Updated 12 Sep 2026
21 min read
In this post

# Setting Up Python, a Virtual Environment, and `pip install openai` ## The Python Version Requirement The OpenAI Python SDK requires **Python 3.10 or higher**. This is not a soft recommendation — the

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](https://www.python.org/downloads/) 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:

```bash
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:

| Change | Version bump | What it means for you |
|---|---|---|
| Bug fix | `3.0.0` → `3.0.1` | Safe. Install it. |
| New feature, backwards-compatible | `3.0.0` → `3.1.0` | Safe. Existing code keeps working. |
| Breaking change | `3.0.0` → `4.0.0` | Unsafe. 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:

```bash
python -m pip install openai httpx
```

```python
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:

```gitignore
# 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:

```bash
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.

| Tool | Strength | Use when |
|---|---|---|
| `venv` + `pip` | Built in, universally understood | Learning; simple projects; anywhere you cannot install extra tools |
| `uv` | Very fast; drop-in for pip commands | You rebuild environments often; CI pipelines |
| Poetry / PDM | Lock files, packaging, dependency groups | Publishing 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.

```python
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`:

```python
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 `httpx` → `httpx2` 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:

```bash
# 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:

```bash
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:

```bash
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:

```bash
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

| Symptom | Most likely cause | Fix |
|---|---|---|
| `ModuleNotFoundError: No module named 'openai'` | Installed outside the active environment, or `pip` and `python` are different interpreters | Activate, then `python -m pip install openai` |
| `AttributeError: module 'openai' has no attribute 'OpenAI'` | A local file named `openai.py` is shadowing the package | Rename the file; delete `__pycache__` |
| `python: command not found` (Windows) | PATH entry skipped during installation | Reinstall with "Add python.exe to PATH", or use `py` |
| `cannot be loaded because running scripts is disabled` (PowerShell) | Execution policy blocks the activation script | `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` |
| `No module named venv` (Debian/Ubuntu) | `python3-venv` is a separate system package | `sudo apt install python3-venv` |
| `error: externally-managed-environment` | Installing globally on a protected system Python | Create and activate a virtual environment |
| Permission denied during `pip install` | Installing globally without an active environment | Activate the environment — do not use `sudo` |
| SSL / certificate verification error on the first API call | HTTPX2 uses the OS trust store; container or proxy lacks the right CA | Install `ca-certificates`, or set `SSL_CERT_FILE` |
| Imports underlined in the editor but the script runs | Editor is pointed at a different interpreter | Select the `.venv` interpreter in the editor |
| Code worked yesterday, `ModuleNotFoundError` today | New shell session, environment not activated | Activate 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.

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.