Skip to content

Conversation

@stabacco
Copy link
Owner

@stabacco stabacco commented Jul 9, 2025

  • drops older python versions
  • adds option to fetch statements data

Summary by Sourcery

Add support for retrieving financial statements and modernize project to Python 3.10+

New Features:

  • Introduce StatementClient with pydantic models to list company statements via new statements endpoint

Enhancements:

  • Raise minimum Python version to >=3.10 and adopt pipe-style union type hints
  • Refactor ASX MarketClient to parse updated market status JSON structure
  • Standardize enum definitions and clean up type annotations across modules

Build:

  • Bump development dependencies in pyproject.toml and update .pre-commit-config.yaml

CI:

  • Update GitHub Actions matrix to Python 3.10–3.13 and bump Poetry to 1.7.1
  • Upgrade pre-commit hook revisions to latest versions

Deployment:

  • Update publish workflow to use Python 3.10 and Poetry 1.7.1

Tests:

  • Add VCR cassettes and async tests for statement listing including empty responses
  • Adjust existing cassettes and assertions for market, funding, and cash endpoints to reflect new API responses
  • Convert tracing_client fixture to use pytest_asyncio

@sourcery-ai
Copy link

sourcery-ai bot commented Jul 9, 2025

Reviewer's Guide

This PR removes support for older Python versions, upgrades dependencies and CI tooling, refactors client code with modern type hints and style, and introduces a new StatementClient (with related constants, models and tests) while adjusting the ASX market client and dozens of VCR cassettes to align with revamped endpoints and response formats.

ER diagram for new NYSEUrl.statement endpoint

