Skip to content

Commit 82de9b4

Browse files
committed
Add uv support and update to python 3.11 minimum
Constructed to support both poetry and uv at the same time currently. Fixes #204
1 parent 071066a commit 82de9b4

7 files changed

Lines changed: 183 additions & 78 deletions

File tree

.github/workflows/docs.yml

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,46 @@ on:
99
- main
1010

1111
permissions:
12-
contents: write
12+
contents: read
13+
pages: write
14+
id-token: write
1315

1416
jobs:
1517
build-docs:
1618
runs-on: ubuntu-latest
19+
environment:
20+
name: github-pages
1721
steps:
18-
- name: Checkout
19-
uses: actions/checkout@v4
20-
with:
21-
fetch-depth: 0
22-
- uses: actions/setup-python@v5
23-
with:
24-
python-version: 3.12
25-
- uses: abatilo/actions-poetry@v2
26-
- name: install
27-
run: poetry install --with=docs
28-
- name: Build documentation
29-
run: |
30-
mkdir html
31-
touch html/.nojekyll
32-
poetry run sphinx-build -b html docs html
33-
- name: Deploy documentation
34-
if: ${{ github.event_name == 'push' }}
35-
uses: JamesIves/github-pages-deploy-action@v4
36-
with:
37-
branch: gh-pages
38-
folder: html
22+
- name: Checkout
23+
uses: actions/checkout@v6
24+
with:
25+
fetch-depth: 0
26+
27+
- name: Set up Python
28+
uses: actions/setup-python@v6
29+
with:
30+
python-version: 3.12
31+
32+
- name: Install Poetry
33+
run: |
34+
pip install pip poetry setuptools wheel -U
35+
36+
- name: Install dependencies
37+
run: |
38+
poetry install --with=docs
39+
40+
- name: Build documentation
41+
run: |
42+
mkdir html
43+
touch html/.nojekyll
44+
poetry run sphinx-build -b html docs html
45+
46+
- name: Upload documentation artifact
47+
if: ${{ github.event_name == 'push' }}
48+
uses: actions/upload-pages-artifact@v4
49+
with:
50+
path: html
51+
52+
- name: Deploy documentation
53+
if: ${{ github.event_name == 'push' }}
54+
uses: actions/deploy-pages@v4

.github/workflows/test.yml

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
name: ib_async
22

3-
on: [ push, pull_request ]
3+
on: [push, pull_request]
44

55
jobs:
6-
build:
7-
# https://github.com/actions/runner-images
6+
build-poetry:
7+
name: poetry (${{ matrix.python-version }})
88
runs-on: ubuntu-latest
99
strategy:
10+
fail-fast: false
1011
matrix:
11-
python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14", "pypy3.10", "pypy3.11" ]
12+
python-version: ["3.11", "3.12", "3.13", "3.14", "pypy3.11"]
1213

1314
steps:
14-
- uses: actions/checkout@v4
15+
- uses: actions/checkout@v6
1516

1617
- name: Set up Python ${{ matrix.python-version }}
17-
uses: actions/setup-python@v5
18+
uses: actions/setup-python@v6
1819
with:
1920
python-version: ${{ matrix.python-version }}
2021

21-
- name: Install dependencies of dependencies
22+
- name: Install Poetry toolchain
2223
run: |
23-
pip install pip poetry uv setuptools wheel -U
24+
pip install pip poetry setuptools wheel -U
2425
25-
- name: Install dependencies
26+
- name: Install dependencies with Poetry
2627
run: |
2728
poetry install --with=dev
2829
@@ -34,3 +35,36 @@ jobs:
3435
- name: Ruff check
3536
run: |
3637
poetry run ruff check
38+
39+
build-uv:
40+
name: uv (${{ matrix.python-version }})
41+
runs-on: ubuntu-latest
42+
strategy:
43+
fail-fast: false
44+
matrix:
45+
python-version: ["3.11", "3.12", "3.13", "3.14", "pypy3.11"]
46+
47+
steps:
48+
- uses: actions/checkout@v6
49+
50+
- name: Set up Python ${{ matrix.python-version }}
51+
uses: actions/setup-python@v6
52+
with:
53+
python-version: ${{ matrix.python-version }}
54+
55+
- name: Install uv toolchain
56+
run: |
57+
pip install pip uv setuptools wheel -U
58+
59+
- name: Install dependencies with uv
60+
run: |
61+
uv sync --group dev
62+
63+
- name: MyPy static code analysis
64+
if: ${{ !startsWith(matrix.python-version, 'pypy') }}
65+
run: |
66+
uv run mypy --pretty ib_async
67+
68+
- name: Ruff check
69+
run: |
70+
uv run ruff check

