From 842cf626482a7b0db2b844711b84c736ffc7049a Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Thu, 19 Mar 2026 16:38:13 +0100 Subject: [PATCH 01/13] Add initial project structure --- .github/workflows/release.yml | 36 +++++ .github/workflows/test.yml | 46 ++++++ README.md | 80 ++++++++++- pyproject.toml | 133 ++++++++++++++++++ .../components/example/__init__.py | 8 ++ .../components/example/example_component.py | 41 ++++++ tests/test_example.py | 36 +++++ 7 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml create mode 100644 pyproject.toml create mode 100644 src/haystack_integrations/components/example/__init__.py create mode 100644 src/haystack_integrations/components/example/example_component.py create mode 100644 tests/test_example.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4f64aad --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2026-present AUTHOR +# +# SPDX-License-Identifier: Apache-2.0 + +name: Release + +on: + push: + tags: + - "v[0-9].[0-9]+.[0-9]+*" + +jobs: + release-on-pypi: + name: Publish on PyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Install Hatch + run: pip install hatch + + - name: Build + run: hatch build + + - name: Publish to PyPI + env: + HATCH_INDEX_USER: __token__ + HATCH_INDEX_AUTH: ${{ secrets.PYPI_API_TOKEN }} + run: hatch publish -y diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..0cdc02e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2026-present AUTHOR +# +# SPDX-License-Identifier: Apache-2.0 + +name: Test + +on: + push: + branches: [main] + pull_request: + +concurrency: + group: test-${{ github.head_ref }} + cancel-in-progress: true + +env: + PYTHONUNBUFFERED: "1" + FORCE_COLOR: "1" + +jobs: + test: + name: Python ${{ matrix.python-version }} on ${{ startsWith(matrix.os, 'macos-') && 'macOS' || startsWith(matrix.os, 'windows-') && 'Windows' || 'Linux' }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: ["3.10", "3.13"] + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Hatch + run: pip install hatch + + - name: Lint + if: matrix.python-version == '3.10' && runner.os == 'Linux' + run: hatch run fmt-check + + - name: Run tests + run: hatch run test:cov diff --git a/README.md b/README.md index 2930ad1..c6c9724 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,78 @@ -# custom-component -A template repository that can be used to create custom Haystack components. +# Custom Component Template + +A template repository for creating custom [Haystack](https://haystack.deepset.ai/) components and publishing them as standalone Python packages. + +For more details, see the Haystack documentation on [creating custom components](https://docs.haystack.deepset.ai/docs/custom-components) and [creating custom document stores](https://docs.haystack.deepset.ai/docs/creating-custom-document-stores). + +## How to use this template + +1. Click **[Use this template](https://github.com/deepset-ai/custom-component/generate)** to create a new repository. + +2. **Rename the package directory** from `src/haystack_integrations/components/example/` to match your integration. See [Namespace convention](#namespace-convention) below for the correct path. + +3. **Update `pyproject.toml`** — search for `TODO` comments and replace: + - `name`: your package name, following the `-haystack` convention (e.g. `opensearch-haystack`) + - `description`, `authors`, `keywords`, `project.urls` + - `dependencies`: add your integration-specific dependencies + - `tool.hatch.version.raw-options`: if you renamed directories, the version path is still derived from git tags so no change is needed here + +4. **Add your component code** in the renamed directory and export your classes from `__init__.py`. + +5. **Add tests** in `tests/` — see the skeleton in `tests/test_example.py`. + +6. **Search for all `TODO` comments** across the project and address them. + +## Namespace convention + +Haystack integrations use the `haystack_integrations` namespace package. The directory structure under `src/` determines the import path for your component. + +**Components** (converters, embedders, generators, rankers, etc.) use: +``` +src/haystack_integrations/components/// +``` +Import path: `from haystack_integrations.components.. import MyComponent` + +Common component types: `converters`, `embedders`, `generators`, `rankers`, `retrievers`, `connectors`, `tools`, `websearch` + +**Document stores** use a separate namespace: +``` +src/haystack_integrations/document_stores// +``` +Import path: `from haystack_integrations.document_stores. import MyDocumentStore` + +## Development + +This project uses [Hatch](https://hatch.pypa.io/) for build and environment management. + +```bash +# Install Hatch +pip install hatch + +# Format and lint +hatch run fmt # auto-fix +hatch run fmt-check # check only + +# Run tests +hatch run test:unit # unit tests only +hatch run test:integration # integration tests only +hatch run test:all # all tests +hatch run test:cov # with coverage +``` + +## Publishing to PyPI + +This template includes a GitHub Actions workflow that publishes your package to PyPI when you push a version tag. + +1. **Add a `PYPI_API_TOKEN` secret** to your repository settings (Settings > Secrets and variables > Actions). + +2. **Create a version tag** and push it: + ```bash + git tag v0.1.0 + git push origin v0.1.0 + ``` + +The release workflow will build and publish the package automatically. + +## License + +`Apache-2.0` - See [LICENSE](LICENSE) for details. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0d81627 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,133 @@ +# SPDX-FileCopyrightText: 2026-present AUTHOR +# +# SPDX-License-Identifier: Apache-2.0 + +[build-system] +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" + +[project] +name = "example-haystack" # TODO: Replace with your package name, e.g. "deepset-ai-haystack" +dynamic = ["version"] +description = "A custom Haystack component" # TODO: Replace with your description +readme = "README.md" +requires-python = ">=3.10" +license = "Apache-2.0" +keywords = [ + "haystack", + # TODO: Add relevant keywords for your integration +] +authors = [ + # TODO: Replace with your name and email + { name = "AUTHOR", email = "your@email.com" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dependencies = [ + "haystack-ai", + # TODO: Add your integration-specific dependencies here +] + +[project.urls] +# TODO: Replace with your repository URL +Documentation = "https://github.com/your-org/example-haystack#readme" +Issues = "https://github.com/your-org/example-haystack/issues" +Source = "https://github.com/your-org/example-haystack" + +[tool.hatch.version] +source = "vcs" +tag-pattern = "v(?P.*)" + +[tool.hatch.version.raw-options] +git_describe_command = 'git describe --tags --match="v[0-9]*"' + +[tool.hatch.build.targets.wheel] +packages = ["src/haystack_integrations"] + +[tool.hatch.envs.default] +installer = "uv" +dependencies = ["ruff"] + +[tool.hatch.envs.default.scripts] +fmt = "ruff check --fix {args:.} && ruff format {args:.}" +fmt-check = "ruff check {args:.} && ruff format --check {args:.}" + +[tool.hatch.envs.test] +dependencies = [ + "pytest", + "pytest-cov", +] + +[tool.hatch.envs.test.scripts] +unit = 'pytest -m "not integration" {args:tests}' +integration = 'pytest -m "integration" {args:tests}' +all = "pytest {args:tests}" +cov = "pytest --cov=haystack_integrations {args:tests}" + +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +select = [ + "A", "ARG", "B", "C", "DTZ", "E", "EM", "F", "I", "ICN", "ISC", + "N", "PLC", "PLE", "PLR", "PLW", "Q", "RUF", "S", "T", "TID", + "UP", "W", "YTT", +] +ignore = [ + "B027", # empty method in abstract base class + "S105", # possible hardcoded password + "S106", # possible hardcoded password + "S107", # possible hardcoded password + "C901", # too complex + "PLR0911", # too many return statements + "PLR0912", # too many branches + "PLR0913", # too many arguments + "PLR0915", # too many statements +] + +[tool.ruff.lint.isort] +known-first-party = ["haystack_integrations"] + +[tool.ruff.lint.flake8-tidy-imports] +ban-relative-imports = "parents" + +[tool.ruff.lint.per-file-ignores] +"tests/**/*" = ["PLR2004", "S101", "TID252"] + +[tool.mypy] +install_types = true +non_interactive = true +check_untyped_defs = true +disallow_incomplete_defs = true + +[[tool.mypy.overrides]] +module = ["haystack.*"] +ignore_missing_imports = true + +[tool.coverage.run] +source = ["haystack_integrations"] +branch = true +parallel = false + +[tool.coverage.report] +omit = ["*/tests/*", "*/__init__.py"] +show_missing = true +exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"] + +[tool.pytest.ini_options] +minversion = "6.0" +markers = [ + "unit: unit tests", + "integration: integration tests", +] +addopts = ["--import-mode=importlib"] diff --git a/src/haystack_integrations/components/example/__init__.py b/src/haystack_integrations/components/example/__init__.py new file mode 100644 index 0000000..0989a98 --- /dev/null +++ b/src/haystack_integrations/components/example/__init__.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2026-present AUTHOR +# +# SPDX-License-Identifier: Apache-2.0 + +# TODO: Rename the import to match your component class. +from .example_component import ExampleComponent + +__all__ = ["ExampleComponent"] diff --git a/src/haystack_integrations/components/example/example_component.py b/src/haystack_integrations/components/example/example_component.py new file mode 100644 index 0000000..f9cadaf --- /dev/null +++ b/src/haystack_integrations/components/example/example_component.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2026-present AUTHOR +# +# SPDX-License-Identifier: Apache-2.0 + +from haystack import component + + +# TODO: Rename this class and update the output types and run method to match your use case. +@component +class ExampleComponent: + """A custom Haystack component. + + Usage: + ```python + from haystack_integrations.components.example import ExampleComponent + + component = ExampleComponent() + result = component.run(input_text="Hello, world!") + ``` + """ + + def __init__(self, param: str = "default"): + """ + Initialize the component. + + :param param: An example parameter. + """ + self.param = param + + @component.output_types(output=str) + def run(self, input_text: str) -> dict[str, str]: + """ + Process the input and return results. + + :param input_text: The text to process. + :returns: A dictionary with the following keys: + - `output`: The processed text. + """ + # TODO: Implement your component logic here. + result = input_text + return {"output": result} diff --git a/tests/test_example.py b/tests/test_example.py new file mode 100644 index 0000000..dc55dbd --- /dev/null +++ b/tests/test_example.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2026-present AUTHOR +# +# SPDX-License-Identifier: Apache-2.0 + +# TODO: Add tests for your component(s). Below is a skeleton to get you started. +# +# import pytest +# +# from haystack_integrations.components.example import MyComponent +# +# +# class TestMyComponent: +# def test_init(self): +# component = MyComponent() +# assert component is not None +# +# def test_run(self): +# component = MyComponent() +# result = component.run(...) +# assert "output_name" in result +# +# def test_to_dict(self): +# component = MyComponent() +# data = component.to_dict() +# assert data is not None +# +# def test_from_dict(self): +# component = MyComponent() +# data = component.to_dict() +# deserialized = MyComponent.from_dict(data) +# assert deserialized is not None +# +# @pytest.mark.integration +# def test_run_with_real_service(self): +# """Integration tests that require external services or API keys.""" +# ... From 9d80affd526f8e0cd85a300bced8e2a17e17af34 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Thu, 19 Mar 2026 16:42:56 +0100 Subject: [PATCH 02/13] Update test workflow --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0cdc02e..b213c69 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,4 +43,4 @@ jobs: run: hatch run fmt-check - name: Run tests - run: hatch run test:cov + run: hatch run test:all From b44f913cb56dcb6efc63812bd4b7794801189716 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Thu, 19 Mar 2026 16:46:31 +0100 Subject: [PATCH 03/13] Update test file --- tests/test_example.py | 64 +++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/test_example.py b/tests/test_example.py index dc55dbd..d1ba0c5 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -2,35 +2,35 @@ # # SPDX-License-Identifier: Apache-2.0 -# TODO: Add tests for your component(s). Below is a skeleton to get you started. -# -# import pytest -# -# from haystack_integrations.components.example import MyComponent -# -# -# class TestMyComponent: -# def test_init(self): -# component = MyComponent() -# assert component is not None -# -# def test_run(self): -# component = MyComponent() -# result = component.run(...) -# assert "output_name" in result -# -# def test_to_dict(self): -# component = MyComponent() -# data = component.to_dict() -# assert data is not None -# -# def test_from_dict(self): -# component = MyComponent() -# data = component.to_dict() -# deserialized = MyComponent.from_dict(data) -# assert deserialized is not None -# -# @pytest.mark.integration -# def test_run_with_real_service(self): -# """Integration tests that require external services or API keys.""" -# ... +# TODO: Replace these example tests with tests for your own component(s). + +from haystack_integrations.components.example import ExampleComponent + + +class TestExampleComponent: + def test_init_default(self): + component = ExampleComponent() + assert component.param == "default" + + def test_init_custom_param(self): + component = ExampleComponent(param="custom") + assert component.param == "custom" + + def test_run(self): + component = ExampleComponent() + result = component.run(input_text="Hello, world!") + assert result == {"output": "Hello, world!"} + + def test_to_dict(self): + component = ExampleComponent(param="custom") + data = component.to_dict() + assert data == { + "type": "haystack_integrations.components.example.example_component.ExampleComponent", + "init_parameters": {"param": "custom"}, + } + + def test_from_dict(self): + component = ExampleComponent(param="custom") + data = component.to_dict() + deserialized = ExampleComponent.from_dict(data) + assert deserialized.param == "custom" From 181c35b68e4968b8e7415151d9a24583db15464b Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Thu, 19 Mar 2026 16:49:05 +0100 Subject: [PATCH 04/13] Update test file --- tests/test_example.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/test_example.py b/tests/test_example.py index d1ba0c5..204d382 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -4,9 +4,12 @@ # TODO: Replace these example tests with tests for your own component(s). +from haystack.core.serialization import component_from_dict, component_to_dict + from haystack_integrations.components.example import ExampleComponent + class TestExampleComponent: def test_init_default(self): component = ExampleComponent() @@ -23,14 +26,16 @@ def test_run(self): def test_to_dict(self): component = ExampleComponent(param="custom") - data = component.to_dict() + data = component_to_dict(component) assert data == { "type": "haystack_integrations.components.example.example_component.ExampleComponent", "init_parameters": {"param": "custom"}, } def test_from_dict(self): - component = ExampleComponent(param="custom") - data = component.to_dict() - deserialized = ExampleComponent.from_dict(data) + data = { + "type": "haystack_integrations.components.example.example_component.ExampleComponent", + "init_parameters": {"param": "custom"}, + } + deserialized = component_from_dict(data) assert deserialized.param == "custom" From 8941260ff50fd0e52bcafc617c4af3c7fdbd3b87 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Thu, 19 Mar 2026 16:50:31 +0100 Subject: [PATCH 05/13] Formatting --- tests/test_example.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_example.py b/tests/test_example.py index 204d382..763b752 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -9,7 +9,6 @@ from haystack_integrations.components.example import ExampleComponent - class TestExampleComponent: def test_init_default(self): component = ExampleComponent() From 9165105973d2b619ead3bcc13d4880ab9fbd9718 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Thu, 19 Mar 2026 16:52:22 +0100 Subject: [PATCH 06/13] Update test --- tests/test_example.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_example.py b/tests/test_example.py index 763b752..a4dff1a 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -25,7 +25,7 @@ def test_run(self): def test_to_dict(self): component = ExampleComponent(param="custom") - data = component_to_dict(component) + data = component_to_dict(component, "ExampleComponent") assert data == { "type": "haystack_integrations.components.example.example_component.ExampleComponent", "init_parameters": {"param": "custom"}, @@ -36,5 +36,5 @@ def test_from_dict(self): "type": "haystack_integrations.components.example.example_component.ExampleComponent", "init_parameters": {"param": "custom"}, } - deserialized = component_from_dict(data) + deserialized = component_from_dict(ExampleComponent, data, "ExampleComponent") assert deserialized.param == "custom" From aa338ab8599de6f53eda02b6050e8df7af1848bc Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Tue, 24 Mar 2026 11:08:20 +0100 Subject: [PATCH 07/13] Pin GitHub Actions to specific commit SHAs --- .github/workflows/release.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4f64aad..7596153 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,12 +14,12 @@ jobs: name: Publish on PyPI runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.13" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b213c69..d31b09b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,10 +28,10 @@ jobs: python-version: ["3.10", "3.13"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} From d2f8224b29aaf0f70b895a8a82d43006b73bf2bc Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Tue, 24 Mar 2026 11:09:12 +0100 Subject: [PATCH 08/13] Test on Python 3.14 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d31b09b..1fa286d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.10", "3.13"] + python-version: ["3.10", "3.14"] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 From 7c54cc0d66d5e853b5039f9abb10cb3511acb365 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Tue, 24 Mar 2026 11:11:24 +0100 Subject: [PATCH 09/13] Docstring formatting --- .../components/example/example_component.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/haystack_integrations/components/example/example_component.py b/src/haystack_integrations/components/example/example_component.py index f9cadaf..28ea130 100644 --- a/src/haystack_integrations/components/example/example_component.py +++ b/src/haystack_integrations/components/example/example_component.py @@ -8,7 +8,8 @@ # TODO: Rename this class and update the output types and run method to match your use case. @component class ExampleComponent: - """A custom Haystack component. + """ + A custom Haystack component. Usage: ```python From f0d08da7333e78e3752ed3b030b9d22af09bc109 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Tue, 24 Mar 2026 11:14:12 +0100 Subject: [PATCH 10/13] Add note on custom serialization methods --- .../components/example/example_component.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/haystack_integrations/components/example/example_component.py b/src/haystack_integrations/components/example/example_component.py index 28ea130..1c66275 100644 --- a/src/haystack_integrations/components/example/example_component.py +++ b/src/haystack_integrations/components/example/example_component.py @@ -40,3 +40,8 @@ def run(self, input_text: str) -> dict[str, str]: # TODO: Implement your component logic here. result = input_text return {"output": result} + + # NOTE: Custom `to_dict` and `from_dict` methods are only needed if the default serialization doesn't work + # for your component (e.g. it has non-serializable attributes). For details, see: + # https://docs.haystack.deepset.ai/docs/serialization#default-serialization-behavior + From a452f737c996f0ef9592133ff94026fe5a7fdca1 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Tue, 24 Mar 2026 11:16:21 +0100 Subject: [PATCH 11/13] Update pyproject.toml --- pyproject.toml | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0d81627..747b062 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] @@ -79,9 +80,38 @@ line-length = 120 [tool.ruff.lint] select = [ - "A", "ARG", "B", "C", "DTZ", "E", "EM", "F", "I", "ICN", "ISC", - "N", "PLC", "PLE", "PLR", "PLW", "Q", "RUF", "S", "T", "TID", - "UP", "W", "YTT", + "A", + "ANN", + "ARG", + "B", + "C", + "D102", # Missing docstring in public method + "D103", # Missing docstring in public function + "D205", # 1 blank line required between summary line and description + "D209", # Closing triple quotes go to new line + "D213", # summary lines must be positioned on the second physical line of the docstring + "D417", # Missing argument descriptions in the docstring + "D419", # Docstring is empty + "DTZ", + "E", + "EM", + "F", + "I", + "ICN", + "ISC", + "N", + "PLC", + "PLE", + "PLR", + "PLW", + "Q", + "RUF", + "S", + "T", + "TID", + "UP", + "W", + "YTT", ] ignore = [ "B027", # empty method in abstract base class @@ -102,7 +132,10 @@ known-first-party = ["haystack_integrations"] ban-relative-imports = "parents" [tool.ruff.lint.per-file-ignores] -"tests/**/*" = ["PLR2004", "S101", "TID252"] +# Tests can use magic values, assertions, and relative imports +"tests/**/*" = ["D", "PLR2004", "S101", "TID252", "ANN"] +# Examples can print their output and don't need type annotations +"examples/**/*" = ["D", "T201", "ANN"] [tool.mypy] install_types = true From 56dc0ee09726606498421548d8ca008d4abb3ef2 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Tue, 24 Mar 2026 11:20:51 +0100 Subject: [PATCH 12/13] Add example usage file --- examples/example.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 examples/example.py diff --git a/examples/example.py b/examples/example.py new file mode 100644 index 0000000..dda8191 --- /dev/null +++ b/examples/example.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2026-present AUTHOR +# +# SPDX-License-Identifier: Apache-2.0 + +from haystack_integrations.components.example import ExampleComponent + +# This is a minimal example showing how to use the component. +# Replace this with a usage example that demonstrates your component's functionality. +component = ExampleComponent(param="my_param") +result = component.run(input_text="Hello, world!") + +print(result) From 954c6d0a2fddbf19949a73ec891922b26d3640f7 Mon Sep 17 00:00:00 2001 From: bogdankostic Date: Tue, 24 Mar 2026 11:28:13 +0100 Subject: [PATCH 13/13] Add return type annotation --- .../components/example/example_component.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/haystack_integrations/components/example/example_component.py b/src/haystack_integrations/components/example/example_component.py index 1c66275..6b286e1 100644 --- a/src/haystack_integrations/components/example/example_component.py +++ b/src/haystack_integrations/components/example/example_component.py @@ -20,7 +20,7 @@ class ExampleComponent: ``` """ - def __init__(self, param: str = "default"): + def __init__(self, param: str = "default") -> None: """ Initialize the component. @@ -44,4 +44,3 @@ def run(self, input_text: str) -> dict[str, str]: # NOTE: Custom `to_dict` and `from_dict` methods are only needed if the default serialization doesn't work # for your component (e.g. it has non-serializable attributes). For details, see: # https://docs.haystack.deepset.ai/docs/serialization#default-serialization-behavior -