Skip to content

Implement AsyncMlb vertical slice #303

Description

@Mattsface

Parent: #297
Depends on: #299, #301, #302

Goal

Add the first public AsyncMlb client with a deliberately small set of representative endpoints to prove the end-to-end architecture before expanding coverage.

Initial scope

Implement AsyncMlb with:

  • public root import: from mlbstatsapi import AsyncMlb
  • constructor shape aligned with Define the v1.1 async API and transport contract #298
  • async with AsyncMlb() support
  • explicit aclose()
  • shared async transport lifecycle
  • existing strict/compatibility settings from the async contract
  • initial endpoints:
    • get_team
    • get_person
    • get_schedule

Scope boundary with #302

#302 locks down the deterministic contract for AsyncMlbDataAdapter and the transport layer. This issue owns the higher-level AsyncMlb lifecycle that cannot be tested until AsyncMlb exists.

In particular, #303 owns deterministic coverage for:

  • async with AsyncMlb(...)
  • AsyncMlb.__aenter__() returning the AsyncMlb instance
  • AsyncMlb.__aexit__() awaiting cleanup
  • automatic cleanup on normal context exit
  • automatic cleanup when an exception leaves the async context
  • automatic cleanup when the surrounding task is cancelled
  • preserving the original exception or asyncio.CancelledError while cleanup runs
  • keeping caller-injected clients open during all of the above

Adapter-level ownership, explicit AsyncMlbDataAdapter.aclose(), retry, transport, and cancellation behavior remain covered by #301/#302 and should not be duplicated here except where needed to prove the public AsyncMlb behavior end to end.

Contract requirements from #298

Public API

  • AsyncMlb is a stable public 1.x API
  • Public async endpoint names/signatures become compatibility commitments once released
  • Existing synchronous Mlb behavior and signatures remain unchanged
  • Return the same existing Pydantic/domain models as sync equivalents
  • Preserve the same None, [], and {} behavior for equivalent responses

Constructor and lifecycle

Public shape should mirror sync as closely as practical:

AsyncMlb(
    hostname="statsapi.mlb.com",
    logger=None,
    timeout=DEFAULT_TIMEOUT,
    client=None,
    *,
    strict_http=True,
)
  • async with returns the AsyncMlb instance
  • __aexit__() awaits aclose()
  • aclose() is idempotent and closes only library-owned async transport
  • Caller-injected clients remain caller-owned and open
  • Cleanup must preserve original exceptions and cancellation
  • Context-manager cleanup behavior on normal exit, exception, and cancellation is part of this issue's deterministic test coverage

Endpoint behavior

For get_team, get_person, and get_schedule:

Concurrency

  • One AsyncMlb instance supports multiple concurrent in-flight endpoint calls on the same event loop
  • Concurrency remains caller-controlled
  • Endpoint methods do not introduce hidden fan-out, detached tasks, worker pools, or unrelated background work
  • Per-request state remains independent
  • Caller-created in-flight operations must finish before the client is closed

Design requirement

Do not duplicate parsing/model-construction logic when a shared helper from #299 can be used.

Transport-specific implementation details must remain behind AsyncMlbDataAdapter rather than leaking into the public AsyncMlb API.

Do not duplicate the exhaustive adapter-level contract matrix from #302. Add only the transport assertions required to prove the public AsyncMlb lifecycle and endpoint behavior end to end.

Acceptance criteria

  • AsyncMlb is importable from mlbstatsapi
  • Constructor/lifecycle behavior matches Define the v1.1 async API and transport contract #298
  • async with correctly cleans up library-owned resources on normal exit
  • async with correctly cleans up library-owned resources when an exception leaves the context
  • async with correctly cleans up library-owned resources when the surrounding task is cancelled
  • Context-manager cleanup preserves the original exception or asyncio.CancelledError
  • Caller-injected clients remain open across explicit and context-manager cleanup
  • Explicit aclose() is idempotent and safe
  • get_team, get_person, and get_schedule work end to end
  • Returned models and empty-result behavior match sync equivalents
  • Initial endpoints reuse shared parsing/model construction
  • Same-client concurrent endpoint calls work on one event loop
  • Endpoint calls do not introduce hidden fan-out/background work
  • Existing Mlb behavior and signatures remain unchanged
  • Initial public async usage has deterministic tests without duplicating Add deterministic async transport contract tests #302's adapter-level contract matrix

Refs #297
Contract: #298

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions