|
| 1 | +# Python Client Compatibility Layer |
| 2 | + |
| 3 | +This directory contains the backward compatibility layer for the OpenAPI v7 generated Python client. |
| 4 | + |
| 5 | +## Purpose |
| 6 | + |
| 7 | +The v7 OpenAPI generator produces clients with breaking changes from v5: |
| 8 | +- `@validate_call` rejects unknown kwargs like `async_req` |
| 9 | +- Union types (oneOf/anyOf) require `.actual_instance` access |
| 10 | +- ModelSimple wrappers removed (no more `.value` on Height, TxInputs, etc.) |
| 11 | +- Pydantic v2 models don't support dict-style access |
| 12 | + |
| 13 | +This compatibility layer patches the generated client to maintain backward compatibility with existing user code. |
| 14 | + |
| 15 | +## Files |
| 16 | + |
| 17 | +- `patch_compat.py` - Post-processing script that patches the generated client |
| 18 | +- `test_compat.py` - Comprehensive tests for backward compatibility |
| 19 | +- `README.md` - This file |
| 20 | + |
| 21 | +## Usage |
| 22 | + |
| 23 | +The patch script is called automatically by the Makefile after code generation: |
| 24 | + |
| 25 | +```bash |
| 26 | +cd clients/python |
| 27 | +make generate-openapi-client |
| 28 | +``` |
| 29 | + |
| 30 | +To run just the compatibility tests: |
| 31 | + |
| 32 | +```bash |
| 33 | +python clients/python-compat/test_compat.py |
| 34 | +# Or specify a client directory: |
| 35 | +python clients/python-compat/test_compat.py clients/python |
| 36 | +``` |
| 37 | + |
| 38 | +## What Gets Patched |
| 39 | + |
| 40 | +### 1. Compat Types (`compat.py`) |
| 41 | + |
| 42 | +- **CompatInt**: Integer subclass with `.value` property and arithmetic that preserves type |
| 43 | +- **CompatList**: List subclass with `.value` property |
| 44 | +- **DictModel**: Dict wrapper with attribute access for oneOf/anyOf models |
| 45 | +- **Height**: Alias for CompatInt |
| 46 | + |
| 47 | +### 2. Model Files |
| 48 | + |
| 49 | +- Height fields (`height`, `before_block`, `after_block`) wrapped in CompatInt |
| 50 | +- List fields (`inputs`, `outputs`, `actors`, `address`) wrapped in CompatList |
| 51 | +- OneOf models get transparent `__getattr__` delegation to `actual_instance` |
| 52 | + |
| 53 | +### 3. API Files |
| 54 | + |
| 55 | +- `@validate_call` replaced with `@validate_call_compat` that: |
| 56 | + - Accepts `async_req`, `_preload_content`, `_return_http_data_only` kwargs |
| 57 | + - Converts datetime to ISO 8601 strings for date parameters |
| 58 | + - Submits to thread pool when `async_req=True` |
| 59 | + |
| 60 | +### 4. ApiClient |
| 61 | + |
| 62 | +- `pool_threads` parameter creates ThreadPoolExecutor for async support |
| 63 | +- List responses wrapped in CompatList |
| 64 | +- Headers properly converted to dict |
| 65 | + |
| 66 | +## Features |
| 67 | + |
| 68 | +### Arithmetic Preservation (Fix 3) |
| 69 | +```python |
| 70 | +h = CompatInt(100) |
| 71 | +result = h + 1 # Returns CompatInt(101), not plain int |
| 72 | +result.value # Still works: 101 |
| 73 | +``` |
| 74 | + |
| 75 | +### Dict Protocol (Fix 2) |
| 76 | +```python |
| 77 | +d = DictModel({'a': 1, 'b': 2}) |
| 78 | +'a' in d # True |
| 79 | +len(d) # 2 |
| 80 | +list(d.keys()) # ['a', 'b'] |
| 81 | +``` |
| 82 | + |
| 83 | +### Serialization (Fix 4) |
| 84 | +```python |
| 85 | +import pickle, copy |
| 86 | +h = CompatInt(100) |
| 87 | +pickle.loads(pickle.dumps(h)) # CompatInt(100) |
| 88 | +copy.deepcopy(h) # CompatInt(100) |
| 89 | +``` |
| 90 | + |
| 91 | +### Caching (Fix 5) |
| 92 | +```python |
| 93 | +d = DictModel({'nested': {'x': 1}}) |
| 94 | +d.nested is d.nested # True (cached) |
| 95 | +``` |
| 96 | + |
| 97 | +### Thread Pool (Fix 1) |
| 98 | +```python |
| 99 | +# Default pool_threads=1 now creates a thread pool |
| 100 | +with ApiClient(config, pool_threads=1) as client: |
| 101 | + api = AddressesApi(client) |
| 102 | + result = api.get_address("btc", "addr", async_req=True) |
| 103 | + data = result.get() # Blocks until complete |
| 104 | +``` |
| 105 | + |
| 106 | +## Known Limitations |
| 107 | + |
| 108 | +- Nested lists in models are not wrapped in CompatList (only top-level) |
| 109 | +- Thread pool warning when `async_req=True` but `pool_threads=0` |
0 commit comments