Skip to content

Commit 6b0c4ad

Browse files
committed
Merge branch 'master' of github.com:redis/redis-py into feat/active-active
2 parents 3ed14e4 + e8317f0 commit 6b0c4ad

24 files changed

+2717
-1649
lines changed

.github/workflows/docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
runs-on: ubuntu-latest
2727
steps:
2828
- uses: actions/checkout@v5
29-
- uses: actions/setup-python@v5
29+
- uses: actions/setup-python@v6
3030
with:
3131
python-version: 3.9
3232
cache: 'pip'

.github/workflows/integration.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
runs-on: ubuntu-latest
4848
steps:
4949
- uses: actions/checkout@v5
50-
- uses: actions/setup-python@v5
50+
- uses: actions/setup-python@v6
5151
with:
5252
python-version: 3.9
5353
cache: 'pip'
@@ -175,7 +175,7 @@ jobs:
175175
extension: ['tar.gz', 'whl']
176176
steps:
177177
- uses: actions/checkout@v5
178-
- uses: actions/setup-python@v5
178+
- uses: actions/setup-python@v6
179179
with:
180180
python-version: 3.9
181181
- name: Run installed unit tests
@@ -194,7 +194,7 @@ jobs:
194194
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.9', 'pypy-3.10']
195195
steps:
196196
- uses: actions/checkout@v5
197-
- uses: actions/setup-python@v5
197+
- uses: actions/setup-python@v6
198198
with:
199199
python-version: ${{ matrix.python-version }}
200200
cache: 'pip'

.github/workflows/pypi-publish.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
steps:
1616
- uses: actions/checkout@v5
1717
- name: install python
18-
uses: actions/setup-python@v5
18+
uses: actions/setup-python@v6
1919
with:
2020
python-version: 3.9
2121
- run: pip install build twine

.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

docs/examples/pipeline_examples.ipynb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,67 @@
156156
"pipe.set(\"a\", \"a value\").set(\"b\", \"b value\").get(\"a\").execute()"
157157
]
158158
},
159+
{
160+
"cell_type": "markdown",
161+
"metadata": {},
162+
"source": [
163+
"Here's a slightly more advanced example for chaining complex operations using the builder pattern."
164+
]
165+
},
166+
{
167+
"cell_type": "code",
168+
"execution_count": null,
169+
"metadata": {},
170+
"outputs": [],
171+
"source": [
172+
"from dataclasses import dataclass\n",
173+
"from typing import Optional\n",
174+
"\n",
175+
"@dataclass\n",
176+
"class User:\n",
177+
" email: str\n",
178+
" username: Optional[str] = None\n",
179+
"\n",
180+
"@dataclass\n",
181+
"class Post:\n",
182+
" title: str\n",
183+
" body: str\n",
184+
" author: Optional[str] = None\n",
185+
"\n",
186+
"class RedisRepository:\n",
187+
" def __init__(self):\n",
188+
" self.pipeline = r.pipeline()\n",
189+
"\n",
190+
" def add_user(self, user: User):\n",
191+
" if not user.username:\n",
192+
" user.username = user.email.split(\"@\")[0]\n",
193+
" self.pipeline.hset(f\"user:{user.username}\", mapping={\"username\": user.username, \"email\": user.email})\n",
194+
" return self\n",
195+
" \n",
196+
" def add_post(self, post: Post):\n",
197+
" self.pipeline.hset(f\"post:#{post.title}#\", mapping={\"title\": post.title, \"body\": post.body, \"author\": post.author})\n",
198+
" if post.author:\n",
199+
" self.pipeline.sadd(f\"user:{post.author}:posts\", f\"post:#{post.title}#\")\n",
200+
" return self\n",
201+
"\n",
202+
" def add_follow(self, follower: str, following: str):\n",
203+
" self.pipeline.sadd(f\"user:{follower}:following\", following)\n",
204+
" self.pipeline.sadd(f\"user:{following}:followers\", follower)\n",
205+
" return self\n",
206+
"\n",
207+
" def execute(self):\n",
208+
" return self.pipeline.execute()\n",
209+
"\n",
210+
"pipe = RedisRepository()\n",
211+
"results = (pipe\n",
212+
" .add_user(User(email=\"[email protected]\"))\n",
213+
" .add_user(User(email=\"[email protected]\"))\n",
214+
" .add_follow(\"alice\", \"bob\")\n",
215+
" .add_post(Post(title=\"Hello World\", body=\"I'm using Redis!\", author=\"alice\"))\n",
216+
" .execute()\n",
217+
")"
218+
]
219+
},
159220
{
160221
"cell_type": "markdown",
161222
"metadata": {},

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

0 commit comments

Comments
 (0)