Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ on:
pull_request:
branches:
- main
- release/0.9.0
- release/1.0.0
push:
branches:
- main
- release/0.9.0
- release/1.0.0
workflow_dispatch:

permissions:
Expand All @@ -23,11 +23,18 @@ jobs:
name: Offline tests - Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
# Report every interpreter result instead of cancelling the matrix on the
# first failure, so a single-version incompatibility is easy to isolate.
fail-fast: false
# Python 3.15 is intentionally absent: it is still a prerelease during the
# 1.0 release work and is not claimed as a supported version.
matrix:
python-version:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -55,10 +62,10 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
- name: Set up Python 3.14
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.14"
- name: Install Poetry
uses: snok/install-poetry@v1
with:
Expand All @@ -70,8 +77,12 @@ jobs:
run: |
rm -rf dist
poetry build
# Validates the built artifacts and a clean wheel installation.
# Runs outside the Poetry environment so the smoke test cannot import
# the repository checkout instead of the installed distribution.
# Validates artifact metadata, source-distribution contents, and a clean
# install of both the wheel and the source distribution. Runs outside the
# Poetry environment so the smoke tests cannot import the repository
# checkout instead of the installed distribution artifact.
- name: Validate release artifacts
run: python scripts/validate_release.py
# Metadata rendering check only; nothing is ever uploaded from CI.
- name: Twine check artifacts
run: poetry run twine check dist/*
109 changes: 59 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ For detailed documentation, check out the [Wiki](https://github.com/zero-sum-sea
python3 -m pip install python-mlb-statsapi
```

### Python support

| Claim | Value |
| --- | --- |
| Minimum declared Python version (`Requires-Python`) | `>=3.10` |
| CI-validated versions | 3.10, 3.11, 3.12, 3.13, 3.14 |

The minimum declared Python version is 3.10 and the CI-validated versions are
3.10 through 3.14. There is no upper Python bound. Prerelease interpreters are
excluded from the required test matrix and are not claimed as supported.

## Quick Start
```python
>>> import mlbstatsapi
Expand Down Expand Up @@ -67,73 +78,70 @@ Seattle Mariners Seattle

## HTTP Sessions, Timeouts, Retries, and Error Behavior

Version 0.8.0 added shared HTTP Sessions, explicit timeouts, optional Session injection, bounded retries, and structured transport exceptions. Version 0.9.0 builds on that transport with configurable HTTP behavior: a public retry policy, richer `MlbHttpError` context, an optional strict mode, compatibility warnings, and a versioned User-Agent.
Version 0.8.0 added shared HTTP Sessions, explicit timeouts, optional Session injection, bounded retries, and structured transport exceptions. Version 0.9.0 made that transport configurable with a public retry policy, richer `MlbHttpError` context, compatibility warnings, and a versioned User-Agent. Version 1.0.0 makes strict HTTP handling the default and documents the stable public API contract.

The `Mlb` client remains synchronous. Shared Sessions pool reusable connections; they do not cache MLB response bodies, and the client does not enable response caching by default.

For the complete reference see the [HTTP transport documentation](docs/http-transport.md). For what changed in this release see the [0.9.0 release notes](docs/releases/0.9.0.md).

### Recommended context-manager usage
For the complete reference see the [HTTP transport documentation](docs/http-transport.md). For what changed in this release see the [1.0.0 release notes](docs/releases/1.0.0.md). For the stable public API boundary see the [public API contract](docs/public-api.md).

Prefer a context manager so library-owned HTTP resources are closed when the block exits, including when the block exits because of an exception:
### Upgrading to version 1.0

```python
import mlbstatsapi
`Mlb()` now uses strict HTTP handling by default. It is equivalent to `Mlb(strict_http=True)`.

with mlbstatsapi.Mlb() as mlb:
player = mlb.get_person(664034)
team = mlb.get_team(136)
```text
Mlb() now uses strict HTTP handling by default
Final non-404 4xx responses raise MlbHttpError
404 keeps endpoint-specific None / [] / {} behavior
Final 5xx still raises MlbHttpError
Timeouts still raise MlbTimeoutError
Transport failures still raise MlbTransportError
Successful invalid JSON still raises MlbDecodeError
```

One `Mlb` client uses one shared `requests.Session`. The v1 and v1.1 adapters share that Session, so repeated requests can reuse pooled connections. A Session manages a pool of reusable connections; it is not one permanent network connection.

Callers who do not use a context manager may call `mlb.close()` instead. Repeated `close()` calls are safe. Closing a client only closes a Session the library created; a caller-injected Session is left open for its owner.

### Compatibility mode is the default

Existing construction continues to work unchanged:
Recommended version 1.0 usage:

```python
import mlbstatsapi

with mlbstatsapi.Mlb() as mlb:
player = mlb.get_person(664034)
try:
with mlbstatsapi.Mlb() as mlb:
player = mlb.get_person(664034)
except mlbstatsapi.MlbHttpError as exc:
print(exc.status_code)
print(exc.reason)
print(exc.url)
```

That is equivalent to:
Temporary compatibility opt-out while migrating:

```python
mlb = mlbstatsapi.Mlb(
strict_http=False,
)
import mlbstatsapi

with mlbstatsapi.Mlb(strict_http=False) as mlb:
player = mlb.get_person(664034)
```

Compatibility mode remains the default in version 0.9.0. A final non-404 4xx response still returns the historical empty result instead of raising, so existing applications keep working after upgrading.
`strict_http=False` is a temporary migration opt-out and an explicit request for historical 0.9 behavior. It is not the recommended long-term 1.0 configuration. See [Migrating from 0.9.x to 1.0](docs/http-transport.md#migrating-from-09x-to-10) for the full process, warning-as-error guidance, and before-and-after examples.

### Optional strict HTTP mode
### Recommended context-manager usage

Applications that would rather fail loudly can opt in to strict mode:
Prefer a context manager so library-owned HTTP resources are closed when the block exits, including when the block exits because of an exception:

```python
import mlbstatsapi

with mlbstatsapi.Mlb(
strict_http=True,
) as mlb:
with mlbstatsapi.Mlb() as mlb:
player = mlb.get_person(664034)
team = mlb.get_team(136)
```

In strict mode:

* A final non-404 4xx response raises `MlbHttpError`
* A final 5xx response raises `MlbHttpError`, as it already did in compatibility mode
* A 404 keeps the existing endpoint-specific behavior and does not raise
One `Mlb` client uses one shared `requests.Session`. The v1 and v1.1 adapters share that Session, so repeated requests can reuse pooled connections. A Session manages a pool of reusable connections; it is not one permanent network connection.

"Final" means after the bounded retry policy has been exhausted. Strict mode is opt-in; it is not the default.
Callers who do not use a context manager may call `mlb.close()` instead. Repeated `close()` calls are safe. Closing a client only closes a Session the library created; a caller-injected Session is left open for its owner.

### Compatibility warnings
### Compatibility mode

When compatibility mode converts a final non-404 4xx response into the historical empty result, the library emits `MlbHttpCompatibilityWarning`. The warning marks a response that strict mode would have raised on, so it doubles as migration guidance.
Callers who need historical 0.9 empty-result behavior for final non-404 4xx responses can pass `strict_http=False`. That path emits `MlbHttpCompatibilityWarning` exactly once per suppressed final response, does not change 404 handling, and does not suppress final 5xx, timeout, transport, or decode failures.

The category inherits from `FutureWarning`, so it stays visible under default Python warning filters. Applications can promote only this package category to an error:

Expand All @@ -147,7 +155,7 @@ warnings.filterwarnings(
)
```

Filter on `mlbstatsapi.MlbHttpCompatibilityWarning` specifically rather than disabling all warnings or all `FutureWarning` instances, which would also hide unrelated notices from other libraries. No warning is emitted for successful responses, 404 responses, intermediate retries, final 5xx responses, or in strict mode.
Filter on `mlbstatsapi.MlbHttpCompatibilityWarning` specifically rather than disabling all warnings or all `FutureWarning` instances, which would also hide unrelated notices from other libraries. Prefer removing `strict_http=False` and catching `MlbHttpError` over permanently ignoring the warning.

### Custom timeouts

Expand Down Expand Up @@ -218,7 +226,7 @@ Caller-injected Session

### Reusing the retry policy on a caller-managed Session

`create_retry_policy()` is public in version 0.9.0. It returns a new instance of the same tested policy the library mounts on Sessions it creates, so a caller-managed Session can opt in to identical retry behavior:
`create_retry_policy()` remains public. It returns a new instance of the same tested policy the library mounts on Sessions it creates, so a caller-managed Session can opt in to identical retry behavior:

```python
import requests
Expand Down Expand Up @@ -250,7 +258,7 @@ A Session created by the library sends a package-specific User-Agent:
python-mlb-statsapi/<installed-version>
```

For this release that resolves to `python-mlb-statsapi/0.9.0`. The version is read from the installed distribution metadata, so it always matches the installed release. Only the `User-Agent` header is set; other Requests defaults such as `Accept-Encoding` remain intact, and the header carries no identifiers beyond the package name and version.
For this release's currently declared package metadata that resolves to `python-mlb-statsapi/1.0.0`. The version is read from the installed distribution metadata, so it always matches the installed release. Only the `User-Agent` header is set; other Requests defaults such as `Accept-Encoding` remain intact, and the header carries no identifiers beyond the package name and version.

Headers on a caller-injected Session are left untouched, so applications that set their own User-Agent keep it.

Expand Down Expand Up @@ -282,7 +290,7 @@ except mlbstatsapi.MlbDecodeError:
* `MlbHttpError` represents an unexpected final HTTP response
* `MlbDecodeError` represents invalid JSON in a successful response

Version 0.9.0 adds `method`, `response_data`, and `body_excerpt` to `MlbHttpError` alongside the existing `status_code`, `reason`, and `url`. `response_data` holds the decoded JSON dictionary or list when the error body contains one, and is `None` otherwise. `body_excerpt` is a bounded excerpt of the response text, capped at 500 characters. Complete response bodies are never automatically logged, and `str(exc)` stays concise.
`MlbHttpError` exposes `method`, `status_code`, `reason`, `url`, `response_data`, and `body_excerpt`. `response_data` holds the decoded JSON dictionary or list when the error body contains one, and is `None` otherwise. `body_excerpt` is a bounded excerpt of the response text, capped at 500 characters. Complete response bodies are never automatically logged, and `str(exc)` stays concise.

### Backward-compatible exception handling

Expand Down Expand Up @@ -318,31 +326,31 @@ Backoff factor: 0.5
Retry-After respected: yes
```

Only GET requests are retried, and retries are bounded. Ordinary client errors such as 400, 401, 403, and 404 are not retried. Invalid JSON and Pydantic validation failures are not retried. Retries improve resilience for transient failures, but they do not guarantee success. The retry values are unchanged from version 0.8.0.
Only GET requests are retried, and retries are bounded. Ordinary client errors such as 400, 401, 403, and 404 are not retried. Invalid JSON and Pydantic validation failures are not retried. Retries improve resilience for transient failures, but they do not guarantee success. The retry values are unchanged from versions 0.8.0 and 0.9.0. The version 1.0 strict default does not change retry or Session behavior.

### Existing 404 compatibility

Version 0.9.0 preserves existing endpoint-specific not-found behavior in both compatibility mode and strict mode. Depending on the endpoint, a 404 may still produce:
Version 1.0.0 preserves existing endpoint-specific not-found behavior under both the default and `strict_http=False`. Depending on the endpoint, a 404 may still produce:

```text
None
[]
{}
```

Not every 404 raises `MlbHttpError`, and strict mode does not change that.
Not every 404 raises `MlbHttpError`, and the strict default does not change that.

### HTTP behavior at a glance

| Final response | Compatibility mode (default) | Strict mode |
| -------------- | ---------------------------- | ----------- |
| Final response | Default 1.0 behavior | Explicit compatibility mode |
| -------------- | -------------------- | --------------------------- |
| Successful 2xx | Normal result | Normal result |
| Non-404 4xx | Warning and historical empty result | `MlbHttpError` |
| Non-404 4xx | `MlbHttpError` | Warning and historical empty result |
| 404 | Existing endpoint behavior | Existing endpoint behavior |
| Final 429 | Warning and historical empty result | `MlbHttpError` |
| Final 429 | `MlbHttpError` after retries | Warning and historical empty result after retries |
| Final 5xx | `MlbHttpError` | `MlbHttpError` |

See the [HTTP transport documentation](docs/http-transport.md) for the complete retry policy, Session ownership rules, warning behavior, cleanup behavior, and exception hierarchy, and the [0.9.0 release notes](docs/releases/0.9.0.md) for the release summary and migration guidance.
See the [HTTP transport documentation](docs/http-transport.md) for the complete retry policy, Session ownership rules, warning behavior, cleanup behavior, and migration guidance, and the [1.0.0 release notes](docs/releases/1.0.0.md) for the release summary.

## Working with Pydantic Models

Expand Down Expand Up @@ -483,9 +491,10 @@ poetry run pytest tests/
rm -rf dist
poetry build
python3 scripts/validate_release.py
poetry run twine check dist/*
```

`scripts/validate_release.py` is the same release check offline CI runs. It inspects the built wheel and source distribution, installs the wheel into a temporary virtual environment, and runs a public-import smoke test against the installed package. It never contacts the MLB API.
`scripts/validate_release.py` is the same release check offline CI runs. It inspects the built wheel and source distribution, clean-installs each artifact into its own temporary virtual environment, and runs the same public-API smoke test against both installed artifacts. The smoke test verifies the declared metadata, the supported package-root imports, the strict HTTP default, explicit strict and compatibility modes, the versioned `User-Agent`, and injected-Session ownership. Every response it observes comes from an injected fake Session, so it never contacts the MLB API.

Offline CI is the normal pull-request gate. External tests are available manually, on a weekly schedule, and before releases.

Expand Down
Loading
Loading