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

DB cache in form of decorator #534

Merged
merged 6 commits into from
Nov 6, 2024
Merged

DB cache in form of decorator #534

merged 6 commits into from
Nov 6, 2024

Conversation

kongzii
Copy link
Contributor

@kongzii kongzii commented Oct 30, 2024

No description provided.

Copy link

coderabbitai bot commented Oct 30, 2024

Warning

Rate limit exceeded

@kongzii has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 16 minutes and 39 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e8d00ec and 1c8028f.

Walkthrough

This pull request introduces a new caching mechanism using a PostgreSQL database through the db_cache decorator, which replaces previous in-memory caching strategies in various functions across multiple files. It includes the creation of a FunctionCache model to store function metadata and results, and updates several existing functions to utilize this new caching approach. Additionally, it removes certain classes related to data storage and adds comprehensive unit tests to validate the caching behavior.

Changes

File Change Summary
prediction_market_agent_tooling/tools/caches/db_cache.py Introduced a new caching mechanism with FunctionCache model and db_cache decorator, supporting various parameters for flexible caching behavior.
prediction_market_agent_tooling/tools/google.py Updated search_google function to use @db_cache(max_age=timedelta(days=1)) instead of in-memory caching, modifying the import statement accordingly.
prediction_market_agent_tooling/tools/is_invalid.py Replaced @persistent_inmemory_cache with @db_cache in is_invalid function, maintaining the function's internal logic.
prediction_market_agent_tooling/tools/is_predictable.py Updated @persistent_inmemory_cache to @db_cache for both is_predictable_binary and is_predictable_without_description functions, preserving their signatures and logic.
prediction_market_agent_tooling/tools/relevant_news_analysis/relevant_news_analysis.py Modified date handling in analyse_news_relevance and get_certified_relevant_news_since functions, changing parameter types and simplifying logic.
prediction_market_agent_tooling/tools/tavily/tavily_models.py Removed TavilyResponseModel class, eliminating structured representation of Tavily responses in the database.
prediction_market_agent_tooling/tools/tavily/tavily_search.py Added @db_cache to tavily_search, updated parameter types from days to news_since, and modified related logic for handling date parameters.
prediction_market_agent_tooling/tools/tavily/tavily_storage.py Deleted TavilyStorage class, which managed storage and retrieval of TavilyResponse data in SQL database.
tests/conftest.py Added a new pytest fixture keys_with_sqlalchemy_db_url to provide an instance of APIKeys initialized with a SQLAlchemy database URL.
tests/tools/test_db_cache.py Introduced unit tests for db_cache decorator, covering various input types and caching scenarios, ensuring correct behavior of the caching mechanism.

Possibly related PRs


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.

@kongzii kongzii force-pushed the peter/decorator/cache branch 2 times, most recently from b5b3986 to a4a2781 Compare October 31, 2024 13:06
@kongzii kongzii force-pushed the peter/decorator/cache branch from a4a2781 to e92251b Compare October 31, 2024 13:38
@kongzii kongzii marked this pull request as ready for review October 31, 2024 13:57
...


def db_cache(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just add @db_cache to any function or method and it should work like magic!

Optionally, specify some of the optional keyword arguments.

It supports:

  • any input/output type -- native types, lists, pydantic models, nested combinations, etc.
  • it stores input arguments in DB
  • it stores the output in DB (along with input we can use it to gather data)
  • it handles if the pydantic model isn't backward compatible with cached value, and it will re-compute the result
  • decorated methods are properly typed
  • we can specify ignored arguments/types, so we don't cache based on APIKeys for example

Screenshot by Dropbox Capture

SQLModel.metadata.create_all(engine)

@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this PR, there are some Any types that I usually fight against, but here they are really necessary, or it would be super hard to type it correctly at least, but because it's all used inside of db_cache, which in the end is cast to the type of decorated function anyway, it shouldn't matter.

@tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_fixed(1))
@observe()
@db_cache
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Beware! Order matters here.

If we do

