diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d67f57e0..40aa64ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,9 +26,9 @@ jobs: timeout-minutes: 5 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Python 3.9 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{env.pythonversion}} #---------------------------------------------- @@ -45,7 +45,7 @@ jobs: #---------------------------------------------- - name: Load cached venv id: cached-poetry-dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .venv key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} @@ -76,7 +76,7 @@ jobs: strategy: matrix: os: [ ubuntu-latest ] - pyver: [ "3.8", "3.9", "3.10", "3.11", "pypy-3.8", "pypy-3.9" ] + pyver: [ "3.8", "3.9", "3.10", "3.11", "3.12", "pypy-3.8", "pypy-3.9", "pypy-3.10" ] redisstack: [ "latest" ] fail-fast: false services: @@ -98,9 +98,9 @@ jobs: INSTALL_DIR: ${{ github.workspace }}/redis steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Python ${{ matrix.pyver }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.pyver }} #---------------------------------------------- @@ -117,7 +117,7 @@ jobs: #---------------------------------------------- - name: Load cached venv id: cached-poetry-dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .venv key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} @@ -144,7 +144,7 @@ jobs: make test poetry run coverage xml - name: Upload coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./coverage.xml flags: unit diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 34849130..f8c178aa 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -25,13 +25,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/pypi-publish.yaml b/.github/workflows/pypi-publish.yaml index 415a4370..5b523b6d 100644 --- a/.github/workflows/pypi-publish.yaml +++ b/.github/workflows/pypi-publish.yaml @@ -27,12 +27,12 @@ jobs: value: "${{ steps.get_version.outputs.VERSION }}" - name: Set up Python 3.9 - uses: actions/setup-python@v4.3.0 + uses: actions/setup-python@v5 with: python-version: 3.9 - name: Cache Poetry virtualenv - uses: actions/cache@v3.0.11 + uses: actions/cache@v4 id: cache with: path: ~/.virtualenvs diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 30a547bb..752c1db1 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v5 + - uses: release-drafter/release-drafter@v6 with: # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml config-name: release-drafter-config.yml diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index e1528415..f739a542 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -6,9 +6,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check Spelling - uses: rojopolis/spellcheck-github-actions@0.33.1 + uses: rojopolis/spellcheck-github-actions@0.36.0 with: config_path: .github/spellcheck-settings.yml task_name: Markdown diff --git a/Makefile b/Makefile index f478eade..9e41a736 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ clean: rm -rf dist rm -rf redis_om rm -rf tests_sync - docker-compose down + docker compose down .PHONY: dist @@ -65,7 +65,7 @@ format: $(INSTALL_STAMP) sync .PHONY: test test: $(INSTALL_STAMP) sync redis REDIS_OM_URL="$(REDIS_OM_URL)" $(POETRY) run pytest -n auto -vv ./tests/ ./tests_sync/ --cov-report term-missing --cov $(NAME) $(SYNC_NAME) - docker-compose down + docker compose down .PHONY: test_oss test_oss: $(INSTALL_STAMP) sync redis @@ -81,7 +81,7 @@ shell: $(INSTALL_STAMP) .PHONY: redis redis: - docker-compose up -d + docker compose up -d .PHONY: all all: lint format test diff --git a/aredis_om/__init__.py b/aredis_om/__init__.py index 7aa699dd..813e3b04 100644 --- a/aredis_om/__init__.py +++ b/aredis_om/__init__.py @@ -8,11 +8,11 @@ FindQuery, HashModel, JsonModel, - VectorFieldOptions, KNNExpression, NotFoundError, QueryNotSupportedError, QuerySyntaxError, RedisModel, RedisModelError, + VectorFieldOptions, ) diff --git a/aredis_om/model/__init__.py b/aredis_om/model/__init__.py index b9ecf36f..fcdce89d 100644 --- a/aredis_om/model/__init__.py +++ b/aredis_om/model/__init__.py @@ -4,8 +4,8 @@ Field, HashModel, JsonModel, - VectorFieldOptions, KNNExpression, NotFoundError, RedisModel, + VectorFieldOptions, ) diff --git a/aredis_om/model/model.py b/aredis_om/model/model.py index a4c6b9e7..a90b3971 100644 --- a/aredis_om/model/model.py +++ b/aredis_om/model/model.py @@ -630,20 +630,30 @@ def resolve_value( values: filter = filter(None, value.split(separator_char)) for value in values: value = escaper.escape(value) - result += f"@{field_name}:{{{value}}}" + result += "@{field_name}:{{{value}}}".format( + field_name=field_name, value=value + ) else: value = escaper.escape(value) - result += f"@{field_name}:{{{value}}}" + result += "@{field_name}:{{{value}}}".format( + field_name=field_name, value=value + ) elif op is Operators.NE: value = escaper.escape(value) - result += f"-(@{field_name}:{{{value}}})" + result += "-(@{field_name}:{{{value}}})".format( + field_name=field_name, value=value + ) elif op is Operators.IN: expanded_value = cls.expand_tag_value(value) - result += f"(@{field_name}:{{{expanded_value}}})" + result += "(@{field_name}:{{{expanded_value}}})".format( + field_name=field_name, expanded_value=expanded_value + ) elif op is Operators.NOT_IN: # TODO: Implement NOT_IN, test this... expanded_value = cls.expand_tag_value(value) - result += f"-(@{field_name}:{{{expanded_value}}})" + result += "-(@{field_name}):{{{expanded_value}}}".format( + field_name=field_name, expanded_value=expanded_value + ) return result @@ -1525,9 +1535,11 @@ async def all_pks(cls): # type: ignore # TODO: We need to decide how we want to handle the lack of # decode_responses=True... return ( - remove_prefix(key, key_prefix) - if isinstance(key, str) - else remove_prefix(key.decode(cls.Meta.encoding), key_prefix) + ( + remove_prefix(key, key_prefix) + if isinstance(key, str) + else remove_prefix(key.decode(cls.Meta.encoding), key_prefix) + ) async for key in cls.db().scan_iter(f"{key_prefix}*", _type="HASH") ) @@ -1698,9 +1710,11 @@ async def all_pks(cls): # type: ignore # TODO: We need to decide how we want to handle the lack of # decode_responses=True... return ( - remove_prefix(key, key_prefix) - if isinstance(key, str) - else remove_prefix(key.decode(cls.Meta.encoding), key_prefix) + ( + remove_prefix(key, key_prefix) + if isinstance(key, str) + else remove_prefix(key.decode(cls.Meta.encoding), key_prefix) + ) async for key in cls.db().scan_iter(f"{key_prefix}*", _type="ReJSON-RL") ) diff --git a/aredis_om/model/query_resolver.py b/aredis_om/model/query_resolver.py index 3657970e..6af598ce 100644 --- a/aredis_om/model/query_resolver.py +++ b/aredis_om/model/query_resolver.py @@ -1,4 +1,4 @@ -from typing import List, Mapping +from typing import List, Mapping, Optional from aredis_om.model.model import Expression @@ -100,5 +100,5 @@ class QueryResolver: def __init__(self, *expressions: Expression): self.expressions = expressions - def resolve(self) -> str: + def resolve(self) -> Optional[str]: """Resolve expressions to a RediSearch query string.""" diff --git a/aredis_om/model/render_tree.py b/aredis_om/model/render_tree.py index 8ac5748d..0a1ce71a 100644 --- a/aredis_om/model/render_tree.py +++ b/aredis_om/model/render_tree.py @@ -2,18 +2,20 @@ This code adapted from the library "pptree," Copyright (c) 2017 Clément Michard and released under the MIT license: https://github.com/clemtoy/pptree """ + import io +from typing import Any, Optional def render_tree( - current_node, - nameattr="name", - left_child="left", - right_child="right", - indent="", - last="updown", - buffer=None, -): + current_node: Any, + nameattr: str = "name", + left_child: str = "left", + right_child: str = "right", + indent: str = "", + last: str = "updown", + buffer: Optional[io.StringIO] = None, +) -> str: """Print a tree-like structure, `current_node`. This is a mostly-direct-copy of the print_tree() function from the ppbtree diff --git a/aredis_om/model/token_escaper.py b/aredis_om/model/token_escaper.py index 19fce96d..4c64a177 100644 --- a/aredis_om/model/token_escaper.py +++ b/aredis_om/model/token_escaper.py @@ -1,5 +1,5 @@ import re -from typing import Optional, Pattern +from typing import Match, Optional, Pattern class TokenEscaper: @@ -11,14 +11,14 @@ class TokenEscaper: # Source: https://redis.io/docs/stack/search/reference/escaping/#the-rules-of-text-field-tokenization DEFAULT_ESCAPED_CHARS = r"[,.<>{}\[\]\\\"\':;!@#$%^&*()\-+=~\/ ]" - def __init__(self, escape_chars_re: Optional[Pattern] = None): + def __init__(self, escape_chars_re: Optional[Pattern[str]] = None): if escape_chars_re: self.escaped_chars_re = escape_chars_re else: self.escaped_chars_re = re.compile(self.DEFAULT_ESCAPED_CHARS) def escape(self, value: str) -> str: - def escape_symbol(match): + def escape_symbol(match: Match[str]) -> str: value = match.group(0) return f"\\{value}" diff --git a/aredis_om/util.py b/aredis_om/util.py index d1feaeb1..268657e5 100644 --- a/aredis_om/util.py +++ b/aredis_om/util.py @@ -1,8 +1,8 @@ import inspect -def is_async_mode(): - async def f(): +def is_async_mode() -> bool: + async def f() -> None: """Unasync transforms async functions in sync functions""" return None diff --git a/docs/getting_started.md b/docs/getting_started.md index a2d6ff13..15fea449 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -603,7 +603,7 @@ andrew.key() With the model's Redis key, you can start `redis-cli` and inspect the data stored under that key. Here, we run `JSON.GET` command with `redis-cli` using the running "redis" container that this project's Docker Compose file defines: ``` -$ docker-compose exec -T redis redis-cli HGETALL mymodel.Customer:01FKGX1DFEV9Z2XKF59WQ6DC9r +$ docker compose exec -T redis redis-cli HGETALL mymodel.Customer:01FKGX1DFEV9Z2XKF59WQ6DC9r 1) "pk" 2) "01FKGX1DFEV9Z2XKF59WQ6DC9T" diff --git a/pyproject.toml b/pyproject.toml index 54d3b272..c92dae15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "redis-om" -version = "0.2.1" +version = "0.2.2" description = "Object mappings, and more, for Redis." authors = ["Redis OSS "] maintainers = ["Redis OSS "] @@ -21,6 +21,7 @@ classifiers = [ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Programming Language :: Python', ] include=[ @@ -35,20 +36,21 @@ include=[ [tool.poetry.dependencies] python = ">=3.8,<4.0" -redis = ">=3.5.3,<5.0.0" -pydantic = ">=1.10.2,<2.1.0" +redis = ">=3.5.3,<6.0.0" +pydantic = ">=1.10.2,<2.5.0" click = "^8.0.1" types-redis = ">=3.5.9,<5.0.0" python-ulid = "^1.0.3" typing-extensions = "^4.4.0" hiredis = "^2.2.3" -more-itertools = ">=8.14,<10.0" +more-itertools = ">=8.14,<11.0" +setuptools = {version = "^69.2.0", markers = "python_version >= '3.12'"} [tool.poetry.dev-dependencies] mypy = "^0.982" -pytest = "^7.1.3" +pytest = "^8.0.2" ipdb = "^0.13.9" -black = "^23.1" +black = "^24.2" isort = "^5.9.3" flake8 = "^5.0.4" bandit = "^1.7.4" @@ -56,9 +58,9 @@ coverage = "^7.1" pytest-cov = "^4.0.0" pytest-xdist = "^3.1.0" unasync = "^0.5.0" -pytest-asyncio = "^0.20.3" +pytest-asyncio = "^0.23.5" email-validator = "^2.0.0" -tox = "^3.26.0" +tox = "^4.14.1" tox-pyenv = "^1.1.0" [tool.poetry.scripts] @@ -67,3 +69,6 @@ migrate = "redis_om.model.cli.migrate:migrate" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + + + diff --git a/tox.ini b/tox.ini index 687666e3..3db89a94 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] skipsdist = true -envlist = py37, py38, py39, py310, py311 +envlist = py38, py39, py310, py311, py312 [testenv] whitelist_externals = poetry