main
  1[project]
  2name = "openai"
  3version = "2.9.0"
  4description = "The official Python library for the openai API"
  5dynamic = ["readme"]
  6license = "Apache-2.0"
  7authors = [
  8{ name = "OpenAI", email = "support@openai.com" },
  9]
 10
 11dependencies = [
 12  "httpx>=0.23.0, <1",
 13  "pydantic>=1.9.0, <3",
 14    "typing-extensions>=4.11, <5",
 15  "anyio>=3.5.0, <5",
 16  "distro>=1.7.0, <2",
 17  "sniffio",
 18    "tqdm > 4",
 19    "jiter>=0.10.0, <1",
 20]
 21
 22requires-python = ">= 3.9"
 23classifiers = [
 24  "Typing :: Typed",
 25  "Intended Audience :: Developers",
 26  "Programming Language :: Python :: 3.9",
 27  "Programming Language :: Python :: 3.10",
 28  "Programming Language :: Python :: 3.11",
 29  "Programming Language :: Python :: 3.12",
 30  "Programming Language :: Python :: 3.13",
 31  "Programming Language :: Python :: 3.14",
 32  "Operating System :: OS Independent",
 33  "Operating System :: POSIX",
 34  "Operating System :: MacOS",
 35  "Operating System :: POSIX :: Linux",
 36  "Operating System :: Microsoft :: Windows",
 37  "Topic :: Software Development :: Libraries :: Python Modules",
 38  "License :: OSI Approved :: Apache Software License"
 39]
 40
 41[project.urls]
 42Homepage = "https://github.com/openai/openai-python"
 43Repository = "https://github.com/openai/openai-python"
 44
 45[project.scripts]
 46openai = "openai.cli:main"
 47
 48[project.optional-dependencies]
 49aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"]
 50realtime = ["websockets >= 13, < 16"]
 51datalib = ["numpy >= 1", "pandas >= 1.2.3", "pandas-stubs >= 1.1.0.11"]
 52voice_helpers = ["sounddevice>=0.5.1", "numpy>=2.0.2"]
 53
 54[tool.rye]
 55managed = true
 56# version pins are in requirements-dev.lock
 57dev-dependencies = [
 58    "pyright==1.1.399",
 59    "mypy==1.17",
 60    "respx",
 61    "pytest",
 62    "pytest-asyncio",
 63    "ruff",
 64    "time-machine",
 65    "nox",
 66    "dirty-equals>=0.6.0",
 67    "importlib-metadata>=6.7.0",
 68    "rich>=13.7.1",
 69    "inline-snapshot>=0.28.0",
 70    "azure-identity >=1.14.1",
 71    "types-tqdm > 4",
 72    "types-pyaudio > 0",
 73    "trio >=0.22.2",
 74    "nest_asyncio==1.6.0",
 75    "pytest-xdist>=3.6.1",
 76    "griffe>=1",
 77]
 78
 79[tool.rye.scripts]
 80format = { chain = [
 81  "format:ruff",
 82  "format:docs",
 83  "fix:ruff",
 84  # run formatting again to fix any inconsistencies when imports are stripped
 85  "format:ruff",
 86]}
 87"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md"
 88"format:ruff" = "ruff format"
 89
 90"lint" = { chain = [
 91  "check:ruff",
 92  "typecheck",
 93  "check:importable",
 94]}
 95"check:ruff" = "ruff check ."
 96"fix:ruff" = "ruff check --fix ."
 97
 98"check:importable" = "python -c 'import openai'"
 99
