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.
- architect_py/__init__.py +18 -2
- architect_py/async_client.py +1089 -658
- architect_py/client.py +36 -33
- architect_py/client_interface.py +63 -0
- architect_py/common_types/__init__.py +6 -0
- architect_py/common_types/order_dir.py +91 -0
- architect_py/common_types/scalars.py +25 -0
- architect_py/common_types/tradable_product.py +59 -0
- architect_py/graphql_client/__init__.py +2 -0
- architect_py/graphql_client/client.py +3 -6
- architect_py/graphql_client/enums.py +5 -0
- architect_py/graphql_client/fragments.py +3 -6
- architect_py/graphql_client/get_fills_query.py +2 -1
- architect_py/graphql_client/search_symbols_query.py +2 -1
- architect_py/graphql_client/subscribe_orderflow.py +2 -1
- architect_py/graphql_client/subscribe_trades.py +2 -1
- architect_py/grpc/__init__.py +145 -0
- architect_py/grpc/client.py +94 -0
- architect_py/{grpc_client → grpc/models}/Accounts/AccountsRequest.py +6 -3
- architect_py/{grpc_client → grpc/models}/Accounts/AccountsResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Accounts/__init__.py +1 -1
- architect_py/grpc/models/Algo/AlgoOrder.py +114 -0
- architect_py/grpc/models/Algo/AlgoOrderRequest.py +46 -0
- architect_py/grpc/models/Algo/AlgoOrdersRequest.py +72 -0
- architect_py/grpc/models/Algo/AlgoOrdersResponse.py +27 -0
- architect_py/grpc/models/Algo/CreateAlgoOrderRequest.py +56 -0
- architect_py/grpc/models/Algo/PauseAlgoRequest.py +42 -0
- architect_py/grpc/models/Algo/PauseAlgoResponse.py +20 -0
- architect_py/grpc/models/Algo/StartAlgoRequest.py +42 -0
- architect_py/grpc/models/Algo/StartAlgoResponse.py +20 -0
- architect_py/grpc/models/Algo/StopAlgoRequest.py +42 -0
- architect_py/grpc/models/Algo/StopAlgoResponse.py +20 -0
- architect_py/{grpc_client → grpc/models}/Algo/__init__.py +1 -1
- architect_py/grpc/models/Auth/CreateJwtRequest.py +47 -0
- architect_py/grpc/models/Auth/CreateJwtResponse.py +23 -0
- architect_py/{grpc_client/Cpty → grpc/models/Auth}/__init__.py +1 -1
- architect_py/grpc/models/Boss/DepositsRequest.py +40 -0
- architect_py/grpc/models/Boss/DepositsResponse.py +27 -0
- architect_py/grpc/models/Boss/RqdAccountStatisticsRequest.py +42 -0
- architect_py/grpc/models/Boss/RqdAccountStatisticsResponse.py +25 -0
- architect_py/grpc/models/Boss/StatementUrlRequest.py +40 -0
- architect_py/grpc/models/Boss/StatementUrlResponse.py +23 -0
- architect_py/grpc/models/Boss/StatementsRequest.py +40 -0
- architect_py/grpc/models/Boss/StatementsResponse.py +27 -0
- architect_py/grpc/models/Boss/WithdrawalsRequest.py +40 -0
- architect_py/grpc/models/Boss/WithdrawalsResponse.py +27 -0
- architect_py/{grpc_client/Folio → grpc/models/Boss}/__init__.py +1 -1
- architect_py/grpc/models/Core/ConfigRequest.py +37 -0
- architect_py/grpc/models/Core/ConfigResponse.py +25 -0
- architect_py/grpc/models/Core/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/Cpty/CptyRequest.py +5 -4
- architect_py/{grpc_client → grpc/models}/Cpty/CptyResponse.py +6 -6
- architect_py/grpc/models/Cpty/CptyStatus.py +48 -0
- architect_py/grpc/models/Cpty/CptyStatusRequest.py +45 -0
- architect_py/grpc/models/Cpty/CptysRequest.py +37 -0
- architect_py/grpc/models/Cpty/CptysResponse.py +27 -0
- architect_py/grpc/models/Cpty/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Folio/AccountSummary.py +1 -1
- architect_py/{grpc_client → grpc/models}/Folio/AccountSummaryRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsRequest.py +7 -4
- architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersRequest.py +3 -3
- architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersResponse.py +1 -1
- architect_py/grpc/models/Folio/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/Health/HealthCheckRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Health/HealthCheckResponse.py +1 -1
- architect_py/grpc/models/Health/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/Marketdata/Candle.py +1 -1
- architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesRequest.py +11 -8
- architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshot.py +52 -5
- architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotRequest.py +8 -3
- architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotsRequest.py +6 -3
- architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshot.py +1 -1
- architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshotRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/Liquidation.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatus.py +1 -1
- architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatusRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCandlesRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCurrentCandlesRequest.py +3 -4
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL1BookSnapshotsRequest.py +6 -3
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL2BookUpdatesRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeLiquidationsRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeManyCandlesRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTickersRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTradesRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/Ticker.py +14 -3
- architect_py/{grpc_client → grpc/models}/Marketdata/TickerRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/TickersRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Marketdata/TickersResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Marketdata/Trade.py +2 -2
- architect_py/grpc/models/Marketdata/__init__.py +2 -0
- architect_py/grpc/models/Oms/Cancel.py +90 -0
- architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Oms/CancelOrderRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Oms/Order.py +6 -13
- architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Oms/PlaceOrderRequest.py +16 -23
- architect_py/grpc/models/Oms/__init__.py +2 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsChain.py +30 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeks.py +30 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeksRequest.py +47 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsChainRequest.py +45 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsExpirations.py +29 -0
- architect_py/grpc/models/OptionsMarketdata/OptionsExpirationsRequest.py +42 -0
- architect_py/grpc/models/OptionsMarketdata/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/Orderflow/DropcopyRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Orderflow/OrderflowRequest.py +2 -1
- architect_py/{grpc_client → grpc/models}/Orderflow/SubscribeOrderflowRequest.py +2 -2
- architect_py/grpc/models/Orderflow/__init__.py +2 -0
- architect_py/grpc/models/Symbology/DownloadProductCatalogRequest.py +42 -0
- architect_py/grpc/models/Symbology/DownloadProductCatalogResponse.py +27 -0
- architect_py/grpc/models/Symbology/ExecutionInfoRequest.py +47 -0
- architect_py/grpc/models/Symbology/ExecutionInfoResponse.py +27 -0
- architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsResponse.py +1 -1
- architect_py/{grpc_client → grpc/models}/Symbology/SubscribeSymbology.py +1 -1
- architect_py/{grpc_client → grpc/models}/Symbology/SymbologyRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Symbology/SymbologySnapshot.py +7 -2
- architect_py/{grpc_client → grpc/models}/Symbology/SymbologyUpdate.py +9 -2
- architect_py/{grpc_client → grpc/models}/Symbology/SymbolsRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Symbology/SymbolsResponse.py +1 -1
- architect_py/grpc/models/Symbology/UploadProductCatalogRequest.py +49 -0
- architect_py/grpc/models/Symbology/UploadProductCatalogResponse.py +20 -0
- architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyRequest.py +2 -2
- architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyResponse.py +1 -1
- architect_py/grpc/models/Symbology/__init__.py +2 -0
- architect_py/grpc/models/__init__.py +2 -0
- architect_py/{grpc_client → grpc/models}/definitions.py +671 -934
- architect_py/grpc/resolve_endpoint.py +70 -0
- architect_py/{grpc_client/grpc_server.py → grpc/server.py} +13 -9
- architect_py/grpc/utils.py +32 -0
- architect_py/internal_utils/__init__.py +0 -0
- architect_py/internal_utils/no_pandas.py +3 -0
- architect_py/tests/conftest.py +91 -85
- architect_py/tests/test_book_building.py +49 -50
- architect_py/tests/test_marketdata.py +168 -0
- architect_py/tests/test_order_entry.py +37 -0
- architect_py/tests/test_orderflow.py +41 -0
- architect_py/tests/test_portfolio_management.py +23 -0
- architect_py/tests/test_rounding.py +28 -28
- architect_py/tests/test_symbology.py +37 -30
- architect_py/utils/nearest_tick.py +2 -5
- architect_py/utils/nearest_tick_2.py +1 -2
- architect_py/utils/orderbook.py +35 -0
- architect_py/utils/pandas.py +44 -0
- architect_py/utils/price_bands.py +0 -3
- architect_py/utils/symbol_parsing.py +29 -0
- architect_py-5.0.0.dist-info/METADATA +54 -0
- architect_py-5.0.0.dist-info/RECORD +214 -0
- {architect_py-3.2.1.dist-info → architect_py-5.0.0.dist-info}/WHEEL +2 -1
- architect_py-5.0.0.dist-info/top_level.txt +4 -0
- examples/__init__.py +0 -0
- examples/book_subscription.py +52 -0
- examples/candles.py +30 -0
- examples/common.py +116 -0
- examples/external_cpty.py +77 -0
- examples/funding_rate_mean_reversion_algo.py +186 -0
- examples/order_sending.py +91 -0
- examples/stream_l1_marketdata.py +25 -0
- examples/stream_l2_marketdata.py +38 -0
- examples/trades.py +21 -0
- examples/tutorial_async.py +86 -0
- examples/tutorial_sync.py +95 -0
- scripts/generate_functions_md.py +166 -0
- scripts/generate_sync_interface.py +226 -0
- scripts/postprocess_grpc.py +604 -0
- scripts/preprocess_grpc_schema.py +708 -0
- templates/exceptions.py +83 -0
- templates/juniper_base_client.py +371 -0
- architect_py/client_protocol.py +0 -52
- architect_py/grpc_client/Algo/AlgoOrderForTwapAlgo.py +0 -61
- architect_py/grpc_client/Algo/CreateAlgoOrderRequestForTwapAlgo.py +0 -59
- architect_py/grpc_client/Algo/ModifyAlgoOrderRequestForTwapAlgo.py +0 -45
- architect_py/grpc_client/Folio/AggregatedAccountSummariesRequest.py +0 -59
- architect_py/grpc_client/Folio/AggregatedAccountSummariesResponse.py +0 -27
- architect_py/grpc_client/Health/__init__.py +0 -2
- architect_py/grpc_client/Marketdata/__init__.py +0 -2
- architect_py/grpc_client/Oms/Cancel.py +0 -42
- architect_py/grpc_client/Oms/__init__.py +0 -2
- architect_py/grpc_client/Orderflow/__init__.py +0 -2
- architect_py/grpc_client/Symbology/__init__.py +0 -2
- architect_py/grpc_client/__init__.py +0 -2
- architect_py/grpc_client/grpc_client.py +0 -405
- architect_py/scalars.py +0 -172
- architect_py/tests/test_accounts.py +0 -31
- architect_py/tests/test_client.py +0 -29
- architect_py/tests/test_grpc_client.py +0 -30
- architect_py/tests/test_order_sending.py +0 -61
- architect_py/tests/test_snapshots.py +0 -52
- architect_py/tests/test_subscriptions.py +0 -129
- architect_py-3.2.1.dist-info/METADATA +0 -212
- architect_py-3.2.1.dist-info/RECORD +0 -146
- /architect_py/{grpc_client → grpc/models}/Marketdata/ArrayOfL1BookSnapshot.py +0 -0
- /architect_py/{grpc_client → grpc/models}/Marketdata/L2BookUpdate.py +0 -0
- /architect_py/{grpc_client → grpc/models}/Marketdata/TickerUpdate.py +0 -0
- /architect_py/{grpc_client → grpc/models}/Orderflow/Dropcopy.py +0 -0
- /architect_py/{grpc_client → grpc/models}/Orderflow/Orderflow.py +0 -0
- {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)
|