architect-py 3.2.1__py3-none-any.whl → 5.0.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.
Files changed (207) hide show
  1. architect_py/__init__.py +18 -2
  2. architect_py/async_client.py +1089 -658
  3. architect_py/client.py +36 -33
  4. architect_py/client_interface.py +63 -0
  5. architect_py/common_types/__init__.py +6 -0
  6. architect_py/common_types/order_dir.py +91 -0
  7. architect_py/common_types/scalars.py +25 -0
  8. architect_py/common_types/tradable_product.py +59 -0
  9. architect_py/graphql_client/__init__.py +2 -0
  10. architect_py/graphql_client/client.py +3 -6
  11. architect_py/graphql_client/enums.py +5 -0
  12. architect_py/graphql_client/fragments.py +3 -6
  13. architect_py/graphql_client/get_fills_query.py +2 -1
  14. architect_py/graphql_client/search_symbols_query.py +2 -1
  15. architect_py/graphql_client/subscribe_orderflow.py +2 -1
  16. architect_py/graphql_client/subscribe_trades.py +2 -1
  17. architect_py/grpc/__init__.py +145 -0
  18. architect_py/grpc/client.py +94 -0
  19. architect_py/{grpc_client → grpc/models}/Accounts/AccountsRequest.py +6 -3
  20. architect_py/{grpc_client → grpc/models}/Accounts/AccountsResponse.py +1 -1
  21. architect_py/{grpc_client → grpc/models}/Accounts/__init__.py +1 -1
  22. architect_py/grpc/models/Algo/AlgoOrder.py +114 -0
  23. architect_py/grpc/models/Algo/AlgoOrderRequest.py +46 -0
  24. architect_py/grpc/models/Algo/AlgoOrdersRequest.py +72 -0
  25. architect_py/grpc/models/Algo/AlgoOrdersResponse.py +27 -0
  26. architect_py/grpc/models/Algo/CreateAlgoOrderRequest.py +56 -0
  27. architect_py/grpc/models/Algo/PauseAlgoRequest.py +42 -0
  28. architect_py/grpc/models/Algo/PauseAlgoResponse.py +20 -0
  29. architect_py/grpc/models/Algo/StartAlgoRequest.py +42 -0
  30. architect_py/grpc/models/Algo/StartAlgoResponse.py +20 -0
  31. architect_py/grpc/models/Algo/StopAlgoRequest.py +42 -0
  32. architect_py/grpc/models/Algo/StopAlgoResponse.py +20 -0
  33. architect_py/{grpc_client → grpc/models}/Algo/__init__.py +1 -1
  34. architect_py/grpc/models/Auth/CreateJwtRequest.py +47 -0
  35. architect_py/grpc/models/Auth/CreateJwtResponse.py +23 -0
  36. architect_py/{grpc_client/Cpty → grpc/models/Auth}/__init__.py +1 -1
  37. architect_py/grpc/models/Boss/DepositsRequest.py +40 -0
  38. architect_py/grpc/models/Boss/DepositsResponse.py +27 -0
  39. architect_py/grpc/models/Boss/RqdAccountStatisticsRequest.py +42 -0
  40. architect_py/grpc/models/Boss/RqdAccountStatisticsResponse.py +25 -0
  41. architect_py/grpc/models/Boss/StatementUrlRequest.py +40 -0
  42. architect_py/grpc/models/Boss/StatementUrlResponse.py +23 -0
  43. architect_py/grpc/models/Boss/StatementsRequest.py +40 -0
  44. architect_py/grpc/models/Boss/StatementsResponse.py +27 -0
  45. architect_py/grpc/models/Boss/WithdrawalsRequest.py +40 -0
  46. architect_py/grpc/models/Boss/WithdrawalsResponse.py +27 -0
  47. architect_py/{grpc_client/Folio → grpc/models/Boss}/__init__.py +1 -1
  48. architect_py/grpc/models/Core/ConfigRequest.py +37 -0
  49. architect_py/grpc/models/Core/ConfigResponse.py +25 -0
  50. architect_py/grpc/models/Core/__init__.py +2 -0
  51. architect_py/{grpc_client → grpc/models}/Cpty/CptyRequest.py +5 -4
  52. architect_py/{grpc_client → grpc/models}/Cpty/CptyResponse.py +6 -6
  53. architect_py/grpc/models/Cpty/CptyStatus.py +48 -0
  54. architect_py/grpc/models/Cpty/CptyStatusRequest.py +45 -0
  55. architect_py/grpc/models/Cpty/CptysRequest.py +37 -0
  56. architect_py/grpc/models/Cpty/CptysResponse.py +27 -0
  57. architect_py/grpc/models/Cpty/__init__.py +2 -0
  58. architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryRequest.py +2 -2
  59. architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryResponse.py +1 -1
  60. architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesRequest.py +2 -2
  61. architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesResponse.py +1 -1
  62. architect_py/{grpc_client → grpc/models}/Folio/AccountSummary.py +1 -1
  63. architect_py/{grpc_client → grpc/models}/Folio/AccountSummaryRequest.py +2 -2
  64. architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsRequest.py +7 -4
  65. architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsResponse.py +1 -1
  66. architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersRequest.py +3 -3
  67. architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersResponse.py +1 -1
  68. architect_py/grpc/models/Folio/__init__.py +2 -0
  69. architect_py/{grpc_client → grpc/models}/Health/HealthCheckRequest.py +2 -2
  70. architect_py/{grpc_client → grpc/models}/Health/HealthCheckResponse.py +1 -1
  71. architect_py/grpc/models/Health/__init__.py +2 -0
  72. architect_py/{grpc_client → grpc/models}/Marketdata/Candle.py +1 -1
  73. architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesRequest.py +11 -8
  74. architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesResponse.py +1 -1
  75. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshot.py +52 -5
  76. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotRequest.py +8 -3
  77. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotsRequest.py +6 -3
  78. architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshot.py +1 -1
  79. architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshotRequest.py +2 -2
  80. architect_py/{grpc_client → grpc/models}/Marketdata/Liquidation.py +2 -2
  81. architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatus.py +1 -1
  82. architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatusRequest.py +2 -2
  83. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCandlesRequest.py +2 -2
  84. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCurrentCandlesRequest.py +3 -4
  85. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL1BookSnapshotsRequest.py +6 -3
  86. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL2BookUpdatesRequest.py +2 -2
  87. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeLiquidationsRequest.py +2 -2
  88. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeManyCandlesRequest.py +2 -2
  89. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTickersRequest.py +2 -2
  90. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTradesRequest.py +2 -2
  91. architect_py/{grpc_client → grpc/models}/Marketdata/Ticker.py +14 -3
  92. architect_py/{grpc_client → grpc/models}/Marketdata/TickerRequest.py +2 -2
  93. architect_py/{grpc_client → grpc/models}/Marketdata/TickersRequest.py +2 -2
  94. architect_py/{grpc_client → grpc/models}/Marketdata/TickersResponse.py +1 -1
  95. architect_py/{grpc_client → grpc/models}/Marketdata/Trade.py +2 -2
  96. architect_py/grpc/models/Marketdata/__init__.py +2 -0
  97. architect_py/grpc/models/Oms/Cancel.py +90 -0
  98. architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersRequest.py +2 -2
  99. architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersResponse.py +1 -1
  100. architect_py/{grpc_client → grpc/models}/Oms/CancelOrderRequest.py +2 -2
  101. architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersRequest.py +2 -2
  102. architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersResponse.py +1 -1
  103. architect_py/{grpc_client → grpc/models}/Oms/Order.py +6 -13
  104. architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsRequest.py +2 -2
  105. architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsResponse.py +1 -1
  106. architect_py/{grpc_client → grpc/models}/Oms/PlaceOrderRequest.py +16 -23
  107. architect_py/grpc/models/Oms/__init__.py +2 -0
  108. architect_py/grpc/models/OptionsMarketdata/OptionsChain.py +30 -0
  109. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeks.py +30 -0
  110. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeksRequest.py +47 -0
  111. architect_py/grpc/models/OptionsMarketdata/OptionsChainRequest.py +45 -0
  112. architect_py/grpc/models/OptionsMarketdata/OptionsExpirations.py +29 -0
  113. architect_py/grpc/models/OptionsMarketdata/OptionsExpirationsRequest.py +42 -0
  114. architect_py/grpc/models/OptionsMarketdata/__init__.py +2 -0
  115. architect_py/{grpc_client → grpc/models}/Orderflow/DropcopyRequest.py +2 -2
  116. architect_py/{grpc_client → grpc/models}/Orderflow/OrderflowRequest.py +2 -1
  117. architect_py/{grpc_client → grpc/models}/Orderflow/SubscribeOrderflowRequest.py +2 -2
  118. architect_py/grpc/models/Orderflow/__init__.py +2 -0
  119. architect_py/grpc/models/Symbology/DownloadProductCatalogRequest.py +42 -0
  120. architect_py/grpc/models/Symbology/DownloadProductCatalogResponse.py +27 -0
  121. architect_py/grpc/models/Symbology/ExecutionInfoRequest.py +47 -0
  122. architect_py/grpc/models/Symbology/ExecutionInfoResponse.py +27 -0
  123. architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsRequest.py +2 -2
  124. architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsResponse.py +1 -1
  125. architect_py/{grpc_client → grpc/models}/Symbology/SubscribeSymbology.py +1 -1
  126. architect_py/{grpc_client → grpc/models}/Symbology/SymbologyRequest.py +2 -2
  127. architect_py/{grpc_client → grpc/models}/Symbology/SymbologySnapshot.py +7 -2
  128. architect_py/{grpc_client → grpc/models}/Symbology/SymbologyUpdate.py +9 -2
  129. architect_py/{grpc_client → grpc/models}/Symbology/SymbolsRequest.py +2 -2
  130. architect_py/{grpc_client → grpc/models}/Symbology/SymbolsResponse.py +1 -1
  131. architect_py/grpc/models/Symbology/UploadProductCatalogRequest.py +49 -0
  132. architect_py/grpc/models/Symbology/UploadProductCatalogResponse.py +20 -0
  133. architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyRequest.py +2 -2
  134. architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyResponse.py +1 -1
  135. architect_py/grpc/models/Symbology/__init__.py +2 -0
  136. architect_py/grpc/models/__init__.py +2 -0
  137. architect_py/{grpc_client → grpc/models}/definitions.py +671 -934
  138. architect_py/grpc/resolve_endpoint.py +70 -0
  139. architect_py/{grpc_client/grpc_server.py → grpc/server.py} +13 -9
  140. architect_py/grpc/utils.py +32 -0
  141. architect_py/internal_utils/__init__.py +0 -0
  142. architect_py/internal_utils/no_pandas.py +3 -0
  143. architect_py/tests/conftest.py +91 -85
  144. architect_py/tests/test_book_building.py +49 -50
  145. architect_py/tests/test_marketdata.py +168 -0
  146. architect_py/tests/test_order_entry.py +37 -0
  147. architect_py/tests/test_orderflow.py +41 -0
  148. architect_py/tests/test_portfolio_management.py +23 -0
  149. architect_py/tests/test_rounding.py +28 -28
  150. architect_py/tests/test_symbology.py +37 -30
  151. architect_py/utils/nearest_tick.py +2 -5
  152. architect_py/utils/nearest_tick_2.py +1 -2
  153. architect_py/utils/orderbook.py +35 -0
  154. architect_py/utils/pandas.py +44 -0
  155. architect_py/utils/price_bands.py +0 -3
  156. architect_py/utils/symbol_parsing.py +29 -0
  157. architect_py-5.0.0.dist-info/METADATA +54 -0
  158. architect_py-5.0.0.dist-info/RECORD +214 -0
  159. {architect_py-3.2.1.dist-info → architect_py-5.0.0.dist-info}/WHEEL +2 -1
  160. architect_py-5.0.0.dist-info/top_level.txt +4 -0
  161. examples/__init__.py +0 -0
  162. examples/book_subscription.py +52 -0
  163. examples/candles.py +30 -0
  164. examples/common.py +116 -0
  165. examples/external_cpty.py +77 -0
  166. examples/funding_rate_mean_reversion_algo.py +186 -0
  167. examples/order_sending.py +91 -0
  168. examples/stream_l1_marketdata.py +25 -0
  169. examples/stream_l2_marketdata.py +38 -0
  170. examples/trades.py +21 -0
  171. examples/tutorial_async.py +86 -0
  172. examples/tutorial_sync.py +95 -0
  173. scripts/generate_functions_md.py +166 -0
  174. scripts/generate_sync_interface.py +226 -0
  175. scripts/postprocess_grpc.py +604 -0
  176. scripts/preprocess_grpc_schema.py +708 -0
  177. templates/exceptions.py +83 -0
  178. templates/juniper_base_client.py +371 -0
  179. architect_py/client_protocol.py +0 -52
  180. architect_py/grpc_client/Algo/AlgoOrderForTwapAlgo.py +0 -61
  181. architect_py/grpc_client/Algo/CreateAlgoOrderRequestForTwapAlgo.py +0 -59
  182. architect_py/grpc_client/Algo/ModifyAlgoOrderRequestForTwapAlgo.py +0 -45
  183. architect_py/grpc_client/Folio/AggregatedAccountSummariesRequest.py +0 -59
  184. architect_py/grpc_client/Folio/AggregatedAccountSummariesResponse.py +0 -27
  185. architect_py/grpc_client/Health/__init__.py +0 -2
  186. architect_py/grpc_client/Marketdata/__init__.py +0 -2
  187. architect_py/grpc_client/Oms/Cancel.py +0 -42
  188. architect_py/grpc_client/Oms/__init__.py +0 -2
  189. architect_py/grpc_client/Orderflow/__init__.py +0 -2
  190. architect_py/grpc_client/Symbology/__init__.py +0 -2
  191. architect_py/grpc_client/__init__.py +0 -2
  192. architect_py/grpc_client/grpc_client.py +0 -405
  193. architect_py/scalars.py +0 -172
  194. architect_py/tests/test_accounts.py +0 -31
  195. architect_py/tests/test_client.py +0 -29
  196. architect_py/tests/test_grpc_client.py +0 -30
  197. architect_py/tests/test_order_sending.py +0 -61
  198. architect_py/tests/test_snapshots.py +0 -52
  199. architect_py/tests/test_subscriptions.py +0 -129
  200. architect_py-3.2.1.dist-info/METADATA +0 -212
  201. architect_py-3.2.1.dist-info/RECORD +0 -146
  202. /architect_py/{grpc_client → grpc/models}/Marketdata/ArrayOfL1BookSnapshot.py +0 -0
  203. /architect_py/{grpc_client → grpc/models}/Marketdata/L2BookUpdate.py +0 -0
  204. /architect_py/{grpc_client → grpc/models}/Marketdata/TickerUpdate.py +0 -0
  205. /architect_py/{grpc_client → grpc/models}/Orderflow/Dropcopy.py +0 -0
  206. /architect_py/{grpc_client → grpc/models}/Orderflow/Orderflow.py +0 -0
  207. {architect_py-3.2.1.dist-info → architect_py-5.0.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,166 @@
1
+ import ast
2
+ import re
3
+ import sys
4
+ from collections import defaultdict
5
+
6
+
7
+ def extract_sections(source_lines):
8
+ """
9
+ Look for section header blocks that look like:
10
+
11
+ # ------------------------------------------------------------
12
+ # Some Section Name
13
+ # ------------------------------------------------------------
14
+
15
+ Returns a list of tuples (section_name, lineno) where lineno is the
16
+ line number (1-indexed) of the section title.
17
+ """
18
+ sections = []
19
+ # Regex for a dashed line
20
+ dashed_re = re.compile(r"^\s*#\s*-{10,}\s*$")
21
+ # Regex for a section title line (should start with "#")
22
+ title_re = re.compile(r"^\s*#\s*(.+\S)\s*$")
23
+
24
+ i = 0
25
+ while i < len(source_lines):
26
+ if dashed_re.match(source_lines[i]):
27
+ if i + 1 < len(source_lines) and title_re.match(source_lines[i + 1]):
28
+ groups = title_re.match(source_lines[i + 1])
29
+ if groups is not None:
30
+ section_name = groups.group(1).strip()
31
+ else:
32
+ continue
33
+ # Check that the next line (i+2) is also a dashed line
34
+ if i + 2 < len(source_lines) and dashed_re.match(source_lines[i + 2]):
35
+ sections.append(
36
+ (section_name, i + 2)
37
+ ) # record line number of closing dashed line, or i+1 as desired
38
+ i += 3
39
+ continue
40
+ i += 1
41
+ return sections
42
+
43
+
44
+ def find_section_for_lineno(lineno, sections):
45
+ """
46
+ Given a line number (1-indexed) and a list of sections (name, lineno),
47
+ return the name of the last section that occurs before the given line number.
48
+ If none found, return "No Section".
49
+ """
50
+ candidate = None
51
+ for name, sec_line in sections:
52
+ if sec_line < lineno:
53
+ candidate = name
54
+ else:
55
+ break
56
+ return candidate if candidate is not None else "No Section"
57
+
58
+
59
+ def get_asyncclient_methods(filename):
60
+ with open(filename, "r", encoding="utf-8") as f:
61
+ source = f.read()
62
+ source_lines = source.splitlines()
63
+ # Extract sections from source lines
64
+ sections = extract_sections(source_lines)
65
+
66
+ tree = ast.parse(source, filename)
67
+ methods = [] # list of tuples (section, func_name, doc_summary, lineno)
68
+
69
+ # Walk the AST to find the class AsyncClient
70
+ for node in ast.walk(tree):
71
+ if isinstance(node, ast.ClassDef) and node.name == "AsyncClient":
72
+ for item in node.body:
73
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
74
+ # Exclude typing overloads
75
+ is_fn_overload = False
76
+ for decorator in item.decorator_list:
77
+ decorator_name = ""
78
+ if isinstance(decorator, ast.Call):
79
+ decorator_name = (
80
+ decorator.func.attr
81
+ if isinstance(decorator.func, ast.Attribute)
82
+ else getattr(decorator.func, "id", "")
83
+ )
84
+ else:
85
+ decorator_name = (
86
+ decorator.attr
87
+ if isinstance(decorator, ast.Attribute)
88
+ else decorator.id
89
+ if isinstance(decorator, ast.Name)
90
+ else ""
91
+ )
92
+ if decorator_name == "overload":
93
+ is_fn_overload = True
94
+
95
+ if is_fn_overload:
96
+ continue
97
+
98
+ # Get docstring using ast.get_docstring
99
+ doc = ast.get_docstring(item)
100
+ if doc:
101
+ # Extract the first non-empty line from the docstring
102
+ first_line = next(
103
+ (line.strip() for line in doc.splitlines() if line.strip()),
104
+ "",
105
+ )
106
+ else:
107
+ first_line = ""
108
+ section = find_section_for_lineno(item.lineno, sections)
109
+ methods.append((section, item.name, first_line, item.lineno))
110
+ break # found our class, stop searching
111
+ return methods
112
+
113
+
114
+ def group_methods_by_section(methods):
115
+ grouped = defaultdict(list)
116
+ for section, name, summary, lineno in methods:
117
+ grouped[section].append((name, summary, lineno))
118
+ # Optionally sort sections by the lowest lineno
119
+ return dict(grouped)
120
+
121
+
122
+ def main(filename):
123
+ methods = get_asyncclient_methods(filename)
124
+ grouped = group_methods_by_section(methods)
125
+ # For predictable order, sort sections by the minimum line number among its methods
126
+ sorted_sections = sorted(
127
+ grouped.items(),
128
+ key=lambda item: min(m[2] for m in grouped[item[0]]) if grouped[item[0]] else 0,
129
+ )
130
+ # Alternatively, sort by section name alphabetically:
131
+ # sorted_sections = sorted(grouped.items())
132
+ for section, funcs in sorted_sections:
133
+ emoji = emoji_dict.get(section, "")
134
+ if emoji == "":
135
+ raise ValueError(f'Section "{section}" does not have an emoji.')
136
+ print(f"### {emoji} {section}")
137
+ print()
138
+ # Sort functions by their line number
139
+ for name, summary, lineno in sorted(funcs, key=lambda x: x[2]):
140
+ # Exclude private methods
141
+ if name.startswith("__"):
142
+ continue
143
+ # Only output if summary is non-empty
144
+ if summary:
145
+ if summary.startswith("@deprecated"):
146
+ continue
147
+ print(f"- **`{name}`**: {summary}")
148
+ else:
149
+ print(f"- **`{name}`**")
150
+ print("\n---\n")
151
+
152
+
153
+ emoji_dict: dict[str, str] = {
154
+ "Initialization and configuration": "🚀",
155
+ "Symbology": "🔍",
156
+ "Portfolio management": "💹",
157
+ "Order management": "📝",
158
+ "Order entry": "📣",
159
+ "Marketdata": "🧮",
160
+ }
161
+
162
+ if __name__ == "__main__":
163
+ if len(sys.argv) < 2:
164
+ print("Usage: python extract_methods.py <filename>")
165
+ else:
166
+ main(sys.argv[1])
@@ -0,0 +1,226 @@
1
+ import argparse
2
+ import collections.abc
3
+ import inspect
4
+ import types
5
+ from decimal import Decimal
6
+ from enum import Enum
7
+ from typing import Annotated, Any, Sequence, Union, get_args, get_origin
8
+
9
+ from architect_py.async_client import AsyncClient
10
+ from architect_py.graphql_client.base_model import UnsetType
11
+
12
+
13
+ def format_type_hint_with_generics(type_hint) -> str:
14
+ """
15
+ Format a type hint to a string representation with support for generic types like list[str].
16
+ """
17
+
18
+ if type_hint == inspect.Parameter.empty:
19
+ return "Any"
20
+
21
+ origin = get_origin(type_hint)
22
+
23
+ if origin is Annotated:
24
+ annotated_args = get_args(type_hint)
25
+ if annotated_args:
26
+ return format_type_hint_with_generics(annotated_args[0])
27
+ return "Any"
28
+
29
+ # Handle `|` unions (Python 3.10+)
30
+ if isinstance(
31
+ type_hint, types.UnionType
32
+ ): # `types.UnionType` represents `|` unions
33
+ args = get_args(type_hint)
34
+ if type(None) in args:
35
+ if len(args) == 2:
36
+ # Handle Optional[X]
37
+ non_none_type = args[0] if args[1] is type(None) else args[1]
38
+ return f"Optional[{format_type_hint_with_generics(non_none_type)}]"
39
+ return (
40
+ f"Union[{', '.join(format_type_hint_with_generics(arg) for arg in args)}]"
41
+ )
42
+
43
+ if origin is Union:
44
+ args = get_args(type_hint)
45
+ if type(None) in args:
46
+ if len(args) == 2:
47
+ # Handle Optional[X]
48
+ non_none_type = args[0] if args[1] is type(None) else args[1]
49
+ return f"Optional[{format_type_hint_with_generics(non_none_type)}]"
50
+ return (
51
+ f"Union[{', '.join(format_type_hint_with_generics(arg) for arg in args)}]"
52
+ ).replace("NoneType", "None")
53
+
54
+ elif origin in (Sequence, collections.abc.Sequence):
55
+ args = get_args(type_hint)
56
+ if args:
57
+ return f"Sequence[{format_type_hint_with_generics(args[0])}]"
58
+ return "Sequence[Any]"
59
+
60
+ elif origin is list:
61
+ args = get_args(type_hint)
62
+ if args:
63
+ return f"list[{format_type_hint_with_generics(args[0])}]"
64
+ return "list[Any]"
65
+
66
+ elif origin is dict:
67
+ args = get_args(type_hint)
68
+ if len(args) == 2:
69
+ return f"dict[{format_type_hint_with_generics(args[0])}, {format_type_hint_with_generics(args[1])}]"
70
+ return "dict[Any, Any]"
71
+
72
+ elif origin is tuple:
73
+ args = get_args(type_hint)
74
+ if args:
75
+ return f"tuple[{', '.join(format_type_hint_with_generics(arg) for arg in args)}]"
76
+ return "tuple[Any, ...]"
77
+
78
+ try:
79
+ if type_hint.__module__ == "pandas" or type_hint.__module__.startswith(
80
+ "pandas."
81
+ ):
82
+ return f"pd.{type_hint.__name__}"
83
+ else:
84
+ return type_hint.__name__
85
+ except AttributeError:
86
+ return str(type_hint)
87
+
88
+
89
+ def autogenerate_protocol(cls, protocol_name: str) -> str:
90
+ """
91
+ Autogenerate a Protocol for the given class for use in static typing.
92
+ Args:
93
+ cls: The class to generate a Protocol for.
94
+ protocol_name: The name of the Protocol to generate.
95
+ Returns:
96
+ A string representing the Protocol definition.
97
+ """
98
+ methods: dict[str, inspect.Signature] = {}
99
+ method_decorators: dict[str, list[str]] = {}
100
+ attributes: dict[str, Any] = {}
101
+
102
+ # Inspect class members
103
+ for name, member in inspect.getmembers(cls):
104
+ if name.startswith("_"):
105
+ continue
106
+ if callable(member):
107
+ # Collect methods
108
+ signature = inspect.signature(member)
109
+ methods[name] = signature
110
+
111
+ raw_member = inspect.getattr_static(cls, name)
112
+ decorators = []
113
+ if isinstance(raw_member, staticmethod):
114
+ decorators.append("@staticmethod")
115
+ elif isinstance(raw_member, classmethod):
116
+ decorators.append("@classmethod")
117
+ method_decorators[name] = decorators
118
+ elif not inspect.isroutine(member):
119
+ # Collect attributes
120
+ attributes[name] = getattr(cls, name, Any)
121
+
122
+ def format_default(value) -> str:
123
+ """
124
+ Format the default value for parameters. Handles special types.
125
+ """
126
+ if isinstance(value, Enum):
127
+ return f"{value.__class__.__name__}.{value.name}"
128
+ elif isinstance(value, (int, float, str, bool, type(None))):
129
+ return repr(value)
130
+ elif isinstance(value, Decimal):
131
+ return f"Decimal('{value}')"
132
+ elif isinstance(value, UnsetType):
133
+ return "UNSET"
134
+ return type(value).__name__
135
+
136
+ protocol_lines = [
137
+ "# fmt: off\n",
138
+ "# mypy: ignore-errors\n",
139
+ "# Autogenerated from generate_sync_interface.py\n",
140
+ "# If you are here for function definitions, please refer to architect_py/async_client.py",
141
+ "# This file is so that the sync client has good type hinting",
142
+ "# It is not used for anything else",
143
+ "# For maintainers: ensure that the types in this file are correct for correct type hinting",
144
+ "\n",
145
+ # "from architect_py.grpc_client.definitions import *",
146
+ "from typing import Any, Union",
147
+ "from .graphql_client import *",
148
+ "from .async_client import *",
149
+ "from .grpc.models.definitions import *",
150
+ f"class {protocol_name}:",
151
+ ]
152
+
153
+ # # Add attributes
154
+ # for attr_name, attr_type in attributes.items():
155
+ # protocol_lines.append(
156
+ # f" {attr_name}: {format_type_hint_with_generics(attr_type)}"
157
+ # )
158
+
159
+ # Add methods
160
+ for name, signature in methods.items():
161
+ if any(
162
+ keyword in name
163
+ for keyword in ("subscribe", "stream", "unsubscribe", "connect")
164
+ ):
165
+ continue
166
+
167
+ if "async" in str(signature).lower():
168
+ continue
169
+
170
+ # for decorators like @staticmethod and @classmethod
171
+ if name in method_decorators:
172
+ for deco in method_decorators[name]:
173
+ protocol_lines.append(f" {deco}")
174
+
175
+ params = []
176
+ keyword_only = False
177
+ for param_name, param in signature.parameters.items():
178
+ if param_name == "self":
179
+ params.append("self")
180
+ continue
181
+
182
+ param_type = format_type_hint_with_generics(param.annotation)
183
+ if param.kind == inspect.Parameter.POSITIONAL_ONLY:
184
+ params.append(f"{param_name}: {param_type}")
185
+ elif param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
186
+ if param.default != inspect.Parameter.empty:
187
+ params.append(
188
+ f"{param_name}: {param_type} = {format_default(param.default)}"
189
+ )
190
+ else:
191
+ params.append(f"{param_name}: {param_type}")
192
+ elif param.kind == inspect.Parameter.VAR_POSITIONAL:
193
+ params.append(f"*{param_name}: {param_type}")
194
+ elif param.kind == inspect.Parameter.KEYWORD_ONLY:
195
+ if not keyword_only:
196
+ params.append("*") # Indicate start of keyword-only arguments
197
+ keyword_only = True
198
+ if param.default != inspect.Parameter.empty:
199
+ params.append(
200
+ f"{param_name}: {param_type} = {format_default(param.default)}"
201
+ )
202
+ else:
203
+ params.append(f"{param_name}: {param_type}")
204
+ elif param.kind == inspect.Parameter.VAR_KEYWORD:
205
+ params.append(f"**{param_name}: {param_type}")
206
+
207
+ params_str = ", ".join(params)
208
+ return_type = format_type_hint_with_generics(signature.return_annotation)
209
+ protocol_lines.append(f" def {name}({params_str}) -> {return_type}: ...")
210
+
211
+ return "\n".join(protocol_lines)
212
+
213
+
214
+ if __name__ == "__main__":
215
+ parser = argparse.ArgumentParser(description="Process gRPC service definitions")
216
+ parser.add_argument(
217
+ "--file_path",
218
+ type=str,
219
+ default="architect_py/grpc_client",
220
+ help="Path to the Python folder with the gRPC service definitions",
221
+ )
222
+ args = parser.parse_args()
223
+ protocol: str = autogenerate_protocol(AsyncClient, "ClientProtocol")
224
+
225
+ with open(args.file_path, "w") as f:
226
+ f.write(protocol)