Skip to content

Commit 88bfc82

Browse files
s3riusCopilot
andauthored
Add tests (#17)
Co-authored-by: s3rius <18153319+s3rius@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 1b26890 commit 88bfc82

33 files changed

Lines changed: 1990 additions & 265 deletions

.github/workflows/test.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,33 @@ jobs:
4949
with:
5050
token: ${{secrets.GITHUB_TOKEN}}
5151
deny: warnings
52+
stubtest:
53+
runs-on: ubuntu-latest
54+
steps:
55+
- uses: actions/checkout@v6
56+
- uses: actions-rs/toolchain@v1
57+
with:
58+
toolchain: stable
59+
components: clippy
60+
override: true
61+
- uses: actions/setup-python@v6
62+
with:
63+
python-version: 3.x
64+
- name: Install uv
65+
uses: astral-sh/setup-uv@v7
66+
- id: setup-venv
67+
name: Setup virtualenv
68+
run: python -m venv .venv
69+
- name: Build lib
70+
uses: PyO3/maturin-action@v1
71+
with:
72+
command: dev --uv
73+
sccache: true
74+
- name: Run stubtest
75+
run: |
76+
set -e
77+
source .venv/bin/activate
78+
stubtest --ignore-disjoint-bases natsrpy
5279
pytest:
5380
runs-on: ubuntu-latest
5481
steps:

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,5 @@ docs/_build/
7070

7171
# Pyenv
7272
.python-version
73+
.venv/
74+
target/

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ async-nats = "0.46"
1818
bytes = "1.11.1"
1919
futures-util = "0.3.32"
2020
log = "0.4.29"
21-
pyo3 = { version = "0.28", features = ["abi3"] }
21+
pyo3 = { version = "0.28", features = ["abi3", "experimental-inspect"] }
2222
pyo3-async-runtimes = { version = "0.28", features = ["tokio-runtime"] }
2323
pyo3-log = "0.13.3"
2424
thiserror = "2.0.18"

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ email = "s3riussan@gmail.com"
2525
[dependency-groups]
2626
dev = [
2727
"anyio>=4,<5",
28+
"mypy>=1.19.1,<2",
2829
"pytest>=9,<10",
2930
"pytest-xdist>=3,<4",
3031
]
@@ -110,6 +111,8 @@ ignore = [
110111
"SLF001", # Private member accessed
111112
"S311", # Standard pseudo-random generators are not suitable for security/cryptographic purposes
112113
"D101", # Missing docstring in public class
114+
"PLR2004", # Magic value used in comparison
115+
"B017", # Do not assert blind exception
113116
]
114117

115118
[tool.ruff.lint.pydocstyle]
Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,66 @@
11
from collections.abc import Awaitable, Callable
22
from datetime import timedelta
3-
from typing import Any, overload
3+
from typing import Any, final, overload
44

5-
from natsrpy._natsrpy_rs.js import JetStream
6-
from natsrpy._natsrpy_rs.message import Message
5+
from typing_extensions import Self
76

7+
from . import js
8+
9+
@final
10+
class Message:
11+
"""
12+
Simple NATS message.
13+
14+
Attributes:
15+
subject: subject where message was published
16+
reply: subject where reply should be sent, if any
17+
payload: message payload
18+
headers: dictionary of message headers,
19+
every value can be a simple value or a list.
20+
status: status is used for reply messages to indicate the status of the reply.
21+
It is None for regular messages.
22+
description: message description is used for reply messages to
23+
provide additional information about the status.
24+
length: a length of the message payload in bytes.
25+
"""
26+
27+
subject: str
28+
reply: str | None
29+
payload: bytes
30+
headers: dict[str, Any]
31+
status: int | None
32+
description: str | None
33+
length: int
34+
35+
@final
836
class IteratorSubscription:
937
def __aiter__(self) -> IteratorSubscription: ...
1038
async def __anext__(self) -> Message: ...
39+
async def next(self, timeout: float | timedelta | None = None) -> Message: ...
1140
async def unsubscribe(self, limit: int | None = None) -> None: ...
1241
async def drain(self) -> None: ...
1342

43+
@final
1444
class CallbackSubscription:
1545
async def unsubscribe(self, limit: int | None = None) -> None: ...
1646
async def drain(self) -> None: ...
1747

48+
@final
1849
class Nats:
19-
def __init__(
20-
self,
50+
def __new__(
51+
cls,
2152
/,
22-
addrs: list[str] = ["nats://localhost:4222"],
53+
addrs: list[str] | None = None,
2354
user_and_pass: tuple[str, str] | None = None,
2455
nkey: str | None = None,
2556
token: str | None = None,
2657
custom_inbox_prefix: str | None = None,
27-
read_buffer_capacity: int = 65535,
28-
sender_capacity: int = 128,
58+
read_buffer_capacity: int = ..., # 65535 bytes
59+
sender_capacity: int = ..., # 128 bytes
2960
max_reconnects: int | None = None,
30-
connection_timeout: float | timedelta = ...,
31-
request_timeout: float | timedelta = ...,
32-
) -> None: ...
61+
connection_timeout: float | timedelta = ..., # 5 sec
62+
request_timeout: float | timedelta = ..., # 10 sec
63+
) -> Self: ...
3364
async def startup(self) -> None: ...
3465
async def shutdown(self) -> None: ...
3566
async def publish(
@@ -41,7 +72,15 @@ class Nats:
4172
reply: str | None = None,
4273
err_on_disconnect: bool = False,
4374
) -> None: ...
44-
async def request(self, subject: str, payload: bytes) -> None: ...
75+
async def request(
76+
self,
77+
subject: str,
78+
payload: bytes | str | bytearray | memoryview,
79+
*,
80+
headers: dict[str, Any] | None = None,
81+
inbox: str | None = None,
82+
timeout: float | timedelta | None = None,
83+
) -> None: ...
4584
async def drain(self) -> None: ...
4685
async def flush(self) -> None: ...
4786
@overload
@@ -56,6 +95,16 @@ class Nats:
5695
subject: str,
5796
callback: None = None,
5897
) -> IteratorSubscription: ...
59-
async def jetstream(self) -> JetStream: ...
98+
async def jetstream(
99+
self,
100+
*,
101+
domain: str | None = None,
102+
api_prefix: str | None = None,
103+
timeout: timedelta | None = None,
104+
ack_timeout: timedelta | None = None,
105+
concurrency_limit: int | None = None,
106+
max_ack_inflight: int | None = None,
107+
backpressure_on_inflight: bool | None = None,
108+
) -> js.JetStream: ...
60109

