Skip to content
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

Merged
merged 12 commits into from
Oct 31, 2024

Conversation

gabrielfior
Copy link
Contributor

-> Created SeerSubgraphHandler with methods for fetching markets and fetching associated pools (will be needed for later placement of bets)
-> Wrote tests

@gabrielfior gabrielfior linked an issue Oct 30, 2024 that may be closed by this pull request
Copy link

coderabbitai bot commented Oct 30, 2024

Walkthrough

The changes introduce several new classes and modify existing ones to enhance the handling of subgraph queries in the prediction market tooling. A new BaseSubgraphHandler class implements a Singleton pattern and includes retry logic for querying subgraphs. The OmenSubgraphHandler class is refactored to inherit from this new base class, simplifying its structure and removing retry logic. Additionally, three new Pydantic data model classes are created for handling market data, and a new SeerSubgraphHandler class is introduced for managing Seer subgraphs. A corresponding test suite is added to validate the functionality of the SeerSubgraphHandler.

Changes

File Change Summary
prediction_market_agent_tooling/markets/base_subgraph_handler.py Added BaseSubgraphHandler class with retry logic for query_json and load_subgraph methods, and methods for parsing items and executing queries.
prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py Updated OmenSubgraphHandler to inherit from BaseSubgraphHandler, removed singleton pattern, and modified error handling and query execution methods.
prediction_market_agent_tooling/markets/seer/data_models.py Introduced SeerMarket, SeerToken, and SeerPool classes using Pydantic for data validation and serialization.
prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py Added SeerSubgraphHandler class for managing Seer subgraphs, including methods for querying market and pool data.
tests_integration/markets/seer/test_seer_subgraph_handler.py Introduced a test suite for SeerSubgraphHandler, covering functionalities like retrieving markets and pools, with assertions for expected behaviors.

Possibly related PRs

Suggested reviewers

  • evangriffiths

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 546c840 and 2d903d4.

📒 Files selected for processing (1)
  • prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@gabrielfior gabrielfior marked this pull request as ready for review October 30, 2024 20:23
@gabrielfior gabrielfior requested a review from kongzii October 30, 2024 20:23
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (9)
prediction_market_agent_tooling/markets/seer/data_models.py (4)

6-13: Add documentation and field validations.

Consider enhancing the model with:

  1. A docstring explaining the model's purpose and field meanings
  2. Field validations for the lists to ensure they're non-empty if required
  3. Clear null/optional field indicators

Example improvements:

 class SeerMarket(BaseModel):
+    """Represents a Seer prediction market.
+    
+    Attributes:
+        id: Unique identifier of the market
+        title: Human-readable market name
+        outcomes: List of possible market outcomes
+        parent_market: ID of the parent market if exists
+        wrapped_tokens: List of wrapped token addresses used in this market
+    """
     model_config = ConfigDict(populate_by_name=True)
 
     id: HexBytes
     title: str = Field(alias="marketName")
-    outcomes: list[str]
+    outcomes: list[str] = Field(..., min_items=1)
     parent_market: HexBytes = Field(alias="parentMarket")
-    wrapped_tokens: list[HexBytes] = Field(alias="wrappedTokens")
+    wrapped_tokens: list[HexBytes] = Field(alias="wrappedTokens", min_items=1)

16-19: Enhance token model with documentation and validations.

The token model should:

  1. Include documentation
  2. Add string validations
  3. Consider adding model_config for consistency with other classes

Suggested improvements:

 class SeerToken(BaseModel):
+    """Represents a token in the Seer protocol.
+    
+    Attributes:
+        id: Token contract address
+        name: Token name
+        symbol: Token symbol
+    """
+    model_config = ConfigDict(populate_by_name=True)
+
     id: HexBytes
-    name: str
-    symbol: str
+    name: str = Field(..., min_length=1)
+    symbol: str = Field(..., min_length=1)

22-27: Add pool documentation and business rule validations.

The pool model should:

  1. Include documentation
  2. Validate liquidity value
  3. Ensure token0 and token1 are different

Suggested improvements:

 class SeerPool(BaseModel):
+    """Represents a liquidity pool in the Seer protocol.
+    
+    Attributes:
+        id: Pool contract address
+        liquidity: Total liquidity in the pool
+        token0: First token in the pair
+        token1: Second token in the pair
+    """
     model_config = ConfigDict(populate_by_name=True)
