meibel 0.1.0b1__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 (36) hide show
  1. meibel-0.1.0b1/PKG-INFO +86 -0
  2. meibel-0.1.0b1/README.md +57 -0
  3. meibel-0.1.0b1/meibel/__init__.py +18 -0
  4. meibel-0.1.0b1/meibel/_http.py +309 -0
  5. meibel-0.1.0b1/meibel/_pagination.py +170 -0
  6. meibel-0.1.0b1/meibel/_streaming.py +226 -0
  7. meibel-0.1.0b1/meibel/_upload.py +104 -0
  8. meibel-0.1.0b1/meibel/client.py +127 -0
  9. meibel-0.1.0b1/meibel/exceptions.py +86 -0
  10. meibel-0.1.0b1/meibel/models.py +1546 -0
  11. meibel-0.1.0b1/meibel/py.typed +0 -0
  12. meibel-0.1.0b1/meibel/resources/__init__.py +85 -0
  13. meibel-0.1.0b1/meibel/resources/blueprints.py +390 -0
  14. meibel-0.1.0b1/meibel/resources/blueprints_executions.py +298 -0
  15. meibel-0.1.0b1/meibel/resources/blueprints_instances.py +612 -0
  16. meibel-0.1.0b1/meibel/resources/confidence_scoring.py +214 -0
  17. meibel-0.1.0b1/meibel/resources/content.py +326 -0
  18. meibel-0.1.0b1/meibel/resources/data_element_metadata.py +150 -0
  19. meibel-0.1.0b1/meibel/resources/data_elements.py +271 -0
  20. meibel-0.1.0b1/meibel/resources/datasources.py +209 -0
  21. meibel-0.1.0b1/meibel/resources/datasources_content.py +447 -0
  22. meibel-0.1.0b1/meibel/resources/datasources_dataelements.py +398 -0
  23. meibel-0.1.0b1/meibel/resources/datasources_metadata_model_catalog.py +115 -0
  24. meibel-0.1.0b1/meibel/resources/datasources_rag.py +432 -0
  25. meibel-0.1.0b1/meibel/resources/datasources_tag.py +592 -0
  26. meibel-0.1.0b1/meibel/resources/documents.py +283 -0
  27. meibel-0.1.0b1/meibel/resources/metadata_configuration.py +170 -0
  28. meibel-0.1.0b1/meibel/resources/metadata_model_catalog.py +115 -0
  29. meibel-0.1.0b1/meibel/resources/tag_descriptions.py +213 -0
  30. meibel-0.1.0b1/meibel.egg-info/PKG-INFO +86 -0
  31. meibel-0.1.0b1/meibel.egg-info/SOURCES.txt +34 -0
  32. meibel-0.1.0b1/meibel.egg-info/dependency_links.txt +1 -0
  33. meibel-0.1.0b1/meibel.egg-info/requires.txt +8 -0
  34. meibel-0.1.0b1/meibel.egg-info/top_level.txt +2 -0
  35. meibel-0.1.0b1/pyproject.toml +55 -0
  36. meibel-0.1.0b1/setup.cfg +4 -0
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: meibel
3
+ Version: 0.1.0b1
4
+ Summary: The Meibel API provides document parsing, datasource management, and AI agent orchestration.
5
+ License: MIT
6
+ Project-URL: Homepage, https://docs.meibel.ai
7
+ Project-URL: Repository, https://github.com/meibel-ai/meibel-python
8
+ Project-URL: Documentation, https://docs.meibel.ai/sdk/python
9
+ Project-URL: Bug Tracker, https://github.com/meibel-ai/meibel-python/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: httpx>=0.25.0
23
+ Requires-Dist: pydantic>=2.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
26
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
27
+ Requires-Dist: black>=23.0.0; extra == "dev"
28
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
29
+
30
+ # Meibel Python SDK
31
+
32
+ The official Python SDK for the [Meibel API](https://docs.meibel.ai). Provides document parsing, datasource management, and AI agent orchestration.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install meibel==0.1.0b1
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ from meibel import MeibelClient
44
+
45
+ client = MeibelClient(
46
+ api_key="your-api-key",
47
+ base_url="https://api.meibel.ai/v2",
48
+ )
49
+
50
+ # Parse a document
51
+ with open("document.pdf", "rb") as f:
52
+ result = client.documents.parse_document(file=f)
53
+ print(result.job_id)
54
+
55
+ # Process a document synchronously (waits for completion)
56
+ with open("document.pdf", "rb") as f:
57
+ result = client.documents.process_document(file=f)
58
+ print(result)
59
+
60
+ # List datasources
61
+ datasources = client.datasources.list_datasources()
62
+ for ds in datasources.items:
63
+ print(ds.name)
64
+ ```
65
+
66
+ ## Async Usage
67
+
68
+ ```python
69
+ from meibel import AsyncMeibelClient
70
+
71
+ client = AsyncMeibelClient(
72
+ api_key="your-api-key",
73
+ base_url="https://api.meibel.ai/v2",
74
+ )
75
+
76
+ result = await client.documents.process_document(file=open("doc.pdf", "rb"))
77
+ ```
78
+
79
+ ## Documentation
80
+
81
+ - [API Reference](https://docs.meibel.ai/api-reference/overview)
82
+ - [SDK Guide](https://docs.meibel.ai/sdk/python)
83
+
84
+ ## License
85
+
86
+ MIT
@@ -0,0 +1,57 @@
1
+ # Meibel Python SDK
2
+
3
+ The official Python SDK for the [Meibel API](https://docs.meibel.ai). Provides document parsing, datasource management, and AI agent orchestration.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install meibel==0.1.0b1
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from meibel import MeibelClient
15
+
16
+ client = MeibelClient(
17
+ api_key="your-api-key",
18
+ base_url="https://api.meibel.ai/v2",
19
+ )
20
+
21
+ # Parse a document
22
+ with open("document.pdf", "rb") as f:
23
+ result = client.documents.parse_document(file=f)
24
+ print(result.job_id)
25
+
26
+ # Process a document synchronously (waits for completion)
27
+ with open("document.pdf", "rb") as f:
28
+ result = client.documents.process_document(file=f)
29
+ print(result)
30
+
31
+ # List datasources
32
+ datasources = client.datasources.list_datasources()
33
+ for ds in datasources.items:
34
+ print(ds.name)
35
+ ```
36
+
37
+ ## Async Usage
38
+
39
+ ```python
40
+ from meibel import AsyncMeibelClient
41
+
42
+ client = AsyncMeibelClient(
43
+ api_key="your-api-key",
44
+ base_url="https://api.meibel.ai/v2",
45
+ )
46
+
47
+ result = await client.documents.process_document(file=open("doc.pdf", "rb"))
48
+ ```
49
+
50
+ ## Documentation
51
+
52
+ - [API Reference](https://docs.meibel.ai/api-reference/overview)
53
+ - [SDK Guide](https://docs.meibel.ai/sdk/python)
54
+
55
+ ## License
56
+
57
+ MIT
@@ -0,0 +1,18 @@
1
+ """
2
+ meibel - The Meibel API provides document parsing, datasource management, and AI agent orchestration.
3
+
4
+ Generated by Forge SDK Generator.
5
+ """
6
+
7
+ __version__ = "0.1.0-beta.1"
8
+
9
+ from .client import MeibelClient
10
+ from .client import AsyncMeibelClient
11
+ from .models import *
12
+ from .exceptions import *
13
+
14
+ __all__ = [
15
+ "MeibelClient",
16
+ "AsyncMeibelClient",
17
+ "__version__",
18
+ ]
@@ -0,0 +1,309 @@
1
+ """
2
+ HTTP Client Module
3
+
4
+ Provides sync and async HTTP clients using httpx.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import httpx
10
+ from typing import Any, Dict, Optional, TypeVar, Type, Union
11
+ from pydantic import BaseModel
12
+
13
+ from .exceptions import ApiError, AuthenticationError, RateLimitError, NotFoundError
14
+
15
+ T = TypeVar("T", bound=BaseModel)
16
+
17
+
18
+ class HttpClient:
19
+ """Synchronous HTTP client."""
20
+
21
+ def __init__(
22
+ self,
23
+ base_url: str,
24
+ api_key: Optional[str] = None,
25
+ bearer_token: Optional[str] = None,
26
+ timeout: float = 30.0,
27
+ headers: Optional[Dict[str, str]] = None,
28
+ ):
29
+ self.base_url = base_url.rstrip("/")
30
+ self._timeout = timeout
31
+ self._headers = headers or {}
32
+
33
+ if api_key:
34
+ self._headers["Meibel-API-Key"] = api_key
35
+ if bearer_token:
36
+ self._headers["Authorization"] = f"Bearer {bearer_token}"
37
+
38
+ self._client = httpx.Client(
39
+ base_url=self.base_url,
40
+ timeout=timeout,
41
+ headers=self._headers,
42
+ )
43
+
44
+ def close(self) -> None:
45
+ """Close the HTTP client."""
46
+ self._client.close()
47
+
48
+ def __enter__(self) -> "HttpClient":
49
+ return self
50
+
51
+ def __exit__(self, *args: Any) -> None:
52
+ self.close()
53
+
54
+ def request(
55
+ self,
56
+ method: str,
57
+ path: str,
58
+ *,
59
+ params: Optional[Dict[str, Any]] = None,
60
+ json: Optional[Any] = None,
61
+ headers: Optional[Dict[str, str]] = None,
62
+ response_model: Optional[Type[T]] = None,
63
+ ) -> Union[T, Dict[str, Any], None]:
64
+ """Make an HTTP request."""
65
+ # Filter out None values from params
66
+ if params:
67
+ params = {k: v for k, v in params.items() if v is not None}
68
+
69
+ # Merge headers
70
+ request_headers = {**self._headers}
71
+ if headers:
72
+ request_headers.update(headers)
73
+
74
+ response = self._client.request(
75
+ method=method,
76
+ url=path,
77
+ params=params,
78
+ json=self._serialize_body(json),
79
+ headers=request_headers,
80
+ )
81
+
82
+ return self._handle_response(response, response_model)
83
+
84
+ def upload(
85
+ self,
86
+ method: str,
87
+ path: str,
88
+ *,
89
+ file: Any,
90
+ file_name: str,
91
+ field_name: str = "file",
92
+ params: Optional[Dict[str, Any]] = None,
93
+ form_fields: Optional[Dict[str, str]] = None,
94
+ headers: Optional[Dict[str, str]] = None,
95
+ response_model: Optional[Type[T]] = None,
96
+ ) -> Union[T, Dict[str, Any], None]:
97
+ """Upload a file with streaming multipart/form-data."""
98
+ from ._upload import create_multipart_stream
99
+
100
+ content_stream, content_type = create_multipart_stream(
101
+ file, field_name=field_name, file_name=file_name, form_fields=form_fields,
102
+ )
103
+
104
+ request_headers = {k: v for k, v in self._headers.items() if k.lower() != "content-type"}
105
+ request_headers["Content-Type"] = content_type
106
+ if headers:
107
+ request_headers.update(headers)
108
+ if params:
109
+ params = {k: v for k, v in params.items() if v is not None}
110
+
111
+ response = self._client.request(
112
+ method=method, url=path, content=content_stream, params=params, headers=request_headers,
113
+ )
114
+ return self._handle_response(response, response_model)
115
+
116
+ def _serialize_body(self, body: Any) -> Any:
117
+ """Serialize request body, converting Pydantic models to dicts."""
118
+ if body is None:
119
+ return None
120
+ if isinstance(body, BaseModel):
121
+ return body.model_dump(mode="json", exclude_none=True)
122
+ if isinstance(body, list):
123
+ return [self._serialize_body(item) for item in body]
124
+ return body
125
+
126
+ def _handle_response(
127
+ self,
128
+ response: httpx.Response,
129
+ response_model: Optional[Type[T]] = None,
130
+ ) -> Union[T, Dict[str, Any], None]:
131
+ """Handle HTTP response, raising errors or parsing data."""
132
+ if response.status_code == 204:
133
+ return None
134
+
135
+ if response.status_code >= 400:
136
+ self._raise_error(response)
137
+
138
+ if response_model:
139
+ return response_model.model_validate(response.json())
140
+
141
+ try:
142
+ return response.json()
143
+ except Exception:
144
+ return None
145
+
146
+ def _raise_error(self, response: httpx.Response) -> None:
147
+ """Raise appropriate error based on status code."""
148
+ try:
149
+ error_body = response.json()
150
+ except Exception:
151
+ error_body = {"message": response.text}
152
+
153
+ message = error_body.get("message", error_body.get("error", "Unknown error"))
154
+
155
+ if response.status_code == 401:
156
+ raise AuthenticationError(message, response.status_code, error_body)
157
+ elif response.status_code == 404:
158
+ raise NotFoundError(message, response.status_code, error_body)
159
+ elif response.status_code == 429:
160
+ raise RateLimitError(message, response.status_code, error_body)
161
+ else:
162
+ raise ApiError(message, response.status_code, error_body)
163
+
164
+
165
+ class AsyncHttpClient:
166
+ """Asynchronous HTTP client."""
167
+
168
+ def __init__(
169
+ self,
170
+ base_url: str,
171
+ api_key: Optional[str] = None,
172
+ bearer_token: Optional[str] = None,
173
+ timeout: float = 30.0,
174
+ headers: Optional[Dict[str, str]] = None,
175
+ ):
176
+ self.base_url = base_url.rstrip("/")
177
+ self._timeout = timeout
178
+ self._headers = headers or {}
179
+
180
+ if api_key:
181
+ self._headers["Meibel-API-Key"] = api_key
182
+ if bearer_token:
183
+ self._headers["Authorization"] = f"Bearer {bearer_token}"
184
+
185
+ self._client = httpx.AsyncClient(
186
+ base_url=self.base_url,
187
+ timeout=timeout,
188
+ headers=self._headers,
189
+ )
190
+
191
+ async def close(self) -> None:
192
+ """Close the HTTP client."""
193
+ await self._client.aclose()
194
+
195
+ async def __aenter__(self) -> "AsyncHttpClient":
196
+ return self
197
+
198
+ async def __aexit__(self, *args: Any) -> None:
199
+ await self.close()
200
+
201
+ async def request(
202
+ self,
203
+ method: str,
204
+ path: str,
205
+ *,
206
+ params: Optional[Dict[str, Any]] = None,
207
+ json: Optional[Any] = None,
208
+ headers: Optional[Dict[str, str]] = None,
209
+ response_model: Optional[Type[T]] = None,
210
+ ) -> Union[T, Dict[str, Any], None]:
211
+ """Make an HTTP request."""
212
+ # Filter out None values from params
213
+ if params:
214
+ params = {k: v for k, v in params.items() if v is not None}
215
+
216
+ # Merge headers
217
+ request_headers = {**self._headers}
218
+ if headers:
219
+ request_headers.update(headers)
220
+
221
+ response = await self._client.request(
222
+ method=method,
223
+ url=path,
224
+ params=params,
225
+ json=self._serialize_body(json),
226
+ headers=request_headers,
227
+ )
228
+
229
+ return self._handle_response(response, response_model)
230
+
231
+ async def upload(
232
+ self,
233
+ method: str,
234
+ path: str,
235
+ *,
236
+ file: Any,
237
+ file_name: str,
238
+ field_name: str = "file",
239
+ params: Optional[Dict[str, Any]] = None,
240
+ form_fields: Optional[Dict[str, str]] = None,
241
+ headers: Optional[Dict[str, str]] = None,
242
+ response_model: Optional[Type[T]] = None,
243
+ ) -> Union[T, Dict[str, Any], None]:
244
+ """Upload a file with streaming multipart/form-data."""
245
+ from ._upload import create_async_multipart_stream
246
+
247
+ content_stream, content_type = await create_async_multipart_stream(
248
+ file, field_name=field_name, file_name=file_name, form_fields=form_fields,
249
+ )
250
+
251
+ request_headers = {k: v for k, v in self._headers.items() if k.lower() != "content-type"}
252
+ request_headers["Content-Type"] = content_type
253
+ if headers:
254
+ request_headers.update(headers)
255
+ if params:
256
+ params = {k: v for k, v in params.items() if v is not None}
257
+
258
+ response = await self._client.request(
259
+ method=method, url=path, content=content_stream, params=params, headers=request_headers,
260
+ )
261
+ return self._handle_response(response, response_model)
262
+
263
+ def _serialize_body(self, body: Any) -> Any:
264
+ """Serialize request body, converting Pydantic models to dicts."""
265
+ if body is None:
266
+ return None
267
+ if isinstance(body, BaseModel):
268
+ return body.model_dump(mode="json", exclude_none=True)
269
+ if isinstance(body, list):
270
+ return [self._serialize_body(item) for item in body]
271
+ return body
272
+
273
+ def _handle_response(
274
+ self,
275
+ response: httpx.Response,
276
+ response_model: Optional[Type[T]] = None,
277
+ ) -> Union[T, Dict[str, Any], None]:
278
+ """Handle HTTP response, raising errors or parsing data."""
279
+ if response.status_code == 204:
280
+ return None
281
+
282
+ if response.status_code >= 400:
283
+ self._raise_error(response)
284
+
285
+ if response_model:
286
+ return response_model.model_validate(response.json())
287
+
288
+ try:
289
+ return response.json()
290
+ except Exception:
291
+ return None
292
+
293
+ def _raise_error(self, response: httpx.Response) -> None:
294
+ """Raise appropriate error based on status code."""
295
+ try:
296
+ error_body = response.json()
297
+ except Exception:
298
+ error_body = {"message": response.text}
299
+
300
+ message = error_body.get("message", error_body.get("error", "Unknown error"))
301
+
302
+ if response.status_code == 401:
303
+ raise AuthenticationError(message, response.status_code, error_body)
304
+ elif response.status_code == 404:
305
+ raise NotFoundError(message, response.status_code, error_body)
306
+ elif response.status_code == 429:
307
+ raise RateLimitError(message, response.status_code, error_body)
308
+ else:
309
+ raise ApiError(message, response.status_code, error_body)
@@ -0,0 +1,170 @@
1
+ """
2
+ Pagination Module
3
+
4
+ Provides iterators for paginated API responses.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import (
10
+ Any,
11
+ AsyncIterator,
12
+ Callable,
13
+ Dict,
14
+ Generic,
15
+ Iterator,
16
+ List,
17
+ Optional,
18
+ TypeVar,
19
+ )
20
+
21
+ T = TypeVar("T")
22
+
23
+
24
+ class PaginatedIterator(Generic[T]):
25
+ """
26
+ Iterator for paginated API responses.
27
+
28
+ Supports cursor-based, offset-based, and page-based pagination.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ fetch_page: Callable[[Optional[str]], Dict[str, Any]],
34
+ extract_items: Callable[[Dict[str, Any]], List[T]],
35
+ extract_cursor: Callable[[Dict[str, Any]], Optional[str]],
36
+ ):
37
+ """
38
+ Initialize the paginated iterator.
39
+
40
+ Args:
41
+ fetch_page: Function that fetches a page given a cursor (None for first page)
42
+ extract_items: Function that extracts items from the response
43
+ extract_cursor: Function that extracts the next cursor from the response
44
+ """
45
+ self._fetch_page = fetch_page
46
+ self._extract_items = extract_items
47
+ self._extract_cursor = extract_cursor
48
+ self._cursor: Optional[str] = None
49
+ self._buffer: List[T] = []
50
+ self._exhausted = False
51
+
52
+ def __iter__(self) -> Iterator[T]:
53
+ return self
54
+
55
+ def __next__(self) -> T:
56
+ # If we have items in the buffer, return the next one
57
+ if self._buffer:
58
+ return self._buffer.pop(0)
59
+
60
+ # If we've exhausted all pages, stop
61
+ if self._exhausted:
62
+ raise StopIteration
63
+
64
+ # Fetch the next page
65
+ response = self._fetch_page(self._cursor)
66
+ items = self._extract_items(response)
67
+ next_cursor = self._extract_cursor(response)
68
+
69
+ # Update state
70
+ if next_cursor:
71
+ self._cursor = next_cursor
72
+ else:
73
+ self._exhausted = True
74
+
75
+ # If no items, stop
76
+ if not items:
77
+ raise StopIteration
78
+
79
+ # Buffer items and return the first one
80
+ self._buffer = items[1:]
81
+ return items[0]
82
+
83
+ def collect(self) -> List[T]:
84
+ """Collect all items into a list."""
85
+ return list(self)
86
+
87
+ def take(self, n: int) -> List[T]:
88
+ """Take up to n items."""
89
+ result: List[T] = []
90
+ for item in self:
91
+ result.append(item)
92
+ if len(result) >= n:
93
+ break
94
+ return result
95
+
96
+
97
+ class AsyncPaginatedIterator(Generic[T]):
98
+ """
99
+ Async iterator for paginated API responses.
100
+
101
+ Supports cursor-based, offset-based, and page-based pagination.
102
+ """
103
+
104
+ def __init__(
105
+ self,
106
+ fetch_page: Callable[[Optional[str]], Any], # Returns Awaitable
107
+ extract_items: Callable[[Dict[str, Any]], List[T]],
108
+ extract_cursor: Callable[[Dict[str, Any]], Optional[str]],
109
+ ):
110
+ """
111
+ Initialize the async paginated iterator.
112
+
113
+ Args:
114
+ fetch_page: Async function that fetches a page given a cursor (None for first page)
115
+ extract_items: Function that extracts items from the response
116
+ extract_cursor: Function that extracts the next cursor from the response
117
+ """
118
+ self._fetch_page = fetch_page
119
+ self._extract_items = extract_items
120
+ self._extract_cursor = extract_cursor
121
+ self._cursor: Optional[str] = None
122
+ self._buffer: List[T] = []
123
+ self._exhausted = False
124
+
125
+ def __aiter__(self) -> AsyncIterator[T]:
126
+ return self
127
+
128
+ async def __anext__(self) -> T:
129
+ # If we have items in the buffer, return the next one
130
+ if self._buffer:
131
+ return self._buffer.pop(0)
132
+
133
+ # If we've exhausted all pages, stop
134
+ if self._exhausted:
135
+ raise StopAsyncIteration
136
+
137
+ # Fetch the next page
138
+ response = await self._fetch_page(self._cursor)
139
+ items = self._extract_items(response)
140
+ next_cursor = self._extract_cursor(response)
141
+
142
+ # Update state
143
+ if next_cursor:
144
+ self._cursor = next_cursor
145
+ else:
146
+ self._exhausted = True
147
+
148
+ # If no items, stop
149
+ if not items:
150
+ raise StopAsyncIteration
151
+
152
+ # Buffer items and return the first one
153
+ self._buffer = items[1:]
154
+ return items[0]
155
+
156
+ async def collect(self) -> List[T]:
157
+ """Collect all items into a list."""
158
+ result: List[T] = []
159
+ async for item in self:
160
+ result.append(item)
161
+ return result
162
+
163
+ async def take(self, n: int) -> List[T]:
164
+ """Take up to n items."""
165
+ result: List[T] = []
166
+ async for item in self:
167
+ result.append(item)
168
+ if len(result) >= n:
169
+ break
170
+ return result