README.md

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[![Build](https://github.com/ib-api-reloaded/ib_async/actions/workflows/test.yml/badge.svg?branch=next)](https://github.com/ib-api-reloaded/ib_async/actions) [![PyVersion](https://img.shields.io/badge/python-3.10+-blue.svg)](#) <!-- [![Status](https://img.shields.io/badge/status-beta-green.svg)](#) --> [![PyPiVersion](https://img.shields.io/pypi/v/ib_async.svg)](https://pypi.python.org/pypi/ib_async) [![License](https://img.shields.io/badge/license-BSD-blue.svg)](#) <!-- [![Downloads](https://static.pepy.tech/badge/ib-insync)](https://pepy.tech/project/ib-insync) --> [![Docs](https://img.shields.io/badge/Documentation-green.svg)](https://ib-api-reloaded.github.io/ib_async/)
1+
[![Build](https://github.com/ib-api-reloaded/ib_async/actions/workflows/test.yml/badge.svg?branch=next)](https://github.com/ib-api-reloaded/ib_async/actions) [![PyVersion](https://img.shields.io/badge/python-3.11+-blue.svg)](#) <!-- [![Status](https://img.shields.io/badge/status-beta-green.svg)](#) --> [![PyPiVersion](https://img.shields.io/pypi/v/ib_async.svg)](https://pypi.python.org/pypi/ib_async) [![License](https://img.shields.io/badge/license-BSD-blue.svg)](#) <!-- [![Downloads](https://static.pepy.tech/badge/ib-insync)](https://pepy.tech/project/ib-insync) --> [![Docs](https://img.shields.io/badge/Documentation-green.svg)](https://ib-api-reloaded.github.io/ib_async/)
22

33
# ib_async
44

@@ -32,13 +32,29 @@ and the [API docs](https://ib-api-reloaded.github.io/ib_async/api.html).
3232

3333
## Installation
3434

35+
`ib_async` now targets Python 3.11+ and publishes standard PEP 621 metadata, so the project installs cleanly with `pip`, `uv`, and `poetry`.
36+
37+
### Install with pip
38+
3539
```
3640
pip install ib_async
3741
```
3842

43+
### Install with uv
44+
45+
```
46+
uv add ib_async
47+
```
48+
49+
### Install with poetry
50+
51+
```
52+
poetry add ib_async
53+
```
54+
3955
Requirements:
4056

41-
- Python 3.10 or higher
57+
- Python 3.11 or higher
4258
- We plan to support Python releases [2 years back](https://devguide.python.org/versions/) which allows us to continue adding newer features and performance improvements over time.
4359
- A running IB Gateway application (or TWS with API mode enabled)
4460
- [stable gateway](https://www.interactivebrokers.com/en/trading/ibgateway-stable.php) — updated every few months
@@ -50,41 +66,59 @@ The ibapi package from IB is not needed. `ib_async` implements the full IBKR API
5066

5167
## Build Manually
5268

53-
First, install poetry:
69+
First, install your preferred environment manager:
5470

5571
```
5672
pip install poetry -U
5773
```
5874

59-
### Installing Only Library
75+
or:
76+
77+
```
78+
pip install uv -U
79+
```
80+
81+
### Install the project with poetry
6082

6183
```
6284
poetry install
6385
```
6486

65-
### Install Everything (enable docs + dev testing)
87+
### Install the project with uv
6688

6789
```
90+
uv sync
91+
```
92+
93+
### Install everything for development and docs
94+
95+
```bash
6896
poetry install --with=docs,dev
97+
uv sync --group dev --group docs
6998
```
7099

71100
## Generate Docs
72101

73-
```
102+
```bash
74103
poetry install --with=docs
75104
poetry run sphinx-build -b html docs html
105+
106+
uv sync --group docs
107+
uv run sphinx-build -b html docs html
76108
```
77109

78110
## Check Types
79111

80-
```
112+
```bash
81113
poetry run mypy ib_async
114+
uv run mypy ib_async
82115
```
83116

84117
## Build Package
85118

86-
```
119+
```bash
87120
poetry build
121+
python -m build
88122
```
89123

90124
## Upload Package (if maintaining)
@@ -481,19 +515,26 @@ The complete [API documentation](https://ib-api-reloaded.github.io/ib_async/api.
481515
```bash
482516
poetry install --with=dev
483517
poetry run pytest
518+
519+
uv sync --group dev
520+
uv run pytest
484521
```
485522

486523
### Type Checking
487524

488525
```bash
489526
poetry run mypy ib_async
527+
uv run mypy ib_async
490528
```
491529

492530
### Code Formatting
493531

494532
```bash
495533
poetry run ruff format
496534
poetry run ruff check --fix
535+
536+
uv run ruff format
537+
uv run ruff check --fix
497538
```
498539

499540
### Local Development
@@ -507,6 +548,7 @@ cd ib_async
507548
2. Install dependencies:
508549
```bash
509550
poetry install --with=dev,docs
551+
uv sync --group dev --group docs
510552
```
511553

512554
3. Make your changes and run tests:

ib_async/ib.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ def waitOnUpdate(self, timeout: float = 0) -> bool:
457457
if timeout:
458458
try:
459459
util.run(asyncio.wait_for(self.updateEvent, timeout))
460-
except asyncio.TimeoutError:
460+
except TimeoutError:
461461
return False
462462
else:
463463
util.run(self.updateEvent)
@@ -2096,7 +2096,7 @@ async def connectAsync(
20962096
if fetchFields & StartupFetch.EXECUTIONS:
20972097
try:
20982098
await asyncio.wait_for(self.reqExecutionsAsync(), timeout)
2099-
except asyncio.TimeoutError:
2099+
except TimeoutError:
21002100
msg = "executions request timed out"
21012101
errors.append(msg)
21022102
self._logger.error(msg)
@@ -2315,7 +2315,7 @@ async def reqMatchingSymbolsAsync(
23152315
try:
23162316
await asyncio.wait_for(future, 4)
23172317
return future.result()
2318-
except asyncio.TimeoutError:
2318+
except TimeoutError:
23192319
self._logger.error("reqMatchingSymbolsAsync: Timeout")
23202320
return None
23212321

@@ -2327,7 +2327,7 @@ async def reqMarketRuleAsync(
23272327
self.client.reqMarketRule(marketRuleId)
23282328
await asyncio.wait_for(future, 1)
23292329
return future.result()
2330-
except asyncio.TimeoutError:
2330+
except TimeoutError:
23312331
self._logger.error("reqMarketRuleAsync: Timeout")
23322332
return None
23332333

@@ -2375,7 +2375,7 @@ async def reqHistoricalDataAsync(
23752375
task = asyncio.wait_for(future, timeout) if timeout else future
23762376
try:
23772377
await task
2378-
except asyncio.TimeoutError:
2378+
except TimeoutError:
23792379
self.client.cancelHistoricalData(reqId)
23802380
self._logger.warning(f"reqHistoricalData: Timeout for {contract}")
23812381
bars.clear()
@@ -2520,7 +2520,7 @@ async def calculateImpliedVolatilityAsync(
25202520
try:
25212521
await asyncio.wait_for(future, 4)
25222522
return future.result()
2523-
except asyncio.TimeoutError:
2523+
except TimeoutError:
25242524
self._logger.error("calculateImpliedVolatilityAsync: Timeout")
25252525
return None
25262526
finally:
@@ -2541,7 +2541,7 @@ async def calculateOptionPriceAsync(
25412541
try:
25422542
await asyncio.wait_for(future, 4)
25432543
return future.result()
2544-
except asyncio.TimeoutError:
2544+
except TimeoutError:
25452545
self._logger.error("calculateOptionPriceAsync: Timeout")
25462546
return None
25472547
finally:
@@ -2596,7 +2596,7 @@ async def reqHistoricalNewsAsync(
25962596
try:
25972597
await asyncio.wait_for(future, 4)
25982598
return future.result()
2599-
except asyncio.TimeoutError:
2599+
except TimeoutError:
26002600
self._logger.error("reqHistoricalNewsAsync: Timeout")
26012601
return None
26022602

@@ -2606,7 +2606,7 @@ async def requestFAAsync(self, faDataType: int):
26062606
try:
26072607
await asyncio.wait_for(future, 4)
26082608
return future.result()
2609-
except asyncio.TimeoutError:
2609+
except TimeoutError:
26102610
self._logger.error("requestFAAsync: Timeout")
26112611

26122612
async def getWshMetaDataAsync(self) -> str:

ib_async/objects.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass, field
6+
from datetime import UTC, datetime, tzinfo
67
from datetime import date as date_
7-
from datetime import datetime, timezone, tzinfo
88
from typing import Any, NamedTuple
99

1010
from eventkit import Event
@@ -591,4 +591,4 @@ class IBDefaults:
591591
unset: Any = nan
592592

593593
# optionally change the timezone used for log history events in objects (no impact on orders or data processing)
594-
timezone: tzinfo = timezone.utc
594+
timezone: tzinfo = UTC

0 commit comments

Comments
 (0)