+
     id: HexBytes
-    liquidity: int
+    liquidity: int = Field(..., ge=0)
     token0: SeerToken
     token1: SeerToken
+
+    def model_post_init(self, *args, **kwargs) -> None:
+        """Validate that token0 and token1 are different."""
+        if self.token0.id == self.token1.id:
+            raise ValueError("token0 and token1 must be different")

1-27: Consider architectural improvements for robustness.

To enhance the robustness and usability of these models:

  1. Consider making models immutable using frozen=True in ConfigDict
  2. Add helper methods for common operations (e.g., is_active(), get_outcome_token())
  3. Consider adding a base class for common blockchain entity attributes (id, etc.)

Example base class:

class BlockchainEntity(BaseModel):
    """Base class for blockchain entities with common attributes."""
    model_config = ConfigDict(populate_by_name=True, frozen=True)
    
    id: HexBytes
tests_integration/markets/seer/test_seer_subgraph_handler.py (2)

11-14: Add error handling to the fixture.

While the fixture is well-typed and appropriately scoped, consider adding error handling for initialization failures.

 @pytest.fixture(scope="module")
 def handler() -> t.Generator[SeerSubgraphHandler, None, None]:
-    yield SeerSubgraphHandler()
+    try:
+        handler = SeerSubgraphHandler()
+        yield handler
+    except Exception as e:
+        pytest.fail(f"Failed to initialize SeerSubgraphHandler: {e}")

16-19: Add more assertions for market properties.

The test only verifies the number of markets. Consider validating other market properties to ensure data integrity.

 def test_get_all_seer_markets(handler: SeerSubgraphHandler) -> None:
     markets = handler.get_binary_markets()
     assert len(markets) > 1
+    for market in markets:
+        assert market.id is not None
+        assert isinstance(market.id, HexBytes)
+        assert len(market.wrapped_tokens) > 0
prediction_market_agent_tooling/markets/base_subgraph_handler.py (1)

37-37: Clarify the docstring for _parse_items_from_json

The docstring on line 37 could be improved for clarity and grammar. Consider rephrasing it for better understanding.

Suggested change:

-"""subgrounds return a weird key as a dict key"""
+"""Parse items from the JSON result returned by Subgrounds.

Subgrounds may return items indexed by a key as a list or as a dictionary if there's only a single element.
"""
prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py (2)

13-13: Redundant use of .lower() on ADDRESS_ZERO

Since ADDRESS_ZERO from web3.constants is already in lowercase ("0x0000000000000000000000000000000000000000"), applying .lower() is unnecessary. Removing it can simplify the code without affecting functionality.

Apply this diff to remove the redundant .lower():

-BINARY_MARKETS_FILTER = {"parentMarket": ADDRESS_ZERO.lower()}
+BINARY_MARKETS_FILTER = {"parentMarket": ADDRESS_ZERO}

41-52: Refactor field selection methods to reduce code duplication

Both _get_fields_for_markets and _get_fields_for_pools construct lists of fields for queries in a similar manner. Consider creating a generic method or utilizing class variables to define the fields, enhancing maintainability.

Apply this refactoring suggestion:

# Define class variables for fields
MARKET_FIELDS = [
    'id',
    'factory',
    'creator',
    'marketName',
    'outcomes',
    'parentMarket',
    'finalizeTs',
    'wrappedTokens',
]

POOL_FIELDS = [
    'id',
    'liquidity',
    'token0.id',
    'token0.name',
    'token0.symbol',
    'token1.id',
    'token1.name',
    'token1.symbol',
]

def _get_fields(self, base_field: FieldPath, fields: list[str]) -> list[FieldPath]:
    return [reduce(getattr, field.split('.'), base_field) for field in fields]

# Update existing methods to use the new function
def _get_fields_for_markets(self, markets_field: FieldPath) -> list[FieldPath]:
    return self._get_fields(markets_field, self.MARKET_FIELDS)

def _get_fields_for_pools(self, pools_field: FieldPath) -> list[FieldPath]:
    return self._get_fields(pools_field, self.POOL_FIELDS)

