openapi-haystack 0.1.0__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. openapi_haystack-1.0.0/CHANGELOG.md +9 -0
  2. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/PKG-INFO +4 -2
  3. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/README.md +2 -0
  4. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/pyproject.toml +1 -0
  5. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/src/haystack_integrations/components/connectors/openapi/openapi.py +15 -5
  6. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/tests/test_openapi_connector.py +80 -50
  7. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/.gitignore +0 -0
  8. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/LICENSE.txt +0 -0
  9. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/pydoc/config_docusaurus.yml +0 -0
  10. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/src/haystack_integrations/components/connectors/openapi/__init__.py +0 -0
  11. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/src/haystack_integrations/components/connectors/openapi/openapi_service.py +0 -0
  12. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/src/haystack_integrations/components/connectors/py.typed +0 -0
  13. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/src/haystack_integrations/components/converters/openapi/__init__.py +0 -0
  14. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/src/haystack_integrations/components/converters/openapi/openapi_functions.py +0 -0
  15. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/src/haystack_integrations/components/converters/py.typed +0 -0
  16. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/tests/__init__.py +0 -0
  17. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/tests/conftest.py +0 -0
  18. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/tests/test_files/json/complex_types_openai_spec.json +0 -0
  19. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/tests/test_files/json/complex_types_openapi_service.json +0 -0
  20. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/tests/test_openapi_functions.py +0 -0
  21. {openapi_haystack-0.1.0 → openapi_haystack-1.0.0}/tests/test_openapi_service.py +0 -0
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## [integrations/openapi-v0.1.0] - 2026-06-22
4
+
5
+ ### 🚀 Features
6
+
7
+ - Add OpenAPI integration (moved from Haystack) (#3478)
8
+
9
+ <!-- generated by git-cliff -->
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: openapi-haystack
3
- Version: 0.1.0
3
+ Version: 1.0.0
4
4
  Summary: Haystack integration for openapi
5
5
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/openapi#readme
6
6
  Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
@@ -38,3 +38,5 @@ Description-Content-Type: text/markdown
38
38
  ## Contributing
39
39
 
40
40
  Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
41
+
42
+ To run integration tests locally, you need to export the `OPENAI_API_KEY` environment variable.
@@ -11,3 +11,5 @@
11
11
  ## Contributing
12
12
 
13
13
  Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
14
+
15
+ To run integration tests locally, you need to export the `OPENAI_API_KEY` environment variable.
@@ -131,6 +131,7 @@ ignore = [
131
131
  "PLR0912",
132
132
  "PLR0913",
133
133
  "PLR0915",
134
+ "PLR0917",
134
135
  # Allow `Any` type - used legitimately for dynamic types and SDK boundaries
135
136
  "ANN401",
136
137
  # Allow string literals and f-strings in exception messages
@@ -61,11 +61,18 @@ class OpenAPIConnector:
61
61
  self.credentials = credentials
62
62
  self.service_kwargs = service_kwargs or {}
63
63
 
64
- self.client = OpenAPIClient.from_spec(
65
- openapi_spec=openapi_spec,
66
- credentials=credentials.resolve_value() if credentials else None,
67
- **self.service_kwargs,
68
- )
64
+ self.client: OpenAPIClient | None = None
65
+
66
+ def warm_up(self) -> None:
67
+ """
68
+ Initialize the OpenAPI client.
69
+ """
70
+ if self.client is None:
71
+ self.client = OpenAPIClient.from_spec(
72
+ openapi_spec=self.openapi_spec,
73
+ credentials=self.credentials.resolve_value() if self.credentials else None,
74
+ **self.service_kwargs,
75
+ )
69
76
 
70
77
  def to_dict(self) -> dict[str, Any]:
71
78
  """
@@ -91,6 +98,9 @@ class OpenAPIConnector:
91
98
  :param arguments: Optional parameters for the endpoint (query, path, or body parameters)
92
99
  :return: Dictionary containing the service response
93
100
  """
101
+ self.warm_up()
102
+ assert self.client is not None # noqa: S101
103
+
94
104
  payload = {"name": operation_id, "arguments": arguments or {}}
95
105
 
96
106
  # Invoke the endpoint using openapi-llm client
@@ -38,8 +38,8 @@ def mock_client():
38
38
  yield client_instance
39
39
 
40
40
 
41
- class TestOpenAPIConnector:
42
- def test_init(self, mock_client):
41
+ class TestInitialization:
42
+ def test_init(self):
43
43
  # Test initialization with credentials and service_kwargs
44
44
  service_kwargs = {"allowed_operations": ["search"]}
45
45
  connector = OpenAPIConnector(
@@ -48,13 +48,17 @@ class TestOpenAPIConnector:
48
48
  assert connector.openapi_spec == MOCK_OPENAPI_SPEC
49
49
  assert connector.credentials.resolve_value() == "test-token"
50
50
  assert connector.service_kwargs == service_kwargs
51
+ assert connector.client is None
51
52
 
52
53
  # Test initialization without credentials and service_kwargs
53
54
  connector = OpenAPIConnector(openapi_spec=MOCK_OPENAPI_SPEC)
54
55
  assert connector.credentials is None
55
56
  assert connector.service_kwargs == {}
57
+ assert connector.client is None
56
58
 
57
- def test_to_dict(self, monkeypatch, mock_client):
59
+
60
+ class TestSerialization:
61
+ def test_to_dict(self, monkeypatch):
58
62
  monkeypatch.setenv("ENV_VAR", "test-api-key")
59
63
  service_kwargs = {"allowed_operations": ["search"]}
60
64
  connector = OpenAPIConnector(
@@ -70,7 +74,7 @@ class TestOpenAPIConnector:
70
74
  },
71
75
  }
72
76
 
73
- def test_from_dict(self, monkeypatch, mock_client):
77
+ def test_from_dict(self, monkeypatch):
74
78
  monkeypatch.setenv("ENV_VAR", "test-api-key")
75
79
  service_kwargs = {"allowed_operations": ["search"]}
76
80
  data = {
@@ -85,52 +89,9 @@ class TestOpenAPIConnector:
85
89
  assert connector.openapi_spec == MOCK_OPENAPI_SPEC
86
90
  assert connector.credentials == Secret.from_env_var("ENV_VAR")
87
91
  assert connector.service_kwargs == service_kwargs
92
+ assert connector.client is None
88
93
 
89
- def test_run(self, mock_client):
90
- service_kwargs = {"allowed_operations": ["search"]}
91
- connector = OpenAPIConnector(
92
- openapi_spec=MOCK_OPENAPI_SPEC, credentials=Secret.from_token("test-token"), service_kwargs=service_kwargs
93
- )
94
-
95
- # Mock the response from the client
96
- mock_client.invoke.return_value = {"results": ["test result"]}
97
-
98
- # Test with arguments
99
- response = connector.run(operation_id="search", arguments={"q": "test query"})
100
- mock_client.invoke.assert_called_with({"name": "search", "arguments": {"q": "test query"}})
101
- assert response == {"response": {"results": ["test result"]}}
102
-
103
- # Test without arguments
104
- response = connector.run(operation_id="search")
105
- mock_client.invoke.assert_called_with({"name": "search", "arguments": {}})
106
- assert response == {"response": {"results": ["test result"]}}
107
-
108
- def test_in_pipeline(self, mock_client):
109
- mock_client.invoke.return_value = {"results": ["test result"]}
110
-
111
- connector = OpenAPIConnector(openapi_spec=MOCK_OPENAPI_SPEC, credentials=Secret.from_token("test-token"))
112
-
113
- pipe = Pipeline()
114
- pipe.add_component("api", connector)
115
-
116
- # Test pipeline execution
117
- results = pipe.run(data={"api": {"operation_id": "search", "arguments": {"q": "test query"}}})
118
-
119
- assert results == {"api": {"response": {"results": ["test result"]}}}
120
-
121
- def test_from_dict_fail_wo_env_var(self, monkeypatch, mock_client):
122
- monkeypatch.delenv("ENV_VAR", raising=False)
123
- data = {
124
- "type": "haystack_integrations.components.connectors.openapi.openapi.OpenAPIConnector",
125
- "init_parameters": {
126
- "openapi_spec": MOCK_OPENAPI_SPEC,
127
- "credentials": {"env_vars": ["ENV_VAR"], "type": "env_var", "strict": True},
128
- },
129
- }
130
- with pytest.raises(ValueError, match=r"None of the .* environment variables are set"):
131
- OpenAPIConnector.from_dict(data)
132
-
133
- def test_serde_in_pipeline(self, monkeypatch, mock_client):
94
+ def test_serde_in_pipeline(self, monkeypatch):
134
95
  """
135
96
  Test serialization/deserialization of OpenAPIConnector in a Pipeline,
136
97
  including detailed dictionary validation
@@ -179,8 +140,77 @@ class TestOpenAPIConnector:
179
140
  assert loaded_connector.service_kwargs == connector.service_kwargs
180
141
 
181
142
 
143
+ class TestComponentLifecycle:
144
+ def test_key_resolved_at_warm_up_not_init(self, monkeypatch):
145
+ monkeypatch.delenv("ENV_VAR", raising=False)
146
+ data = {
147
+ "type": "haystack_integrations.components.connectors.openapi.openapi.OpenAPIConnector",
148
+ "init_parameters": {
149
+ "openapi_spec": MOCK_OPENAPI_SPEC,
150
+ "credentials": {"env_vars": ["ENV_VAR"], "type": "env_var", "strict": True},
151
+ },
152
+ }
153
+ connector = OpenAPIConnector.from_dict(data)
154
+ assert connector.client is None
155
+
156
+ with pytest.raises(ValueError, match=r"None of the .* environment variables are set"):
157
+ connector.warm_up()
158
+
159
+ def test_warm_up_is_idempotent(self):
160
+ connector = OpenAPIConnector(
161
+ openapi_spec=MOCK_OPENAPI_SPEC,
162
+ credentials=Secret.from_token("test-token"),
163
+ service_kwargs={"allowed_operations": ["search"]},
164
+ )
165
+
166
+ with patch("haystack_integrations.components.connectors.openapi.openapi.OpenAPIClient") as client_class:
167
+ connector.warm_up()
168
+ connector.warm_up()
169
+
170
+ client_class.from_spec.assert_called_once_with(
171
+ openapi_spec=MOCK_OPENAPI_SPEC,
172
+ credentials="test-token",
173
+ allowed_operations=["search"],
174
+ )
175
+ assert connector.client is client_class.from_spec.return_value
176
+
177
+
178
+ class TestRun:
179
+ def test_run(self, mock_client):
180
+ service_kwargs = {"allowed_operations": ["search"]}
181
+ connector = OpenAPIConnector(
182
+ openapi_spec=MOCK_OPENAPI_SPEC, credentials=Secret.from_token("test-token"), service_kwargs=service_kwargs
183
+ )
184
+
185
+ # Mock the response from the client
186
+ mock_client.invoke.return_value = {"results": ["test result"]}
187
+
188
+ # Test with arguments
189
+ response = connector.run(operation_id="search", arguments={"q": "test query"})
190
+ mock_client.invoke.assert_called_with({"name": "search", "arguments": {"q": "test query"}})
191
+ assert response == {"response": {"results": ["test result"]}}
192
+
193
+ # Test without arguments
194
+ response = connector.run(operation_id="search")
195
+ mock_client.invoke.assert_called_with({"name": "search", "arguments": {}})
196
+ assert response == {"response": {"results": ["test result"]}}
197
+
198
+ def test_run_in_pipeline(self, mock_client):
199
+ mock_client.invoke.return_value = {"results": ["test result"]}
200
+
201
+ connector = OpenAPIConnector(openapi_spec=MOCK_OPENAPI_SPEC, credentials=Secret.from_token("test-token"))
202
+
203
+ pipe = Pipeline()
204
+ pipe.add_component("api", connector)
205
+
206
+ # Test pipeline execution
207
+ results = pipe.run(data={"api": {"operation_id": "search", "arguments": {"q": "test query"}}})
208
+
209
+ assert results == {"api": {"response": {"results": ["test result"]}}}
210
+
211
+
182
212
  @pytest.mark.integration
183
- class TestOpenAPIConnectorIntegration:
213
+ class TestIntegration:
184
214
  @pytest.mark.integration
185
215
  def test_open_meteo_integration(self):
186
216
  open_meteo_spec = {