diff --git a/.github/actions/spelling/README.md b/.github/actions/spelling/README.md
new file mode 100644
index 00000000..8dd5e9f8
--- /dev/null
+++ b/.github/actions/spelling/README.md
@@ -0,0 +1,16 @@
+# check-spelling/check-spelling configuration
+
+File | Purpose | Format | Info
+-|-|-|-
+[allow.txt](allow.txt) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow)
+[reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject)
+[excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes)
+[only.txt](only.txt) | Only check matching files (applied after excludes) | perl regular expression | [only](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-only)
+[patterns.txt](patterns.txt) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
+[candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns)
+[line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
+[expect.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect)
+[advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice)
+
+Note: you can replace any of these files with a directory by the same name (minus the suffix)
+and then include multiple files inside that directory (with that suffix) to merge multiple files together.
diff --git a/.github/actions/spelling/advice.md b/.github/actions/spelling/advice.md
new file mode 100644
index 00000000..a32d1090
--- /dev/null
+++ b/.github/actions/spelling/advice.md
@@ -0,0 +1,31 @@
+
+If the flagged items are :exploding_head: false positives
+
+If items relate to a ...
+* binary file (or some other file you wouldn't want to check at all).
+
+ Please add a file path to the `excludes.txt` file matching the containing file.
+
+ File paths are Perl 5 Regular Expressions - you can [test](
+https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.
+
+ `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
+../tree/HEAD/README.md) (on whichever branch you're using).
+
+* well-formed pattern.
+
+ If you can write a [pattern](
+https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
+) that would match it,
+ try adding it to the `patterns.txt` file.
+
+ Patterns are Perl 5 Regular Expressions - you can [test](
+https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.
+
+ Note that patterns can't match multiline strings.
+
+
+
+
+:steam_locomotive: If you're seeing this message and your PR is from a branch that doesn't have check-spelling,
+please merge to your PR's base branch to get the version configured for your repository.
diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
new file mode 100644
index 00000000..61567618
--- /dev/null
+++ b/.github/actions/spelling/allow.txt
@@ -0,0 +1,5 @@
+github
+https
+ssh
+ubuntu
+workarounds
diff --git a/.github/actions/spelling/candidate.patterns b/.github/actions/spelling/candidate.patterns
new file mode 100644
index 00000000..53f282b9
--- /dev/null
+++ b/.github/actions/spelling/candidate.patterns
@@ -0,0 +1,787 @@
+# Repeated letters
+\b([A-Za-z])\g{-1}{2,}\b
+
+# marker to ignore all code on line
+^.*/\* #no-spell-check-line \*/.*$
+# marker to ignore all code on line
+^.*\bno-spell-check(?:-line|)(?:\s.*|)$
+
+# https://cspell.org/configuration/document-settings/
+# cspell inline
+^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b
+
+# copyright
+Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
+
+# patch hunk comments
+^@@ -\d+(?:,\d+|) \+\d+(?:,\d+|) @@ .*
+# git index header
+index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
+
+# file permissions
+['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
+
+# css fonts
+\bfont(?:-family|):[^;}]+
+
+# css url wrappings
+\burl\([^)]+\)
+
+# cid urls
+(['"])cid:.*?\g{-1}
+
+# data url in parens
+\(data:(?:[^) ][^)]*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
+# data url in quotes
+([`'"])data:(?:[^ `'"].*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
+# data url
+\bdata:[-a-zA-Z=;:/0-9+_]*,\S*
+
+# https/http/file urls
+(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/*%?=~_|!:,.;]+[-A-Za-z0-9+&@#/*%=~_|]
+
+# mailto urls
+#mailto:[-a-zA-Z=;:/?%&0-9+@._]{3,}
+
+# magnet urls
+magnet:[?=:\w]+
+
+# magnet urls
+"magnet:[^"]+"
+
+# obs:
+"obs:[^"]*"
+
+# The `\b` here means a break, it's the fancy way to handle urls, but it makes things harder to read
+# In this examples content, I'm using a number of different ways to match things to show various approaches
+# asciinema
+\basciinema\.org/a/[0-9a-zA-Z]+
+
+# asciinema v2
+^\[\d+\.\d+, "[io]", ".*"\]$
+
+# apple
+\bdeveloper\.apple\.com/[-\w?=/]+
+# Apple music
+\bembed\.music\.apple\.com/fr/playlist/usr-share/[-\w.]+
+
+# appveyor api
+\bci\.appveyor\.com/api/projects/status/[0-9a-z]+
+# appveyor project
+\bci\.appveyor\.com/project/(?:[^/\s"]*/){2}builds?/\d+/job/[0-9a-z]+
+
+# Amazon
+
+# Amazon
+\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
+# AWS ARN
+arn:aws:[-/:\w]+
+# AWS S3
+\b\w*\.s3[^.]*\.amazonaws\.com/[-\w/%_?:=]*
+# AWS execute-api
+\b[0-9a-z]{10}\.execute-api\.[-0-9a-z]+\.amazonaws\.com\b
+# AWS ELB
+\b\w+\.[-0-9a-z]+\.elb\.amazonaws\.com\b
+# AWS SNS
+\bsns\.[-0-9a-z]+.amazonaws\.com/[-\w/%_?:=]*
+# AWS VPC
+vpc-\w+
+
+# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
+# YouTube url
+\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
+# YouTube music
+\bmusic\.youtube\.com/youtubei/v1/browse(?:[?&]\w+=[-a-zA-Z0-9?&=_]*)
+# YouTube tag
+<\s*youtube\s+id=['"][-a-zA-Z0-9?_]*['"]
+# YouTube image
+\bimg\.youtube\.com/vi/[-a-zA-Z0-9?&=_]*
+# Google Accounts
+\baccounts.google.com/[-_/?=.:;+%&0-9a-zA-Z]*
+# Google Analytics
+\bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]*
+# Google APIs
+\bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+
+# Google Artifact Registry
+\.pkg\.dev(?:/[-\w]+)+(?::[-\w]+|)
+# Google Storage
+\b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|)
+# Google Calendar
+\bcalendar\.google\.com/calendar(?:/u/\d+|)/embed\?src=[@./?=\w&%]+
+\w+\@group\.calendar\.google\.com\b
+# Google DataStudio
+\bdatastudio\.google\.com/(?:(?:c/|)u/\d+/|)(?:embed/|)(?:open|reporting|datasources|s)/[-0-9a-zA-Z]+(?:/page/[-0-9a-zA-Z]+|)
+# The leading `/` here is as opposed to the `\b` above
+# ... a short way to match `https://` or `http://` since most urls have one of those prefixes
+# Google Docs
+/docs\.google\.com/[a-z]+/(?:ccc\?key=\w+|(?:u/\d+|d/(?:e/|)[0-9a-zA-Z_-]+/)?(?:edit\?[-\w=#.]*|/\?[\w=&]*|))
+# Google Drive
+\bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]*
+# Google Groups
+\bgroups\.google\.com(?:/[a-z]+/(?:#!|)[^/\s"]+)*
+# Google Maps
+\bmaps\.google\.com/maps\?[\w&;=]*
+# Google themes
+themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
+# Google CDN
+\bclients2\.google(?:usercontent|)\.com[-0-9a-zA-Z/.]*
+# Goo.gl
+/goo\.gl/[a-zA-Z0-9]+
+# Google Chrome Store
+\bchrome\.google\.com/webstore/detail/[-\w]*(?:/\w*|)
+# Google Books
+\bgoogle\.(?:\w{2,4})/books(?:/\w+)*\?[-\w\d=.]*
+# Google Fonts
+\bfonts\.(?:googleapis|gstatic)\.com/[-/?=:;+&0-9a-zA-Z]*
+# Google Forms
+\bforms\.gle/\w+
+# Google Scholar
+\bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+
+# Google Colab Research Drive
+\bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]*
+# Google Cloud regions
+(?:us|(?:north|south)america|europe|asia|australia|me|africa)-(?:north|south|east|west|central){1,2}\d+
+
+# GitHub SHAs (api)
+\bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b
+# GitHub SHAs (markdown)
+(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
+# GitHub SHAs
+\bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b
+# GitHub SHA refs
+\[([0-9a-f]+)\]\(https://(?:www\.|)github.com/[-\w]+/[-\w]+/commit/\g{-1}[0-9a-f]*
+# GitHub wiki
+\bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b
+# githubusercontent
+/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
+# githubassets
+\bgithubassets.com/[0-9a-f]+(?:[-/\w.]+)
+# gist github
+\bgist\.github\.com/[^/\s"]+/[0-9a-f]+
+# git.io
+\bgit\.io/[0-9a-zA-Z]+
+# GitHub JSON
+"node_id": "[-a-zA-Z=;:/0-9+_]*"
+# Contributor
+\[[^\]]+\]\(https://github\.com/[^/\s"]+/?\)
+# GHSA
+GHSA(?:-[0-9a-z]{4}){3}
+
+# GitHub actions
+\buses:\s+(['"]?)[-\w.]+/[-\w./]+@[-\w.]+\g{-1}
+
+# GitLab commit
+\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
+# GitLab merge requests
+\bgitlab\.[^/\s"]*/\S+/\S+/-/merge_requests/\d+/diffs#[0-9a-f]{40}\b
+# GitLab uploads
+\bgitlab\.[^/\s"]*/uploads/[-a-zA-Z=;:/0-9+]*
+# GitLab commits
+\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b
+
+# #includes
+^\s*#include\s*(?:<.*?>|".*?")
+
+# #pragma lib
+^\s*#pragma comment\(lib, ".*?"\)
+
+# binance
+accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
+
+# bitbucket diff
+\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+
+# bitbucket repositories commits
+\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
+# bitbucket commits
+\bbitbucket\.org/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
+
+# bit.ly
+\bbit\.ly/\w+
+
+# bitrise
+\bapp\.bitrise\.io/app/[0-9a-f]*/[\w.?=&]*
+
+# bootstrapcdn.com
+\bbootstrapcdn\.com/[-./\w]+
+
+# cdn.cloudflare.com
+\bcdnjs\.cloudflare\.com/[./\w]+
+
+# circleci
+\bcircleci\.com/gh(?:/[^/\s"]+){1,5}.[a-z]+\?[-0-9a-zA-Z=&]+
+
+# gitter
+\bgitter\.im(?:/[^/\s"]+){2}\?at=[0-9a-f]+
+
+# gravatar
+\bgravatar\.com/avatar/[0-9a-f]+
+
+# ibm
+[a-z.]*ibm\.com/[-_#=:%!?~.\\/\d\w]*
+
+# imgur
+\bimgur\.com/[^.]+
+
+# Internet Archive
+\barchive\.org/web/\d+/(?:[-\w.?,'/\\+&%$#_:]*)
+
+# discord
+/discord(?:app\.com|\.gg)/(?:invite/)?[a-zA-Z0-9]{7,}
+
+# Disqus
+\bdisqus\.com/[-\w/%.()!?&=_]*
+
+# medium link
+\blink\.medium\.com/[a-zA-Z0-9]+
+# medium
+\bmedium\.com/@?[^/\s"]+/[-\w]+
+
+# microsoft
+\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]*
+# powerbi
+\bapp\.powerbi\.com/reportEmbed/[^"' ]*
+# vs devops
+\bvisualstudio.com(?::443|)/[-\w/?=%&.]*
+# microsoft store
+\bmicrosoft\.com/store/apps/\w+
+
+# mvnrepository.com
+\bmvnrepository\.com/[-0-9a-z./]+
+
+# now.sh
+/[0-9a-z-.]+\.now\.sh\b
+
+# oracle
+\bdocs\.oracle\.com/[-0-9a-zA-Z./_?#&=]*
+
+# chromatic.com
+/\S+.chromatic.com\S*[")]
+
+# codacy
+\bapi\.codacy\.com/project/badge/Grade/[0-9a-f]+
+
+# compai
+\bcompai\.pub/v1/png/[0-9a-f]+
+
+# mailgun api
+\.api\.mailgun\.net/v3/domains/[0-9a-z]+\.mailgun.org/messages/[0-9a-zA-Z=@]*
+# mailgun
+\b[0-9a-z]+.mailgun.org
+
+# /message-id/
+/message-id/[-\w@./%]+
+
+# Reddit
+\breddit\.com/r/[/\w_]*
+
+# requestb.in
+\brequestb\.in/[0-9a-z]+
+
+# sched
+\b[a-z0-9]+\.sched\.com\b
+
+# Slack url
+slack://[a-zA-Z0-9?&=]+
+# Slack
+\bslack\.com/[-0-9a-zA-Z/_~?&=.]*
+# Slack edge
+\bslack-edge\.com/[-a-zA-Z0-9?&=%./]+
+# Slack images
+\bslack-imgs\.com/[-a-zA-Z0-9?&=%.]+
+
+# shields.io
+\bshields\.io/[-\w/%?=&.:+;,]*
+
+# stackexchange -- https://stackexchange.com/feeds/sites
+\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/)
+
+# Sentry
+[0-9a-f]{32}\@o\d+\.ingest\.sentry\.io\b
+
+# Twitter markdown
+\[@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\)
+# Twitter hashtag
+\btwitter\.com/hashtag/[\w?_=&]*
+# Twitter status
+\btwitter\.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)
+# Twitter profile images
+\btwimg\.com/profile_images/[_\w./]*
+# Twitter media
+\btwimg\.com/media/[-_\w./?=]*
+# Twitter link shortened
+\bt\.co/\w+
+
+# facebook
+\bfburl\.com/[0-9a-z_]+
+# facebook CDN
+\bfbcdn\.net/[\w/.,]*
+# facebook watch
+\bfb\.watch/[0-9A-Za-z]+
+
+# dropbox
+\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+
+
+# ipfs protocol
+ipfs://[0-9a-zA-Z]{3,}
+# ipfs url
+/ipfs/[0-9a-zA-Z]{3,}
+
+# w3
+\bw3\.org/[-0-9a-zA-Z/#.]+
+
+# loom
+\bloom\.com/embed/[0-9a-f]+
+
+# regex101
+\bregex101\.com/r/[^/\s"]+/\d+
+
+# figma
+\bfigma\.com/file(?:/[0-9a-zA-Z]+/)+
+
+# freecodecamp.org
+\bfreecodecamp\.org/[-\w/.]+
+
+# image.tmdb.org
+\bimage\.tmdb\.org/[/\w.]+
+
+# mermaid
+\bmermaid\.ink/img/[-\w]+|\bmermaid-js\.github\.io/mermaid-live-editor/#/edit/[-\w]+
+
+# Wikipedia
+\ben\.wikipedia\.org/wiki/[-\w%.#]+
+
+# gitweb
+[^"\s]+/gitweb/\S+;h=[0-9a-f]+
+
+# HyperKitty lists
+/archives/list/[^@/]+@[^/\s"]*/message/[^/\s"]*/
+
+# lists
+/thread\.html/[^"\s]+
+
+# list-management
+\blist-manage\.com/subscribe(?:[?&](?:u|id)=[0-9a-f]+)+
+
+# kubectl.kubernetes.io/last-applied-configuration
+"kubectl.kubernetes.io/last-applied-configuration": ".*"
+
+# pgp
+\bgnupg\.net/pks/lookup[?&=0-9a-zA-Z]*
+
+# Spotify
+\bopen\.spotify\.com/embed/playlist/\w+
+
+# Mastodon
+\bmastodon\.[-a-z.]*/(?:media/|@)[?&=0-9a-zA-Z_]*
+
+# scastie
+\bscastie\.scala-lang\.org/[^/]+/\w+
+
+# images.unsplash.com
+\bimages\.unsplash\.com/(?:(?:flagged|reserve)/|)[-\w./%?=%&.;]+
+
+# pastebin
+\bpastebin\.com/[\w/]+
+
+# heroku
+\b\w+\.heroku\.com/source/archive/\w+
+
+# quip
+\b\w+\.quip\.com/\w+(?:(?:#|/issues/)\w+)?
+
+# badgen.net
+\bbadgen\.net/badge/[^")\]'\s]+
+
+# statuspage.io
+\w+\.statuspage\.io\b
+
+# media.giphy.com
+\bmedia\.giphy\.com/media/[^/]+/[\w.?&=]+
+
+# tinyurl
+\btinyurl\.com/\w+
+
+# codepen
+\bcodepen\.io/[\w/]+
+
+# registry.npmjs.org
+\bregistry\.npmjs\.org/(?:@[^/"']+/|)[^/"']+/-/[-\w@.]+
+
+# getopts
+\bgetopts\s+(?:"[^"]+"|'[^']+')
+
+# ANSI color codes
+(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
+
+# URL escaped characters
+%[0-9A-F][A-F](?=[A-Za-z])
+# lower URL escaped characters
+%[0-9a-f][a-f](?=[a-z]{2,})
+# IPv6
+\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
+# c99 hex digits (not the full format, just one I've seen)
+0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP]
+# Punycode
+\bxn--[-0-9a-z]+
+# sha
+sha\d+:[0-9a-f]*?[a-f]{3,}[0-9a-f]*
+# sha-... -- uses a fancy capture
+(\\?['"]|")[0-9a-f]{40,}\g{-1}
+# hex runs
+\b(?=(?:[a-fA-F]{0,2}\d)*[a-fA-F]{3})[0-9a-fA-F]{16,}\b
+# hex in url queries
+=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?&
+# ssh
+(?:ssh-\S+|-nistp256) [-a-zA-Z=;:/0-9+]{12,}
+
+# PGP
+\b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b
+# GPG keys
+\b(?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}\b
+# Well known gpg keys
+.well-known/openpgpkey/[\w./]+
+
+# pki
+-----BEGIN.*-----END
+
+# pki (base64)
+LS0tLS1CRUdJT.*
+
+# C# includes
+#^\s*using [^;]+;
+
+# uuid:
+\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
+# hex digits including css/html color classes:
+(?:[\\0][xX]|\\u\{?|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b
+
+# integrity
+integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1}
+
+# https://www.gnu.org/software/groff/manual/groff.html
+# man troff content
+\\f[BCIPR]
+# '/"
+\\\([ad]q
+
+# .desktop mime types
+^MimeTypes?=.*$
+# .desktop localized entries
+^[A-Z][a-z]+\[[a-z]+\]=.*$
+# Localized .desktop content
+Name\[[^\]]+\]=.*
+
+# IServiceProvider / isAThing
+(?:(?:\b|_|(?<=[a-z]))I|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
+
+# python
+#\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
+
+# crypt
+(['"])\$2[ayb]\$.{56}\g{-1}
+
+# apache/old crypt
+(['"]|)\$+(?:apr|)1\$+.{8}\$+.{22}\g{-1}
+
+# sha1 hash
+\{SHA\}[-a-zA-Z=;:/0-9+]{3,}
+
+# machine learning (?)
+\b(?i)ml(?=[a-z]{2,})
+
+# scrypt / argon
+\$(?:scrypt|argon\d+[di]*)\$\S+
+
+# go.sum
+\bh1:\S+
+
+# golang print-f-style functions
+(?i)(?<=append|comma|debug|equal|err|error|info|log|name|print|skip|scan|trace|true|warn|warning|wrap)f\(
+
+# imports
+^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+(?:\s+from (['"]).*?\g{-1}|)
+
+# scala modules
+("[^"]+"\s*%%?\s*){2,3}"[^"]+"
+
+# container images
+image: [-\w./:@]+
+
+# Docker images
+^\s*(?i)FROM\s+\S+:\S+(?:\s+AS\s+\S+|)
+
+# `docker images` REPOSITORY TAG IMAGE ID CREATED SIZE
+\s*\S+/\S+\s+\S+\s+[0-9a-f]{8,}\s+\d+\s+(?:hour|day|week)s ago\s+[\d.]+[KMGT]B
+
+# Intel intrinsics
+_mm\d*_(?!dd)\w+
+
+# Input to GitHub JSON
+content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}
+
+# This does not cover multiline strings, if your repository has them,
+# you'll want to remove the `(?=.*?")` suffix.
+# The `(?=.*?")` suffix should limit the false positives rate
+# printf
+%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA])(?=[a-zA-Z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%)\b)(?=.*?['"])
+
+# Alternative printf
+# %s
+%(?:s(?=[a-z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%[^s])\b)(?=.*?['"])
+
+# Python string prefix / binary prefix
+# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
+(?|m([|!/@#,;']).*?\g{-1})
+
+# perl qr regex
+(?|\(.*?\)|([|!/@#,;']).*?\g{-1})
+
+# perl run
+perl(?:\s+-[a-zA-Z]\w*)+
+
+# C network byte conversions
+#(?:\d|\bh)to(?!ken)(?=[a-z])|to(?=[adhiklpun]\()
+
+# Go regular expressions
+regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\)
+
+# regex choice
+\((?:\?:|)[^)|]+\|[^)|][^)]*\)
+
+# proto
+^\s*(\w+)\s\g{-1} =
+
+# sed regular expressions
+sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2}
+
+# node packages
+(["'])@[^/'" ]+/[^/'" ]+\g{-1}
+
+# go install
+go install(?:\s+[a-z]+\.[-@\w/.]+)+
+
+# pom.xml
+<(?:group|artifact)Id>.*?<
+
+# jetbrains schema https://youtrack.jetbrains.com/issue/RSRP-489571
+urn:shemas-jetbrains-com
+
+# Debian changelog severity
+[-\w]+ \(.*\) (?:\w+|baseline|unstable|experimental); urgency=(?:low|medium|high|emergency|critical)\b
+
+# kubernetes pod status lists
+# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
+\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
+
+# kubectl - pods in CrashLoopBackOff
+\w+-[0-9a-f]+-\w+\s+\d+/\d+\s+CrashLoopBackOff\s+
+
+# kubernetes applications
+\.apps/[-\w]+
+
+# kubernetes object suffix
+-[0-9a-f]{10}-\w{5}\s
+
+# kubernetes crd patterns
+^\s*pattern: .*$
+
+# posthog secrets
+([`'"])phc_[^"',]+\g{-1}
+
+# xcode
+
+# xcodeproject scenes
+(?:Controller|destination|(?:first|second)Item|ID|id)="\w{3}-\w{2}-\w{3}"
+
+# xcode api botches
+customObjectInstantitationMethod
+
+# msvc api botches
+PrependWithABINamepsace
+
+# configure flags
+.* \| --\w{2,}.*?(?=\w+\s\w+)
+
+# font awesome classes
+\.fa-[-a-z0-9]+
+
+# bearer auth
+(['"])[Bb]ear[e][r] .{3,}?\g{-1}
+
+# bearer auth
+\b[Bb]ear[e][r]:? [-a-zA-Z=;:/0-9+.]{3,}
+
+# basic auth
+(['"])[Bb]asic [-a-zA-Z=;:/0-9+]{3,}\g{-1}
+
+# basic auth
+: [Bb]asic [-a-zA-Z=;:/0-9+.]{3,}
+
+# base64 encoded content
+([`'"])[-a-zA-Z=;:/0-9+]{3,}=\g{-1}
+# base64 encoded content in xml/sgml
+>[-a-zA-Z=;:/0-9+]{3,}=
+# base64 encoded content, possibly wrapped in mime
+(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
+# base64 encoded json
+\beyJ[-a-zA-Z=;:/0-9+]+
+# base64 encoded pkcs
+\bMII[-a-zA-Z=;:/0-9+]+
+
+# uuencoded
+#[!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_]{40,}
+
+# DNS rr data
+(?:\d+\s+){3}(?:[-+/=.\w]{2,}\s*){1,2}
+
+# encoded-word
+=\?[-a-zA-Z0-9"*%]+\?[BQ]\?[^?]{0,75}\?=
+
+# numerator
+\bnumer\b(?=.*denom)
+
+# Time Zones
+\b(?:Africa|Atlantic|America|Antarctica|Arctic|Asia|Australia|Europe|Indian|Pacific)(?:/[-\w]+)+
+
+# linux kernel info
+^(?:bugs|flags|Features)\s+:.*
+
+# systemd mode
+systemd.*?running in system mode \([-+].*\)$
+
+# Lorem
+# Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally )
+# grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review -
+# Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into:
+# ... Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary).
+# ... You could manually change `(?i)X...` to use `[Xx]...`
+# ... or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path)
+#(?:(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])*)
+
+# Non-English
+# Even repositories expecting pure English content can unintentionally have Non-English content... People will occasionally mistakenly enter [homoglyphs](https://en.wikipedia.org/wiki/Homoglyph) which are essentially typos, and using this pattern will mean check-spelling will not complain about them.
+#
+# If the content to be checked should be written in English and the only Non-English items will be people's names, then you can consider adding this.
+#
+# Alternatively, if you're using check-spelling v0.0.25+, and you would like to _check_ the Non-English content for spelling errors, you can. For information on how to do so, see:
+# https://docs.check-spelling.dev/Feature:-Configurable-word-characters.html#unicode
+#[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
+
+# highlighted letters
+\[[A-Z]\][a-z]+
+
+# French
+# This corpus only had capital letters, but you probably want lowercase ones as well.
+\b[LN]'+[a-z]{2,}\b
+
+# latex (check-spelling >= 0.0.22)
+\\\w{2,}\{
+
+# American Mathematical Society (AMS) / Doxygen
+TeX/AMS
+
+# File extensions
+\*\.[+\w]+,
+
+# eslint
+"varsIgnorePattern": ".+"
+
+# nolint
+nolint:\s*[\w,]+
+
+# Windows short paths
+[/\\][^/\\]{5,6}~\d{1,2}(?=[/\\])
+
+# Windows Resources with accelerators
+\b[A-Z]&[a-z]+\b(?!;)
+
+# signed off by
+(?i)Signed-off-by: .*
+
+# cygwin paths
+/cygdrive/[a-zA-Z]/(?:Program Files(?: \(.*?\)| ?)(?:/[-+.~\\/()\w ]+)*|[-+.~\\/()\w])+
+
+# in check-spelling@v0.0.22+, printf markers aren't automatically consumed
+# printf markers
+(?v#
+(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
+
+# Compiler flags (Unix, Java/Scala)
+# Use if you have things like `-Pdocker` and want to treat them as `docker`
+(?:^|[\t ,>"'`=(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
+
+# Compiler flags (Windows / PowerShell)
+# This is a subset of the more general compiler flags pattern.
+# It avoids matching `-Path` to prevent it from being treated as `ath`
+(?:^|[\t ,"'`=(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
+
+# Compiler flags (linker)
+,-B
+
+# Library prefix
+# e.g., `lib`+`archive`, `lib`+`raw`, `lib`+`unwind`
+# (ignores some words that happen to start with `lib`)
+(?:\b|_)[Ll]ib(?!era[lt])(?:re(?=office)|era|)(?!ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
+
+# iSCSI iqn (approximate regex)
+\biqn\.[0-9]{4}-[0-9]{2}(?:[\.-][a-z][a-z0-9]*)*\b
+
+# WWNN/WWPN (NAA identifiers)
+\b(?:0x)?10[0-9a-f]{14}\b|\b(?:0x|3)?[25][0-9a-f]{15}\b|\b(?:0x|3)?6[0-9a-f]{31}\b
+
+# curl arguments
+\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
+# set arguments
+\b(?:bash|sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*
+# tar arguments
+\b(?:\\n|)g?tar(?:\.exe|)(?:\s-C \S+|(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
+# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
+\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
+# macOS temp folders
+/var/folders/\w\w/[+\w]+/(?:T|-Caches-)/
+# github runner temp folders
+/home/runner/work/_temp/[-_/a-z0-9]+
diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt
new file mode 100644
index 00000000..5475dbed
--- /dev/null
+++ b/.github/actions/spelling/excludes.txt
@@ -0,0 +1,96 @@
+# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes
+(?:^|/)(?i)COPYRIGHT
+(?:^|/)(?i)LICEN[CS]E
+(?:^|/)(?i)third[-_]?party/
+(?:^|/)3rdparty/
+(?:^|/)\.keep$
+(?:^|/)generated/
+(?:^|/)go\.sum$
+(?:^|/)jsontestdata\.py$
+(?:^|/)package(?:-lock|)\.json$
+(?:^|/)Pipfile$
+(?:^|/)pyproject.toml
+(?:^|/)vendor/
+(?:^|/|\b)requirements(?:-dev|-doc|-test|)\.txt$
+\.a$
+\.ai$
+\.all-contributorsrc$
+\.avi$
+\.bmp$
+\.bz2$
+\.cert?$|\.crt$
+\.class$
+\.coveragerc$
+\.crl$
+\.csr$
+\.dll$
+\.docx?$
+\.drawio$
+\.DS_Store$
+\.eot$
+\.eps$
+\.exe$
+\.gif$
+\.git-blame-ignore-revs$
+\.gitattributes$
+\.gitkeep$
+\.graffle$
+\.gz$
+\.icns$
+\.ico$
+\.ipynb$
+\.jar$
+\.jks$
+\.jpe?g$
+\.key$
+\.lib$
+\.lock$
+\.map$
+\.min\..
+\.mo$
+\.mod$
+\.mp[34]$
+\.o$
+\.ocf$
+\.otf$
+\.p12$
+\.parquet$
+\.pdf$
+\.pem$
+\.pfx$
+\.png$
+\.psd$
+\.pyc$
+\.pylintrc$
+\.qm$
+\.s$
+\.sig$
+\.so$
+\.svgz?$
+\.sys$
+\.tar$
+\.tgz$
+\.tiff?$
+\.ttf$
+\.wav$
+\.webm$
+\.webp$
+\.woff2?$
+\.xcf$
+\.xlsx?$
+\.xpm$
+\.xz$
+\.zip$
+^\.github/actions/spelling/
+^\.github/CODEOWNERS$
+^\Q.github/wordlist.txt\E$
+^\Q.github/workflows/spelling.yml\E$
+^benchmarks/__init__\.py$
+^CODE_OF_CONDUCT\.md$
+^tests/__init__\.py$
+^tests/test_asyncio/__init__\.py$
+^tests/test_graph_utils/__init__\.py$
+^valkey/py\.typed$
+/test_connection_pool\.py$
+/testdata/
+ignore$
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
new file mode 100644
index 00000000..5e4ef2b4
--- /dev/null
+++ b/.github/actions/spelling/expect.txt
@@ -0,0 +1,623 @@
+absttl
+ACLCAT
+aclfile
+ACLs
+ADDIFNX
+addnx
+aggregateplan
+ahmedsobeh
+ahmedszakaria
+aia
+aiorc
+aiovalkey
+alertmanager
+alertname
+ALLSPHINXOPTS
+AOF
+argvalues
+arrappend
+arrindex
+arrinsert
+arrlen
+arrpop
+arrtrim
+aserver
+autoclass
+autolabeler
+automodule
+Autosection
+autosectionlabel
+autouse
+avalkey
+avgrandom
+badssl
+barbarbar
+bazz
+bazzz
+bgrewriteaof
+bgsave
+bitop
+bitpos
+blmove
+blmpop
+blpop
+bogususer
+breakingchange
+brpop
+brpoplpush
+BUCKETSIZE
+BUCKETTIMESTAMP
+bufflen
+bumpepoch
+BUSYTIMEFLAG
+BYBOX
+byfields
+bylex
+BYRADIUS
+byrank
+byrevrank
+byscore
+bysource
+bzfp
+bzmpop
+bzpopmax
+bzpopmin
+cachetools
+cadata
+canonname
+capath
+captest
+casesensitive
+checkpid
+chunklen
+ciaoc
+ckquorum
+clickhouse
+clientid
+clusterclient
+CLUSTERDOWN
+cmdname
+cmdstat
+cmsgpack
+cmsincrby
+commandmixin
+commitish
+connstream
+consumername
+corofunc
+countkeysinslot
+createconsumer
+createrule
+crossslot
+curvals
+cwe
+dbsize
+dco
+Decorrelated
+decrby
+deff
+delconsumer
+deleterule
+DELIFX
+delslots
+delslotsrange
+deluser
+DESTENV
+devenv
+devhelp
+DHE
+dic
+DICTADD
+DICTDEL
+DICTDUMP
+dirhtml
+DIRSTRING
+DISMAX
+distinctish
+distinctishtitle
+distincttitle
+DOCNORM
+DOCSCORE
+doctests
+doctrees
+documentclass
+dropindex
+echoerr
+ENTRIESREAD
+evalsha
+evictable
+exat
+excinfo
+exconly
+EXECABORT
+expireat
+expiretime
+explaincli
+extfile
+faz
+fca
+fcall
+fieldstrs
+filterby
+floatreg
+flt
+flushall
+flushconfig
+flushslots
+flynt
+fookey
+foomod
+foomodule
+fromaddr
+FROMLONLAT
+FROMMEMBER
+ftindex
+funcname
+furo
+genindex
+genpass
+getargspec
+getbit
+getbuffer
+getdel
+getex
+getkeys
+getkeysandflags
+GETKEYSINSLOT
+getoption
+getrange
+getredir
+getset
+grl
+Grokzen
+groupid
+hascontent
+haveit
+hazmat
+hdel
+hellobar
+hellobarbaz
+hellobarbazbar
+hexists
+hget
+hgetall
+hhp
+hincrby
+hincrbyfloat
+hkeys
+hlen
+hmget
+hmset
+HNSW
+hostmetrics
+hostnames
+hostport
+hqx
+hrandfield
+hscan
+hset
+hsetnx
+hstrlen
+htmlhelp
+hvals
+Hyperlog
+HYPERLOGLOG
+idef
+idletime
+ifmodversion
+incrby
+incrbyfloat
+infotype
+initbydim
+initbyprob
+INKEYS
+INORDER
+insertnx
+Instrumentor
+intlist
+intreg
+ipynb
+isbn
+ISBUSY
+ISSUB
+jdata
+johny
+JOINSTR
+jset
+jsonget
+jsonkey
+jsonmget
+jsons
+jsonset
+jsonsetexistentialmodifiersshouldsucceed
+jtypes
+justid
+keepttl
+keycloak
+keyspace
+killuser
+KInfo
+KNN
+ktv
+kvparams
+kvs
+kwagrs
+kwexpr
+kwparams
+laddr
+lastsave
+latexpdf
+lbls
+lcs
+letterpaper
+lfu
+LIBRARYNAME
+libvalkey
+lindex
+linkcheck
+linsert
+llen
+lmove
+lmpop
+loadall
+loadchunk
+loadex
+loadfields
+lolwut
+lorm
+lpop
+lpos
+lpush
+lpushx
+lrange
+lset
+Magnocavallo
+mailhog
+makeinfo
+mallocs
+MASTERDOWN
+maxage
+MAXIDLE
+MAXITERATIONS
+maxmemory
+MAXTEXTFIELDS
+maxval
+mema
+memb
+memc
+memoryviews
+mexists
+mgetshouldsucceed
+minid
+minlen
+minmatchlen
+minval
+mkstream
+Moby
+mockobj
+modindex
+moduleauthor
+movablekeys
+mrange
+mrevrange
+msetnx
+msgpack
+multikey
+multipleoptions
+myshardid
+nativestr
+nbsphinx
+nbytes
+newobj
+newsocket
+newtext
+newttl
+nitpicky
+noack
+NOAUTH
+NOCONTENT
+nocreate
+nocryptography
+nodemap
+noeviction
+NOFIELDS
+NOFREQS
+nohl
+nojsonfile
+nokey
+noloop
+nomkstream
+nooffset
+nopass
+noq
+nostem
+NOSTOPWORDS
+notakey
+notarealkey
+NOTREAL
+notset
+novalues
+NSPHINXOPTS
+numby
+numincrby
+numkeys
+nummultby
+numpat
+numreq
+numsub
+numsubbed
+numval
+objkeys
+objlen
+ocsps
+odown
+ohmytext
+oll
+omem
+onlycluster
+onlynoncluster
+opensearch
+opentelemetry
+optin
+optval
+opvs
+otelcol
+otlp
+pagerefs
+pandoc
+PAPEROPT
+papersize
+payloadidx
+pdflatex
+PEL
+pexpire
+pexpireat
+pexpiretime
+pfadd
+pfail
+pfcount
+pfmerge
+pmessage
+pointsize
+popleft
+prettytable
+PROPERTYKEYS
+psetex
+psubscribe
+psync
+pttl
+pubsubs
+punsubscribe
+pxat
+pyspelling
+pytestmark
+qaz
+qbuf
+qcollectiongenerator
+qhc
+qhcp
+qthelp
+quantilerandom
+queryindex
+racuda
+ramen
+randomkey
+rca
+RDB
+readexact
+Redi
+redisearch
+REDISGEARS
+redisjson
+rediss
+REFRESHCLUSTER
+refvalue
+RELATIONSHIPTYPES
+rels
+reltype
+renamenx
+replicaof
+replicationid
+requirepass
+resetchannels
+resetkeys
+resetpass
+resetstat
+resharding
+resourcedetection
+resub
+retrycount
+revrange
+revrank
+riceratops
+rpoplpush
+rpush
+rpushx
+sadd
+saveconfig
+scandump
+scard
+scoreidx
+sdiff
+sdiffstore
+sdown
+SEARCHSTORE
+sectionauthor
+sedrik
+serializedlength
+serie
+sessionstart
+setbinarykey
+setex
+setgetdelete
+setgetdeleteforget
+setid
+setinfo
+setname
+setnx
+setrange
+setslot
+setuser
+shardchannels
+shardid
+shardnumsub
+singlehtml
+sintercard
+sinterstore
+sismember
+skipif
+skipinitial
+SKIPINITIALSCAN
+skipme
+slaveof
+slotsrange
+slowlog
+smarthost
+smartypants
+smembers
+smismember
+soba
+socktype
+Solovyov
+someotherkey
+sometestfuncname
+someval
+sourcelink
+spaceballs
+spellcheck
+spfmerge
+SPHINXBUILD
+spop
+spublish
+squarespace
+srandmember
+srem
+sscan
+sslclient
+sslctx
+sslsock
+sso
+ssubscribe
+STAGETESTS
+stddevrandom
+stopword
+storedist
+stralgo
+strappend
+strem
+subcmd
+subv
+sug
+sugadd
+sugdel
+sugget
+suglen
+sugsize
+sunion
+sunionstore
+sunsubscribe
+swapdb
+SYNADD
+syndump
+synupdate
+tagfield
+tagvals
+tdigest
+telmatosaurus
+testc
+tfcall
+TFCALLASYNC
+TFIDF
+tfunction
+tobytes
+tonumber
+topkincrby
+topklist
+trackinginfo
+triaging
+TRYAGAIN
+tsinfo
+typehints
+ujson
+unittests
+unixtime
+unsub
+unwatch
+Unwatches
+usedby
+uvloop
+vae
+valkey
+valkeybloom
+valkeycluster
+valkeyearch
+valkeymod
+valkeymodules
+valkeypy
+venvforinstall
+viewcode
+vtotal
+vulns
+waitaof
+WAITFORIT
+weakrefs
+WESTMORELAND
+whl
+withcode
+withcoord
+withcount
+withcrypto
+WITHCURSOR
+withdist
+withhash
+withlabels
+withmatchlen
+WITHPAYLOADS
+WITHSCHEMA
+withscore
+withsuffixtrie
+withvalues
+wordlists
+WRONGPASS
+xack
+xadd
+xautoclaim
+xbdo
+xclaim
+xdel
+xfga
+xgroup
+xinfo
+xlen
+xpending
+xread
+xreadgroup
+xrevrange
+xtrim
+yayakey
+ynet
+yqq
+zadd
+zaggregate
+zaz
+zcard
+zcount
+zdiff
+zdiffstore
+zincrby
+zinter
+zlexcount
+zmpop
+zmscore
+zpages
+zpopmax
+zpopmin
+zrange
+zrank
+zrem
+zrevrange
+zrevrank
+zscan
+zset
+zunion
+zunionstore
diff --git a/.github/actions/spelling/line_forbidden.patterns b/.github/actions/spelling/line_forbidden.patterns
new file mode 100644
index 00000000..6af3ad47
--- /dev/null
+++ b/.github/actions/spelling/line_forbidden.patterns
@@ -0,0 +1,788 @@
+# reject `m_data` as VxWorks defined it and that breaks things if it's used elsewhere
+# see [fprime](https://github.com/nasa/fprime/commit/d589f0a25c59ea9a800d851ea84c2f5df02fb529)
+# and [Qt](https://github.com/qtproject/qt-solutions/blame/fb7bc42bfcc578ff3fa3b9ca21a41e96eb37c1c7/qtscriptclassic/src/qscriptbuffer_p.h#L46)
+#\bm_data\b
+
+# Were you debugging using a framework with `fit()`?
+# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test,
+# you might not want to check in code where you skip all the other tests.
+#\bfit\(
+
+# English does not use a hyphen between adverbs and nouns
+# https://twitter.com/nyttypos/status/1894815686192685239
+(?:^|\s)[A-Z]?[a-z]+ly-(?=[a-z]{3,})(?:[.,?!]?\s|$)
+
+# Smart quotes should match
+\s’[^.?!‘’]+’[^.?!‘’]+‘[^.?!‘’]+’|\s‘[^.?!‘’]+’[^.?!‘’]+’[^.?!‘’]+’|\s”[^.?!“”]+”[^.?!“”]+“[^.?!“”]+”|\s“[^.?!“”]+”[^.?!“”]+”[^.?!“”]+”
+
+# Don't write double negatives
+\w+n't not(?=\s)
+
+# Generally spaces follow instead of preceding `,`s
+# It's possible this is some strange CSV dialect, but, even so, you could probably move the space.
+\s[a-z]{3,} ,[a-z]{3,}\s
+
+# Generally words are written with `'s`, not `"s`
+[^=|+']\s\w+"s\s
+
+# Don't miswrite **irreversible binomials**
+# https://en.wikipedia.org/wiki/Irreversible_binomial
+(?i)\b(?:cheese and macroni|honey and milk|sweet and short|die or do|roll and rock|the bees and the birds|match and mix|tear and wear|clear and loud|death and life|span and spick|vigor and vim|abet and aid|says and deposes|means and ways|dryer and washer|relaxation and rest|famous and rich|loan and savings|come high\s?water or hell|tuck and nip|turf and surf|between a hard place and a rock|dime and five|mouse and cat|tired and sick|pregnant and barefoot|feathered and tarred|feathers and tar|subtraction and addition|liabilities and assets|forth and back|strikes and balls|end to beginning|white and black|small and big|bust or boom|groom and bride|sister and brother|pass and butt|sell and buy|release and catch|effect and cause|state and church|robbers and cops|go and come|going and coming|Indians and cowboys|nights and days|wide and deep|flow and ebb|ice and fire|last and first|ceiling to floor|drink and food|aft and fore|domestic and foreign|backward and forward|foe or friend|back to front|vegetables and fruits|take and give|evil and good|foot and hand|heels over head|Hell and Heaven|there and here|seek and hide|dale and hill|her and him|low and high|valleys and hills|hers and his|thither and hither|yon and hither|cold and hot|wife and husband|out and in|gentlemen and ladies|sea and land|death or life|short and long|found and lost|hate and love|war and love|wife and man|matter over mind|pop and mom|nice or naughty|far and near|tuck and nip|south to north|then and now|later and now|shut and open|under and over|ride and park|starboard and port|cons and pros|pull and push|file and rank|fall and rise|loan and savings|water and soap|finish to start|go and stop|dip and strike|sour and sweet|thin and thick|ring and tip|fro and to|bottom to top|country and town|down and up|downs and ups|downtown and uptown|peace and war|dryer and washer|wane and wax|no and yes|yang and yin|a curse and a blessing|don'ts and dos |farewell and hail|wait and hurry up|difference\s(?:\w+\s+)+day and night|in health and in sickness|from stern to stem|the dead and the quick|a place and a time|generations and ages|comfort and aid|alack and alas|pieces and bits|soul and body|early and bright|mortar and brick|jowl by cheek|tidy and clean|verse and chapter|saucer and cup|cents and dollars|loathing and fear|chips and fish|foremost and first|farewell and hail|fist over hand|shoulders and head|soul and heart|spices and herbs|home and house|thirst and hunger|fork and knife|bounds and leaps|behold and lo|tidy and neat|dime and nickel|cranny and nook|void and null|bolts and nuts|suffering and pain|quiet and peace|ink and pen|choose and pick|simple and plain|proper and prim|rave and rant|shoals and rocks|awe and shock|wonders and signs|bones and skull|crossbones and skull|narrow and strait|narrow and straight|strain and stress|roundabouts and swings|chiggers and ticks|complain and whine|rain and wind|amen and yea|(?:raised|bred) and born|by crook or by hook|(?<=it was a )stormy and dark(?= night)|(?<=this ) age and day|cross the t's and dot the i's|high minded and haughty|best and highest(?= use)|like daughter, like mother|done and over with|(?<=on ) needles and pins|half a dozen of the other, six of one|(?<=up ) personal and close|baggage and bag|beads and baubles|balance and beams|breakfast and bed|braces and belt|bar and bench|bad and big|bosh bash bish|blue and black|beautiful and bold|Baptists and bootleggers|briefs or boxers|butter and bread|boar and bull|carry and cash|cheese and chalk|clans and cliques|control and command|cream and cookies|dumb and deaf|dash and dine|dirty and down|drabs and dribs|drive and drink|disorderly and drunk|furious and fast|famine or feast|forget and fire|fury and fire|fauna and flora|forget and forgive|function and form|foe or friend|frolics and fun|feathers and fur|goblins and ghosts|giggles and grins|home and hearth|haw and hem|holler and hoot|handgrenades and horseshoes|Gentile and Jew|jiving and juking|country and king|caboodle and kit|kin and kith|longitude and latitude|limb and life|learn and live|load and lock|match and mix|mild and meek|number and name|parcel and part|pencil and pen|post to pillar|pans and pots|perish or publish|riches to rags|raving and ranting|write and read|rumble to ready|wrong and right|roll and rock|ready and rough|regulations and rules|secure and safe|sound and safe|shell and shot|shave and shower|symptoms and signs|slide and slip|span and spick|shine and spit|Stripes and Stars|stones and sticks|spice and sugar|that or this|tat for tit|tail and top|turn and toss|treat or trick|tribulations and trials|tested and tried|true and tried|trailer and truck|wear and wash|waiting and watching|wail and weep|wild and wet|hollering and whooping|woolly and wild|wonderful and wise|warlocks and witches|ruin and wrack|the bees and the birds|(?<=between the) deep blue sea and the devil|Dragons & Dungeons|fuck off or fit in|flop-flip|fancy-free and footloose|to hold and to have|least but not last|Lease-Lend|leave ['‘]em and love ['‘]em|leave it or love it|paper and pen(?:cil|)|patter-pitter|relaxation and rest|(?<=without )reason or rhyme|tacky-ticky|take and break|zoom and boom|cox and box|talk and chalk|darts and charts|dip and chips|drive and dive|square and fair|dime and five|jetsam and flotsam|dry and high|fire and hire|split and hit|thither and hither|trot to hot|puff and huff|bustle and hustle|gap and lap|greatest and latest|proud and loud|greet and meet|right makes might|shame and name|dear and near|sods and odds|upwards and onwards|about and out|proud and out|dump and pump|tough and rough|gun and run|clout and shout|bake and shake|surely but slowly|joke and smoke|dash and stash|bitch and stitch|drop and stop|turf and surf|tide and time|gown and town|bake and wake|tear and wear|feed and weed|dealing and wheeling|dine and wine|nay or yea|trouble double|bender fender|dandy-handy|panky-hanky|scarum-harum|skelter helter|piggledy higgledy|quit it and hit|pocus hocus|toity[- ]hoity|potch-hotch|burly-hurly|bitty-itty|bitsy-itsy|votor moter|the highway or my way|pamby-namby|claim it and name it|ever, never|gritty nitty|porgy orgy|mell-pell|baggy saggy|so good, so far|weeny-teeny|blue true|lose it or use it|nilly willy|(?<=the )nays and (?:the |)yeas|beyond and above|graces and airs|muster and alarm|kicking and alive|well and alive|dangerous and armed|oranges and apples|fill and back|forth and back|eggs and bacon|mash and bangers|switch and bait|tackle and bait|pregnant and barefoot|sale and bargain|breakfast and bed|call and beck|whistles and bells|suspenders and belt|bold and big|tall and big|better and bigger|purge and binge|bridle and bit|bobs and bits|pieces and bits|blue and black|tackle and block|guts and blood|gore and blood|weave and bob|arrow and bow|determined and bound|gagged and bound|scrape and bow|bit and brace|water and bread|circuses and bread|roses and bread|serve and brown|spade and bucket|grind and bump|run and bump|large and by|gown and cap|driver and car|mouse and cat|balances and checks|dumplings and chicken|change and chop|sober and clean|dagger and cloak|tie and coat|doughnuts and coffee|go and come|burn and crash|sugar and cream|punishment and crime|saucer and cup|paste and cut|run and cut|burdock and dandelion|night and day|buried and dead|gone and dead|taxes and death|dash and dine|conquer and divide|out and down|cover and duck|dive and duck|every and each|ears and eyes|figures and facts|wide and far|furious and fast|loose and fast|dandy and fine|thumbs and fingers|brimstone and fire|foremost and first|chips and fish|blood and flesh|bone and flesh|ever and forever|center and front|games and fun|bother and fuss|take and give|aspirations and goals|plenty and good|light and goodness|pound and ground|slash and hack|hearty and hale|fast and hard|eggs and ham|nail and hammer|sickle and hammer|tongs and hammer|minds and hearts|now and here|seek and hide|watch and hide|mighty and high|dry and high|tight and high|miss and hit|run and hit|yon and hither|thither and hither|hosed and home|dry and home|eye and hook|loop and hook|buggy and horse|carriage and horse|heavy and hot|high and hot|bothered and hot|puff and huff|when and if|custard and kippers|tell and kiss|kin and kith|fork and knife|screaming and kicking|streams and lakes|order and law|behold and lo|dam and lock|key and lock|feel and look|clear and loud|boy and man|potatoes and meat|women and men|cookies and milk|honey and milk|tenon and mortise|shakers and movers|address and name|faces and names|easy and nice|cranny and nook|crosses and noughts|bolts and nuts|ends and odds|away and off|done and one|about and out|out and over|terminer and oyer|cream and peaches|Qs and Ps|carrots and peas|axe and pick|moan and piss|vinegar and piss|whine and piss|proper and prim|booty and prize|cons and pros|beans and pork|simple and pure|dirty and quick|pinion and rack|ruin and rack|pillage and rape|famous and rich|fall and rise|shine and rise|board and room|tumble and rough|jump and run|pepper and salt|vinegar and salt|sniff and scratch|rescue and search|destroy and seek|tie and shirt|fat and short|sweet and short|stout and short|tell and show|jive and shuck|tired and sick|burn and slash|arrows and slings|fall and slip|steady and slow|grab and smash|mirrors and smoke|ladders and snakes|dance and song|fury and sound|polish and spit|deliver and stand|strain and stress|Drang und Sturm|debonair and suave|tie and suit|rainbows and sunshine|demand and supply|light and sweetness|sandal and sword|chairs and tables|thin and tall|feathers and tar|crumpets and tea|lightning and thunder|ass and tits|fro and to|nail and tooth|go and touch|field and track|error and trial|tribulations and trials|roll and tuck|turn and twist|about and up|coming and up|vigor and vim|see and wait|fuzzy and warm|weft and warp|ward and watch|wane and wax|means and ways|good and well|whine and whinge|roses and wine|phrases and words|no and yes|a leg and an arm|(?<=old )chain and ball|by golly and by guess|bull-and-cock|dried (dry) and cut|(?<=in this )age and day|pony and dog show|(?<=by )starts and fits|grin and bear it|(?<=move ) earth and heaven|quit it and hit it|kisses and hugs|(?<=for all )purposes and intents|make up and kiss|last testament and will|make do and mend|(?<=every ) then and now|for all and once(?=[,.;!?])|jelly and peanut butter|ice cream and pickles|raining dogs and cats|development and research|blues and rhythm|(?<=between a )hard place and a rock|(?<=all's )done and said|(?<=different )sizes and shapes|bones? and skin|(?<=in )spirit and (?:in |)truth|a miss and a swing|(?<=through )thin and thick|O's and X's|a day and a year|nothing or all|worse or better|small or big|white or black|pleasure or business|night or day|alive or dead|die or do|flight or fight|take or give|bad or good|simple or gentle|she or he|tails or heads|her or his|miss or hit|cure or kill|break or make|less or more|never or now|shine or rain|reason or rhyme|wrong or right|swim or sink|later or sooner|more or two|down or up|death or victory|lose or win|no or yes|the egg or (?:the |)chicken|(?<=neither )fowl nor fish|(?<=come )high water or hell|(?<=neither )there nor here|(?<=neither )hair nor hide|(?<=not one )tittle or jot|(?<=neither )money nor love|shut up or put up|leave it or take it|(?<=neither )ornament nor use|gatherer-hunter|cheese corn|Costello and Abbott|Isaac and Abraham|Patroclus and Achilles|Eve and Adam|Anicetus and Alexiares|Cleopatra and Antony|Ant & Dec|Robin and Batman|Clyde and Bonnie|Abel and Cain|Ball and Cannon|Pollux and Castor|Psyche and Cupid|Clack and Click|Pythias and Damon|Goliath and David|Guattari and Deleuze|Jane and Dick|Marguerite and Faust|Swann and Flanders|Saunders and French|Frack and Frick|Laurie and Fry|Sullivan and Gilbert|Aga and Gilgamesh|Gretel and Hansel|Hellman & Friedman|Esau and Jacob|Jill and Jack|Victor and Jack|Vijaya and Jaya|Jekyll & Hyde|Hardy and Laurel|McCartney and Lennon|Loewe and Lerner|Clark and Lewis|Lilo & Stitch|Large and Little|Meslamta-ea and Lugal-irra|Luigi and Mario|Lewis and Martin|Ashley and Mary-Kate Olsen|Sue and Mel|Wise and Morecambe|Mindy and Mork|Eurydice and Orpheus|Horse-Face and Ox-Head|Penn & Teller|Aristotle and Phyllis|Ferb and Phineas|Pinky & The Brain|Galatea and Pygmalion|Ren & Stimpy|Rhett & Link|Morty and Rick|Hart and Rodgers|Hammerstein and Rodgers|Juliet and Romeo|Remus and Romulus|Guildenstern and Rosencrantz|Max and Sam|Delilah and Samson|Simon & Garfunkel|Sonny & Cher|Thelma & Louise|Thompson and Thomson|Tom & Jerry|Isolde and Tristan|Tim & Eric|Adonis and Venus|Vic & Bob|Crick and Watson|Eve and Adam|pears and apples|glass and bottle|Liszt and Brahms|bone and dog|toad and frog|blister and hand|south and north|pork and rabbit|strife and trouble|eight and two|flute and whistle)\b
+
+# Don't use `requires that` + `to be`
+# https://twitter.com/nyttypos/status/1894816551435641027
+\brequires that \w+\b[^.]+to be\b
+
+# A fully parenthetical sentence’s period goes inside the parentheses, not outside.
+# https://twitter.com/nyttypos/status/1898844061873639490
+\([A-Z][a-z]{2,}(?: [a-z]+){3,}\)\.\s
+
+# Complete sentences shouldn't be in the middle of another sentence as a parenthetical.
+(?]|depth|frame|page|project|select)\s[1-9] (?!byte|day|hour|meaning|minute|month|(?:new |)page|people|(?:more |)space|year)[a-z]+ [a-z]+\s
+
+# Write out numbers at the start of a sentence
+# https://www.scribendi.com/academy/articles/when_to_spell_out_numbers_in_writing.en.html#:~:text=Beginning%20a%20Sentence%20with%20a%20Number,may%20be%2e
+(?:\b[a-z]{4,}|\s(?:[a-eg-z][a-z]{2}|f[a-hj-z][a-z]|fi[a-fh][a-z]))[:.?!] [1-9] [a-z]{3,} [a-z]+\s\w+
+
+# Don't write two numbers in a row
+# https://www.scribendi.com/academy/articles/when_to_spell_out_numbers_in_writing.en.html#:~:text=Paired%20Numbers%20%28Two%20Numbers%20in,librarian%20to%20begin%20story%20time%2e
+(?:[a-z]{4,}|\s(?!apr|aug|dec|feb|fri|jan|mar|mon|nov|oct|sat|sep|sun|thu|tue|wed)[a-z]{3})\s\d+\s\d+(?!--)[-\s](?:(?!--)[-A-Za-z]){2,}\s
+
+# This probably indicates Mojibake https://en.wikipedia.org/wiki/Mojibake
+# You probably should try to unbake this content
+Ã(?:Â[¤¶¥]|[£¢])|Ã
+
+# Should be `HH:MM:SS`
+\bHH:SS:MM\b
+
+# Should be `86400` (seconds in a standard day)
+\b84600\b(?:.*\bday\b)
+
+# Should probably be `2006-01-02` (yyyy-mm-dd)
+# Assuming that the time is being passed to https://go.dev/src/time/format.go
+\b2006-02-01\b
+
+# Should probably have a trailing `.`
+\s([a-z]\.){2,}[a-z]\s
+
+# Should probably end with `”`
+# Likely bad OCR
+“.+[^'‘\\\[]+’'(?!['"])
+
+# Should probably end with `”` or with only one of `’`/`'`
+\s\w+[^'‘\\\[]+’'(?!['"])
+
+# Should probably be matching (smart)quotes or backticks (if Markdown)
+# Unless the file format is Tex
+#(? Don't use `can not` when you mean `cannot`. The only time you're likely to see `can not` written as separate words is when the word `can` happens to precede some other phrase that happens to start with `not`.
+# > `Can't` is a contraction of `cannot`, and it's best suited for informal writing.
+# > In formal writing and where contractions are frowned upon, use `cannot`.
+# > It is possible to write `can not`, but you generally find it only as part of some other construction, such as `not only . . . but also.`
+# - if you encounter such a case, add a pattern for that case to patterns.txt.
+\b[Cc]an not\b(?! only\b)
+
+# Should be `chart`
+(?i)\bhelm\b.*\bchard\b
+
+# Should be `counter-intuitive`
+\bcounter intuitive\b
+
+# Do not use `(click) here` links
+# For more information, see:
+# * https://www.w3.org/QA/Tips/noClickHere
+# * https://webaim.org/techniques/hypertext/link_text
+# * https://granicus.com/blog/why-click-here-links-are-bad/
+# * https://heyoka.medium.com/dont-use-click-here-f32f445d1021
+(?i)(?:>|\[)(?:(?:click |)here|link|(?:read |)more)(?:|\]\()
+
+# Including "image of" or "picture of" in alt text is unnecessary.
+\balt=['"](?:an? |)(?:image|picture) of
+
+# Alt text should be short
+\balt=(?:'[^']{126,}'|"[^"]{126,}")
+
+# Should be either of `default` or `fallback`, but not both
+# Unless you have a non-default fallback, but that's just weird.
+\bdefault fallback\b
+
+# Should be `effect`
+(?<=\btake )affect\b
+
+# Should be `end`
+(?<=\b[Ww]e )ends\b
+
+# Should be `ends`
+\bend's(?= up\b)
+
+# Should be `-endian`
+\b(?i)(?<=big|little) endian\b
+
+# Should be `equals` to `is equal to`
+\bequals to\b
+
+# Should be `ECMA` 262 (JavaScript)
+(?i)\bTS\/EMCA\b|\bEMCA(?: \d|\s*Script)|\bEMCA\b(?=.*\bTS\b)
+
+# Should be `ECMA` 340 (Near Field Communications)
+(?i)EMCA[- ]340
+
+# Should be `exceed`
+\bare higher than\b
+
+# Should be `exceeds`
+\bis higher than\b
+
+# Should be `fall back`
+\bfallback(?= to)\b
+
+# Should be `for`, `for, to` or `to`
+\b(?:for to|to for)\b
+
+# Should be `ghcr.io`
+# https://bmitch.net/blog/2025-08-22-ghrc-appears-malicious/
+\bghrc\.io\b
+
+# Should be `GitHub`
+(?> /etc/apt/sources.list.d/something-distro.list
+# ````
+\bapt-key add\b
+
+# Should be `nearby`
+\bnear by\b
+
+# Should probably be a person named `Nick` or the abbreviation `NIC`
+\bNic\b
+
+# Should be `not supposed`
+\bsupposed not\b
+
+# Should be `Once this` or `On this` or even `One that`. Rarely `One, this`
+[?!.] One this\b
+
+# Should probably be `much more`
+\bmore much\b
+
+# Should be `perform its`
+\bperform it's\b
+
+# Should be `PowerPoint`
+\bPowerpoint\b
+
+# Should be `opt-in`
+(? below for the`
+(?i)\bfind below the\b
+
+# Should be `then any` unless there's a comparison before the `,`
+, than any\b
+
+# Should be `did not exist`
+\bwere not existent\b
+
+# Should be `nonexistent`
+\bnon existing\b
+
+# Should be `nonexistent`
+\b[Nn]o[nt][- ]existent\b
+
+# Should be `only does`
+\bdoes only(?! (?:seem|match))\b
+
+# Should be `only matches`
+\bdoes only match\b
+
+# Should be `only seems`
+\bdoes only seem\b
+
+# Should be `our`
+\bspending out time\b
+
+# Should be `@brief` / `@details` / `@param` / `@return` / `@retval`
+(?:^\s*|(?:\*|//|/*)\s+`)[\\@](?:breif|(?:detail|detials)|(?:params(?!\.)|prama?)|ret(?:uns?)|retvl)\b
+
+# Should be `more than` or `more, then`
+\bmore then\b
+
+# Should be `Pipeline`/`pipeline`
+(?:(?<=\b|[A-Z])p|P)ipeLine(?:\b|(?=[A-Z]))
+
+# Should be `please do not`/`you do not need`
+[Pp]lease do not need
+
+# Should be `preexisting`
+[Pp]re[- ]existing
+
+# Should be `preempt`
+[Pp]re[- ]empt\b
+
+# Should be `preemptively`
+[Pp]re[- ]emptively
+
+# Should be `prepopulate`
+[Pp]re[- ]populate
+
+# Should be `prerequisite`
+[Pp]re[- ]requisite
+
+# Should be `principal`
+[Pp]rinciple(?= [Ee]ngineer)
+
+# Should be `QuickTime`
+\bQuicktime\b
+
+# Should be `recently changed` or `recent changes`
+[Rr]ecent changed
+
+# Should be `reentrancy`
+[Rr]e[- ]entrancy
+
+# Should be `reentrant`
+[Rr]e[- ]entrant
+
+# Should be `room for`
+\brooms for (?!lease|rent|sale)
+
+# Reword
+\b(?i)same as for\b
+
+# Should be `socioeconomic`
+# https://dictionary.cambridge.org/us/dictionary/english/socioeconomic
+socio-economic
+
+# Should be `strong suit`
+\b(?:my|his|her|their) strong suite\b
+
+# Should probably be `temperatures` unless actually talking about thermal drafts (things birds may fly on)
+\bthermals\b
+
+# Should be `there are` or `they are` (or `they're`)
+(?i)\btheir are\b
+
+# Should be `they have` or `have the` or ...
+\bthe (?:have(?! nots)|has)\b
+
+# Should be `too`
+(?<=\sit's )to(?= (?:big|large|small|tiny|important))
+
+# Should be `too`
+(?<=\bway )to(?= many\s)
+
+# Should be `true`
+(?i)(?
+
+WATCH(?=[a-z])
+
+# tolerated irreversible binomials
+the low and high cutoff quantiles|longitude and latitude
+
+# Automatically suggested patterns
+
+# hit-count: 638 file-count: 54
+# https/http/file urls
+(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/*%?=~_|!:,.;]+[-A-Za-z0-9+&@#/*%=~_|]
+
+# hit-count: 303 file-count: 42
+# Python string prefix / binary prefix
+# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
+(?v#
+(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
+
+# hit-count: 35 file-count: 4
+# Repeated letters
+\b([A-Za-z])\g{-1}{2,}\b
+
+# hit-count: 17 file-count: 5
+# URL escaped characters
+%[0-9A-F][A-F](?=[A-Za-z])
+
+# hit-count: 12 file-count: 2
+# container images
+image: [-\w./:@]+
+
+# hit-count: 7 file-count: 5
+# sha-... -- uses a fancy capture
+(\\?['"]|")[0-9a-f]{40,}\g{-1}
+
+# hit-count: 6 file-count: 6
+# GitHub actions
+\buses:\s+(['"]?)[-\w.]+/[-\w./]+@[-\w.]+\g{-1}
+
+# hit-count: 4 file-count: 1
+# Markdown anchor links
+\(#\S*?[a-zA-Z]\S*?\)
+
+# hit-count: 1 file-count: 1
+# GHSA
+GHSA(?:-[0-9a-z]{4}){3}
+
+# hit-count: 1 file-count: 1
+# Docker images
+^\s*(?i)FROM\s+\S+:\S+(?:\s+AS\s+\S+|)
+
+# Questionably acceptable forms of `in to`
+# Personally, I prefer `log into`, but people object
+# https://www.tprteaching.com/log-into-log-in-to-login/
+\b(?:(?:[Ll]og(?:g(?=[a-z])|)|[Ss]ign)(?:ed|ing)?) in to\b
+
+# to opt in
+\bto opt in\b
+
+# pass(ed|ing) in
+\bpass(?:ed|ing) in\b
+
+# acceptable duplicates
+# ls directory listings
+[-bcdlpsw](?:[-r][-w][-SsTtx]){3}[\.+*]?\s+\d+\s+\S+\s+\S+\s+[.\d]+(?:[KMGT]|)\s+
+# mount
+\bmount\s+-t\s+(\w+)\s+\g{-1}\b
+# C types and repeated CSS values
+\s(auto|await|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?:\s\g{-1})+\s
+# C enum and struct
+\b(?:enum|struct)\s+(\w+)\s+\g{-1}\b
+# go templates
+\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml):
+# doxygen / javadoc / .net
+(?:[\\@](?:brief|defgroup|groupname|link|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+(?:static|override|readonly|required|virtual))*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s
+
+# macOS file path
+(?:Contents\W+|(?!iOS)/)MacOS\b
+
+# Python package registry has incorrect spelling for macOS / Mac OS X
+"Operating System :: MacOS :: MacOS X"
+
+# "company" in Germany
+\bGmbH\b
+
+# IntelliJ
+\bIntelliJ\b
+
+# Commit message -- Signed-off-by and friends
+^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$
+
+# Autogenerated revert commit message
+^This reverts commit [0-9a-f]{40}\.$
+
+# ignore long runs of a single character:
+\b([A-Za-z])\g{-1}{3,}\b
diff --git a/.github/actions/spelling/reject.txt b/.github/actions/spelling/reject.txt
new file mode 100644
index 00000000..799b2ec7
--- /dev/null
+++ b/.github/actions/spelling/reject.txt
@@ -0,0 +1,24 @@
+^attache$
+^bellows?$
+benefitting
+occurences?
+^dependan.*
+^develope$
+^developement$
+^developpe
+^Devers?$
+^devex
+^devide
+^Devinn?[ae]
+^devisal
+^devisor
+^diables?$
+^immediatly$
+^oer$
+Sorce
+^[Ss]pae.*
+^Teh$
+^untill$
+^untilling$
+^venders?$
+^wether.*
diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml
deleted file mode 100644
index 1517c339..00000000
--- a/.github/workflows/spellcheck.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-name: spellcheck
-on:
- pull_request:
-jobs:
- check-spelling:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v5
- - name: Check Spelling
- uses: rojopolis/spellcheck-github-actions@0.52.0
- with:
- config_path: .github/spellcheck-settings.yml
- task_name: Markdown
diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
new file mode 100644
index 00000000..5d876abf
--- /dev/null
+++ b/.github/workflows/spelling.yml
@@ -0,0 +1,94 @@
+name: Check Spelling
+
+# SARIF reporting
+#
+# Access to SARIF reports is generally restricted (by GitHub) to members of the repository.
+#
+# Requires enabling `security-events: write`
+# and configuring the action with `use_sarif: 1`
+#
+# For information on the feature, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-SARIF-output
+
+# Minimal workflow structure:
+#
+# on:
+# push:
+# ...
+# pull_request_target:
+# ...
+# jobs:
+# # you only want the spelling job, all others should be omitted
+# spelling:
+# # remove `security-events: write` and `use_sarif: 1`
+# # remove `experimental_apply_changes_via_bot: 1`
+# ... otherwise, adjust the `with:` as you wish
+
+on:
+ push:
+ branches:
+ - '**'
+ tags-ignore:
+ - '**'
+ pull_request_target:
+ branches:
+ - '**'
+ types:
+ - 'opened'
+ - 'reopened'
+ - 'synchronize'
+
+permissions: {}
+
+jobs:
+ spelling:
+ name: Check Spelling
+ permissions:
+ contents: read
+ pull-requests: read
+ actions: read
+ security-events: write
+ outputs:
+ followup: ${{ steps.spelling.outputs.followup }}
+ runs-on: ubuntu-latest
+ if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }}
+ concurrency:
+ group: spelling-${{ github.event.pull_request.number || github.ref }}
+ # note: If you use only_check_changed_files, you do not want cancel-in-progress
+ cancel-in-progress: true
+ steps:
+ - name: check-spelling
+ id: spelling
+ uses: check-spelling/check-spelling@v0.0.25
+ with:
+ suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }}
+ checkout: true
+ check_file_names: 1
+ post_comment: 0
+ use_magic_file: 1
+ warnings: bad-regex,binary-file,deprecated-feature,ignored-expect-variant,large-file,limited-references,no-newline-at-eof,noisy-file,non-alpha-in-dictionary,token-is-substring,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,no-files-to-check,unclosed-block-ignore-begin,unclosed-block-ignore-end
+ use_sarif: 1
+ check_extra_dictionaries: ""
+ dictionary_source_prefixes: >
+ {
+ "asm": "https://raw.githubusercontent.com/check-spelling/assembly-dictionaries/20231110/",
+ "cspell": "https://raw.githubusercontent.com/check-spelling/cspell-dicts/v20230509/dictionaries/",
+ "cspell1": "https://raw.githubusercontent.com/check-spelling/cspell-dicts/v20241114/dictionaries/",
+ "census": "https://raw.githubusercontent.com/check-spelling-sandbox/census/dictionaries-d90e686f89dd241ad61d30f26619e54d73e73c6e/dictionaries/"
+ }
+ extra_dictionaries: |
+ cspell1:software-terms/softwareTerms.txt
+ cspell1:python/python/python-lib.txt
+ cspell1:python/python/python.txt
+ census:census-5.txt
+ cspell1:python/common/extra.txt
+ cspell1:fonts/fonts.txt
+ cspell1:php/php.txt
+ cspell1:java/java.txt
+ cspell1:fullstack/fullstack.txt
+ cspell1:java/java-terms.txt
+ cspell1:npm/npm.txt
+ cspell1:r/r.txt
+ asm:ia64-isa.txt
+ cspell1:cpp/people.txt
+ cspell1:golang/go.txt
+ cspell1:django/django.txt
diff --git a/docs/advanced_features.rst b/docs/advanced_features.rst
index 4db58460..ab1211fc 100644
--- a/docs/advanced_features.rst
+++ b/docs/advanced_features.rst
@@ -229,8 +229,8 @@ following keys.
- **type**: One of the following: 'subscribe', 'unsubscribe',
'psubscribe', 'punsubscribe', 'message', 'pmessage'
-- **channel**: The channel [un]subscribed to or the channel a message
- was published to
+- **channel**: The channel [un]subscribed to or the channel to which a message
+ was published
- **pattern**: The pattern that matched a published message's channel.
Will be None in all cases except for 'pmessage' types.
- **data**: The message data. With [un]subscribe messages, this value
diff --git a/docs/clustering.rst b/docs/clustering.rst
index 19354776..dc12f2bc 100644
--- a/docs/clustering.rst
+++ b/docs/clustering.rst
@@ -121,10 +121,10 @@ topology and attempt to retry executing the command.
>>> rc.bgsave(Valkey.PRIMARIES)
You could also pass ClusterNodes directly if you want to execute a
-command on a specific node / node group that isn’t addressed by the
+command on a specific node / node group that isn't addressed by the
nodes flag. However, if the command execution fails due to cluster
topology changes, a retry attempt will not be made, since the passed
-target node/s may no longer be valid, and the relevant cluster or
+target node(s) may no longer be valid, and the relevant cluster or
connection error will be returned.
.. code:: python
diff --git a/docs/examples/redis-stream-example.ipynb b/docs/examples/redis-stream-example.ipynb
index 6f7f5a8c..d5e3d49d 100644
--- a/docs/examples/redis-stream-example.ipynb
+++ b/docs/examples/redis-stream-example.ipynb
@@ -548,7 +548,7 @@
}
],
"source": [
- "# check pending status (read messages without a ack)\n",
+ "# check pending status (read messages without an ack)\n",
"def print_pending_info( key_group ):\n",
" for s,k in key_group:\n",
" pr = r.xpending( name=s, groupname=k )\n",
diff --git a/tests/test_asyncio/test_bloom.py b/tests/test_asyncio/test_bloom.py
index 04528c1c..259e45dc 100644
--- a/tests/test_asyncio/test_bloom.py
+++ b/tests/test_asyncio/test_bloom.py
@@ -89,7 +89,7 @@ async def do_verify():
await decoded_r.bf().add("myBloom", x)
rv = await decoded_r.bf().exists("myBloom", x)
assert rv
- rv = await decoded_r.bf().exists("myBloom", f"nonexist_{x}")
+ rv = await decoded_r.bf().exists("myBloom", f"nonexistent_{x}")
res += rv == x
assert res < 5
@@ -163,7 +163,7 @@ async def test_bf_card(decoded_r: valkey.Valkey):
assert await decoded_r.bf().add("bf1", "item_foo") == 1
assert await decoded_r.bf().card("bf1") == 1
- # Error when key is of a type other than Bloom filtedecoded_r.
+ # Error when key is of a type other than Bloom filter decoded_r.
with pytest.raises(valkey.ResponseError):
await decoded_r.set("setKey", "value")
await decoded_r.bf().card("setKey")
diff --git a/tests/test_asyncio/test_cluster.py b/tests/test_asyncio/test_cluster.py
index 01c515a0..789f0eb0 100644
--- a/tests/test_asyncio/test_cluster.py
+++ b/tests/test_asyncio/test_cluster.py
@@ -2657,7 +2657,7 @@ async def test_blocked_methods(self, r: ValkeyCluster) -> None:
)
async def test_empty_stack(self, r: ValkeyCluster) -> None:
- """If a pipeline is executed with no commands it should return a empty list."""
+ """If a pipeline is executed with no commands it should return an empty list."""
p = r.pipeline()
result = await p.execute()
assert result == []
diff --git a/tests/test_asyncio/test_search.py b/tests/test_asyncio/test_search.py
index 20c9799f..9a4396ac 100644
--- a/tests/test_asyncio/test_search.py
+++ b/tests/test_asyncio/test_search.py
@@ -53,7 +53,7 @@ async def waitForIndex(env, idx, timeout=None):
def getClient(decoded_r: valkey.Valkey):
"""
- Gets a client client attached to an index name which is ready to be
+ Gets a client attached to an index name which is ready to be
created
"""
return decoded_r
@@ -485,7 +485,7 @@ async def test_example(decoded_r: valkey.Valkey):
"doc1",
mapping={
"title": "RediSearch",
- "body": "RediSearch impements a search engine on top of valkey",
+ "body": "RediSearch implements a search engine on top of valkey",
},
)
@@ -1121,7 +1121,7 @@ async def test_aggregations_groupby(decoded_r: valkey.Valkey):
"search",
mapping={
"title": "RediSearch",
- "body": "RediSearch impements a search engine on top of valkey",
+ "body": "RediSearch implements a search engine on top of valkey",
"parent": "valkey",
"random_num": 10,
},
diff --git a/tests/test_asyncio/test_timeseries.py b/tests/test_asyncio/test_timeseries.py
index 3e917b8e..af7f9296 100644
--- a/tests/test_asyncio/test_timeseries.py
+++ b/tests/test_asyncio/test_timeseries.py
@@ -67,7 +67,7 @@ async def test_alter(decoded_r: valkey.Valkey):
@skip_ifmodversion_lt("1.4.0", "timeseries")
-async def test_alter_diplicate_policy(decoded_r: valkey.Valkey):
+async def test_alter_duplicate_policy(decoded_r: valkey.Valkey):
assert await decoded_r.ts().create(1)
info = await decoded_r.ts().info(1)
assert_resp_response(
diff --git a/tests/test_bloom.py b/tests/test_bloom.py
index 93b5c4bd..41bdc849 100644
--- a/tests/test_bloom.py
+++ b/tests/test_bloom.py
@@ -112,7 +112,7 @@ def do_verify():
client.bf().add("myBloom", x)
rv = client.bf().exists("myBloom", x)
assert rv
- rv = client.bf().exists("myBloom", f"nonexist_{x}")
+ rv = client.bf().exists("myBloom", f"nonexistent_{x}")
res += rv == x
assert res < 5
diff --git a/tests/test_cluster.py b/tests/test_cluster.py
index 83d6e96c..b4654eae 100644
--- a/tests/test_cluster.py
+++ b/tests/test_cluster.py
@@ -3268,7 +3268,7 @@ def raise_error(target_node, *args, **kwargs):
def test_empty_stack(self, r):
"""
If pipeline is executed with no commands it should
- return a empty list.
+ return an empty list.
"""
p = r.pipeline()
result = p.execute()
diff --git a/tests/test_commands.py b/tests/test_commands.py
index dace6ca2..7eef31be 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -2623,7 +2623,7 @@ def test_zpopmin(self, r):
)
@skip_if_server_version_lt("6.2.0")
- def test_zrandemember(self, r):
+ def test_zrandmember(self, r):
r.zadd("a", {"a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5})
assert r.zrandmember("a") is not None
assert len(r.zrandmember("a", 2)) == 2
diff --git a/tests/test_search.py b/tests/test_search.py
index 7e119058..898aeae1 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -62,7 +62,7 @@ def waitForIndex(env, idx, timeout=None):
def getClient(client):
"""
- Gets a client client attached to an index name which is ready to be
+ Gets a client attached to an index name which is ready to be
created
"""
return client
@@ -459,7 +459,7 @@ def test_example(client):
"doc1",
mapping={
"title": "RediSearch",
- "body": "RediSearch impements a search engine on top of valkey",
+ "body": "RediSearch implements a search engine on top of valkey",
},
)
@@ -1026,7 +1026,7 @@ def test_aggregations_groupby(client):
"search",
mapping={
"title": "RediSearch",
- "body": "RediSearch impements a search engine on top of valkey",
+ "body": "RediSearch implements a search engine on top of valkey",
"parent": "valkey",
"random_num": 10,
},
diff --git a/tests/test_timeseries.py b/tests/test_timeseries.py
index 36322ab3..bb481691 100644
--- a/tests/test_timeseries.py
+++ b/tests/test_timeseries.py
@@ -69,7 +69,7 @@ def test_alter(client):
@skip_ifmodversion_lt("1.4.0", "timeseries")
-def test_alter_diplicate_policy(client):
+def test_alter_duplicate_policy(client):
assert client.ts().create(1)
info = client.ts().info(1)
assert_resp_response(
diff --git a/valkey/_parsers/helpers.py b/valkey/_parsers/helpers.py
index 67557044..d0adaf08 100644
--- a/valkey/_parsers/helpers.py
+++ b/valkey/_parsers/helpers.py
@@ -392,8 +392,8 @@ def parse_slowlog_get(response, **options):
def parse_item(item):
result = {"id": item[0], "start_time": int(item[1]), "duration": int(item[2])}
# Valkey Enterprise injects another entry at index [3], which has
- # the complexity info (i.e. the value N in case the command has
- # an O(N) complexity) instead of the command.
+ # the complexity info (i.e. the value N for a command of complexity
+ # O(N)) instead of the command.
if isinstance(item[3], list):
result["command"] = space.join(item[3])
result["client_address"] = item[4]
diff --git a/valkey/asyncio/connection.py b/valkey/asyncio/connection.py
index 5bc3e46c..9f4c06ca 100644
--- a/valkey/asyncio/connection.py
+++ b/valkey/asyncio/connection.py
@@ -1136,7 +1136,7 @@ async def disconnect(self, inuse_connections: bool = True):
Disconnects connections in the pool
If ``inuse_connections`` is True, disconnect connections that are
- current in use, potentially by other tasks. Otherwise only disconnect
+ current in use, potentially by other tasks. Otherwise, only disconnect
connections that are idle in the pool.
"""
if inuse_connections:
diff --git a/valkey/commands/cluster.py b/valkey/commands/cluster.py
index 3f6e5325..3d8bf07c 100644
--- a/valkey/commands/cluster.py
+++ b/valkey/commands/cluster.py
@@ -225,7 +225,7 @@ def delete(self, *keys: KeyT) -> ResponseT:
The keys are first split up into slots
and then an DEL command is sent for every slot
- Non-existent keys are ignored.
+ Nonexistent keys are ignored.
Returns the number of keys that were deleted.
For more information see https://valkey.io/commands/del
@@ -240,7 +240,7 @@ def touch(self, *keys: KeyT) -> ResponseT:
The keys are first split up into slots
and then an TOUCH command is sent for every slot
- Non-existent keys are ignored.
+ Nonexistent keys are ignored.
Returns the number of keys that were touched.
For more information see https://valkey.io/commands/touch
@@ -254,7 +254,7 @@ def unlink(self, *keys: KeyT) -> ResponseT:
The keys are first split up into slots
and then an TOUCH command is sent for every slot
- Non-existent keys are ignored.
+ Nonexistent keys are ignored.
Returns the number of keys that were unlinked.
For more information see https://valkey.io/commands/unlink
diff --git a/valkey/commands/core.py b/valkey/commands/core.py
index 80a71be6..36c8cd9d 100644
--- a/valkey/commands/core.py
+++ b/valkey/commands/core.py
@@ -2838,7 +2838,7 @@ def lpos(
Get position of ``value`` within the list ``name``
If specified, ``rank`` indicates the "rank" of the first element to
- return in case there are multiple copies of ``value`` in the list.
+ return if there are multiple copies of ``value`` in the list.
By default, LPOS returns the position of the first occurrence of
``value`` in the list. When ``rank`` 2, LPOS returns the position of
the second ``value`` in the list. If ``rank`` is negative, LPOS
@@ -2889,7 +2889,7 @@ def sort(
``start`` and ``num`` allow for paging through the sorted data
``by`` allows using an external key to weight and sort the items.
- Use an "*" to indicate where in the key the item value is located
+ Use an "*" to indicate the location of the item value in the key
``get`` allows for returning items from external keys rather than the
sorted data itself. Use an "*" to indicate where in the key
@@ -2961,7 +2961,7 @@ def sort_ro(
``start`` and ``num`` allow for paging through the sorted data
``by`` allows using an external key to weight and sort the items.
- Use an "*" to indicate where in the key the item value is located
+ Use an "*" to indicate the location of the item value in the key
``get`` allows for returning items from external keys rather than the
sorted data itself. Use an "*" to indicate where in the key
@@ -3608,7 +3608,7 @@ def xautoclaim(
try:
if int(count) < 0:
- raise DataError("XPENDING count must be a integer >= 0")
+ raise DataError("XPENDING count must be an integer >= 0")
pieces.extend([b"COUNT", count])
except TypeError:
pass
@@ -3891,14 +3891,14 @@ def xpending_range(
# idle
try:
if int(idle) < 0:
- raise DataError("XPENDING idle must be a integer >= 0")
+ raise DataError("XPENDING idle must be an integer >= 0")
pieces.extend(["IDLE", idle])
except TypeError:
pass
# count
try:
if int(count) < 0:
- raise DataError("XPENDING count must be a integer >= 0")
+ raise DataError("XPENDING count must be an integer >= 0")
pieces.extend([min, max, count])
except TypeError:
pass
diff --git a/valkey/commands/search/commands.py b/valkey/commands/search/commands.py
index 14823d6f..9e389fab 100644
--- a/valkey/commands/search/commands.py
+++ b/valkey/commands/search/commands.py
@@ -383,7 +383,7 @@ def add_document_hash(self, doc_id, score=1.0, language=None, replace=False):
### Parameters
- **doc_id**: the document's id. This has to be an existing HASH key
- in Valkey that will hold the fields the index needs.
+ in Valkey that will hold the fields needed by the index.
- **score**: the document ranking, between 0.0 and 1.0
- **replace**: if True, and the document already is in the index, we
perform an update and reindex the document
@@ -441,7 +441,7 @@ def get(self, *ids):
def info(self):
"""
- Get info an stats about the the current index, including the number of
+ Get info and stats about the current index, including the number of
documents, memory consumption, etc
For more information see `FT.INFO `_.
@@ -898,7 +898,7 @@ def syndump(self):
class AsyncSearchCommands(SearchCommands):
async def info(self):
"""
- Get info an stats about the the current index, including the number of
+ Get info and stats about the current index, including the number of
documents, memory consumption, etc
For more information see `FT.INFO `_.
diff --git a/valkey/commands/search/indexDefinition.py b/valkey/commands/search/indexDefinition.py
index a668e85b..8ace3434 100644
--- a/valkey/commands/search/indexDefinition.py
+++ b/valkey/commands/search/indexDefinition.py
@@ -9,7 +9,7 @@ class IndexType(Enum):
class IndexDefinition:
- """IndexDefinition is used to define a index definition for automatic
+ """IndexDefinition is used to define an index definition for automatic
indexing on Hash or Json update."""
def __init__(
diff --git a/valkey/commands/search/query.py b/valkey/commands/search/query.py
index c2dcccdd..2207b58c 100644
--- a/valkey/commands/search/query.py
+++ b/valkey/commands/search/query.py
@@ -303,7 +303,7 @@ def sort_by(self, field: str, asc: bool = True) -> "Query":
def expander(self, expander: str) -> "Query":
"""
- Add a expander field to the query.
+ Add an expander field to the query.
- **expander** - the name of the expander
"""
diff --git a/valkey/commands/timeseries/info.py b/valkey/commands/timeseries/info.py
index 91794f6e..f0a85f50 100644
--- a/valkey/commands/timeseries/info.py
+++ b/valkey/commands/timeseries/info.py
@@ -32,7 +32,7 @@ def __init__(self, args):
rules:
A list of compaction rules of the time series.
sourceKey:
- Key name for source time series in case the current series
+ Key name for source time series if the current series
is a target of a rule.
chunkCount:
Number of Memory Chunks used for the time series.
diff --git a/valkey/connection.py b/valkey/connection.py
index 699b3e51..5ee5a44c 100644
--- a/valkey/connection.py
+++ b/valkey/connection.py
@@ -1191,7 +1191,7 @@ def disconnect(self, inuse_connections: bool = True) -> None:
Disconnects connections in the pool
If ``inuse_connections`` is True, disconnect connections that are
- current in use, potentially by other threads. Otherwise only disconnect
+ current in use, potentially by other threads. Otherwise, only disconnect
connections that are idle in the pool.
"""
self._checkpid()
diff --git a/valkey/ocsp.py b/valkey/ocsp.py
index 644358b3..509d83b9 100644
--- a/valkey/ocsp.py
+++ b/valkey/ocsp.py
@@ -47,7 +47,7 @@ def _verify_response(issuer_cert, ocsp_response):
def _check_certificate(issuer_cert, ocsp_bytes, validate=True):
- """A wrapper the return the validity of a known ocsp certificate"""
+ """A wrapper to return the validity of a known ocsp certificate"""
ocsp_response = ocsp.load_der_ocsp_response(ocsp_bytes)