Also applies to: 70-81

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6626d8a and d9cd35c.

📒 Files selected for processing (5)
  • prediction_market_agent_tooling/markets/base_subgraph_handler.py (1 hunks)
  • prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py (4 hunks)
  • prediction_market_agent_tooling/markets/seer/data_models.py (1 hunks)
  • prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py (1 hunks)
  • tests_integration/markets/seer/test_seer_subgraph_handler.py (1 hunks)
🧰 Additional context used
🪛 Ruff
prediction_market_agent_tooling/markets/base_subgraph_handler.py

40-40: Loop control variable k not used within loop body

Rename unused k to _k

(B007)

🔇 Additional comments (9)
prediction_market_agent_tooling/markets/seer/data_models.py (1)

1-4: LGTM! Imports are clean and appropriate.

The imports are minimal and well-chosen for the data models' requirements.

tests_integration/markets/seer/test_seer_subgraph_handler.py (2)

1-9: LGTM! Clean and minimal imports.

The imports are well-organized and include all necessary dependencies with proper type hints.


1-46: Consider adding these test scenarios for better coverage:

  1. Test handling of malformed market IDs
  2. Test pagination/limits when retrieving all markets
  3. Test error cases for network issues
  4. Test concurrent access to the handler (since it's a singleton)
  5. Test empty or invalid pool scenarios
prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py (6)

7-7: Approved: Import of FieldPath

The import statement correctly adds FieldPath from the subgrounds library, which is necessary for handling field paths in subgraph queries.


17-19: Approved: Import of BaseSubgraphHandler

Importing BaseSubgraphHandler allows OmenSubgraphHandler to inherit common functionalities, promoting code reuse and maintainability.


53-53: Verify Singleton Behavior After Changing Base Class

The class OmenSubgraphHandler now inherits from BaseSubgraphHandler instead of using SingletonMeta as a metaclass. Ensure that the singleton pattern is preserved if it's still required. Confirm that BaseSubgraphHandler implements the singleton behavior or adjust accordingly.


71-97: Approved: Simplified Subgraph Loading and API Key Access

The initialization method now utilizes super().__init__() to initialize the base class and loads subgraphs using self.sg.load_subgraph. Accessing the API key via self.keys.graph_api_key.get_secret_value() enhances encapsulation and security.


435-435: Approved: Use of do_query Method for Market Queries

Refactoring to use self.do_query with fields and pydantic_model parameters streamlines query execution and leverages shared logic from the base class.


443-444: Approved: Consistent Query Execution in get_omen_market_by_market_id

The method now uses self.do_query for executing the query, ensuring consistency across different market-fetching methods.

Comment on lines 21 to 33
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


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

Copy link

Choose a reason for hiding this comment

The 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:

  1. Adding docstrings explaining the test cases
  2. Moving market IDs to a test data fixture
  3. Adding negative test cases
+@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

Committable suggestion was skipped due to low confidence.

"""subgrounds return a weird key as a dict key"""
items = []
for result_chunk in result:
for k, v in result_chunk.items():
Copy link

@coderabbitai coderabbitai bot Oct 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Rename unused variable k to _k

The variable k in the loop is not used within the loop body. Renaming it to _k indicates that it is intentionally unused and follows Python conventions.

Apply this diff to fix the issue:

-for k, v in result_chunk.items():
+for _k, v in result_chunk.items():
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for k, v in result_chunk.items():
for _k, v in result_chunk.items():
🧰 Tools
🪛 Ruff

40-40: Loop control variable k not used within loop body

Rename unused k to _k

(B007)

Copy link
Contributor

Choose a reason for hiding this comment

The 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 result_chunk.values() to not have the extra variable at all!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kongzii, you're absolutely right! Using result_chunk.values() would simplify the code and eliminate the need for the unused variable altogether. Thank you for pointing that out!

🐰🔍


✏️ Learnings added
Learnt from: kongzii
PR: gnosis/prediction-market-agent-tooling#535
File: prediction_market_agent_tooling/markets/base_subgraph_handler.py:37-37
Timestamp: 2024-10-31T16:29:04.548Z
Learning: In `BaseSubgraphHandler._parse_items_from_json`, when iterating over a dictionary and only the values are needed, prefer using `dict.values()` instead of `dict.items()` to avoid unused variables.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +27 to +39
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()
)
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for subgraph loading failures

