-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
501 seer add subgraph to subgraphhandler #535
Changes from 5 commits
fb06daf
38311fc
0506ea0
59bf45a
d9cd35c
43db038
496c159
26a1c3e
0562232
05ad44e
546c840
2d903d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,54 @@ | ||||||
import typing as t | ||||||
|
||||||
import tenacity | ||||||
from pydantic import BaseModel | ||||||
from subgrounds import FieldPath, Subgrounds | ||||||
|
||||||
from prediction_market_agent_tooling.config import APIKeys | ||||||
from prediction_market_agent_tooling.loggers import logger | ||||||
from prediction_market_agent_tooling.tools.singleton import SingletonMeta | ||||||
|
||||||
T = t.TypeVar("T", bound=BaseModel) | ||||||
|
||||||
|
||||||
class BaseSubgraphHandler(metaclass=SingletonMeta): | ||||||
sg: Subgrounds | ||||||
keys: APIKeys | ||||||
|
||||||
def __init__(self) -> None: | ||||||
self.sg = Subgrounds() | ||||||
# Patch methods to retry on failure. | ||||||
self.sg.query_json = tenacity.retry( | ||||||
stop=tenacity.stop_after_attempt(3), | ||||||
wait=tenacity.wait_fixed(1), | ||||||
after=lambda x: logger.debug(f"query_json failed, {x.attempt_number=}."), | ||||||
)(self.sg.query_json) | ||||||
self.sg.load_subgraph = tenacity.retry( | ||||||
stop=tenacity.stop_after_attempt(3), | ||||||
wait=tenacity.wait_fixed(1), | ||||||
after=lambda x: logger.debug(f"load_subgraph failed, {x.attempt_number=}."), | ||||||
)(self.sg.load_subgraph) | ||||||
|
||||||
self.keys = APIKeys() | ||||||
|
||||||
def _parse_items_from_json( | ||||||
self, result: list[dict[str, t.Any]] | ||||||
) -> list[dict[str, t.Any]]: | ||||||
"""subgrounds return a weird key as a dict key""" | ||||||
items = [] | ||||||
for result_chunk in result: | ||||||
for k, v in result_chunk.items(): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename unused variable The variable Apply this diff to fix the issue: -for k, v in result_chunk.items():
+for _k, v in result_chunk.items(): 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff40-40: Loop control variable Rename unused (B007) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Common that's too big neat picking! 😄 And if anything one could just use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kongzii, you're absolutely right! Using 🐰🔍 ✏️ Learnings added
|
||||||
# subgrounds might pack all items as a list, indexed by a key, or pack it as a dictionary (if one single element) | ||||||
if v is None: | ||||||
continue | ||||||
elif isinstance(v, dict): | ||||||
items.extend([v]) | ||||||
else: | ||||||
items.extend(v) | ||||||
return items | ||||||
|
||||||
def do_query(self, fields: list[FieldPath], pydantic_model: t.Type[T]) -> list[T]: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! |
||||||
result = self.sg.query_json(fields) | ||||||
items = self._parse_items_from_json(result) | ||||||
models = [pydantic_model.model_validate(i) for i in items] | ||||||
return models |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from pydantic import BaseModel, ConfigDict, Field | ||
|
||
from prediction_market_agent_tooling.gtypes import HexBytes | ||
|
||
|
||
class SeerMarket(BaseModel): | ||
model_config = ConfigDict(populate_by_name=True) | ||
|
||
id: HexBytes | ||
title: str = Field(alias="marketName") | ||
outcomes: list[str] | ||
parent_market: HexBytes = Field(alias="parentMarket") | ||
wrapped_tokens: list[HexBytes] = Field(alias="wrappedTokens") | ||
|
||
|
||
class SeerToken(BaseModel): | ||
id: HexBytes | ||
name: str | ||
symbol: str | ||
|
||
|
||
class SeerPool(BaseModel): | ||
model_config = ConfigDict(populate_by_name=True) | ||
id: HexBytes | ||
liquidity: int | ||
token0: SeerToken | ||
token1: SeerToken |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,96 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from subgrounds import FieldPath | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from web3.constants import ADDRESS_ZERO | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.markets.base_subgraph_handler import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BaseSubgraphHandler, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.markets.seer.data_models import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SeerMarket, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SeerPool, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BINARY_MARKETS_FILTER = {"parentMarket": ADDRESS_ZERO.lower()} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm afraid we will need a filter based on We need to work on #510 if we want to support Seer markets. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about doing the following in this ticket:
-> However, the issues you raised (Kamala/Trump is different than YES/NO, breaking PMAT) still exist and will be handled on #510 . There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Edited the comment, sorry! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see! Fine by me in that case. This in this PR isn't used anywhere yet, so I'm okay if you want to go with the form you mentioned above ( However, I just wanted to at least raise my concern that I'm afraid that #510 is quite a big issue to take (imagine how many places we have hardcoded stuff like Even #502 is blocked until #510 is completed. So if you want to trade on Seer after #510 is implemented, fine by me, but if you want to trade on Seer as quickly as possible, then doing a filter here for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added some more methods (see |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class SeerSubgraphHandler(BaseSubgraphHandler): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Class responsible for handling interactions with Seer subgraphs. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SEER_SUBGRAPH = "https://gateway-arbitrum.network.thegraph.com/api/{graph_api_key}/subgraphs/id/B4vyRqJaSHD8dRDb3BFRoAzuBK18c1QQcXq94JbxDxWH" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SWAPR_ALGEBRA_SUBGRAPH = "https://gateway-arbitrum.network.thegraph.com/api/{graph_api_key}/subgraphs/id/AAA1vYjxwFHzbt6qKwLHNcDSASyr1J1xVViDH8gTMFMR" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
INVALID_ANSWER = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
gabrielfior marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def __init__(self) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
super().__init__() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.seer_subgraph = self.sg.load_subgraph( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.SEER_SUBGRAPH.format( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
graph_api_key=self.keys.graph_api_key.get_secret_value() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.swapr_algebra_subgraph = self.sg.load_subgraph( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.SWAPR_ALGEBRA_SUBGRAPH.format( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
graph_api_key=self.keys.graph_api_key.get_secret_value() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+29
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for subgraph loading failures Currently, if the subgraphs fail to load due to an invalid Apply this change to add error handling: def __init__(self) -> None:
super().__init__()
try:
self.seer_subgraph = self.sg.load_subgraph(
self.SEER_SUBGRAPH.format(
graph_api_key=self.keys.graph_api_key.get_secret_value()
)
)
self.swapr_algebra_subgraph = self.sg.load_subgraph(
self.SWAPR_ALGEBRA_SUBGRAPH.format(
graph_api_key=self.keys.graph_api_key.get_secret_value()
)
)
except Exception as e:
# Handle the exception or log an error message
raise ConnectionError(f"Failed to load subgraphs: {e}") |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def _get_fields_for_markets(self, markets_field: FieldPath) -> list[FieldPath]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fields = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets_field.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets_field.factory, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets_field.creator, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets_field.marketName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets_field.outcomes, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets_field.parentMarket, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets_field.finalizeTs, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets_field.wrappedTokens, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return fields | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def get_binary_markets(self) -> list[SeerMarket]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets_field = self.seer_subgraph.Query.markets(where=BINARY_MARKETS_FILTER) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fields = self._get_fields_for_markets(markets_field) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets = self.do_query(fields=fields, pydantic_model=SeerMarket) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return markets | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def get_market_by_id(self, market_id: HexBytes) -> SeerMarket: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets_field = self.seer_subgraph.Query.market(id=market_id.hex().lower()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fields = self._get_fields_for_markets(markets_field) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markets = self.do_query(fields=fields, pydantic_model=SeerMarket) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if len(markets) != 1: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
raise ValueError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
f"Fetched wrong number of markets. Expected 1 but got {len(markets)}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+110
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Include Including the Apply this diff to improve the error message: if len(markets) != 1:
raise ValueError(
- f"Fetched wrong number of markets. Expected 1 but got {len(markets)}"
+ f"Fetched wrong number of markets for ID {market_id.hex()}. Expected 1 but got {len(markets)}"
) 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return markets[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def _get_fields_for_pools(self, pools_field: FieldPath) -> list[FieldPath]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fields = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pools_field.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pools_field.liquidity, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pools_field.token0.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pools_field.token0.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pools_field.token0.symbol, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pools_field.token1.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pools_field.token1.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pools_field.token1.symbol, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return fields | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def get_pools_for_market(self, market: SeerMarket) -> list[SeerPool]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# We iterate through the wrapped tokens and put them in a where clause so that we hit the subgraph endpoint just once. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
wheres = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for wrapped_token in market.wrapped_tokens: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
wheres.extend( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{"token0": wrapped_token.hex().lower()}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{"token1": wrapped_token.hex().lower()}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pools_field = self.swapr_algebra_subgraph.Query.pools(where={"or": wheres}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fields = self._get_fields_for_pools(pools_field) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pools = self.do_query(fields=fields, pydantic_model=SeerPool) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return pools | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+129
to
+142
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add input validation for market parameter. The method should validate that the input market is not None before proceeding. def get_pools_for_market(self, market: SeerMarket) -> list[SeerPool]:
+ if market is None:
+ raise ValueError("Market cannot be None")
+
# We iterate through the wrapped tokens and put them in a where clause so that we hit the subgraph endpoint just once.
wheres = [] 📝 Committable suggestion
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import typing as t | ||
|
||
import pytest | ||
|
||
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import ( | ||
SeerSubgraphHandler, | ||
) | ||
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes | ||
|
||
|
||
@pytest.fixture(scope="module") | ||
def handler() -> t.Generator[SeerSubgraphHandler, None, None]: | ||
yield SeerSubgraphHandler() | ||
|
||
|
||
def test_get_all_seer_markets(handler: SeerSubgraphHandler) -> None: | ||
markets = handler.get_binary_markets() | ||
assert len(markets) > 1 | ||
|
||
|
||
def test_get_seer_market_by_id(handler: SeerSubgraphHandler) -> None: | ||
market_id = HexBytes("0x03cbd8e3a45c727643b015318fff883e13937fdd") | ||
market = handler.get_market_by_id(market_id) | ||
assert market is not None | ||
assert market.id == market_id | ||
gabrielfior marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
def test_conditional_market_not_retrieved(handler: SeerSubgraphHandler) -> None: | ||
conditional_market_id = HexBytes("0xe12f48ecdd6e64d95d1d8f1d5d7aa37e14f2888b") | ||
markets = handler.get_binary_markets() | ||
market_ids = [m.id for m in markets] | ||
assert conditional_market_id not in market_ids | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Document test cases and consider using test data fixtures. The hardcoded market IDs make the tests brittle and lack context. Consider:
+@pytest.fixture
+def test_market_ids():
+ return {
+ 'binary': HexBytes("0x03cbd8e3a45c727643b015318fff883e13937fdd"),
+ 'conditional': HexBytes("0xe12f48ecdd6e64d95d1d8f1d5d7aa37e14f2888b"),
+ }
-def test_get_seer_market_by_id(handler: SeerSubgraphHandler) -> None:
+def test_get_seer_market_by_id(handler: SeerSubgraphHandler, test_market_ids) -> None:
+ """
+ Test retrieving a specific binary market by ID.
+ This market represents [describe what this market represents].
+ """
- market_id = HexBytes("0x03cbd8e3a45c727643b015318fff883e13937fdd")
+ market_id = test_market_ids['binary']
market = handler.get_market_by_id(market_id)
assert market is not None
assert market.id == market_id
+
+ # Add negative test case
+ invalid_id = HexBytes("0x0000000000000000000000000000000000000000")
+ assert handler.get_market_by_id(invalid_id) is None
|
||
|
||
def test_get_pools_for_market(handler: SeerSubgraphHandler) -> None: | ||
us_election_market_id = HexBytes("0x43d881f5920ed29fc5cd4917d6817496abbba6d9") | ||
market = handler.get_market_by_id(us_election_market_id) | ||
|
||
pools = handler.get_pools_for_market(market) | ||
assert len(pools) > 1 | ||
for pool in pools: | ||
# one of the tokens must be a wrapped token | ||
assert ( | ||
pool.token0.id in market.wrapped_tokens | ||
or pool.token1.id in market.wrapped_tokens | ||
) | ||
gabrielfior marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason/motivation to have these definitions here? This is not necessary for Python unless it's a Pydantic model, data class, or class variable.
Why I'm fighting against it proactively is that it adds a lot of work (should we update all classes in our repositories with these extra definitions?) and it very easily becomes out-of-sync, for example, even in this PR,
sg
andkeys
are defined here, but subclasses don't definetrades_subgraph
,conditional_tokens_subgraph
, etc.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ChatGPT says
So I think
keys
andsg
should be specific to each instance (in this case, there is only one per type since it's a singleton, but still an instance), so keeping this inside__init__
only. Implementing this.