diff --git a/README.md b/README.md index 1e10bbc..638c0e9 100644 --- a/README.md +++ b/README.md @@ -67,73 +67,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). +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). -### Recommended context-manager usage - -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: @@ -147,7 +144,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 @@ -218,7 +215,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 @@ -250,7 +247,7 @@ A Session created by the library sends a package-specific User-Agent: python-mlb-statsapi/ ``` -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/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. Headers on a caller-injected Session are left untouched, so applications that set their own User-Agent keep it. @@ -282,7 +279,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 @@ -318,11 +315,11 @@ 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 @@ -330,19 +327,19 @@ 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 diff --git a/docs/http-transport.md b/docs/http-transport.md index df04dc0..67b3651 100644 --- a/docs/http-transport.md +++ b/docs/http-transport.md @@ -1,12 +1,19 @@ # HTTP Transport -This document describes the HTTP transport behavior of the current release, version 0.9.0. +This document describes the HTTP transport behavior of the current release, +version 1.0.0. -Version 0.8.0 introduced shared Sessions, explicit timeouts, bounded retries, and structured exceptions. Version 0.9.0 keeps all of that and adds 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 introduced shared Sessions, explicit timeouts, bounded retries, +and structured exceptions. Version 0.9.0 introduced configurable strict +behavior and compatibility warnings. Version 1.0.0 makes strict handling the +default and defines the stable public contract. -The public client remains synchronous. Ordinary usage does not need to configure sessions or retries. +The public client remains synchronous. Ordinary usage does not need to +configure sessions or retries. -See [the 0.9.0 release notes](releases/0.9.0.md) for a shorter summary of what changed. +See [the 1.0.0 release notes](releases/1.0.0.md) for a shorter summary of what +changed. For the authoritative public API boundary see +[the public API contract](public-api.md). ## Public transport API @@ -26,8 +33,9 @@ from mlbstatsapi import ( ) ``` -Names that are not exported from `mlbstatsapi` are internal and may change without a -deprecation cycle. +Names that are not exported from `mlbstatsapi` are internal and may change +without a deprecation cycle. See [public-api.md](public-api.md) for the +complete stability classification. ## Existing usage @@ -40,11 +48,13 @@ mlb = mlbstatsapi.Mlb() player = mlb.get_person(664034) ``` -The client remains synchronous. Async support is not part of version 0.9.0. +In version 1.0.0 that construction uses strict HTTP handling by default. The +client remains synchronous. Async support is not part of version 1.0.0. ## Context manager -Prefer a context manager when you want automatic cleanup of a library-created Session: +Prefer a context manager when you want automatic cleanup of a library-created +Session: ```python import mlbstatsapi @@ -89,7 +99,8 @@ That means: 30 seconds: read timeout ``` -The read timeout is the maximum wait while reading response data. It is not one total wall-clock duration for the complete request. +The read timeout is the maximum wait while reading response data. It is not +one total wall-clock duration for the complete request. ## Custom timeout @@ -132,8 +143,8 @@ A Session is not: ## Session ownership -Session ownership is the single most important rule in this document. Whoever creates the -Session configures it and closes it. +Session ownership is the single most important rule in this document. Whoever +creates the Session configures it and closes it. ```text Library-created Session @@ -147,9 +158,12 @@ Caller-injected Session Existing headers remain untouched ``` -The library never installs adapters, replaces headers, or closes a Session it did not -create. `Mlb.close()` and exiting `with Mlb(session=session)` both leave an injected -Session open. +The library never installs adapters, replaces headers, or closes a Session it +did not create. `Mlb.close()` and exiting `with Mlb(session=session)` both +leave an injected Session open. + +The version 1.0 strict default does not change Session ownership, injection, +or cleanup behavior. ## Session injection @@ -168,10 +182,10 @@ finally: session.close() ``` -Callers who inject a Session control its retry, TLS, proxy, header, and adapter -configuration. See [Reusing the retry policy on a caller-managed -Session](#reusing-the-retry-policy-on-a-caller-managed-session) for opting in to the -library's tested retry policy. +Callers who inject a Session control its retry, TLS, proxy, header, and +adapter configuration. See [Reusing the retry policy on a caller-managed +Session](#reusing-the-retry-policy-on-a-caller-managed-session) for opting in +to the library's tested retry policy. ## User-Agent @@ -181,14 +195,14 @@ Library-created Sessions send a package-specific User-Agent: python-mlb-statsapi/ ``` -For this release that resolves to: +With the package version currently declared in project metadata that resolves to: ```text python-mlb-statsapi/0.9.0 ``` -The version comes from the installed package metadata, so it always matches the -installed release without a separately maintained version string. +The version comes from the installed package metadata, so it always matches +the installed release without a separately maintained version string. Notes: @@ -229,10 +243,12 @@ finally: ## Default retry policy -Library-created Sessions mount a bounded retry policy for GET requests automatically. +Library-created Sessions mount a bounded retry policy for GET requests +automatically. -Caller-injected Sessions are never automatically reconfigured. Retry settings on an -injected Session remain under the caller's control unless the caller opts in. +Caller-injected Sessions are never automatically reconfigured. Retry settings +on an injected Session remain under the caller's control unless the caller +opts in. ```text Initial request: 1 @@ -270,68 +286,81 @@ Additional rules: * Pydantic validation failures are not retried * Application parsing failures are not retried * A final 404 preserves existing not-found behavior -* A final 429 preserves existing 4xx compatibility by default and warns +* A final non-404 4xx, including a final 429, raises `MlbHttpError` under the default * A final 5xx raises `MlbHttpError` -* In strict mode, a final non-404 4xx (including a final 429) raises `MlbHttpError` +* Explicit `strict_http=False` preserves the historical empty result for final non-404 4xx and warns -Retries improve resilience for transient failures. They do not guarantee success. +Retries improve resilience for transient failures. They do not guarantee +success. The version 1.0 strict default does not change retry values or which +statuses are retried. -## HTTP compatibility modes +## Default HTTP behavior -Compatibility mode remains the default: +Version 1.0.0 defaults to strict HTTP handling. These constructions are +equivalent: ```python mlb = mlbstatsapi.Mlb() +mlb = mlbstatsapi.Mlb(strict_http=True) ``` -or: +Default behavior: -```python -mlb = mlbstatsapi.Mlb( - strict_http=False, -) -``` +```text +Successful 2xx + Return the normal endpoint result -Callers may explicitly enable strict mode: +Final non-404 4xx + Raise MlbHttpError by default -```python -mlb = mlbstatsapi.Mlb( - strict_http=True, -) +404 + Preserve endpoint-specific None, [], or {} behavior + +Final 429 + Retry first, then raise MlbHttpError under the default + +Final 5xx + Retry where configured, then raise MlbHttpError + +Timeout + Raise MlbTimeoutError + +Transport failure + Raise MlbTransportError + +Successful malformed JSON + Raise MlbDecodeError ``` -Behavior: +"Final" means the response remaining after the bounded retry policy has +completed. Intermediate retried responses neither raise nor warn. -| Final response | Compatibility mode | Strict mode | -| -------------- | ----------------------------------- | -------------------------- | -| Successful 2xx | Normal result | Normal result | -| Non-404 4xx | Warning and historical empty result | `MlbHttpError` | -| 404 | Existing endpoint behavior | Existing endpoint behavior | -| Final 429 | Warning and historical empty result | `MlbHttpError` | -| Final 5xx | `MlbHttpError` | `MlbHttpError` | +## Behavior table -Every row describes the *final* response. A retryable status such as 429, 500, 502, 503, -or 504 is evaluated against this table only after the bounded retry policy has been -exhausted; intermediate retried responses neither raise nor warn. +| Final response | Default 1.0 behavior | Explicit `strict_http=False` | +| -------------- | ---------------------------- | ------------------------------------------------- | +| Successful 2xx | Normal result | Normal result | +| Non-404 4xx | `MlbHttpError` | Warning and historical empty result | +| 404 | Existing endpoint behavior | Existing endpoint behavior | +| Final 429 | `MlbHttpError` after retries | Warning and historical empty result after retries | +| Final 5xx | `MlbHttpError` | `MlbHttpError` | Notes: -* Compatibility mode remains the default -* Strict mode is explicitly opt-in -* Strict mode applies only after retries are exhausted -* Strict mode does not make 404 raise -* Strict mode does not change transport or decode exceptions -* Strict-mode exceptions include the richer context from `MlbHttpError` -* Existing constructor usage remains valid -* Compatibility mode emits `MlbHttpCompatibilityWarning` for a suppressed non-404 4xx +* `Mlb()` and `Mlb(strict_http=True)` are equivalent spellings of the default +* `strict_http=False` is an explicit compatibility opt-out, not the preferred long-term configuration +* Strict handling applies only after retries are exhausted +* Strict handling does not make 404 raise +* Strict handling does not change transport or decode exceptions +* Raised `MlbHttpError` instances include the richer response context attributes -Example: +Recommended default usage: ```python import mlbstatsapi try: - with mlbstatsapi.Mlb(strict_http=True) as mlb: + with mlbstatsapi.Mlb() as mlb: player = mlb.get_person(664034) except mlbstatsapi.MlbHttpError as exc: print(exc.method) @@ -342,44 +371,66 @@ except mlbstatsapi.MlbHttpError as exc: print(exc.body_excerpt) ``` -## Compatibility warnings +## Compatibility mode -Version 0.9.0 emits `MlbHttpCompatibilityWarning` when compatibility mode returns the -historical empty result for a final non-404 4xx response. +```python +mlb = mlbstatsapi.Mlb(strict_http=False) +``` -The warning means strict mode would have raised `MlbHttpError` for the same response. +is an explicit compatibility opt-out. It: -```python -import mlbstatsapi +* Preserves the historical empty result for final non-404 4xx responses +* Emits `MlbHttpCompatibilityWarning` exactly once per suppressed final response +* Does not change 404 behavior +* Does not suppress final 5xx errors +* Does not alter timeout, transport, or decode failures +* Runs only after retry exhaustion -mlb = mlbstatsapi.Mlb() -sports = mlb.get_sports() -``` +Compatibility mode is a temporary migration path and an explicit request for +historical 0.9 behavior. It is not the recommended long-term 1.0 +configuration. + +## Compatibility warnings + +When `strict_http=False` converts a final non-404 4xx response into the +historical empty result, the library emits `MlbHttpCompatibilityWarning`. -A representative warning looks like: +The warning means the default strict path would have raised `MlbHttpError` for +the same response. It is emitted once per suppressed final response and is +attributed to the public caller frame outside the `mlbstatsapi` package +namespace. + +The warning's semantic content includes: ```text -HTTP 403 for https://statsapi.mlb.com/api/v1/sports was handled through -compatibility mode and returned the historical empty result. Pass -strict_http=True to raise MlbHttpError. This compatibility behavior may -change in version 1.0. +Status code +Request URL +strict_http=False selected compatibility mode +Historical empty result was returned +Strict handling is the version 1.0 default +How to receive MlbHttpError instead ``` -The category inherits from `FutureWarning` so the migration notice stays visible under -default Python warning filters. +Warning messages contain only the status code and request URL from the +response. Response bodies, headers, credentials, cookies, and tokens are never +included. Do not treat the complete prose string as a stable public contract; +filter and handle the warning by category. + +The category inherits from `FutureWarning` so the migration notice stays +visible under default Python warning filters. A warning is emitted only when all three of the following are true: -* Compatibility mode is active +* Compatibility mode is active (`strict_http=False`) * The final response is a non-404 4xx -* Strict mode would have raised `MlbHttpError` for the same response +* The default strict path would have raised `MlbHttpError` for the same response No warning is emitted for: ```text Successful responses 404 -Strict mode +Default strict handling Intermediate retries Final 5xx Timeouts @@ -390,38 +441,26 @@ Pydantic validation failures When the warning is emitted: -| Response | Warning | -| ------------------------- | ------- | -| Non-404 4xx, compatibility mode | Yes | -| Final 429, compatibility mode, after retries | Yes | -| Non-404 4xx, strict mode | No, `MlbHttpError` is raised instead | -| 404, either mode | No | -| Successful 2xx | No | -| Final 5xx | No, `MlbHttpError` is raised in both modes | -| Timeout, transport, decode, or validation failure | No | +| Response | Warning | +| -------------------------------------------------- | ------- | +| Non-404 4xx, `strict_http=False` | Yes | +| Final 429, `strict_http=False`, after retries | Yes | +| Non-404 4xx, default / `strict_http=True` | No, `MlbHttpError` is raised instead | +| 404, either mode | No | +| Successful 2xx | No | +| Final 5xx | No, `MlbHttpError` is raised in both modes | +| Timeout, transport, decode, or validation failure | No | Additional rules: -* The warning never changes the return value; compatibility mode still returns the - historical empty result in version 0.9.0 +* The warning never changes the return value; compatibility mode still returns the historical empty result * A final 404 remains warning-free and keeps existing `None` / `[]` / `{}` behavior * Warnings are emitted only after retries are exhausted, so a retried 429 warns once -* Strict mode does not warn because it raises `MlbHttpError` directly -* Warning messages contain only the status code and request URL, never response - bodies, headers, or credentials -* Stricter defaults may be introduced in version 1.0 +* Default strict handling does not warn because it raises `MlbHttpError` directly -Enabling strict mode is the recommended migration: - -```python -import mlbstatsapi +## Warning-as-error environments -mlb = mlbstatsapi.Mlb( - strict_http=True, -) -``` - -Applications may also turn only this package warning into an exception: +Applications may treat warnings as exceptions: ```python import warnings @@ -433,7 +472,11 @@ warnings.filterwarnings( ) ``` -Or silence only this category: +Under `strict_http=False`, this can turn a suppressed 4xx into a warning +exception. The preferred migration is to remove `strict_http=False` and catch +`MlbHttpError`. A temporary targeted warning filter is acceptable. + +Temporary targeted ignore, labeled as migration-only behavior: ```python import warnings @@ -445,21 +488,75 @@ warnings.filterwarnings( ) ``` -Prefer enabling strict mode over permanently ignoring the warning when the application -wants explicit HTTP failures. Filter by `mlbstatsapi.MlbHttpCompatibilityWarning` rather -than disabling all `FutureWarning` or all warnings, which would also hide unrelated -notices from other libraries. +Ignoring the warning is temporary migration behavior. Disabling every warning +or every `FutureWarning` is not recommended; that would also hide unrelated +notices from other libraries. Filter by +`mlbstatsapi.MlbHttpCompatibilityWarning` specifically. + +## Migrating from 0.9.x to 1.0 + +Recommended process: + +1. Identify code that relied on empty results for failed non-404 4xx responses +2. Add handling for `MlbHttpError` +3. Distinguish 404 domain results from other HTTP failures +4. Use `strict_http=False` only where migration cannot happen immediately +5. Test warning-as-error configurations +6. Remove `strict_http=False` +7. Confirm injected Session and retry behavior remain correct + +Version 0.9-style compatibility: + +```python +import mlbstatsapi + +with mlbstatsapi.Mlb(strict_http=False) as mlb: + player = mlb.get_person(664034) +``` + +Recommended 1.0 state: + +```python +import mlbstatsapi + +try: + with mlbstatsapi.Mlb() as mlb: + player = mlb.get_person(664034) +except mlbstatsapi.MlbHttpError as exc: + print(exc.status_code) + print(exc.url) +``` + +A missing person may still return `None` on a 404 and is not necessarily an +exception. Catch `MlbHttpError` for unexpected HTTP failures; continue treating +endpoint-specific 404 empty results as domain-level not-found outcomes. + +## Public API stability + +Version 1.0.0 documents the stable public API in +[public-api.md](public-api.md). That contract covers package-root imports, +constructor signatures, the exception hierarchy, Session ownership, documented +endpoint methods, Python support, and the boundary between public and +internal APIs. + +This transport guide does not duplicate that contract. In particular, version +1.0 does not promise that: + +* Every upstream MLB response field is frozen +* Every class in `mlbstatsapi.models` is permanently stable +* The unofficial MLB API itself will never change +* Private underscore-prefixed names are public ## Reusing the retry policy on a caller-managed Session -`create_retry_policy()` returns a new instance of the same tested retry policy used -internally for library-created Sessions. +`create_retry_policy()` returns a new instance of the same tested retry policy +used internally for library-created Sessions. -Callers who inject a Session must mount the policy themselves. The library does not -install or replace adapters on caller-injected Sessions. +Callers who inject a Session must mount the policy themselves. The library +does not install or replace adapters on caller-injected Sessions. -Callers retain control over connection-pool sizes and other `HTTPAdapter` options. -The caller remains responsible for closing an injected Session. +Callers retain control over connection-pool sizes and other `HTTPAdapter` +options. The caller remains responsible for closing an injected Session. ```python import requests @@ -481,8 +578,8 @@ finally: session.close() ``` -Mounting the same adapter instance for both schemes is valid. Callers may also mount -separate adapters when they need different settings for HTTP and HTTPS. +Mounting the same adapter instance for both schemes is valid. Callers may also +mount separate adapters when they need different settings for HTTP and HTTPS. ## Structured exceptions @@ -525,7 +622,8 @@ except MlbDecodeError: print("The MLB API returned invalid JSON") ``` -Backward-compatible handling remains valid because all new errors inherit from `TheMlbStatsApiException`: +Backward-compatible handling remains valid because all new errors inherit from +`TheMlbStatsApiException`: ```python try: @@ -591,8 +689,8 @@ except mlbstatsapi.MlbHttpError as exc: ## Existing 404 behavior -Version 0.9.0 preserves endpoint-specific not-found behavior in both compatibility mode -and strict mode. +Version 1.0.0 preserves endpoint-specific not-found behavior under both the +default and `strict_http=False`. Depending on the endpoint, a 404 may become: @@ -602,27 +700,13 @@ None {} ``` -Not every 404 raises `MlbHttpError`. Strict mode does not change this, and a 404 never -emits `MlbHttpCompatibilityWarning`. - -## Version 1.0 migration direction - -Version 0.9.0 keeps compatibility mode as the default. - -The `MlbHttpCompatibilityWarning` notices exist to give applications advance migration -guidance: each warning marks a response that strict mode would already have raised on. - -A future 1.0 release may make stricter non-404 4xx behavior the default. No final 1.0 -decision is implemented here, and nothing about the current return shapes changes in -version 0.9.0. - -Applications that want the future-facing behavior today can enable strict mode, and -applications that want to find affected call sites early can turn -`MlbHttpCompatibilityWarning` into an error. +Not every 404 raises `MlbHttpError`. The strict default does not change this, +and a 404 never emits `MlbHttpCompatibilityWarning`. ## No response caching -Shared Sessions pool network connections. They do not cache MLB response bodies. +Shared Sessions pool network connections. They do not cache MLB response +bodies. The client has no default response cache. @@ -630,4 +714,4 @@ The client has no default response cache. The client remains synchronous. -Async support is not part of version 0.9.0. +Async support is not part of version 1.0.0. diff --git a/docs/releases/1.0.0.md b/docs/releases/1.0.0.md new file mode 100644 index 0000000..21413e5 --- /dev/null +++ b/docs/releases/1.0.0.md @@ -0,0 +1,251 @@ +# python-mlb-statsapi 1.0.0 + +Version 1.0.0 is the stable HTTP contract release. + +Version 0.8.0 made the network layer reliable. Version 0.9.0 made HTTP behavior +configurable and introduced a warning-backed migration path toward stricter +failures. Version 1.0.0 completes that migration: strict HTTP handling is now +the default, while an explicit compatibility opt-out remains available for +callers who need more time to migrate. + +The primary breaking change is that `Mlb()` and `MlbDataAdapter()` now default +to `strict_http=True`. A final non-404 4xx response raises `MlbHttpError` +instead of returning the historical empty result. + +Endpoint-specific 404 behavior, Session ownership, retry values, structured +exceptions, and the synchronous public client remain unchanged. + +## Breaking change + +### Strict HTTP behavior is now the default + +In version 0.9.x, these constructions were equivalent and kept compatibility +mode: + +```python +import mlbstatsapi + +mlb = mlbstatsapi.Mlb() +mlb = mlbstatsapi.Mlb(strict_http=False) +``` + +In version 1.0.0, the default matches explicit strict handling: + +```python +import mlbstatsapi + +mlb = mlbstatsapi.Mlb() +mlb = mlbstatsapi.Mlb(strict_http=True) +``` + +Default behavior for a final response: + +```text +Successful 2xx + Return the normal endpoint result + +Final non-404 4xx + Raise MlbHttpError + +404 + Preserve endpoint-specific None, [], or {} behavior + +Final 5xx + Raise MlbHttpError + +Timeout + Raise MlbTimeoutError + +Transport failure + Raise MlbTransportError + +Successful invalid JSON + Raise MlbDecodeError +``` + +"Final" means the response remaining after the bounded retry policy has +completed. + +## Highlights + +### Endpoint-specific 404 behavior is preserved + +A 404 still follows the existing per-endpoint contract. Depending on the +endpoint, a missing resource may return: + +```text +None +[] +{} +``` + +Not every 404 raises `MlbHttpError`. The strict default does not change that. + +### Explicit compatibility mode remains available + +Callers who need the historical empty-result path can opt out temporarily: + +```python +import mlbstatsapi + +with mlbstatsapi.Mlb(strict_http=False) as mlb: + player = mlb.get_person(664034) +``` + +Compatibility mode: + +* Preserves the historical empty result for final non-404 4xx responses +* Emits `MlbHttpCompatibilityWarning` exactly once per suppressed final response +* Does not change 404 behavior +* Does not suppress final 5xx errors +* Does not alter timeout, transport, or decode failures +* Runs only after retry exhaustion + +It 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. + +Compatibility warnings point to the public caller frame outside the package +namespace. Messages include the status code and request URL, state that +`strict_http=False` selected compatibility mode, note that the historical empty +result was returned, identify strict handling as the version 1.0 default, and +explain how to receive `MlbHttpError` instead. Response bodies, headers, +credentials, cookies, and tokens are never included. + +### Stable public API contract + +Version 1.0.0 establishes the stable public API contract documented in +[public-api.md](../public-api.md). That document is the authoritative +classification for package-root imports, constructor signatures, the exception +hierarchy, Session ownership, documented endpoint methods, Python support, and +internal or private APIs. + +This release does not freeze every upstream MLB response field, every class +under `mlbstatsapi.models`, or private underscore-prefixed names, and it does +not promise that the unofficial MLB API itself will never change. + +### Session ownership remains explicit + +```text +Library-created Session + Configured and closed by the library + Receives retry adapters + Receives the package User-Agent + +Caller-injected Session + Configured and closed by the caller + Existing adapters remain untouched + Existing headers remain untouched +``` + +Ownership rules are unchanged from versions 0.8.0 and 0.9.0. The new strict +default does not change Session creation, injection, or cleanup behavior. + +### Retry behavior remains bounded + +Library-created Sessions continue to retry temporary GET failures for: + +```text +429 +500 +502 +503 +504 +``` + +```text +Initial request: 1 +Maximum retries: 3 +Maximum total attempts: 4 +Backoff factor: 0.5 +Retry-After respected: yes +``` + +Retry values are unchanged. Ordinary client errors such as 400, 401, 403, and +404 are not retried. Invalid JSON and Pydantic validation failures are not +retried. The new strict default is evaluated only after retries are exhausted. + +### Structured exceptions + +The exception hierarchy is unchanged: + +```text +TheMlbStatsApiException +├── MlbTransportError +│ └── MlbTimeoutError +├── MlbHttpError +└── MlbDecodeError +``` + +`MlbHttpError` continues to expose `method`, `status_code`, `reason`, `url`, +`response_data`, and `body_excerpt`. Broad catches of +`TheMlbStatsApiException` remain valid. + +### Testing and release validation + +Deterministic offline coverage documents the version 1.0 HTTP contract, +including the strict default, explicit compatibility mode, warning behavior, +404 return shapes, Session ownership, and retry exhaustion. + +`scripts/validate_release.py` remains the packaging smoke check for the built +wheel and source distribution. It never contacts the MLB API. + +## Migration guidance + +Recommended process when upgrading from 0.9.x: + +1. Identify code that relied on empty results for failed non-404 4xx responses +2. Add handling for `MlbHttpError` +3. Distinguish 404 domain results from other HTTP failures +4. Use `strict_http=False` only where migration cannot happen immediately +5. Test warning-as-error configurations +6. Remove `strict_http=False` +7. Confirm injected Session and retry behavior remain correct + +Version 0.9-style temporary compatibility: + +```python +import mlbstatsapi + +with mlbstatsapi.Mlb(strict_http=False) as mlb: + player = mlb.get_person(664034) +``` + +Recommended 1.0 state: + +```python +import mlbstatsapi + +try: + with mlbstatsapi.Mlb() as mlb: + player = mlb.get_person(664034) +except mlbstatsapi.MlbHttpError as exc: + print(exc.status_code) + print(exc.url) +``` + +A missing person may still return `None` on a 404 and is not necessarily an +exception. + +Applications that treat warnings as exceptions should prefer removing +`strict_http=False` and catching `MlbHttpError`. A temporary targeted filter +on `MlbHttpCompatibilityWarning` is acceptable during migration; disabling +every warning or every `FutureWarning` is not recommended. + +## Documentation + +* [HTTP transport documentation](../http-transport.md) +* [Public API contract](../public-api.md) +* README upgrading section and HTTP behavior summary + +## Not included + +Version 1.0.0 does not add: + +* Async support +* Response caching +* New MLB endpoints +* Global rate limiting +* Strict handling for endpoint-specific 404 responses +* Automatic modification of injected Sessions +* Telemetry +* New retry values