Currently, if the subgraphs fail to load due to an invalid graph_api_key or network issues, the constructor will raise an exception, possibly causing the application to crash. Consider adding try-except blocks to handle exceptions during subgraph loading and provide informative error messages or fallback mechanisms.

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}")

Comment on lines +64 to +67
if len(markets) != 1:
raise ValueError(
f"Fetched wrong number of markets. Expected 1 but got {len(markets)}"
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Include market_id in the error message for clarity

Including the market_id in the error message provides context and aids in debugging when the expected market is not retrieved.

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if len(markets) != 1:
raise ValueError(
f"Fetched wrong number of markets. Expected 1 but got {len(markets)}"
)
if len(markets) != 1:
raise ValueError(
f"Fetched wrong number of markets for ID {market_id.hex()}. Expected 1 but got {len(markets)}"
)

items.extend(v)
return items

def do_query(self, fields: list[FieldPath], pydantic_model: t.Type[T]) -> list[T]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


class BaseSubgraphHandler(metaclass=SingletonMeta):
sg: Subgrounds
keys: APIKeys
Copy link
Contributor

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 and keys are defined here, but subclasses don't define trades_subgraph, conditional_tokens_subgraph, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChatGPT says

In general, attributes that vary by instance are safer as instance attributes, especially for subclasses. Only define an attribute at the class level if it’s truly static or shared across all instances.

So I think keys and sg 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.

)
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes

BINARY_MARKETS_FILTER = {"parentMarket": ADDRESS_ZERO.lower()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I randomly tried one non-binary market, and it also has parentMarket set to address zero:

Screenshot by Dropbox Capture

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid we will need a filter based on Yes, No outcomes (or whatever they are using for Yes/No markets), instead of binary markets. I agree that market like Who will win the 2024 U.S. Presidential Election? with outcomes Kamala, Trump is binary, but our code doesn't support it at all.

We need to work on #510 if we want to support Seer markets.

Copy link
Contributor Author

@gabrielfior gabrielfior Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about doing the following in this ticket:
-> Let's get only binary markets on Seer - those are defined as

  1. outcomes contains "Invalid" AND len(outcomes) == 3

-> However, the issues you raised (Kamala/Trump is different than YES/NO, breaking PMAT) still exist and will be handled on #510 .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> However, the issues you raised (Kamala/Trump is different than YES/NO, breaking PMAT) still exist and will be handled on #501 .

Isn't this PR for issue #501? 😄 At least it's called 501 seer add subgraph to subgraphhandler

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edited the comment, sorry!

Copy link
Contributor

Choose a reason for hiding this comment

The 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 (outcomes contains "Invalid" AND len(outcomes) == 3).

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 p_yes, from_bool_outcome, etc.).

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 Yes, No is a small change, and you can have Seer-trading relatively quickly I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some more methods (see get_binary_markets and get_bicategorical_markets) to address this.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (7)
tests_integration/markets/seer/test_seer_subgraph_handler.py (2)

15-17: Add docstring to fixture.

Document the purpose and scope of this fixture to improve test maintainability.

 @pytest.fixture(scope="module")
 def handler() -> t.Generator[SeerSubgraphHandler, None, None]:
+    """
+    Provides a SeerSubgraphHandler instance for integration testing.
+    Uses module scope to reuse the same handler across all tests for efficiency.
+    """
     yield SeerSubgraphHandler()

20-22: Add docstring and improve assertions.

The test would benefit from documentation and more specific assertions.

 def test_get_all_seer_markets(handler: SeerSubgraphHandler) -> None:
+    """
+    Test retrieval of all bicategorical markets.
+    Verifies that the market list is non-empty and contains valid market objects.
+    """
     markets = handler.get_bicategorical_markets()
     assert len(markets) > 1
+    # Verify market objects have required attributes
+    assert all(market.id is not None for market in markets)
prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py (4)

43-55: Add docstring to _get_fields_for_markets method for clarity

Including a docstring for the _get_fields_for_markets method will enhance code readability by clearly explaining its purpose and usage.


56-62: Add docstring to filter_bicategorical_markets method for better documentation

