Skip to content

Commit 6b3e9df

Browse files
authored
Merge pull request #6 from AIBlockOfficial/ci/publish-pypi
Release 0.2.9: Consistent IResult handling, AIBLOCK_* env vars, docs/CI updates
2 parents c6a85d3 + 5e972f5 commit 6b3e9df

File tree

16 files changed

+736
-248
lines changed

16 files changed

+736
-248
lines changed

.github/workflows/publish.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@ concurrency:
99
cancel-in-progress: false
1010

1111
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.11"
19+
- name: Install uv
20+
run: curl -LsSf https://astral.sh/uv/install.sh | sh
21+
- name: Run tests
22+
run: |
23+
~/.local/bin/uv pip install -q pytest requests-mock
24+
~/.local/bin/uv run pytest -q
25+
1226
build-and-publish:
1327
name: Build and publish
1428
runs-on: ubuntu-latest
@@ -36,5 +50,6 @@ jobs:
3650
with:
3751
password: ${{ secrets.PYPI_API_TOKEN }}
3852
skip-existing: true
53+
needs: [test]
3954

4055

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Changelog
2+
3+
## 0.2.9
4+
5+
- Consistent network error handling across `BlockchainClient` and `Wallet` (IResult everywhere)
6+
- `Wallet.get_balance` now returns `IResult[dict]` (breaking)
7+
- Unified headers and response handling via shared helpers
8+
- Added `NetworkNotInitialized` error
9+
- Docs updated for IResult usage and error mappings
10+
- CI: added test job and publish-on-main with PyPI token
11+
- Config: standardized environment variables to `AIBLOCK_*` (`AIBLOCK_MEMPOOL_HOST`, `AIBLOCK_STORAGE_HOST`, `AIBLOCK_PASSPHRASE`, optional `AIBLOCK_VALENCE_HOST`)
12+
13+
## 0.2.8
14+
15+
- Docs refresh and minor fixes
16+

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ config = {
6868
result = wallet.from_seed(seed_phrase, config)
6969
if result.is_ok:
7070
print(f"Wallet address: {wallet.get_address()}")
71+
else:
72+
print(result.error, result.error_message)
7173
```
7274

7375
## Features
@@ -110,15 +112,14 @@ if result.is_ok:
110112
data = result.get_ok()
111113
print(f"Success: {data}")
112114
else:
113-
print(f"Error: {result.error_message}")
115+
print(result.error, result.error_message)
114116
```
115117

116118
## Development
117119

118120
1. Clone the repository
119-
2. Install dependencies: `pip install -r requirements.txt`
120-
3. Install test dependencies: `pip install -r requirements-test.txt`
121-
4. Run tests: `pytest`
121+
2. Install uv (https://docs.astral.sh/uv/)
122+
3. Run tests: `uv pip install -q pytest requests-mock && uv run pytest -q`
122123

123124
All 68 tests pass, ensuring reliability and compatibility.
124125

aiblock/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
construct_address
2424
)
2525

26-
__version__ = "0.2.8"
26+
__version__ = "0.2.9"
2727

2828
__all__ = [
2929
'BlockchainClient',

aiblock/blockchain.py

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import random
1111
from urllib.parse import urlparse
1212
from aiblock.interfaces import IResult, IErrorInternal
13+
from importlib import metadata as _importlib_metadata
1314
import json
1415

1516
# Set up logging
@@ -91,7 +92,9 @@ def handle_response(response) -> IResult[APIResponse]:
9192
success_messages = {
9293
'latest_block': 'Latest block retrieved successfully',
9394
'block': 'Block retrieved successfully',
95+
'block_by_num': 'Block retrieved successfully',
9496
'blockchain': 'Blockchain entry retrieved successfully',
97+
'blockchain_entry': 'Blockchain entry retrieved successfully',
9598
'total_supply': 'Total supply retrieved successfully',
9699
'issued_supply': 'Issued supply retrieved successfully'
97100
}
@@ -174,48 +177,30 @@ def _make_request(self, endpoint: str, method: str = 'GET', data: Any = None) ->
174177
# Determine which host to use based on endpoint
175178
if endpoint.startswith(('total_supply', 'issued_supply', 'fetch_balance', 'create_item_asset', 'create_transactions')):
176179
if not self.mempool_host:
177-
raise ValueError("Mempool host is required for this endpoint")
180+
return IResult.err(IErrorInternal.NetworkNotInitialized, "Mempool host is required for this endpoint")
178181
url = f"{self.mempool_host}/{endpoint}"
179182
else:
180183
if not self.storage_host:
181-
raise ValueError("Storage host is required for this endpoint")
184+
return IResult.err(IErrorInternal.NetworkNotInitialized, "Storage host is required for this endpoint")
182185
url = f"{self.storage_host}/{endpoint}"
183-
184-
# Prepare headers
185-
headers = {
186-
'Content-Type': 'application/json',
187-
'Accept': 'application/json',
188-
'User-Agent': f'AIBlock-Python-SDK/{self._get_version()}',
189-
'Request-ID': str(uuid.uuid4()),
190-
'Nonce': self._get_random_string(32)
191-
}
192-
186+
187+
# Prepare headers using shared generator
188+
headers = get_headers()
189+
headers['User-Agent'] = f"AIBlock-Python-SDK/{self._get_version()}"
190+
193191
# Make the request
194192
if method.upper() == 'POST':
195193
if data is not None:
196-
# Use the same format as the working example: requests.request with data parameter
197194
payload = json.dumps(data)
198195
response = requests.request('POST', url, headers=headers, data=payload, timeout=30)
199196
else:
200197
response = requests.post(url, headers=headers, timeout=30)
201198
else:
202199
response = requests.get(url, headers=headers, timeout=30)
203-
204-
# Handle response
205-
if response.status_code == 200:
206-
try:
207-
json_response = response.json()
208-
return IResult.ok(APIResponse(**json_response))
209-
except (ValueError, json.JSONDecodeError):
210-
return IResult.err(IErrorInternal.InvalidNetworkResponse, f"Invalid JSON response: {response.text}")
211-
else:
212-
try:
213-
error_response = response.json()
214-
reason = error_response.get('reason', f'HTTP {response.status_code}')
215-
return IResult.err(IErrorInternal.InvalidNetworkResponse, reason)
216-
except (ValueError, json.JSONDecodeError):
217-
return IResult.err(IErrorInternal.InvalidNetworkResponse, f"HTTP {response.status_code}: {response.text}")
218-
200+
201+
# Delegate response handling to shared handler
202+
return handle_response(response)
203+
219204
except requests.exceptions.Timeout:
220205
return IResult.err(IErrorInternal.NetworkError, "Request timeout")
221206
except requests.exceptions.ConnectionError:
@@ -327,8 +312,11 @@ def fetch_transactions(self, transaction_hashes: list[str]) -> IResult[APIRespon
327312
return self._make_request('blockchain_entry', method='POST', data=transaction_hashes)
328313

329314
def _get_version(self) -> str:
330-
"""Get the SDK version."""
331-
return "0.2.7" # Current version from pyproject.toml
315+
"""Get the SDK version from installed metadata, fallback to project version."""
316+
try:
317+
return _importlib_metadata.version('aiblock')
318+
except Exception:
319+
return "0.2.8"
332320

333321
def _get_random_string(self, length: int) -> str:
334322
"""Generate a random string of specified length."""

aiblock/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ def get_config() -> IResult[Dict[str, str]]:
3333

3434
# Required environment variables
3535
required_vars = {
36-
'MEMPOOL_HOST': 'mempoolHost',
37-
'STORAGE_HOST': 'storageHost',
38-
'PASSPHRASE': 'passphrase'
36+
'AIBLOCK_MEMPOOL_HOST': 'mempoolHost',
37+
'AIBLOCK_STORAGE_HOST': 'storageHost',
38+
'AIBLOCK_PASSPHRASE': 'passphrase'
3939
}
4040

4141
# Check for required environment variables

aiblock/interfaces.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class IErrorInternal(Enum):
6464
WalletNotInitialized = auto()
6565
UnableToFetchBalance = auto()
6666
UnableToInitializeNetwork = auto()
67+
NetworkNotInitialized = auto()
6768
UnableToGenerateHeaders = auto()
6869
UnableToGetDebugData = auto()
6970
NoHostsProvided = auto()

0 commit comments

Comments
 (0)