[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "openmed" dynamic = ["version"] description = "Local-first SDK for clinical extraction and de-identification workflows on hardware you control." readme = "README.md" requires-python = ">=3.10" classifiers = [ "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Operating System :: POSIX :: Linux", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS", ] license = { text = "Apache-2.0" } keywords = [ "biomedical", "clinical", "NLP", "LLM", "healthcare", "medical", "natural language processing", "medical language models", "medical LLMs", "medical NLP", "medical NER", "medical de-identification", "medical extraction", "medical reasoning", ] dependencies = [ "pysbd>=0.3.4,<0.4", "faker>=22.0", "jieba>=0.42.1,<0.43", "pyyaml>=6.0", "jsonschema>=4.18,<5", "tomli>=2.0; python_version < '3.11'", ] authors = [ { name = "Maziyar Panahi" } ] [project.optional-dependencies] dev = [ # 3.20.3 predates the affected lock code; patched 4.x requires Python 3.11+. "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", # Patched virtualenv, used by pre-commit, needs the newer filelock branch. "pre-commit>=4.0; python_version >= '3.11'", "cryptography>=50,<51", "rich>=13.0", "typer>=0.12", "pytest>=7.0", "pytest-cov>=4.0", "pytest-timeout>=2.3", "huggingface-hub>=0.30", "jinja2>=3.1.6", "mcp>=1.30,<2", "mypy==2.3.0", "opencc>=1.4.1,<2", # Pillow 13 fixes the image-parser advisories but requires Python 3.11+. "Pillow==13.0.0; python_version >= '3.11'", "duckdb>=1.0,<2", "numpy>=1.26", "openpyxl>=3.1,<4", "fastapi>=0.110", "httpx>=0.27", "dask[dataframe]>=2024.8", "opentelemetry-api>=1.45.1,<2", "opentelemetry-exporter-otlp-proto-http>=1.26,<2", "opentelemetry-sdk>=1.26,<2", "pandas>=2.0", "polars>=0.20", "python-dateutil>=2.8", "hypothesis>=6.100", "fsspec>=2026.6.0", "fonttools[woff]==4.61.1", "grpcio>=1.64", "grpcio-tools>=1.64", "protobuf>=5.26,<7", "ruff==0.15.22", "strawberry-graphql[fastapi]>=0.327.2,<1", "sqlalchemy>=2.0,<3", "tomli>=2.0; python_version < '3.11'", "nbclient>=0.8.0", "nbformat>=5.9.0", "ipykernel>=6.0", ] cli = [ "rich>=13.0", "typer>=0.12", ] integrity = [ "cryptography>=50,<51", ] mcp = [ "mcp>=1.30,<2", ] lid = [ "pycld2>=0.41,<1", ] openmrs = [ "httpx>=0.27", ] fhir = [ "httpx>=0.27", ] presidio = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "presidio-analyzer>=2.2.354,<3", ] medspacy = [ "setuptools>=84.1.0; python_version >= '3.12'", # Older Python versions require spaCy below the supported security floor. "medspacy>=1.3.1,<2; python_version >= '3.12'", ] philter = [ "philter-ucsf>=1.0.3,<2", ] pydeid = [ "pyDeid>=0.0.1", ] scrubadub = [ "scrubadub>=2.0,<3", ] pandas = [ "pandas>=2.0", ] polars = [ "polars>=0.20", ] columnar = [ "pyarrow>=16", ] dask = [ "fsspec>=2026.6.0", "jinja2>=3.1.6", "dask[dataframe]>=2024.8", ] dagster = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "mako>=1.4.2", "fsspec>=2026.6.0", "jinja2>=3.1.6", "dagster>=1.8,<2", ] sqlalchemy = [ "sqlalchemy>=2.0,<3", ] spacy = [ "setuptools>=84.1.0", "click>=8.0", "spacy>=3.8.9", ] zh = [ "jieba>=0.42", "opencc>=1.4.1,<2", "pypinyin>=0.51", ] indic = [ "indic-nlp-library>=0.92", ] scispacy = [ "setuptools>=84.1.0", "scispacy>=0.5.4,<1", ] quickumls = [ "setuptools>=84.1.0", "quickumls>=1.4,<2", ] # sdcMicro/R (GPL-2.0) and MedCAT/CogStack (Elastic License 2.0) are installed # separately. This explicit extra opts into subprocess-only bridges without # adding restricted code or dependencies. interop-gpl = [] # The official HL7 FHIR validator is a user-fetched Java artifact invoked in a # subprocess. This marker extra intentionally installs no validator code. grounding-validate = [] # extract-msg is GPL-3.0 and is invoked only through OpenMed's isolated # stdin/stdout subprocess bridge. This explicit extra keeps GPL code out of the # base and general multimodal environments. email-msg-gpl = [ "extract-msg>=0.56,<0.57", ] multimodal = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "fsspec>=2026.6.0", "jinja2>=3.1.6", "torch>=2.13.0", "numpy>=1.26", "openpyxl>=3.1,<4", "onnx>=1.21", "pdfplumber>=0.11", "python-docx>=1.1", "python-pptx>=1.0", "Pillow>=13.0.0", "python-doctr>=1.0", "piexif>=1.1", "pikepdf>=9.0", "pydicom>=3.0", "pytesseract>=0.3.10", "markdown-it-py>=3.0,<5", "easyocr>=1.7", ] notebook = [ "nbformat>=5.9.0", ] # PaddleOCR is a heavy, platform-sensitive backend (pulls in paddlepaddle), so # it is opt-in rather than bundled into the general multimodal extra. ocr-paddle = [ "Pillow>=13.0.0", "paddleocr>=2.7", ] zh-pkuseg = [ "pkuseg>=0.0.25,<0.1", ] zh-hanlp = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "hanlp>=2.1,<3", "fsspec>=2026.6.0", "jinja2>=3.1.6", "torch>=2.13.0", ] awq = [ "filelock==3.20.3; python_version < '3.11' and sys_platform == 'linux'", "filelock>=4.2.1; python_version >= '3.11' and sys_platform == 'linux'", "setuptools>=84.1.0; sys_platform == 'linux'", "multidict>=6.9.1; sys_platform == 'linux'", "aiohttp>=3.14.4; sys_platform == 'linux'", "fsspec>=2026.6.0; sys_platform == 'linux'", "jinja2>=3.1.6; sys_platform == 'linux'", "autoawq==0.2.9; sys_platform == 'linux'", "datasets>=5.0.1; sys_platform == 'linux'", "torch>=2.13.0; sys_platform == 'linux'", "transformers==5.12.1; sys_platform == 'linux'", ] gptq = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "multidict>=6.9.1", "aiohttp>=3.14.4", "fsspec>=2026.6.0", "jinja2>=3.1.6", "auto-gptq>=0.7.1", "datasets>=5.0.1", "torch>=2.13.0", ] grounding = [ "rapidfuzz>=3.9,<4", "hnswlib>=0.8,<1", "numpy>=1.26", ] langchain = [ "langchain-core>=0.2,<2", ] langgraph = [ "langgraph>=0.2,<2", "langgraph-sdk>=0.4.4", ] haystack = [ "haystack-ai>=2,<3", ] llamaindex = [ "setuptools>=84.1.0", "Pillow>=13.0.0", "tinytag>=2.3.0", "multidict>=6.9.1", "aiohttp>=3.14.4", "fsspec>=2026.6.0", "jinja2>=3.1.6", "llama-index-core>=0.10,<1", "banks>=2.5.1", ] agents = [ "setuptools>=84.1.0", "Pillow>=13.0.0", "tinytag>=2.3.0", "multidict>=6.9.1", "aiohttp>=3.14.4", "fsspec>=2026.6.0", "jinja2>=3.1.6", "langchain-core>=0.2,<2", "langgraph>=0.2,<2", "langgraph-sdk>=0.4.4", "llama-index-core>=0.10,<1", "banks>=2.5.1", ] beam = [ "setuptools>=84.1.0", "Pillow>=13.0.0", "multidict>=6.9.1", "apache-beam>=2.73,<3", "pymongo>=4.18.2", "h2>=4.4.1", ] airflow = [ "mako>=1.4.2", "fsspec>=2026.6.0", "jinja2>=3.1.6", "apache-airflow>=3.2.2,<4", ] duckdb = [ "duckdb>=1.0,<2", "numpy>=1.26", ] kafka = [ "confluent-kafka>=2.3", ] prefect = [ "mako>=1.4.2", "fsspec>=2026.6.0", "jinja2>=3.1.6", "prefect>=3.7,<4", "h2>=4.4.1", "oauthlib>=4.0.0", ] cloud = [ "multidict>=6.9.1", "aiohttp>=3.14.4", "fsspec>=2026.6.0", "jinja2>=3.1.6", "s3fs>=2025.9.0", "gcsfs>=2025.9.0", "adlfs>=2025.8.0", "oauthlib>=4.0.0", ] journey = [ "fsspec>=2026.6.0", "jinja2>=3.1.6", "pg8000>=1.31.5,<2", ] ray = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "fsspec>=2026.6.0", "jinja2>=3.1.6", "ray[data]>=2.30,<3", ] spark = [ "pyspark>=3.5,<4", "pandas>=2.0", "pyarrow>=16", ] snowflake = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "snowflake-snowpark-python>=1.0,<2", ] hf = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "fsspec>=2026.6.0", "jinja2>=3.1.6", "torch>=2.13.0", "transformers>=4.50", "huggingface-hub>=0.30", "accelerate>=0.29", "tokenizers>=0.15" ] # Core pipeline observability. The SDK enables caller-owned in-process # providers and test exporters; no network exporter is included or configured. otel = [ "opentelemetry-api>=1.45.1,<2", "opentelemetry-sdk>=1.26,<2", ] service = [ "cryptography>=50,<51", "fastapi>=0.110", "grpcio>=1.64", "httpx>=0.27", "opentelemetry-api>=1.45.1,<2", "opentelemetry-exporter-otlp-proto-http>=1.26,<2", "opentelemetry-sdk>=1.26,<2", "protobuf>=5.26,<7", "strawberry-graphql[fastapi]>=0.327.2,<1", "uvicorn[standard]>=0.29", ] # Client libraries only. OpenMed never installs Triton or KServe servers. triton = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "fsspec>=2026.6.0", "jinja2>=3.1.6", "grpcio>=1.64", "httpx>=0.27", "numpy>=1.26", "protobuf>=5.26,<7", "transformers>=4.50", ] operator = [ "multidict>=6.9.1", "aiohttp>=3.14.4", "kopf>=1.44.6,<2", ] gliner = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "fsspec>=2026.6.0", "jinja2>=3.1.6", "gliner[tokenizers]>=0.2.0", "torch>=2.13.0" ] gliformer = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "fsspec>=2026.6.0", "jinja2>=3.1.6", "gliformer>=0.1.3,<0.2", "Pillow>=13.0.0", # GLiFormer imports the current GLiNER packing API at module import time. "gliner>=0.2.29", "torch>=2.13.0", ] gliner2 = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "fsspec>=2026.6.0", "jinja2>=3.1.6", "gliner2[local]>=2.1.0,<3", "torch>=2.13.0", ] mlx = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "fsspec>=2026.6.0", "jinja2>=3.1.6", "mlx>=0.22", "mlx-lm>=0.31", "huggingface-hub>=0.30", "Pillow>=13.0.0", "transformers>=4.50", "tokenizers>=0.15", "safetensors>=0.4", "tiktoken>=0.7", ] coreml = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "fsspec>=2026.6.0", "jinja2>=3.1.6", "coremltools>=8.0", "huggingface-hub>=0.30", "torch>=2.13.0", "transformers>=4.50", ] onnx = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "fsspec>=2026.6.0", "jinja2>=3.1.6", "onnx>=1.21", "onnxruntime>=1.16", "onnxscript>=0.1.0", "torch>=2.13.0", "transformers>=4.50", ] onnx-runtime = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "fsspec>=2026.6.0", "jinja2>=3.1.6", "huggingface-hub>=0.30", "numpy>=1.26", "onnxruntime>=1.16", "tokenizers>=0.15", ] # Minimal, local-artifact ONNX Runtime profile for aarch64 SBCs. It excludes # graph-building, Torch, and Transformers dependencies; use onnx-runtime when # the application also needs OpenMed's Hub download path. edge-sbc = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "fsspec>=2026.6.0", "jinja2>=3.1.6", "numpy>=1.26", "onnxruntime>=1.16", "tokenizers>=0.15", ] openvino = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "fsspec>=2026.6.0", "jinja2>=3.1.6", "openvino>=2024.0", "nncf>=2.10", "onnxruntime>=1.16", "transformers>=4.50", ] docs = [ "mkdocs>=1.6", "mkdocs-material>=9.5", "mkdocs-git-revision-date-localized-plugin>=1.2.6", "mkdocs-llmstxt>=0.5.0,<1", "mkdocs-minify-plugin>=0.8.0", "mkdocs-static-i18n>=1.3,<2", "pymdown-extensions>=11.0.1,<12", "gitpython>=3.2.1,<4", "mkdocstrings[python]", "griffe", ] yasbd = [ "yasbd-lib>=0.14.0,<0.15", ] [project.scripts] openmed = "openmed.cli:main" openmed-sidecar = "openmed.service.sidecar:main" openmed-executable-udf = "openmed.integrations.executable_udf:main" openmed-mcp = "openmed.mcp.server:main" openmed-journey = "openmed.deploy.journey:main" [tool.uv] constraint-dependencies = [ "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", "setuptools>=84.1.0", "certifi>=2026.10.19", "Pillow>=13.0.0; python_version >= '3.11'", "tinytag>=2.3.0", "mako>=1.4.2", "multidict>=6.9.1", "aiohttp>=3.14.4", "mcp>=1.30,<2", "fsspec>=2026.6.0", "jinja2>=3.1.6", "banks>=2.5.1", "langgraph-sdk>=0.4.4", "pymongo>=4.18.2", "datasets>=5.0.1", "h2>=4.4.1", "oauthlib>=4.0.0", # Keep transitive optional model backends above the checkpoint/native-op fixes. "torch>=2.13.0", "tornado>=6.5.9", "urllib3>=2.8.0", "virtualenv>=21.14.9", "brotli>=1.2.1", "brotlicffi>=1.2.1.0", "graphql-core>=3.2.12", "opentelemetry-api>=1.45.1", "gitpython>=3.2.1", "betterproto==2.0.0b6; python_version >= '3.11'", "pyjwt>=2.14.0", ] override-dependencies = [ # Snowflake 4.x caps filelock below its security fixes; validate cache locking. "filelock==3.20.3; python_version < '3.11'", "filelock>=4.2.1; python_version >= '3.11'", # Beam 2.75 caps these below their patched releases even though its # DirectRunner remains compatible with the secure versions. "cryptography>=50,<51", "httplib2>=0.32", # Beam also caps Pillow below the patched release on Python 3.11+. "Pillow>=13.0.0; python_version >= '3.11'", # Prefect 3.7 caps packaging below the release patched virtualenv needs, and # newer Prefect requires FastAPI releases that Airflow 3.3 excludes. "packaging>=26.3,<27", ] conflicts = [ [ { extra = "airflow" }, { extra = "ocr-paddle" }, ], [ { extra = "awq" }, { extra = "gliner" }, ], [ { extra = "awq" }, { extra = "gliformer" }, ], [ { extra = "onnx" }, { extra = "scispacy" }, ], [ { extra = "multimodal" }, { extra = "scispacy" }, ], ] [tool.hatch.build] include = [ "/openmed/**", "/openmed/py.typed", "/openmed/core/thresholds.json", "/openmed/core/policies/*.json", "/openmed/core/schemas/json/*.json", "/openmed/risk/data/*.json", "/eval/redteam/corpus/adversarial_phi.jsonl", "/gates/baseline.json", "/gates/registry_state.json", "/models.jsonl", "/models.jsonl.sigstore.json", "/LICENSE", "/NOTICE", "/README.md", "/pyproject.toml" ] [tool.hatch.build.targets.sdist] core-metadata-version = "2.4" [tool.hatch.build.targets.wheel] core-metadata-version = "2.4" [tool.hatch.build.targets.wheel.force-include] "eval/redteam/corpus/adversarial_phi.jsonl" = "openmed/eval/data/adversarial_phi.jsonl" [tool.hatch.version] path = "openmed/__about__.py" [tool.hatch.publish.index] disable = false [tool.hatch.publish.index.repos] pypi = { url = "https://upload.pypi.org/legacy/" } [tool.pytest.ini_options] markers = [ "integration: marks end-to-end or external integration tests", "slow: marks tests that are expected to run slowly", "contract: marks property-based stage-boundary contract tests", "fuzz: marks property-based (Hypothesis) fuzz tests", "doctest_examples: runs doctest examples for targeted public modules" ] python_files = [ "test_*.py", "*_test.py", ] testpaths = [ "tests", ] [tool.ruff] target-version = "py310" line-length = 88 extend-exclude = [ "examples/notebooks", "openmed/service/proto/generated", ] [tool.ruff.lint] select = [ "E9", "F63", "F7", "F82", "I", ] fixable = ["I"] unfixable = [ "E9", "F63", "F7", "F82", ] [tool.ruff.lint.isort] known-first-party = ["openmed"] [tool.ruff.format] quote-style = "double" indent-style = "space" line-ending = "lf" # Scoped type-checking configuration. This intentionally covers only the newer # v1.6 public surfaces that already ship precise annotations and a py.typed # marker; it is not a strict project-wide gate. The pinned development checker, # local Make target, regression test, and CI lint job all use this same scope. [tool.mypy] python_version = "3.10" files = [ "openmed/core/audit.py", "openmed/core/pipeline.py", "openmed/clinical/context.py", "openmed/clinical/exporters/fhir/bundle.py", ] ignore_missing_imports = true follow_imports = "silent" warn_unused_ignores = false