61-
__all__ = ["CallbackSubscription", "IteratorSubscription", "Message", "Nats"]
110+
__all__ = ["CallbackSubscription", "IteratorSubscription", "Message", "Nats", "js"]

python/natsrpy/_natsrpy_rs/js/__init__.pyi

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,68 @@
11
from datetime import datetime, timedelta
2-
from typing import Any
2+
from typing import Any, Literal, final, overload
33

4+
from . import consumers, kv, managers, object_store, stream
45
from .managers import KVManager, ObjectStoreManager, StreamsManager
56

7+
__all__ = [
8+
"JetStream",
9+
"JetStreamMessage",
10+
"Publication",
11+
"consumers",
12+
"kv",
13+
"managers",
14+
"object_store",
15+
"stream",
16+
]
17+
18+
@final
19+
class Publication:
20+
stream: str
21+
sequence: int
22+
domain: str
23+
duplicate: bool
24+
value: str | None
25+
26+
@final
627
class JetStream:
28+
@overload
729
async def publish(
830
self,
931
subject: str,
1032
payload: str | bytes | bytearray | memoryview,
1133
*,
1234
headers: dict[str, str] | None = None,
13-
reply: str | None = None,
1435
err_on_disconnect: bool = False,
36+
wait: Literal[True],
37+
) -> Publication: ...
38+
@overload
39+
async def publish(
40+
self,
41+
subject: str,
42+
payload: str | bytes | bytearray | memoryview,
43+
*,
44+
headers: dict[str, str] | None = None,
45+
err_on_disconnect: bool = False,
46+
wait: Literal[False] = False,
1547
) -> None: ...
48+
@overload
49+
async def publish(
50+
self,
51+
subject: str,
52+
payload: str | bytes | bytearray | memoryview,
53+
*,
54+
headers: dict[str, str] | None = None,
55+
err_on_disconnect: bool = False,
56+
wait: bool = False,
57+
) -> Publication | None: ...
1658
@property
1759
def kv(self) -> KVManager: ...
1860
@property
1961
def streams(self) -> StreamsManager: ...
2062
@property
2163
def object_store(self) -> ObjectStoreManager: ...
2264

65+
@final
2366
class JetStreamMessage:
2467
@property
2568
def subject(self) -> str: ...

python/natsrpy/_natsrpy_rs/js/consumers.pyi

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
from datetime import timedelta
2+
from typing import final
23

