Skip to content

Commit 376eda7

Browse files
authored
Merge branch 'master' into fix_unique_cache_key
2 parents 7694776 + e138000 commit 376eda7

File tree

13 files changed

+2215
-1598
lines changed

13 files changed

+2215
-1598
lines changed

.github/workflows/stale-issues.yml

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,95 @@
1-
name: "Close stale issues"
1+
name: "Stale Issue Management"
22
on:
33
schedule:
4-
- cron: "0 0 * * *"
4+
# Run daily at midnight UTC
5+
- cron: "0 0 * * *"
6+
workflow_dispatch: # Allow manual triggering
7+
8+
env:
9+
# Default stale policy timeframes
10+
DAYS_BEFORE_STALE: 365
11+
DAYS_BEFORE_CLOSE: 30
12+
13+
# Accelerated timeline for needs-information issues
14+
NEEDS_INFO_DAYS_BEFORE_STALE: 30
15+
NEEDS_INFO_DAYS_BEFORE_CLOSE: 7
516

6-
permissions: {}
717
jobs:
818
stale:
9-
permissions:
10-
issues: write # to close stale issues (actions/stale)
11-
pull-requests: write # to close stale PRs (actions/stale)
12-
1319
runs-on: ubuntu-latest
1420
steps:
15-
- uses: actions/stale@v9
16-
with:
17-
repo-token: ${{ secrets.GITHUB_TOKEN }}
18-
stale-issue-message: 'This issue is marked stale. It will be closed in 30 days if it is not updated.'
19-
stale-pr-message: 'This pull request is marked stale. It will be closed in 30 days if it is not updated.'
20-
days-before-stale: 365
21-
days-before-close: 30
22-
stale-issue-label: "Stale"
23-
stale-pr-label: "Stale"
24-
operations-per-run: 20
25-
remove-stale-when-updated: true
21+
# First step: Handle regular issues (excluding needs-information)
22+
- name: Mark regular issues as stale
23+
uses: actions/stale@v9
24+
with:
25+
repo-token: ${{ secrets.GITHUB_TOKEN }}
26+
27+
# Default stale policy
28+
days-before-stale: ${{ env.DAYS_BEFORE_STALE }}
29+
days-before-close: ${{ env.DAYS_BEFORE_CLOSE }}
30+
31+
# Explicit stale label configuration
32+
stale-issue-label: "stale"
33+
stale-pr-label: "stale"
34+
35+
stale-issue-message: |
36+
This issue has been automatically marked as stale due to inactivity.
37+
It will be closed in 30 days if no further activity occurs.
38+
If you believe this issue is still relevant, please add a comment to keep it open.
39+
40+
close-issue-message: |
41+
This issue has been automatically closed due to inactivity.
42+
If you believe this issue is still relevant, please reopen it or create a new issue with updated information.
43+
44+
# Exclude needs-information issues from this step
45+
exempt-issue-labels: 'no-stale,needs-information'
46+
47+
# Remove stale label when issue/PR becomes active again
48+
remove-stale-when-updated: true
49+
50+
# Apply to pull requests with same timeline
51+
days-before-pr-stale: ${{ env.DAYS_BEFORE_STALE }}
52+
days-before-pr-close: ${{ env.DAYS_BEFORE_CLOSE }}
53+
54+
stale-pr-message: |
55+
This pull request has been automatically marked as stale due to inactivity.
56+
It will be closed in 30 days if no further activity occurs.
57+
58+
close-pr-message: |
59+
This pull request has been automatically closed due to inactivity.
60+
If you would like to continue this work, please reopen the PR or create a new one.
61+
62+
# Only exclude no-stale PRs (needs-information PRs follow standard timeline)
63+
exempt-pr-labels: 'no-stale'
64+
65+
# Second step: Handle needs-information issues with accelerated timeline
66+
- name: Mark needs-information issues as stale
67+
uses: actions/stale@v9
68+
with:
69+
repo-token: ${{ secrets.GITHUB_TOKEN }}
70+
71+
# Accelerated timeline for needs-information
72+
days-before-stale: ${{ env.NEEDS_INFO_DAYS_BEFORE_STALE }}
73+
days-before-close: ${{ env.NEEDS_INFO_DAYS_BEFORE_CLOSE }}
74+
75+
# Explicit stale label configuration
76+
stale-issue-label: "stale"
77+
78+
# Only target ISSUES with needs-information label (not PRs)
79+
only-issue-labels: 'needs-information'
80+
81+
stale-issue-message: |
82+
This issue has been marked as stale because it requires additional information
83+
that has not been provided for 30 days. It will be closed in 7 days if the
84+
requested information is not provided.
85+
86+
close-issue-message: |
87+
This issue has been closed because the requested information was not provided within the specified timeframe.
88+
If you can provide the missing information, please reopen this issue or create a new one.
89+
90+
# Disable PR processing for this step
91+
days-before-pr-stale: -1
92+
days-before-pr-close: -1
93+
94+
# Remove stale label when issue becomes active again
95+
remove-stale-when-updated: true