Providing a docstring will help other developers understand what criteria are used to filter bicategorical markets.


63-70: Add docstring to filter_binary_markets method

A docstring explaining how binary markets are identified will improve maintainability and assist future developers in understanding the filtering logic.


71-76: Add docstring to build_filter_for_conditional_markets method

Documenting this method will clarify its role in constructing query filters based on whether conditional markets are included.

prediction_market_agent_tooling/markets/base_subgraph_handler.py (1)

34-34: Clarify the docstring for _parse_items_from_json

The current docstring could be more precise. Consider providing a clearer description of the function's purpose and behavior.

Proposed change:

-"""subgrounds return a weird key as a dict key"""
+"""Parses the results from Subgrounds queries, handling different response formats to extract items."""
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d9cd35c and 43db038.

📒 Files selected for processing (3)
  • prediction_market_agent_tooling/markets/base_subgraph_handler.py (1 hunks)
  • prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py (1 hunks)
  • tests_integration/markets/seer/test_seer_subgraph_handler.py (1 hunks)
🧰 Additional context used
🪛 Ruff
prediction_market_agent_tooling/markets/base_subgraph_handler.py

37-37: Loop control variable k not used within loop body

Rename unused k to _k

(B007)

🔇 Additional comments (2)
prediction_market_agent_tooling/markets/base_subgraph_handler.py (2)

15-28: Initialization and retry logic implementation looks good

The __init__ method correctly initializes the Subgrounds instance and applies retry logic to query_json and load_subgraph methods using tenacity.


47-51: do_query method implementation is correct

The do_query method effectively executes the query and parses the results into Pydantic models.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
tests_integration/markets/seer/test_seer_subgraph_handler.py (2)

20-22: Enhance test documentation and assertions.

The test would benefit from:

  1. A docstring explaining the test's purpose
  2. More specific assertions about the expected market properties
 def test_get_all_seer_markets(handler: SeerSubgraphHandler) -> None:
+    """
+    Verify that the handler can retrieve multiple bicategorical markets.
+    Tests the basic functionality of market retrieval.
+    """
     markets = handler.get_bicategorical_markets()
     assert len(markets) > 1
+    # Verify basic market properties
+    for market in markets:
+        assert market.id is not None, "Market should have an ID"
+        assert market.wrapped_tokens, "Market should have wrapped tokens"

44-49: Add test documentation.

The test should include a docstring explaining what types of markets are being verified and why.

 def test_binary_market_retrieved(handler: SeerSubgraphHandler) -> None:
+    """
+    Verify that both regular binary markets and binary conditional markets
+    are retrieved when include_conditional_markets is True.
+    """
     markets = handler.get_binary_markets(include_conditional_markets=True)
prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py (3)

43-55: Add docstring to _get_fields_for_markets method.

Add a docstring explaining the purpose of this method and its parameters/return value for better code maintainability.

