Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[run]
branch = True
source = sieve_cache
omit =
__init__.py
concurrency = multiprocessing,thread
[report]
ignore_errors = True
exclude_lines =
pragma: no cover
def __repr__
if self.debug:
if settings.DEBUG
raise AssertionError
raise NotImplementedError
if 0:
if __name__ == .__main__.:
class .*\bProtocol\):
@(abc\.)?abstractmethod
35 changes: 35 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Python CI

on:
push:
branches:
- main
pull_request:

jobs:
tox:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Set up uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Install tox
run: uv tool install tox --with tox-uv

- name: Run tox (tests + lint)
run: tox -e py3,pep8
47 changes: 47 additions & 0 deletions .github/workflows/release-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Release to PyPI

on:
release:
types: [published]

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Build distribution artifacts
run: |
python -m pip install build
python -m build

- name: Upload distribution artifacts
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/

upload-to-pypi:
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write

steps:
- name: Download distribution artifacts
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
3 changes: 3 additions & 0 deletions .stestr.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[DEFAULT]
test_path=${OS_TEST_PATH:-./sieve_cache/tests}
top_dir=./
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ dependencies = [
redis = ["redis"]

[project.entry-points."dogpile.cache"]
sieve_cache.memory = "sieve_cache.backends.memory:InMemoryDriver"
"sieve_cache.memory" = "sieve_cache.backends.memory:InMemoryDriver"

[tool.setuptools.dynamic]
version = { attr = "sieve_cache.version.version_string" }
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.

pbr>=6.0.0
stevedore>=5.1.0
dogpile.cache>=1.3.0
iso8601>=2.1.0
78 changes: 78 additions & 0 deletions sieve_cache/tests/test_memory_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from unittest import TestCase
from unittest import mock

from dogpile.cache import api

from sieve_cache.backends.memory import InMemoryDriver


class TestInMemoryDriver(TestCase):
def _make_backend(self, expiration_time):
backend = InMemoryDriver.__new__(InMemoryDriver)
backend.expiration_time = expiration_time
backend.cache = {}
return backend

def test_set_and_get_without_expiration(self):
backend = self._make_backend(expiration_time=0)

backend.set("a", 1)

self.assertEqual(1, backend.get("a"))
self.assertEqual(api.NO_VALUE, backend.get("missing"))

def test_get_multi_and_delete_methods(self):
backend = self._make_backend(expiration_time=0)
backend.set_multi({"a": 1, "b": 2})

values = list(backend.get_multi(["a", "b", "c"]))

self.assertEqual([1, 2, api.NO_VALUE], values)

backend.delete("a")
backend.delete_multi(["b", "missing"])
self.assertEqual(api.NO_VALUE, backend.get("a"))
self.assertEqual(api.NO_VALUE, backend.get("b"))

def test_get_returns_no_value_for_expired_keys(self):
backend = self._make_backend(expiration_time=10)

with mock.patch(
"sieve_cache.backends.memory.timeutils.utcnow_ts"
) as now:
now.return_value = 100
backend.set("a", 1)

now.return_value = 105
self.assertEqual(1, backend.get("a"))

now.return_value = 110
self.assertEqual(api.NO_VALUE, backend.get("a"))
self.assertNotIn("a", backend.cache)

def test_set_multi_clears_expired_keys(self):
backend = self._make_backend(expiration_time=10)

with mock.patch(
"sieve_cache.backends.memory.timeutils.utcnow_ts"
) as now:
now.return_value = 100
backend.set("old", "value")

now.return_value = 500
backend.set_multi({"new": "value"})

self.assertIn("new", backend.cache)
self.assertNotIn("old", backend.cache)
48 changes: 48 additions & 0 deletions sieve_cache/tests/test_node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from unittest import TestCase

from sieve_cache.node import Node


class TestNode(TestCase):
def test_to_dict_handles_nested_nodes_and_sequences(self):
child = Node(value=123, key="child")
parent = Node(value=[child, "raw"], key="parent", visited=True)

payload = parent.to_dict()

self.assertEqual("parent", payload["key"])
self.assertTrue(payload["visited"])
self.assertEqual(123, payload["value"][0]["value"])
self.assertEqual("raw", payload["value"][1])

def test_dict_method_returns_serialized_payload(self):
node = Node(value=1, key="k", next="n", prev="p")

payload = node.__dict__()

self.assertEqual("k", payload["key"])
self.assertEqual("n", payload["next"])
self.assertEqual("p", payload["prev"])

def test_getitem_and_setitem(self):
node = Node(value=1, key="k")

self.assertEqual("k", node["key"])

node["visited"] = True
self.assertTrue(node.visited)

with self.assertRaises(KeyError):
node["missing"]
57 changes: 57 additions & 0 deletions sieve_cache/tests/test_timeutils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import datetime
from unittest import TestCase
from unittest import mock

import iso8601

from sieve_cache.common import timeutils


class TestTimeutils(TestCase):
def tearDown(self):
timeutils.utcnow.override_time = None

def test_utcnow_ts_uses_time_module_when_no_override(self):
with mock.patch("time.time", return_value=1700000000.75):
self.assertEqual(1700000000, timeutils.utcnow_ts())
self.assertEqual(
1700000000.75, timeutils.utcnow_ts(microsecond=True)
)

def test_utcnow_ts_uses_override_time_and_microseconds(self):
fixed = datetime.datetime(2024, 1, 1, 12, 0, 1, 500000)
timeutils.utcnow.override_time = fixed

self.assertEqual(1704110401, timeutils.utcnow_ts())
self.assertEqual(1704110401.5, timeutils.utcnow_ts(microsecond=True))

def test_utcnow_returns_timezone_aware_when_requested(self):
value = timeutils.utcnow(with_timezone=True)

self.assertIsNotNone(value.tzinfo)
self.assertEqual(iso8601.iso8601.UTC, value.tzinfo)

def test_utcnow_returns_naive_datetime_by_default(self):
value = timeutils.utcnow()

self.assertIsNone(value.tzinfo)

def test_utcnow_uses_override_list(self):
first = datetime.datetime(2024, 1, 1, 0, 0, 0)
second = datetime.datetime(2024, 1, 1, 0, 0, 1)
timeutils.utcnow.override_time = [first, second]

self.assertEqual(first, timeutils.utcnow())
self.assertEqual(second, timeutils.utcnow())
21 changes: 21 additions & 0 deletions sieve_cache/tests/test_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from unittest import TestCase

from sieve_cache import version


class TestVersionModule(TestCase):
def test_version_info_and_version_string_are_initialized(self):
self.assertIsNotNone(version.version_info)
self.assertIsNotNone(version.version_string)
1 change: 1 addition & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
coverage
stestr
bandit
flake8
Loading
Loading