doctests/dt_bitmap.py

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
"""
77
import redis
88

9-
r = redis.Redis(decode_responses=True)
9+
# Connect without the usual `decode_responses=True` to
10+
# see the binary values in the responses more easily.
11+
r = redis.Redis()
1012
# HIDE_END
1113

1214
# REMOVE_START
13-
r.delete("pings:2024-01-01-00:00")
15+
r.delete("pings:2024-01-01-00:00", "A", "B", "C", "R")
1416
# REMOVE_END
1517

1618
# STEP_START ping
@@ -38,3 +40,116 @@
3840
# REMOVE_START
3941
assert res4 == 1
4042
# REMOVE_END
43+
44+
# STEP_START bitop_setup
45+
r.setbit("A", 0, 1)
46+
r.setbit("A", 1, 1)
47+
r.setbit("A", 3, 1)
48+
r.setbit("A", 4, 1)
49+
50+
res5 = r.get("A")
51+
print("{:08b}".format(int.from_bytes(res5, "big")))
52+
# >>> 11011000
53+
54+
r.setbit("B", 3, 1)
55+
r.setbit("B", 4, 1)
56+
r.setbit("B", 7, 1)
57+
58+
res6 = r.get("B")
59+
print("{:08b}".format(int.from_bytes(res6, "big")))
60+
# >>> 00011001
61+
62+
r.setbit("C", 1, 1)
63+
r.setbit("C", 2, 1)
64+
r.setbit("C", 4, 1)
65+
r.setbit("C", 5, 1)
66+
67+
res7 = r.get("C")
68+
print("{:08b}".format(int.from_bytes(res7, "big")))
69+
# >>> 01101100
70+
# STEP_END
71+
# REMOVE_START
72+
assert int.from_bytes(res5, "big") == 0b11011000
73+
assert int.from_bytes(res6, "big") == 0b00011001
74+
assert int.from_bytes(res7, "big") == 0b01101100
75+
# REMOVE_END
76+
77+
# STEP_START bitop_and
78+
r.bitop("AND", "R", "A", "B", "C")
79+
res8 = r.get("R")
80+
print("{:08b}".format(int.from_bytes(res8, "big")))
81+
# >>> 00001000
82+
# STEP_END
83+
# REMOVE_START
84+
assert int.from_bytes(res8, "big") == 0b00001000
85+
# REMOVE_END
86+
87+
# STEP_START bitop_or
88+
r.bitop("OR", "R", "A", "B", "C")
89+
res9 = r.get("R")
90+
print("{:08b}".format(int.from_bytes(res9, "big")))
91+
# >>> 11111101
92+
# STEP_END
93+
# REMOVE_START
94+
assert int.from_bytes(res9, "big") == 0b11111101
95+
# REMOVE_END
96+
97+
# STEP_START bitop_xor
98+
r.bitop("XOR", "R", "A", "B")
99+
res10 = r.get("R")
100+
print("{:08b}".format(int.from_bytes(res10, "big")))
101+
# >>> 11000001
102+
# STEP_END
103+
# REMOVE_START
104+
assert int.from_bytes(res10, "big") == 0b11000001
105+
# REMOVE_END
106+
107+
# STEP_START bitop_not
108+
r.bitop("NOT", "R", "A")
109+
res11 = r.get("R")
110+
print("{:08b}".format(int.from_bytes(res11, "big")))
111+
# >>> 00100111
112+
# STEP_END
113+
# REMOVE_START
114+
assert int.from_bytes(res11, "big") == 0b00100111
115+
# REMOVE_END
116+
117+
# STEP_START bitop_diff
118+
r.bitop("DIFF", "R", "A", "B", "C")
119+
res12 = r.get("R")
120+
print("{:08b}".format(int.from_bytes(res12, "big")))
121+
# >>> 10000000
122+
# STEP_END
123+
# REMOVE_START
124+
assert int.from_bytes(res12, "big") == 0b10000000
125+
# REMOVE_END
126+
127+
# STEP_START bitop_diff1
128+
r.bitop("DIFF1", "R", "A", "B", "C")
129+
res13 = r.get("R")
130+
print("{:08b}".format(int.from_bytes(res13, "big")))
131+
# >>> 00100101
132+
# STEP_END
133+
# REMOVE_START
134+
assert int.from_bytes(res13, "big") == 0b00100101
135+
# REMOVE_END
136+
137+
# STEP_START bitop_andor
138+
r.bitop("ANDOR", "R", "A", "B", "C")
139+
res14 = r.get("R")
140+
print("{:08b}".format(int.from_bytes(res14, "big")))
141+
# >>> 01011000
142+
# STEP_END
143+
# REMOVE_START
144+
assert int.from_bytes(res14, "big") == 0b01011000
145+
# REMOVE_END
146+
147+
# STEP_START bitop_one
148+
r.bitop("ONE", "R", "A", "B", "C")
149+
res15 = r.get("R")
150+
print("{:08b}".format(int.from_bytes(res15, "big")))
151+
# >>> 10100101
152+
# STEP_END
153+
# REMOVE_START
154+
assert int.from_bytes(res15, "big") == 0b10100101
155+
# REMOVE_END

redis/_parsers/base.py

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
from asyncio import IncompleteReadError, StreamReader, TimeoutError
55
from typing import Awaitable, Callable, List, Optional, Protocol, Union
66

7-
from redis.maintenance_events import (
8-
MaintenanceEvent,
9-
NodeFailedOverEvent,
10-
NodeFailingOverEvent,
11-
NodeMigratedEvent,
12-
NodeMigratingEvent,
13-
NodeMovingEvent,
7+
from redis.maint_notifications import (
8+
MaintenanceNotification,
9+
NodeFailedOverNotification,
10+
NodeFailingOverNotification,
11+
NodeMigratedNotification,
12+
NodeMigratingNotification,
13+
NodeMovingNotification,
1414
)
1515

1616
if sys.version_info.major >= 3 and sys.version_info.minor >= 11:
@@ -175,14 +175,14 @@ class MaintenanceNotificationsParser:
175175

176176
@staticmethod
177177
def parse_maintenance_start_msg(response, notification_type):
178-
# Expected message format is: <event_type> <seq_number> <time>
178+
# Expected message format is: <notification_type> <seq_number> <time>
179179
id = response[1]
180180
ttl = response[2]
181181
return notification_type(id, ttl)
182182

183183
@staticmethod
184184
def parse_maintenance_completed_msg(response, notification_type):
185-
# Expected message format is: <event_type> <seq_number>
185+
# Expected message format is: <notification_type> <seq_number>
186186
id = response[1]
187187
return notification_type(id)
188188

@@ -200,7 +200,7 @@ def parse_moving_msg(response):
200200
host, port = value.split(":")
201201
port = int(port) if port is not None else None
202202

203-
return NodeMovingEvent(id, host, port, ttl)
203+
return NodeMovingNotification(id, host, port, ttl)
204204

205205

206206
_INVALIDATION_MESSAGE = "invalidate"
@@ -217,25 +217,27 @@ def parse_moving_msg(response):
217217
_FAILED_OVER_MESSAGE,
218218
)
219219

220-
MSG_TYPE_TO_EVENT_PARSER_MAPPING: dict[str, tuple[type[MaintenanceEvent], Callable]] = {
220+
MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING: dict[
221+
str, tuple[type[MaintenanceNotification], Callable]
222+
] = {
221223
_MIGRATING_MESSAGE: (
222-
NodeMigratingEvent,
224+
NodeMigratingNotification,
223225
MaintenanceNotificationsParser.parse_maintenance_start_msg,
224226
),
225227
_MIGRATED_MESSAGE: (
226-
NodeMigratedEvent,
228+
NodeMigratedNotification,
227229
MaintenanceNotificationsParser.parse_maintenance_completed_msg,
228230
),
229231
_FAILING_OVER_MESSAGE: (
230-
NodeFailingOverEvent,
232+
NodeFailingOverNotification,
231233
MaintenanceNotificationsParser.parse_maintenance_start_msg,
232234
),
233235
_FAILED_OVER_MESSAGE: (
234-
NodeFailedOverEvent,
236+
NodeFailedOverNotification,
235237
MaintenanceNotificationsParser.parse_maintenance_completed_msg,
236238
),
237239
_MOVING_MESSAGE: (
238-
NodeMovingEvent,
240+
NodeMovingNotification,
239241
MaintenanceNotificationsParser.parse_moving_msg,
240242
),
241243
}
@@ -273,14 +275,20 @@ def handle_push_response(self, response, **kwargs):
273275
return self.invalidation_push_handler_func(response)
274276

275277
if msg_type == _MOVING_MESSAGE and self.node_moving_push_handler_func:
276-
parser_function = MSG_TYPE_TO_EVENT_PARSER_MAPPING[msg_type][1]
278+
parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
279+
msg_type
280+
][1]
277281

278282
notification = parser_function(response)
279283
return self.node_moving_push_handler_func(notification)
280284

281285
if msg_type in _MAINTENANCE_MESSAGES and self.maintenance_push_handler_func:
282-
parser_function = MSG_TYPE_TO_EVENT_PARSER_MAPPING[msg_type][1]
283-
notification_type = MSG_TYPE_TO_EVENT_PARSER_MAPPING[msg_type][0]
286+
parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
287+
msg_type
288+
][1]
289+
notification_type = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
290+
msg_type
291+
][0]
284292
notification = parser_function(response, notification_type)
285293

286294
if notification is not None:
@@ -342,13 +350,19 @@ async def handle_push_response(self, response, **kwargs):
342350
msg_type = msg_type.decode()
343351

344352
if msg_type == _MOVING_MESSAGE and self.node_moving_push_handler_func:
345-
parser_function = MSG_TYPE_TO_EVENT_PARSER_MAPPING[msg_type][1]
353+
parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
354+
msg_type
355+
][1]
346356
notification = parser_function(response)
347357
return await self.node_moving_push_handler_func(notification)
348358

349359
if msg_type in _MAINTENANCE_MESSAGES and self.maintenance_push_handler_func:
350-
parser_function = MSG_TYPE_TO_EVENT_PARSER_MAPPING[msg_type][1]
351-
notification_type = MSG_TYPE_TO_EVENT_PARSER_MAPPING[msg_type][0]
360+
parser_function = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
361+
msg_type
362+
][1]
363+
notification_type = MSG_TYPE_TO_MAINT_NOTIFICATION_PARSER_MAPPING[
364+
msg_type
365+
][0]
352366
notification = parser_function(response, notification_type)
353367

354368
if notification is not None:

0 commit comments

Comments
 (0)