[project]
name = "dstack"
dynamic = ["version", "readme"]
authors = [{ name = "Andrey Cheptsov", email = "andrey@dstack.ai" }]
description = "dstack is an open-source orchestration engine for running AI workloads on any cloud or on-premises."
requires-python = ">=3.10"
classifiers = [
"Development Status :: 4 - Beta",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
"Programming Language :: Python :: 3",
]
dependencies = [
"pyyaml",
"requests",
"requests-unixsocket>=0.4.1",
"typing-extensions>=4.0.0",
"tomli>=2.0; python_version < '3.11'",
"cryptography",
"packaging",
"python-dateutil",
"cachetools",
"gitpython",
"jsonschema",
"paramiko>=3.2.0",
"cursor",
"rich",
"rich-argparse",
"tqdm",
"questionary>=2.0.1",
"pydantic>=2.12",
"websocket-client",
"python-multipart>=0.0.16",
"filelock",
"psutil",
"gpuhunt==0.1.34",
"argcomplete>=3.5.0",
"ignore-python>=0.2.0",
"apscheduler<4",
]
[project.urls]
Homepage = "https://dstack.ai"
Source = "https://github.com/dstackai/dstack"
Documentation = "https://dstack.ai/docs"
Issues = "https://github.com/dstackai/dstack/issues"
Changelog = "https://github.com/dstackai/dstack/releases"
Discord = "https://discord.gg/u8SmfwPpMd"
[build-system]
requires = ["hatchling", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"
[project.scripts]
dstack = "dstack._internal.cli.main:main"
[tool.hatch.version]
path = "src/dstack/version.py"
[tool.hatch.build.targets.sdist]
artifacts = [
"src/dstack/_internal/server/statics/**",
]
[tool.hatch.build.targets.sdist.force-include]
"skills/dstack/SKILL.md" = "skills/dstack/SKILL.md"
"skills/dstack-prototyping/SKILL.md" = "skills/dstack-prototyping/SKILL.md"
[tool.hatch.build.targets.wheel]
artifacts = [
"src/dstack/_internal/server/statics/**",
]
[tool.hatch.build.targets.wheel.force-include]
"skills/dstack/SKILL.md" = "dstack/_internal/cli/services/presets/resources/skills/dstack/SKILL.md"
"skills/dstack-prototyping/SKILL.md" = "dstack/_internal/cli/services/presets/resources/skills/dstack-prototyping/SKILL.md"
[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\s*|]*>\s*|\s*|\s*|### Demo\s*'
replacement = ''
ignore-case = true
[tool.uv.sources]
dstack-plugin-server = { path = "examples/plugins/example_plugin_server", editable = true }
[tool.ruff]
target-version = "py310"
line-length = 99
[tool.ruff.lint]
select = ["E", "F", "I", "Q", "W", "PGH", "FLY", "S113", "PLR1722"]
ignore = [
"E501",
"E712",
]
[tool.ruff.lint.isort]
known-first-party = ["dstack"]
[tool.pyright]
typeCheckingMode = "standard"
include = [
"src/dstack/plugins",
"src/dstack/_internal/server",
"src/dstack/_internal/core/services",
"src/dstack/_internal/core/backends",
"src/dstack/_internal/cli/services/configurators",
"src/dstack/_internal/cli/commands",
"src/tests/_internal/server/background/pipeline_tasks",
]
exclude = [
# OCI has pyright errors. Excluded rather than ignored because checking it makes pyright analyze
# the large OCI SDK, which is slow and takes a lot of memory. Editors still show its errors
"src/dstack/_internal/core/backends/oci",
]
ignore = [
"src/dstack/_internal/server/migrations/versions",
]
[tool.pytest.ini_options]
testpaths = ["src/tests"]
addopts = [
"--disable-socket",
"--allow-hosts=127.0.0.1,localhost",
# unix socket for Docker/testcontainers
"--allow-unix-socket",
]
# One session-scoped event loop for fixtures and tests so that `sqlite_db` and `postgres_db` engines are reused.
# Tests must not leave pending asyncio tasks behind; nothing enforces it automatically.
asyncio_default_fixture_loop_scope = "session"
asyncio_default_test_loop_scope = "session"
markers = [
"shim_version",
"dockerized",
]
env = [
"DSTACK_SSHPROXY_API_TOKEN=test-token",
# Pin every rich console in the process to non-terminal output so tests asserting on plain CLI
# text never see ANSI escapes.
"TTY_COMPATIBLE=0",
]
filterwarnings = [
# Fail on any use of a pydantic v1 shim (`.dict()`, `parse_obj_as`, class-based `Config`, ...)
# so they cannot creep back after the v2 migration.
"error::pydantic.PydanticDeprecatedSince20",
# Remove or change to 3.11 once support for Python 3.10 is dropped.
"ignore:You are using a Python version \\(3.10:FutureWarning:^google\\.",
]
[tool.tox]
env_list = ["default"]
[tool.tox.env_run_base]
description = "Run tests under {base_python}"
extras = ["all"]
dependency_groups = ["test"]
commands = [["pytest", { replace = "posargs", extend = true }]]
# `tox run [-e default]` -- run tests inside an isolated environment
# with Python version read from `.python-version` file
[tool.tox.env.default]
base_python_file = [".python-version"]
# `tox run -e dummy --skip-env-install` -- run tests inside the current environment
# all dependencies must be already installed; a dummy environment is still created
[tool.tox.env.dummy]
description = "Run tests inside the current environment"
# Must be called with --skip-env-install
dependency_groups = ["nonexistent-group-to-trigger-failure"]
allowlist_externals = ["pytest"]
[dependency-groups]
dev = [
"pre-commit>=4.2.0",
"ruff==0.12.7", # should match .pre-commit-config.yaml
"pyinstrument>=5.0.0",
"kubernetes-stubs-elephant-fork>=35.0.0.post3",
{include-group = "test"},
{include-group = "docs"},
]
test = [
"pytest~=8.0",
"pytest-asyncio>=0.25.2",
"pytest-mock>=3.14.0",
"pytest-httpbin>=2.1.0",
"pytest-socket>=0.7.0",
"pytest-env>=1.1.0",
"pytest-unordered>=0.7.0",
"pytest-xdist>=3.6.1",
"requests-mock>=1.12.1",
"time-machine>=3.5.1",
"httpbin>=0.10.2", # indirect to make compatible with Werkzeug 3
"openai>=1.68.2",
"testcontainers>=4.15.0",
]
docs = [
"dstack[server]",
"dstack-plugin-server",
"pillow",
"cairosvg",
"mkdocs-material>=9.7.0",
"mkdocs-material[imaging]",
"mkdocs-material-extensions",
"mkdocs-redirects",
"mkdocs-gen-files",
"mkdocstrings[python]",
"mkdocs-render-swagger-plugin",
]
[project.optional-dependencies]
gateway = [
"fastapi",
"starlette>=0.26.0",
"uvicorn",
# Supervise long-lived SSH children without a waiting thread for each replica.
"uvloop>=0.18.0; sys_platform != 'win32' and platform_python_implementation != 'PyPy'",
"aiorwlock",
"aiocache",
"httpx>=0.28.0",
# Gateway authorization needs the pool cancellation and recovery fixes in httpx2 (#4338).
"httpx2>=2.13.1",
"jinja2",
]
server = [
"fastapi",
"starlette>=0.26.0",
"uvicorn[standard]",
"aiorwlock",
"aiocache",
"httpx>=0.28.0",
"jinja2",
"watchfiles",
"sqlalchemy[asyncio]>=2.0.0",
"sqlalchemy_utils>=0.43.0",
"alembic>=1.16.0",
"aiosqlite",
"docker>=6.0.0",
"python-dxf>=12.1.1",
"sentry-sdk[fastapi]>=2.27.0",
"alembic-postgresql-enum",
"asyncpg",
"python-json-logger>=3.1.0",
"prometheus-client",
"grpcio>=1.81.0",
"protobuf>=6.33.5",
"smg-grpc-proto==0.4.9", # Pin to avoid grpcio version mismatch when smg-grpc-proto bumps grpcio dependency https://github.com/dstackai/dstack/pull/3971
]
aws = [
"boto3>=1.38.13",
"botocore",
"dstack[server]",
]
azure = [
"azure-identity>=1.12.0",
"azure-mgmt-subscription>=3.1.1",
"azure-mgmt-compute>=29.1.0",
"azure-mgmt-network>=23.0.0,<28.0.0",
"azure-mgmt-resource>=22.0.0",
"azure-mgmt-authorization>=3.0.0",
"azure-mgmt-msi>=7.0.0",
"dstack[server]",
]
gcp = [
"google-auth>=2.3.0",
"google-cloud-storage>=2.0.0",
"google-cloud-compute>=1.5.0",
"google-cloud-logging>=2.0.0",
"google-api-python-client>=2.80.0",
"google-cloud-billing>=1.11.0",
"google-cloud-tpu>=1.18.3",
"dstack[server]",
]
datacrunch = [
"verda>=1.23.0",
"dstack[server]",
]
verda = [
"verda>=1.23.0",
"dstack[server]",
]
kubernetes = [
"kubernetes",
"dstack[server]",
]
lambda = [
"boto3>=1.38.13",
"botocore",
"dstack[server]",
]
oci = [
"oci>=2.150.0",
"cryptography>=44.0.3",
# pyopenssl is indirect to avoid uv falling back to the old version
# due to an upper limit from oci
"pyopenssl>=23.2.0",
"dstack[server]",
]
nebius = [
"nebius>=0.6.13,<0.7",
"dstack[server]",
]
fluentbit = [
"fluent-logger>=0.10.0",
"elasticsearch>=8.0.0",
"dstack[server]",
]
otel = [
"opentelemetry-sdk>=1.30.0",
"opentelemetry-exporter-otlp-proto-http>=1.30.0",
"opentelemetry-exporter-prometheus>=0.51b0",
"opentelemetry-instrumentation-fastapi>=0.51b0",
"opentelemetry-instrumentation-sqlalchemy>=0.51b0",
"opentelemetry-instrumentation-httpx>=0.51b0",
"opentelemetry-instrumentation-requests>=0.51b0",
"opentelemetry-instrumentation-system-metrics>=0.51b0",
"opentelemetry-instrumentation-logging>=0.61b0",
"dstack[server]",
]
crusoe = [
"dstack[server]",
]
all = [
"dstack[gateway,server,aws,azure,gcp,verda,kubernetes,lambda,nebius,oci,crusoe,fluentbit,otel]",
]