erDiagram
    NYSEUrl ||--o{ StatementClient : uses
    StatementClient ||--o{ Statement : fetches
    Statement {
        date date
        quarter int
        year int
    }
    NYSEUrl {
        statement str
    }
Loading

Class diagram for the new StatementClient and related models

classDiagram
    class StatementClient {
        +async list(request: StatementRequest) List[Statement]
    }
    class StatementRequest {
        +str symbol
        +date start_date
    }
    class Data {
        +str data_code
        +float value
    }
    class StatementData {
        +List[Data] balance_sheet
        +List[Data] income_statement
        +List[Data] cash_flow
        +List[Data] overview
    }
    class Statement {
        +date date
        +int quarter
        +int year
        +StatementData statement_data
    }
    StatementClient --|> BaseClient
    StatementClient ..> StatementRequest
    StatementClient ..> Statement
    StatementData o-- Data
    Statement o-- StatementData
Loading

Class diagram for StakeClient with StatementClient integration

classDiagram
    class StakeClient {
        +equities
        +fundings
        +market
        +orders
        +products
        +ratings
        +trades
        +transactions
        +statements
        +set_exchange(exchange)
        +async get(url, payload)
        +async post(url, payload)
        +async delete(url, payload)
    }
    class StatementClient
    StakeClient --> StatementClient : statements
Loading

Class diagram for updated ASX MarketClient and Status models

classDiagram
    class MarketClient {
        +async get() MarketStatus
        +async is_open() bool
    }
    class MarketStatus {
        +Optional[datetime] last_trading_date
        +Status status
    }
    class Status {
        +str current
    }
    MarketClient --|> BaseClient
    MarketClient ..> MarketStatus
    MarketStatus o-- Status
Loading

File-Level Changes

Change Details Files
Add statements support to the API client
  • Create new StatementClient and Pydantic models
  • Add statement URL template in constants
  • Register self.statements in StakeClient init
  • Add statement list tests and VCR cassettes
stake/statement.py
stake/constant.py
stake/client.py
tests/test_statement.py
tests/cassettes/test_statement/*
Raise minimum Python requirement and bump tooling/dependencies
  • Update python constraint to >=3.10 in pyproject.toml
  • Upgrade pre-commit hooks versions
  • Adjust GitHub Actions matrices for Python and Poetry versions
pyproject.toml
.pre-commit-config.yaml
.github/workflows/test.yml
.github/workflows/publish.yml
Modernize code style and type hints
  • Switch HTTP method signatures to `
None` unions
  • Simplify Enum definitions by removing explicit type annotations
  • Remove stray print and update Pydantic alias generator usage
  • Convert pytest fixture to async via pytest_asyncio
  • Refactor ASX market client to new endpoint and format
    • Point ASXUrl.market_status to the new quoteTwo endpoint
    • Parse list response and map first item into MarketStatus
    • Change last_trading_date to datetime and enforce uppercase status
    stake/constant.py
    stake/asx/market.py
    tests/test_market.py
    Revise VCR cassette recordings to match updated API
    • Update market URIs and status fields
    • Refresh funding and transaction reference IDs and paths
    • Adjust cash-available cassette fields and keys
    • Add and update statement cassettes
    tests/cassettes/test_market/*
    tests/cassettes/test_funding/*
    tests/cassettes/test_cash_available/*
    tests/cassettes/test_statement/*

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    Copy link

    @sourcery-ai sourcery-ai bot left a comment

    Choose a reason for hiding this comment

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

    Hey @stabacco - I've reviewed your changes and they look great!

    Prompt for AI Agents
    Please address the comments from this code review:
    ## Individual Comments
    
    ### Comment 1
    <location> `stake/asx/market.py:27` </location>
    <code_context>
         async def get(self) -> MarketStatus:
             data = await self._client.get(self._client.exchange.market_status)
    -        return MarketStatus(**data)
    +        return MarketStatus(
    +            last_trading_date=data[0]["lastTradedTimestamp"],
    +            status=Status(current=data[0]["marketStatus"]),
    </code_context>
    
    <issue_to_address>
    Directly indexing data[0] assumes a non-empty list response.
    
    This could raise an IndexError if the list is empty. Please add a check to handle empty responses.
    </issue_to_address>
    
    ### Comment 2
    <location> `stake/statement.py:40` </location>
    <code_context>
    +    model_config = pydantic.ConfigDict(alias_generator=camelcase, populate_by_name=True)
    +
    +
    +class StatementClient(BaseClient):
    +    """This client is in charge listing the experts' statements for symbols."""
    +
    +    async def list(self, request: StatementRequest) -> List[Statement]:
    +        """Lists all the statements for the symbols specified in the request.
    +
    +        Returns:
    +            List[Statement]: The list of statements.
    +        """
    +        data = await self._client.get(
    +            self._client.exchange.statement.format(
    +                symbol=request.symbol,
    +                date=request.start_date,
    +            )
    +        )
    +
    +        if data == {"message": "No data returned"}:
    +            return []
    +        return [Statement(**d) for d in data]
    </code_context>
    
    <issue_to_address>
    No error handling for unexpected API response structure in StatementClient.list.
    
    Catch pydantic ValidationError when parsing the API response to handle unexpected structures and provide clearer error messages or fallback behavior.
    
    Suggested implementation:
    
    ```python
    from pydantic import ValidationError
    
    class Statement(pydantic.BaseModel):
        date: date
        quarter: int
        year: int
        statement_data: StatementData
        model_config = pydantic.ConfigDict(alias_generator=camelcase, populate_by_name=True)
    
    ```
    
    ```python
            if data == {"message": "No data returned"}:
                return []
            try:
                return [Statement(**d) for d in data]
            except ValidationError as e:
                # You can customize this behavior as needed (e.g., log, return [], etc.)
                raise RuntimeError(f"Failed to parse statement data: {e}") from e
    
    ```
    </issue_to_address>

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    async def get(self) -> MarketStatus:
    data = await self._client.get(self._client.exchange.market_status)
    return MarketStatus(**data)
    return MarketStatus(
    Copy link

    Choose a reason for hiding this comment

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

    issue (bug_risk): Directly indexing data[0] assumes a non-empty list response.

    This could raise an IndexError if the list is empty. Please add a check to handle empty responses.

    Comment on lines +40 to +49
    class StatementClient(BaseClient):
    """This client is in charge listing the experts' statements for symbols."""

    async def list(self, request: StatementRequest) -> List[Statement]:
    """Lists all the statements for the symbols specified in the request.
    Returns:
    List[Statement]: The list of statements.
    """
    data = await self._client.get(
    Copy link

    Choose a reason for hiding this comment

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

    suggestion (bug_risk): No error handling for unexpected API response structure in StatementClient.list.

    Catch pydantic ValidationError when parsing the API response to handle unexpected structures and provide clearer error messages or fallback behavior.

    Suggested implementation:

    from pydantic import ValidationError
    
    class Statement(pydantic.BaseModel):
        date: date
        quarter: int
        year: int
        statement_data: StatementData
        model_config = pydantic.ConfigDict(alias_generator=camelcase, populate_by_name=True)
            if data == {"message": "No data returned"}:
                return []
            try:
                return [Statement(**d) for d in data]
            except ValidationError as e:
                # You can customize this behavior as needed (e.g., log, return [], etc.)
                raise RuntimeError(f"Failed to parse statement data: {e}") from e

    self.trades: Union[
    asx.trade.TradesClient, trade.TradesClient
    ] = asx.trade.TradesClient(self)
    self.equities: Union[asx.equity.EquitiesClient, equity.EquitiesClient] = (
    Copy link

    Choose a reason for hiding this comment

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

    issue (code-quality): Extract code out into method [×2] (extract-method)

    @stabacco stabacco changed the title Add statements Add statements Data Jul 9, 2025
    @stabacco stabacco merged commit 1dbc9e0 into master Jul 9, 2025
    16 checks passed
    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