def _get_fields_for_markets(self, markets_field: FieldPath) -> list[FieldPath]:
+    """
+    Define the fields to be retrieved for market queries.
+    
+    Args:
+        markets_field (FieldPath): The base field path for markets query
+        
+    Returns:
+        list[FieldPath]: List of field paths for market-related data
+    """
    fields = [
        markets_field.id,
        markets_field.factory,

77-91: Consider implementing query result pagination.

The get_bicategorical_markets method fetches all markets at once, which could be inefficient for large datasets. Consider implementing pagination to fetch markets in chunks, especially if the number of markets could grow significantly.


112-124: Add docstring to _get_fields_for_pools method.

Add a docstring explaining the purpose of this method and its parameters/return value for better code maintainability.

def _get_fields_for_pools(self, pools_field: FieldPath) -> list[FieldPath]:
+    """
+    Define the fields to be retrieved for pool queries.
+    
+    Args:
+        pools_field (FieldPath): The base field path for pools query
+        
+    Returns:
+        list[FieldPath]: List of field paths for pool-related data
+    """
    fields = [
        pools_field.id,
        pools_field.liquidity,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 43db038 and 546c840.

📒 Files selected for processing (2)
  • prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py (1 hunks)
  • tests_integration/markets/seer/test_seer_subgraph_handler.py (1 hunks)
🔇 Additional comments (4)
tests_integration/markets/seer/test_seer_subgraph_handler.py (4)

15-18: LGTM! Well-structured fixture.

The fixture is properly scoped, typed, and follows pytest best practices.


25-29: LGTM with previous review comments.

The previous review comments about adding negative test cases and improving error handling are still valid.


51-62: LGTM with previous review comments.

The previous review comments about enhancing pool validation and adding error handling are still valid.


10-12: 🛠️ Refactor suggestion

Consider moving market IDs to a typed fixture with documentation.

The market ID constants would be better organized in a fixture with proper documentation explaining what each market represents. This would improve maintainability and provide context for the test cases.

-CONDITIONAL_MARKET_ID = HexBytes("0xe12f48ecdd6e64d95d1d8f1d5d7aa37e14f2888b")
-BINARY_MARKET_ID = HexBytes("0x7d72aa56ecdda207005fd7a02dbfd33f92d0def7")
-BINARY_CONDITIONAL_MARKET_ID = HexBytes("0xbc82402814f7db8736980c0debb01df6aad8846e")

+@pytest.fixture(scope="module")
+def market_ids() -> t.Dict[str, HexBytes]:
+    """
+    Provides test market IDs for different market types.
+    
+    Returns:
+        Dict containing:
+        - conditional: ID of a conditional market
+        - binary: ID of a regular binary market
+        - binary_conditional: ID of a binary conditional market
+    """
+    return {
+        "conditional": HexBytes("0xe12f48ecdd6e64d95d1d8f1d5d7aa37e14f2888b"),
+        "binary": HexBytes("0x7d72aa56ecdda207005fd7a02dbfd33f92d0def7"),
+        "binary_conditional": HexBytes("0xbc82402814f7db8736980c0debb01df6aad8846e")
+    }

Likely invalid or redundant comment.

Comment on lines +32 to +42
def test_conditional_market_not_retrieved(handler: SeerSubgraphHandler) -> None:
markets = handler.get_bicategorical_markets(include_conditional_markets=False)
market_ids = [m.id for m in markets]
assert CONDITIONAL_MARKET_ID not in market_ids


def test_conditional_market_retrieved(handler: SeerSubgraphHandler) -> None:
markets = handler.get_bicategorical_markets(include_conditional_markets=True)
market_ids = [m.id for m in markets]
assert CONDITIONAL_MARKET_ID in market_ids

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using parametrized tests for conditional market scenarios.

The two conditional market tests could be combined using pytest's parametrize feature for better maintainability.

-def test_conditional_market_not_retrieved(handler: SeerSubgraphHandler) -> None:
-    markets = handler.get_bicategorical_markets(include_conditional_markets=False)
-    market_ids = [m.id for m in markets]
-    assert CONDITIONAL_MARKET_ID not in market_ids
-
-def test_conditional_market_retrieved(handler: SeerSubgraphHandler) -> None:
-    markets = handler.get_bicategorical_markets(include_conditional_markets=True)
-    market_ids = [m.id for m in markets]
-    assert CONDITIONAL_MARKET_ID in market_ids

+@pytest.mark.parametrize("include_conditional,expected_present", [
+    (False, False),
+    (True, True)
+])
+def test_conditional_market_handling(
+    handler: SeerSubgraphHandler,
+    market_ids: t.Dict[str, HexBytes],
+    include_conditional: bool,
+    expected_present: bool
+) -> None:
+    """
+    Test conditional market inclusion/exclusion based on the include_conditional_markets flag.
+    
+    Args:
+        include_conditional: Whether to include conditional markets
+        expected_present: Whether the conditional market should be present in results
+    """
+    markets = handler.get_bicategorical_markets(include_conditional_markets=include_conditional)
+    market_ids_list = [m.id for m in markets]
+    if expected_present:
+        assert market_ids["conditional"] in market_ids_list
+    else:
+        assert market_ids["conditional"] not in market_ids_list

Committable suggestion was skipped due to low confidence.

Comment on lines +125 to +138
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
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 = []
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

@gabrielfior gabrielfior merged commit 6862cbf into main Oct 31, 2024
14 checks passed
@gabrielfior gabrielfior deleted the 501-seer-add-subgraph-to-subgraphhandler branch October 31, 2024 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Seer - add subgraph to SubgraphHandler
2 participants