34
from natsrpy._natsrpy_rs.js import JetStreamMessage
5+
from typing_extensions import Self
46

7+
__all__ = [
8+
"AckPolicy",
9+
"DeliverPolicy",
10+
"MessagesIterator",
11+
"PriorityPolicy",
12+
"PullConsumer",
13+
"PullConsumerConfig",
14+
"PushConsumer",
15+
"PushConsumerConfig",
16+
"ReplayPolicy",
17+
]
18+
19+
@final
520
class DeliverPolicy:
621
ALL: DeliverPolicy
722
LAST: DeliverPolicy
@@ -10,21 +25,25 @@ class DeliverPolicy:
1025
BY_START_TIME: DeliverPolicy
1126
LAST_PER_SUBJECT: DeliverPolicy
1227

28+
@final
1329
class AckPolicy:
1430
EXPLICIT: AckPolicy
1531
NONE: AckPolicy
1632
ALL: AckPolicy
1733

34+
@final
1835
class ReplayPolicy:
1936
INSTANT: ReplayPolicy
2037
ORIGINAL: ReplayPolicy
2138

39+
@final
2240
class PriorityPolicy:
2341
NONE: PriorityPolicy
2442
OVERFLOW: PriorityPolicy
2543
PINNED_CLIENT: PriorityPolicy
2644
PRIORITIZED: PriorityPolicy
2745

46+
@final
2847
class PullConsumerConfig:
2948
name: str | None
3049
durable_name: str | None
@@ -55,8 +74,8 @@ class PullConsumerConfig:
5574
priority_groups: list[str]
5675
pause_until: int | None
5776

58-
def __init__(
59-
self,
77+
def __new__(
78+
cls,
6079
name: str | None = None,
6180
durable_name: str | None = None,
6281
description: str | None = None,
@@ -85,8 +104,9 @@ class PullConsumerConfig:
85104
priority_policy: PriorityPolicy | None = None,
86105
priority_groups: list[str] | None = None,
87106
pause_until: int | None = None,
88-
) -> None: ...
107+
) -> Self: ...
89108

109+
@final
90110
class PushConsumerConfig:
91111
deliver_subject: str
92112
name: str | None
@@ -116,8 +136,8 @@ class PushConsumerConfig:
116136
inactive_threshold: timedelta
117137
pause_until: int | None
118138

119-
def __init__(
120-
self,
139+
def __new__(
140+
cls,
121141
deliver_subject: str,
122142
name: str | None = None,
123143
durable_name: str | None = None,
@@ -145,8 +165,9 @@ class PushConsumerConfig:
145165
backoff: list[timedelta] | None = None,
146166
inactive_threshold: timedelta | None = None,
147167
pause_until: int | None = None,
148-
) -> None: ...
168+
) -> Self: ...
149169

170+
@final
150171
class MessagesIterator:
151172
def __aiter__(self) -> MessagesIterator: ...
152173
async def __anext__(self) -> JetStreamMessage: ...
@@ -155,9 +176,11 @@ class MessagesIterator:
155176
timeout: float | timedelta | None = None,
156177
) -> JetStreamMessage: ...
157178

179+
@final
158180
class PushConsumer:
159181
async def messages(self) -> MessagesIterator: ...
160182

183+
@final
161184
class PullConsumer:
162185
async def fetch(
163186
self,

python/natsrpy/_natsrpy_rs/js/kv.pyi

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1+
from typing import final
2+
13
from natsrpy._natsrpy_rs.js.stream import Placement, Republish, Source, StorageType
4+
from typing_extensions import Self
5+
6+
__all__ = [
7+
"KVConfig",
8+
"KeyValue",
9+
]
210

11+
@final
312
class KVConfig:
413
"""
514
KV bucket config.
@@ -23,8 +32,8 @@ class KVConfig:
2332
placement: Placement | None
2433
limit_markers: float | None
2534

26-
def __init__(
27-
self,
35+
def __new__(
36+
cls,
2837
bucket: str,
2938
description: str | None = None,
3039
max_value_size: int | None = None,
@@ -40,8 +49,9 @@ class KVConfig:
4049
compression: bool | None = None,
4150
placement: Placement | None = None,
4251
limit_markers: float | None = None,
43-
) -> None: ...
52+
) -> Self: ...
4453

54+
@final
4555
class KeyValue:
4656
@property
4757
def stream_name(self) -> str: ...

0 commit comments

Comments
 (0)