threadify-sdk 0.2.0__py3-none-any.whl

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.
threadify/__init__.py ADDED
@@ -0,0 +1,51 @@
1
+ from threadify.client import (
2
+ Threadify,
3
+ ThreadifyFactory,
4
+ )
5
+ from threadify.connection import Connection
6
+ from threadify.data_retriever import ArchivedStep, ArchivedThread, DataRetriever
7
+ from threadify.models import (
8
+ CompleteDataOptions,
9
+ ConnectOptions,
10
+ HistoryQueryOptions,
11
+ InviteOptions,
12
+ InviteResponse,
13
+ NotificationData,
14
+ RefQuery,
15
+ StepResult,
16
+ SubStepData,
17
+ ThreadEndResponse,
18
+ WaitOptions,
19
+ )
20
+ from threadify.notification import Notification
21
+ from threadify.otel_exporter import ThreadifySpanExporter
22
+ from threadify.step import DuplicateStepError, ThreadStep, is_duplicate_error
23
+ from threadify.thread import ThreadInstance
24
+
25
+ __all__ = [
26
+ "Threadify",
27
+ "ThreadifyFactory",
28
+ "Connection",
29
+ "ThreadInstance",
30
+ "ThreadStep",
31
+ "DuplicateStepError",
32
+ "is_duplicate_error",
33
+ "Notification",
34
+ "DataRetriever",
35
+ "ArchivedThread",
36
+ "ArchivedStep",
37
+ "ThreadifySpanExporter",
38
+ "ConnectOptions",
39
+ "StepResult",
40
+ "SubStepData",
41
+ "InviteOptions",
42
+ "InviteResponse",
43
+ "ThreadEndResponse",
44
+ "WaitOptions",
45
+ "NotificationData",
46
+ "RefQuery",
47
+ "CompleteDataOptions",
48
+ "HistoryQueryOptions",
49
+ ]
50
+
51
+ __version__ = "0.1.0"
threadify/client.py ADDED
@@ -0,0 +1,191 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ import logging
6
+ from dataclasses import fields
7
+ from typing import Any
8
+
9
+ import websockets
10
+
11
+ from threadify.connection import Connection
12
+ from threadify.models import (
13
+ ACTION_CONNECT,
14
+ FIELD_ACTION,
15
+ FIELD_API_KEY,
16
+ FIELD_MAX_IN_FLIGHT,
17
+ FIELD_MESSAGE,
18
+ FIELD_SERVICE_NAME,
19
+ FIELD_STATUS,
20
+ STATUS_SUCCESS,
21
+ ConnectOptions,
22
+ require_non_empty,
23
+ )
24
+
25
+
26
+ def _copy_connect_options(src: ConnectOptions) -> ConnectOptions:
27
+ data = {f.name: getattr(src, f.name) for f in fields(ConnectOptions)}
28
+ return ConnectOptions(**data)
29
+
30
+
31
+ def _build_connect_options(
32
+ *,
33
+ base: ConnectOptions | None,
34
+ service_name: str | None,
35
+ ws_url: str | None,
36
+ graphql_url: str | None,
37
+ debug: bool | None,
38
+ max_in_flight: int | None,
39
+ connect_timeout: float | None,
40
+ logger: logging.Logger | None = None,
41
+ ) -> ConnectOptions:
42
+ cfg = _copy_connect_options(base) if base else ConnectOptions()
43
+
44
+ if service_name is not None:
45
+ cfg.service_name = service_name
46
+ if ws_url is not None:
47
+ cfg.ws_url = ws_url
48
+ if graphql_url is not None:
49
+ cfg.graphql_url = graphql_url
50
+ if debug is not None:
51
+ cfg.debug = debug
52
+ if max_in_flight is not None:
53
+ cfg.max_in_flight = max_in_flight
54
+ if connect_timeout is not None:
55
+ cfg.connect_timeout = connect_timeout
56
+ if logger is not None:
57
+ cfg.logger = logger
58
+
59
+ cfg.with_defaults()
60
+ cfg.validate()
61
+ return cfg
62
+
63
+
64
+ class Threadify:
65
+ """Factory for creating Threadify connections."""
66
+
67
+ @staticmethod
68
+ async def connect(
69
+ api_key: str,
70
+ *args: Any,
71
+ service_name: str | None = None,
72
+ ws_url: str | None = None,
73
+ graphql_url: str | None = None,
74
+ debug: bool | None = None,
75
+ max_in_flight: int | None = None,
76
+ connect_timeout: float | None = None,
77
+ logger: logging.Logger | None = None,
78
+ options: ConnectOptions | None = None,
79
+ ) -> Connection:
80
+ require_non_empty("api_key", api_key)
81
+
82
+ legacy_service_name: str | None = None
83
+ legacy_config: ConnectOptions | None = None
84
+ for arg in args:
85
+ if isinstance(arg, str) and legacy_service_name is None:
86
+ legacy_service_name = arg
87
+ continue
88
+ if isinstance(arg, ConnectOptions) and legacy_config is None:
89
+ legacy_config = arg
90
+ continue
91
+ raise TypeError(
92
+ "invalid connect argument; expected service_name (str) or ConnectOptions"
93
+ )
94
+
95
+ cfg = _build_connect_options(
96
+ base=options or legacy_config,
97
+ service_name=service_name if service_name is not None else legacy_service_name,
98
+ ws_url=ws_url,
99
+ graphql_url=graphql_url,
100
+ debug=debug,
101
+ max_in_flight=max_in_flight,
102
+ connect_timeout=connect_timeout,
103
+ logger=logger,
104
+ )
105
+
106
+ ws = await asyncio.wait_for(
107
+ websockets.connect(cfg.ws_url),
108
+ timeout=cfg.connect_timeout,
109
+ )
110
+
111
+ connect_msg = {
112
+ FIELD_ACTION: ACTION_CONNECT,
113
+ FIELD_API_KEY: api_key,
114
+ FIELD_SERVICE_NAME: cfg.service_name,
115
+ FIELD_MAX_IN_FLIGHT: cfg.max_in_flight,
116
+ }
117
+ await ws.send(json.dumps(connect_msg))
118
+
119
+ raw = await asyncio.wait_for(ws.recv(), timeout=cfg.connect_timeout)
120
+ resp = json.loads(raw)
121
+
122
+ if resp.get(FIELD_ACTION) != ACTION_CONNECT or resp.get(FIELD_STATUS) != STATUS_SUCCESS:
123
+ await ws.close()
124
+ msg = resp.get(FIELD_MESSAGE, "connection failed")
125
+ raise ConnectionError(msg)
126
+
127
+ conn = Connection(
128
+ ws=ws,
129
+ api_key=api_key,
130
+ service_name=cfg.service_name,
131
+ graphql_url=cfg.graphql_url,
132
+ debug=cfg.debug,
133
+ max_in_flight=cfg.max_in_flight,
134
+ logger=cfg.logger,
135
+ )
136
+
137
+ return conn
138
+
139
+ @staticmethod
140
+ def create(
141
+ api_key: str,
142
+ *args: Any,
143
+ service_name: str | None = None,
144
+ ws_url: str | None = None,
145
+ graphql_url: str | None = None,
146
+ debug: bool | None = None,
147
+ max_in_flight: int | None = None,
148
+ connect_timeout: float | None = None,
149
+ logger: logging.Logger | None = None,
150
+ options: ConnectOptions | None = None,
151
+ ) -> ThreadifyFactory:
152
+ legacy_service_name: str | None = None
153
+ legacy_config: ConnectOptions | None = None
154
+ for arg in args:
155
+ if isinstance(arg, str) and legacy_service_name is None:
156
+ legacy_service_name = arg
157
+ continue
158
+ if isinstance(arg, ConnectOptions) and legacy_config is None:
159
+ legacy_config = arg
160
+ continue
161
+ raise TypeError(
162
+ "invalid create argument; expected service_name (str) or ConnectOptions"
163
+ )
164
+
165
+ cfg = _build_connect_options(
166
+ base=options or legacy_config,
167
+ service_name=service_name if service_name is not None else legacy_service_name,
168
+ ws_url=ws_url,
169
+ graphql_url=graphql_url,
170
+ debug=debug,
171
+ max_in_flight=max_in_flight,
172
+ connect_timeout=connect_timeout,
173
+ logger=logger,
174
+ )
175
+ return ThreadifyFactory(
176
+ api_key=api_key,
177
+ options=cfg,
178
+ )
179
+
180
+
181
+ class ThreadifyFactory:
182
+ def __init__(
183
+ self,
184
+ api_key: str,
185
+ options: ConnectOptions,
186
+ ):
187
+ self._api_key = api_key
188
+ self._options = _copy_connect_options(options)
189
+
190
+ async def connect(self) -> Connection:
191
+ return await Threadify.connect(self._api_key, options=self._options)