100typecheck = { chain = [
101  "typecheck:pyright",
102  "typecheck:mypy"
103]}
104"typecheck:pyright" = "pyright"
105"typecheck:verify-types" = "pyright --verifytypes openai --ignoreexternal"
106"typecheck:mypy" = "mypy ."
107
108[build-system]
109requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"]
110build-backend = "hatchling.build"
111
112[tool.hatch.build]
113include = [
114  "src/*"
115]
116
117[tool.hatch.build.targets.wheel]
118packages = ["src/openai"]
119
120[tool.hatch.build.targets.sdist]
121# Basically everything except hidden files/directories (such as .github, .devcontainers, .python-version, etc)
122include = [
123  "/*.toml",
124  "/*.json",
125  "/*.lock",
126  "/*.md",
127  "/mypy.ini",
128  "/noxfile.py",
129  "bin/*",
130  "examples/*",
131  "src/*",
132  "tests/*",
133]
134
135[tool.hatch.metadata.hooks.fancy-pypi-readme]
136content-type = "text/markdown"
137
138[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
139path = "README.md"
140
141[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
142# replace relative links with absolute links
143pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
144replacement = '[\1](https://github.com/openai/openai-python/tree/main/\g<2>)'
145
146[tool.pytest.ini_options]
147testpaths = ["tests"]
148addopts = "--tb=short -n auto"
149xfail_strict = true
150asyncio_mode = "auto"
151asyncio_default_fixture_loop_scope = "session"
152filterwarnings = [
153  "error"
154]
155
156[tool.inline-snapshot]
157format-command="ruff format --stdin-filename {filename}"
158
159[tool.pyright]
160# this enables practically every flag given by pyright.
161# there are a couple of flags that are still disabled by
162# default in strict mode as they are experimental and niche.
163typeCheckingMode = "strict"
164pythonVersion = "3.9"
165
166exclude = [
167    "_dev",
168    ".venv",
169    ".nox",
170    ".git",
171
172    # uses inline `uv` script dependencies
173    # which means it can't be type checked
174    "examples/realtime/audio_util.py",
175    "examples/realtime/push_to_talk_app.py"
176]
177
178reportImplicitOverride = true
179reportOverlappingOverload = false
180
181reportImportCycles = false
182reportPrivateUsage = false
183
184[tool.mypy]
185pretty = true
186show_error_codes = true
187
188# Exclude _files.py because mypy isn't smart enough to apply
189# the correct type narrowing and as this is an internal module
190# it's fine to just use Pyright.
191#
192# We also exclude our `tests` as mypy doesn't always infer
193# types correctly and Pyright will still catch any type errors.
194#
195# realtime examples use inline `uv` script dependencies
196# which means it can't be type checked
197exclude = [
198  'src/openai/_files.py',
199  '_dev/.*.py',
200  'tests/.*',
201  'src/openai/_utils/_logs.py',
202  'examples/realtime/audio_util.py',
203  'examples/realtime/push_to_talk_app.py',
204]
205
206strict_equality = true
207implicit_reexport = true
208check_untyped_defs = true
209no_implicit_optional = true
210
211warn_return_any = true
212warn_unreachable = true
213warn_unused_configs = true
214
215# Turn these options off as it could cause conflicts
216# with the Pyright options.
217warn_unused_ignores = false
218warn_redundant_casts = false
219
220disallow_any_generics = true
221disallow_untyped_defs = true
222disallow_untyped_calls = true
223disallow_subclassing_any = true
224disallow_incomplete_defs = true
225disallow_untyped_decorators = true
226cache_fine_grained = true
227
228# By default, mypy reports an error if you assign a value to the result
229# of a function call that doesn't return anything. We do this in our test
230# cases:
231# ```
232# result = ...
233# assert result is None
234# ```
235# Changing this codegen to make mypy happy would increase complexity
236# and would not be worth it.
237disable_error_code = "func-returns-value,overload-cannot-match"
238
239# https://github.com/python/mypy/issues/12162
240[[tool.mypy.overrides]]
241module = "black.files.*"
242ignore_errors = true
243ignore_missing_imports = true
244
245[tool.ruff]
246line-length = 120
247output-format = "grouped"
248target-version = "py38"
249
250[tool.ruff.format]
251docstring-code-format = true
252
253[tool.ruff.lint]
254select = [
255  # isort
256  "I",
257  # bugbear rules
258  "B",
259  # remove unused imports
260  "F401",
261  # check for missing future annotations
262  "FA102",
263  # bare except statements
264  "E722",
265  # unused arguments
266  "ARG",
267  # print statements
268  "T201",
269  "T203",
270  # misuse of typing.TYPE_CHECKING
271  "TC004",
272  # import rules
273  "TID251",
274]
275ignore = [
276  # mutable defaults
277  "B006",
278]
279unfixable = [
280  # disable auto fix for print statements
281  "T201",
282  "T203",
283]
284
285extend-safe-fixes = ["FA102"]
286
287[tool.ruff.lint.flake8-tidy-imports.banned-api]
288"functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead"
289
290[tool.ruff.lint.isort]
291length-sort = true
292length-sort-straight = true
293combine-as-imports = true
294extra-standard-library = ["typing_extensions"]
295known-first-party = ["openai", "tests"]
296
297[tool.ruff.lint.per-file-ignores]
298"bin/**.py" = ["T201", "T203"]
299"scripts/**.py" = ["T201", "T203"]
300"tests/**.py" = ["T201", "T203"]
301"examples/**.py" = ["T201", "T203"]