@observe()
@db_cache
def something(): ... 

then all calls will be observed in Langfuse.

If we do

@db_cache
@observe()
def something(): ... 

Then only the first (non-cached) call is observed in Langfuse.

@@ -55,7 +54,7 @@
def analyse_news_relevance(
raw_content: str,
question: str,
date_of_interest: datetime,
date_of_interest: date,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to date to not invalidate cache because of difference in seconds or hours (or even milliseconds!)

results = get_relevant_news_since(
question=question,
days_ago=days_ago,
news_since=news_since,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Passing date into get_relevant_news_since (and therefore tavily_search) to have cache based on absolute values, not relative ones. E.g. if we would call days_ago=1 for a week, then it could be always used from cache, because the argument is still the same, yet the date of 1 day ago is different!

@@ -140,7 +138,6 @@ def get_certified_relevant_news_since_cached(
question: str,
days_ago: int,
cache: RelevantNewsResponseCache,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I left the implementation of RelevantNewsResponseCache because there is news-specific logic that was just too hard to do in a general way using decorator. Evan's explanation:

Screenshot by Dropbox Capture

@@ -23,62 +16,3 @@ class TavilyResponse(BaseModel):
images: list[str]
results: list[TavilyResult]
response_time: float


class TavilyResponseModel(SQLModel, table=True):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tavily cache can be removed completely, I'll just re-fill the new database table with old data

@kongzii
Copy link
Contributor Author

kongzii commented Nov 1, 2024

Also did a script to refill the data: #540 But it's too hacky to be merged, so just closed the PR to keep it at least somewhere.

Comment on lines 301 to 314
def convert_pydantic_to_dict(value: Any) -> Any:
"""
Convert Pydantic models to dictionaries, including if they are in nested structures.
"""
if isinstance(value, BaseModel):
return value.model_dump()
elif isinstance(value, dict):
return {k: convert_pydantic_to_dict(v) for k, v in value.items()}
elif isinstance(value, (list, tuple)):
return type(value)(convert_pydantic_to_dict(v) for v in value)
elif isinstance(value, set):
return {convert_pydantic_to_dict(v) for v in value}
else:
return value
Copy link
Contributor

Choose a reason for hiding this comment

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

For simplicity - can't we always store JSON objects in the DB (in the value column)? This way, we can then afterwards do model_validate_json, wrapped by try-catch and handle it appropriately.
I don't 100% like this approach of having many types with if-elif-else - ultimately this is a design decision depending on personal taste, but from my understanding, JSON would be enough, i.e. value should not be Any, instead str (JSON-compatible).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are two separate things: what you wrote here and on Slack:

can't we always store JSON objects in the DB (in the value column)

This PR is doing exactly that, storing args and result as JSONB in Postgres. Thanks to that, values in the database are properly formatted, and we can even query them as normal columns and filter by them:

Screenshot by Dropbox Capture

However! You made me think and indeed, convert_pydantic_to_dict can be removed fully, simplified in this commit.

For ex, here I would find amazing if we simply did model_validate_json instead of the complex logic here - if it fails, then an error is thrown.

The issue here is that the current implementation also handles nested structures and non-pydantic structures. Let's say the output type is list[AgentMarket], then the JSONized form (doesn't matter if stored as JSONB or STRING in the database) will be like [{'id': '...', 'question': ...}, ...] and we can't call model_validate_json on lists, only on Pydantic models.

So convert_cached_output_to_pydantic is indeed necessary because the current implementation can handle all varies of outputs, as seen in the tests:

Screenshot by Dropbox Capture

So it's either this complex implementation, or forcing us in the future to always return a single Pydantic model out of cached functions, which we already aren't doing:

Screenshot by Dropbox Capture

Screenshot by Dropbox Capture

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

🧹 Outside diff range and nitpick comments (17)
tests/conftest.py (1)

10-15: Consider adding cache cleanup and error handling.

While the fixture implementation is generally good, consider these improvements:

  1. Add cleanup to prevent test pollution
  2. Add error handling for connection issues
  3. Document the fixture's purpose and usage

Here's a suggested implementation:

 @pytest.fixture
 def keys_with_sqlalchemy_db_url(
     postgresql: psycopg.Connection,
 ) -> Generator[APIKeys, None, None]:
+    """Provides APIKeys instance with SQLAlchemy URL for testing DB cache.
+    
+    Args:
+        postgresql: Pytest PostgreSQL fixture providing connection details
+        
+    Yields:
+        APIKeys instance with DB URL and cache enabled
+    """
     sqlalchemy_db_url = f"postgresql+psycopg2://{postgresql.info.user}:@{postgresql.info.host}:{postgresql.info.port}/{postgresql.info.dbname}"
-    yield APIKeys(SQLALCHEMY_DB_URL=SecretStr(sqlalchemy_db_url), ENABLE_CACHE=True)
+    try:
+        api_keys = APIKeys(SQLALCHEMY_DB_URL=SecretStr(sqlalchemy_db_url), ENABLE_CACHE=True)
+        yield api_keys
+    except Exception as e:
+        pytest.fail(f"Failed to setup database fixture: {e}")
+    finally:
+        # Clean up any cached data
+        from prediction_market_agent_tooling.tools.caches.db_cache import FunctionCache
+        with api_keys.get_db_session() as session:
+            session.query(FunctionCache).delete()
+            session.commit()
prediction_market_agent_tooling/tools/google.py (1)

17-17: Excellent caching strategy for external API calls

The transition to database caching with a 1-day TTL is a robust choice for Google search results:

  • Provides persistence across application restarts
  • Reduces external API calls and associated costs
  • Works well with the existing retry mechanism

Consider implementing a cache warming strategy for frequently searched queries to maintain fresh results without impacting user response times. This could be particularly useful given the 1-day TTL.

prediction_market_agent_tooling/tools/tavily/tavily_search.py (3)

35-38: Enhance error messages for better clarity.

While the validation logic is correct, the error messages could be more descriptive to help users understand the requirements better.

Consider this improvement:

-        raise ValueError("When topic is 'news', news_since must be provided")
+        raise ValueError("The 'news_since' date is required when topic='news' to specify the starting date for news search")
-        raise ValueError("When topic is 'general', news_since must be None")
+        raise ValueError("The 'news_since' parameter should not be provided when topic='general' as it only applies to news searches")

104-114: Add docstring to document the new parameter.

The function would benefit from a docstring explaining the news_since parameter and its usage.

Consider adding:

 def get_relevant_news_since(
     question: str,
     news_since: date,
     score_threshold: float = DEFAULT_SCORE_THRESHOLD,
     max_results: int = 3,
 ) -> list[TavilyResult]:
+    """
+    Retrieve relevant news articles since a specific date.
+    
+    Args:
+        question: The search query to find relevant news.
+        news_since: The starting date from which to search for news.
+        score_threshold: Minimum relevance score (0-1) for including results.
+        max_results: Maximum number of results to return.
+    
+    Returns:
+        List of TavilyResult objects with score > score_threshold.
+    """

17-17: Consider cache invalidation strategy.

The DB caching implementation looks solid, but consider implementing a cache invalidation strategy for cases where:

  1. The Tavily API response format changes
  2. Manual cache clearing is needed
  3. Cache size grows too large

This could be achieved through:

  • Version tagging in cache keys
  • Admin endpoints for cache management
  • Periodic cleanup of old entries
prediction_market_agent_tooling/tools/relevant_news_analysis/relevant_news_analysis.py (3)

1-1: LGTM! Good architectural decision.

Using date instead of datetime is a smart choice for caching as it prevents unnecessary cache invalidation due to time components (seconds, hours, milliseconds).


99-102: LGTM! Consider adding date validation.

The changes improve caching effectiveness by using absolute dates instead of relative ones. This prevents cache misses when the same relative period (e.g., days_ago=1) is requested multiple times.

Consider adding validation to ensure news_since is not in the future:

 news_since = date.today() - timedelta(days=days_ago)
+if news_since > date.today():
+    raise ValueError("Cannot search for news from the future")
 results = get_relevant_news_since(

Also applies to: 119-119


Line range hint 140-140: LGTM! Good decision on keeping RelevantNewsResponseCache.

Maintaining the specialized cache alongside the new DB cache is justified due to the news-specific logic that would be complex to generalize. This hybrid approach provides a good balance between reusability and specialized functionality.

prediction_market_agent_tooling/tools/is_predictable.py (1)

81-81: Consider configuring cache parameters for optimal performance.

The @db_cache decorator is well-suited for this function as it makes expensive OpenAI API calls. However, consider explicitly configuring cache parameters like TTL (time-to-live) to prevent stale results and manage database growth.

Example configuration:

-@db_cache
+@db_cache(ttl_seconds=86400)  # Cache for 24 hours
tests/tools/test_db_cache.py (3)

10-27: Improve boolean comparison style while maintaining test clarity.

The test logic is correct, but the boolean comparisons can be improved following Python's style guidelines.

Consider this style improvement:

-    assert bools(True) == True
-    assert bools(True) == True
-    assert bools(False) == False
+    assert bools(True)
+    assert bools(True)
+    assert not bools(False)
🧰 Tools
🪛 Ruff

23-23: Avoid equality comparisons to True; use if bools(True): for truth checks

Replace with bools(True)

(E712)


24-24: Avoid equality comparisons to True; use if bools(True): for truth checks

Replace with bools(True)

(E712)


25-25: Avoid equality comparisons to False; use if not bools(False): for false checks

Replace with not bools(False)

(E712)


253-298: Consider restructuring the cache invalidation test for better maintainability.

While the test effectively validates cache invalidation behavior, it could be improved for clarity and maintainability.

Consider these improvements:

  1. Instead of redefining the model class, create two distinct model classes with different names:
class OriginalOutputModel(TestOutputModel):
    pass

class ModifiedOutputModel(TestOutputModel):
    new_field: str
  1. Split the test into two clear phases with better documentation:
# Phase 1: Cache the original results
@db_cache(api_keys=keys_with_sqlalchemy_db_url)
def multiply_models_v1(a: TestInputModel, b: TestInputModel) -> OriginalOutputModel:
    nonlocal call_count
    call_count += 1
    return OriginalOutputModel(result=a.value * b.value)

# Phase 2: Verify cache invalidation with modified model
@db_cache(api_keys=keys_with_sqlalchemy_db_url)
def multiply_models_v2(a: TestInputModel, b: TestInputModel) -> ModifiedOutputModel:
    nonlocal call_count
    call_count += 1
    return ModifiedOutputModel(result=a.value * b.value, new_field="modified")

This approach would:

  • Eliminate the need for type ignore comments
  • Make the test's intention clearer
  • Be more maintainable

1-320: Consider adding tests for additional scenarios.

The test suite is comprehensive but could benefit from additional test cases for:

  1. Concurrent access:
@pytest.mark.asyncio
async def test_postgres_cache_concurrent_access(
    keys_with_sqlalchemy_db_url: APIKeys,
) -> None:
    call_count = 0

    @db_cache(api_keys=keys_with_sqlalchemy_db_url)
    async def cached_function(x: int) -> int:
        nonlocal call_count
        call_count += 1
        return x * 2

    # Simulate concurrent access
    tasks = [cached_function(1) for _ in range(10)]
    results = await asyncio.gather(*tasks)
    
    assert all(r == 2 for r in results)
    assert call_count == 1  # Should only be called once despite concurrent access
  1. Cache expiration:
def test_postgres_cache_expiration(
    keys_with_sqlalchemy_db_url: APIKeys,
) -> None:
    call_count = 0

    @db_cache(api_keys=keys_with_sqlalchemy_db_url, max_age=timedelta(seconds=1))
    def cached_function(x: int) -> int:
        nonlocal call_count
        call_count += 1
        return x * 2

    assert cached_function(1) == 2
    assert call_count == 1
    
    time.sleep(2)  # Wait for cache to expire
    
    assert cached_function(1) == 2
    assert call_count == 2  # Should be called again after expiration
  1. Error handling:
def test_postgres_cache_error_handling(
    keys_with_sqlalchemy_db_url: APIKeys,
) -> None:
    call_count = 0

    @db_cache(api_keys=keys_with_sqlalchemy_db_url)
    def cached_function(x: int) -> int:
        nonlocal call_count
        call_count += 1
        if x < 0:
            raise ValueError("Negative values not allowed")
        return x * 2

    with pytest.raises(ValueError):
        cached_function(-1)
    
    with pytest.raises(ValueError):
        cached_function(-1)  # Should not hit cache for errored executions
    
    assert call_count == 2
🧰 Tools
🪛 Ruff

23-23: Avoid equality comparisons to True; use if bools(True): for truth checks

Replace with bools(True)

(E712)


24-24: Avoid equality comparisons to True; use if bools(True): for truth checks

Replace with bools(True)

(E712)


25-25: Avoid equality comparisons to False; use if not bools(False): for false checks

Replace with not bools(False)

(E712)

prediction_market_agent_tooling/tools/caches/db_cache.py (5)

97-97: Remove unnecessary f-string prefix

The log message on line 97 does not contain any placeholders, so the f prefix is unnecessary and can be removed to simplify the code.

Apply this diff to fix the issue:

-logger.warning(
-    f"SQLALCHEMY_DB_URL not provided in the environment, skipping function caching."
-)
+logger.warning(
+    "SQLALCHEMY_DB_URL not provided in the environment, skipping function caching."
+)
🧰 Tools
🪛 Ruff

97-97: f-string without any placeholders

Remove extraneous f prefix

(F541)


251-251: Fix typo in docstring

The word "recursivelly" on line 251 is misspelled. It should be "recursively".

Apply this diff:

-If this function returns a dictionary, it will be called recursivelly.
+If this function returns a dictionary, it will be called recursively.

263-263: Correct typo in exception message

In the exception message on line 263, "Unsuported" should be "Unsupported".

Apply this diff:

-    f"Unsuported type for the default json serialize function, value is {y}."
+    f"Unsupported type for the default json serialize function, value is {y}."

160-160: Handle missing return type annotations

The caching mechanism depends on the function's return type annotation to determine if the result is a Pydantic model. If a decorated function lacks a return type annotation, cached Pydantic models may not be reconstructed properly. Consider adding a check or warning for missing return type annotations to ensure correct caching behavior.


229-229: Ensure the wrapper function preserves type annotations

While casting the wrapper function to FunctionT helps with type checking, it's important to ensure that the wrapper maintains the signature of the original function. Consider using a more precise approach to preserve type annotations and improve maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

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

⛔ Files ignored due to path filters (2)
  • poetry.lock is excluded by !**/*.lock, !**/*.lock
  • pyproject.toml is excluded by !**/*.toml
📒 Files selected for processing (10)
  • prediction_market_agent_tooling/tools/caches/db_cache.py (1 hunks)
  • prediction_market_agent_tooling/tools/google.py (1 hunks)
  • prediction_market_agent_tooling/tools/is_invalid.py (2 hunks)
  • prediction_market_agent_tooling/tools/is_predictable.py (3 hunks)
  • prediction_market_agent_tooling/tools/relevant_news_analysis/relevant_news_analysis.py (5 hunks)
  • prediction_market_agent_tooling/tools/tavily/tavily_models.py (0 hunks)
  • prediction_market_agent_tooling/tools/tavily/tavily_search.py (4 hunks)
  • prediction_market_agent_tooling/tools/tavily/tavily_storage.py (0 hunks)
  • tests/conftest.py (1 hunks)
  • tests/tools/test_db_cache.py (1 hunks)
💤 Files with no reviewable changes (2)
  • prediction_market_agent_tooling/tools/tavily/tavily_models.py
  • prediction_market_agent_tooling/tools/tavily/tavily_storage.py
🧰 Additional context used
🪛 Ruff
prediction_market_agent_tooling/tools/caches/db_cache.py

97-97: f-string without any placeholders

Remove extraneous f prefix

(F541)

tests/tools/test_db_cache.py

23-23: Avoid equality comparisons to True; use if bools(True): for truth checks

Replace with bools(True)

(E712)


24-24: Avoid equality comparisons to True; use if bools(True): for truth checks

Replace with bools(True)

(E712)


25-25: Avoid equality comparisons to False; use if not bools(False): for false checks

Replace with not bools(False)

(E712)

🔇 Additional comments (14)
tests/conftest.py (2)

1-8: LGTM! Clean and well-organized imports.

The imports are properly organized with clear separation between standard library, third-party, and local imports. All imports are relevant to the fixture's functionality.


14-14: Verify database connection security.

The connection string doesn't include a password. While this might be intentional for tests, we should verify this is the expected behavior.

✅ Verification successful

Database connection in tests is correctly configured without password

The connection string without a password is the expected behavior here because:

  1. This is a test fixture using pytest-postgresql which automatically creates a temporary database
  2. The postgresql fixture is injected by pytest-postgresql and provides connection details for this temporary database
  3. The temporary database is configured for local testing without password authentication by design
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any other database connection strings in the codebase that might indicate the expected pattern
rg -g '!*.pyc' -g '!*.git' -A 2 -B 2 'postgresql\+psycopg2:\/\/'

Length of output: 179


Script:

#!/bin/bash
# Search for database connection strings with proper regex escaping
rg -g '!*.pyc' -g '!*.git' -A 2 -B 2 'postgresql\+psycopg2://'

# Also check for any configuration files that might contain database settings
fd -e yml -e yaml -e ini -e conf -e cfg -e env | xargs cat 2>/dev/null

# Look for any test configuration files specifically
fd -e py -g '*test*' -g '*conf*' | xargs grep -l "postgresql" 2>/dev/null

Length of output: 2400


Script:

#!/bin/bash
# Let's check the specific test configuration file content
cat tests/conftest.py

# Also search for any test database fixtures or setup
rg -g '!*.pyc' -A 5 -B 5 'def postgresql_connection|pytest.fixture.*postgresql'

Length of output: 614

prediction_market_agent_tooling/tools/google.py (2)

9-9: LGTM: Clean import organization

The import statement follows Python's import organization conventions, properly separating standard library, third-party, and local imports.


Line range hint 17-54: Verify cache performance and error handling

The switch to database caching requires verification of:

  1. Error handling for both API and cache failures
  2. Performance impact compared to the previous in-memory implementation
  3. Impact on application startup time
✅ Verification successful

Let me gather more information about the caching implementation and error handling.


Based on the gathered information, I can now provide a final response as the code analysis shows comprehensive error handling and caching implementation:

Database caching implementation is robust and well-tested

The codebase demonstrates:

  • Proper error handling for both API and cache failures through try-except blocks
  • Graceful degradation when cache validation fails (treats as cache miss)
  • Comprehensive test coverage for various data types and edge cases
  • Performance optimization through configurable cache parameters:
    • max_age for cache invalidation
    • ignore_args for selective caching
    • cache_none for controlling None value caching
    • Global cache toggle via ENABLE_CACHE

The switch to database caching is a safe improvement that maintains reliability while adding persistence.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify error handling and cache implementation

# Test 1: Check for proper error handling in other functions using db_cache
echo "Checking error handling patterns..."
rg -A 3 "@db_cache" | rg -B 3 "try|except|raise"

# Test 2: Look for potential cache initialization code
echo "Checking cache initialization..."
ast-grep --pattern 'class $_(FunctionCache|Cache|DBCache) {
  $$$
}'

# Test 3: Check for similar caching patterns across the codebase
echo "Checking caching patterns consistency..."
rg "@db_cache\(max_age=" -A 1

Length of output: 759


Script:

#!/bin/bash
# Check the db_cache implementation and its error handling
echo "Searching for db_cache implementation..."
fd "cache|db_cache" --type f --exec rg -l "class.*Cache|def.*cache"

# Once found, examine the implementation
echo "Examining cache implementation..."
rg -A 20 "class.*Cache|def.*cache" $(fd "cache|db_cache" --type f)

# Check for error handling in the cache implementation
echo "Checking cache error handling..."
rg -B 3 -A 10 "try|except|raise" $(fd "cache|db_cache" --type f)

Length of output: 41165

prediction_market_agent_tooling/tools/tavily/tavily_search.py (2)

2-8: LGTM: Cache configuration looks good!

The @db_cache decorator is well-configured with appropriate parameters:

  • 1-day caching duration is reasonable for search results
  • Correctly ignores api_keys in cache key as they don't affect results

Also applies to: 17-17


Line range hint 40-56: Verify date difference calculation bounds.

The date difference calculation (date.today() - news_since).days could potentially overflow or return unexpected results for distant dates.

Consider adding bounds checking:

-    days = None if news_since is None else (date.today() - news_since).days
+    if news_since is not None:
+        delta = date.today() - news_since
+        if delta.days < 0:
+            raise ValueError("news_since date cannot be in the future")
+        if delta.days > 365:  # or your preferred maximum
+            raise ValueError("news_since date cannot be more than 365 days in the past")
+        days = delta.days
+    else:
+        days = None
prediction_market_agent_tooling/tools/is_invalid.py (3)

5-5: LGTM: Clean import addition

The db_cache import is properly organized with other internal imports.


58-59: Consider optimizing decorator order based on monitoring needs

The current order @observe() -> @db_cache means all calls (including cached ones) will be observed in Langfuse. If you only need to monitor non-cached calls, consider swapping the order to @db_cache -> @observe().

Choose based on your monitoring requirements:

  • Current order: Monitor all calls (including cached)
  • Swapped order: Monitor only non-cached calls

Line range hint 59-67: Consider cache invalidation strategy for rule changes

The db_cache implementation should account for potential changes in the evaluation rules (defined in QUESTION_IS_INVALID_PROMPT). Consider:

  1. Including a version hash of the prompt template in the cache key
  2. Implementing a mechanism to invalidate cache entries when rules are updated

Run this script to check if the db_cache implementation handles prompt template changes:

prediction_market_agent_tooling/tools/relevant_news_analysis/relevant_news_analysis.py (1)

57-57: Verify datetime to date migration impact.

The parameter type change from datetime to date looks good and aligns with the caching strategy. Let's verify all callers have been updated.

✅ Verification successful

Parameter type change is consistent with usage

The change from datetime to date is correctly implemented:

  • The function is only used within the same file
  • The caller uses date.today() to create the news_since parameter
  • The imports are correctly updated to use from datetime import date, timedelta
  • No other usages or imports of datetime exist in the file
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining datetime usage with analyse_news_relevance
rg "analyse_news_relevance.*datetime" 

Length of output: 39


Script:

#!/bin/bash
# Search for all usages of analyse_news_relevance function
rg "analyse_news_relevance" -A 3

Length of output: 1017


Script:

#!/bin/bash
# Find all Python files that might import and use the function
fd "\.py$" --exec grep -l "analyse_news_relevance"

Length of output: 139


Script:

#!/bin/bash
# Look for the function definition to understand its usage pattern
ast-grep --pattern 'def analyse_news_relevance($$$)'

Length of output: 54


Script:

#!/bin/bash
# Check the type of news_since variable used in the caller
rg -B 5 "news_since.*=" prediction_market_agent_tooling/tools/relevant_news_analysis/relevant_news_analysis.py

Length of output: 448


Script:

#!/bin/bash
# Look for date/datetime imports and usage in the file
rg "(from datetime import|import datetime)" prediction_market_agent_tooling/tools/relevant_news_analysis/relevant_news_analysis.py

Length of output: 169

prediction_market_agent_tooling/tools/is_predictable.py (3)

5-5: LGTM: Import changes align with the new caching strategy.

The addition of the db_cache import is consistent with the PR's objective of transitioning from in-memory to database-based caching.


117-117: Consider configuring cache parameters for optimal performance.

Similar to is_predictable_binary, this function would benefit from explicit cache configuration to manage database growth and result freshness.


Line range hint 81-89: Verify decorator interaction and caching behavior.

The functions use multiple decorators (@tenacity.retry, @observe, @db_cache) which need careful consideration:

  1. Verify that retries don't create duplicate cache entries
  2. Ensure observability data is captured correctly with caching
  3. Confirm caching works with different input parameters (question, description, engine, prompt_template)

Also applies to: 117-125

✅ Verification successful

Let me generate additional scripts to verify the caching behavior and decorator interactions.


Let me check the actual implementation of the cache and decorator interaction by looking at the cache implementation file.


Based on the detailed cache implementation I've reviewed, I can now provide the final response:

Cache implementation is robust and handles decorator interactions correctly

The caching implementation is well-designed with proper handling of decorator interactions:

  • The @db_cache decorator uses @wraps to preserve function metadata
  • Cache entries are uniquely identified by function name, full qualified name, and args hash
  • Retries from @tenacity.retry won't create duplicate entries as they use the same function signature and arguments
  • The @observe decorator can properly track both cache hits and misses through the explicit logging of [cache-hit] and [cache-miss] events
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential issues with decorator interaction

# Test 1: Check if there are any other functions using similar decorator combinations
echo "Checking for similar decorator patterns..."
rg -B 2 -A 1 "@db_cache" 

# Test 2: Look for any error handling patterns with these decorators
echo "Checking error handling patterns..."
ast-grep --pattern 'try {
  $$$
  from langchain $$$
  $$$
} except ImportError:
  $$$
  return $$$
}'

Length of output: 5807


Script:

#!/bin/bash
# Check for test cases related to decorator interactions and caching behavior

# Look for test files related to these functions
echo "Checking test files for decorator interaction tests..."
rg -l "test.*is_predictable" tests/

# Check test cases for caching behavior
echo "Checking specific test cases..."
rg -A 10 "def test.*is_predictable" tests/

# Look for cache configuration and implementation
echo "Checking cache implementation..."
rg -l "class.*Cache" 
rg -A 5 "def db_cache"

Length of output: 3508


Script:

#!/bin/bash
# Check the cache implementation details

# Look at the db_cache implementation
echo "Checking db_cache implementation..."
cat prediction_market_agent_tooling/tools/caches/db_cache.py

# Check for retry handling in cache
echo "Checking retry handling..."
rg -A 5 "def wrapped" prediction_market_agent_tooling/tools/caches/db_cache.py

# Check observe decorator implementation
echo "Checking observe decorator..."
fd observe.py --exec cat {}

Length of output: 13573

tests/tools/test_db_cache.py (1)

1-8: LGTM! Clean and well-organized imports.

The imports are properly organized and include all necessary dependencies for the test suite.

}

# Compute a hash of the function arguments used for lookup of cached results
arg_string = json.dumps(args_dict, sort_keys=True, default=str)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure consistent JSON serialization for argument hashing

Currently, args_dict is serialized using json.dumps(args_dict, sort_keys=True, default=str), which may not handle complex types correctly. To ensure consistent serialization, consider using the custom json_serializer function designed to handle special types.

Apply this diff to fix the issue:

-arg_string = json.dumps(args_dict, sort_keys=True, default=str)
+arg_string = json_serializer(args_dict)

Committable suggestion skipped: line range outside the PR's diff.

@kongzii kongzii merged commit c6ae204 into main Nov 6, 2024
16 checks passed
@kongzii kongzii deleted the peter/decorator/cache branch November 6, 2024 08:01
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.

2 participants