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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- name: Checkout 🔁
uses: actions/checkout@v3
Expand Down
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Expand Down Expand Up @@ -32,15 +32,15 @@ repos:
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 23.9.1
rev: 25.1.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 6.1.0
rev: 7.1.2
hooks:
- id: flake8
- repo: https://github.com/twu/skjold
rev: v0.6.1
rev: v0.6.2
hooks:
- id: skjold
- repo: https://github.com/pre-commit/mirrors-prettier
Expand Down
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

## [0.10.0] - 2025-02-26

### Added

- Added support for Python 3.13.

### Changed

- Updated underlying libraries, such as pydantic, grpcio and google-cloud-firestore, as
well as dev dependencies.
- Updates to pre-commit hooks.

### Removed

- Removed support for Python 3.8, it was end of life on 2024-10-07.
- Removed the `remove_prefix` from `firedantic.utils` (needed only for Python 3.8).

## [0.9.0] - 2025-02-21

### Added
Expand Down Expand Up @@ -266,7 +283,8 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Update README.md
- Update .gitignore

[unreleased]: https://github.com/ioxiocom/firedantic/compare/0.9.0...HEAD
[unreleased]: https://github.com/ioxiocom/firedantic/compare/0.10.0...HEAD
[0.10.0]: https://github.com/ioxiocom/firedantic/compare/0.9.0...0.10.0
[0.9.0]: https://github.com/ioxiocom/firedantic/compare/0.8.1...0.9.0
[0.8.1]: https://github.com/ioxiocom/firedantic/compare/0.8.0...0.8.1
[0.8.0]: https://github.com/ioxiocom/firedantic/compare/0.7.2...0.8.0
Expand Down
2 changes: 1 addition & 1 deletion firedantic/_async/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ class Collection(AsyncBareSubCollection, ABC):
pass

@classmethod
def _create(cls, **kwargs) -> TAsyncBareSubModel:
def _create(cls: Type[TAsyncBareSubModel], **kwargs) -> TAsyncBareSubModel:
return cls( # type: ignore
**kwargs,
)
Expand Down
3 changes: 1 addition & 2 deletions firedantic/_async/ttl_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
)

from firedantic._async.model import AsyncBareModel
from firedantic.utils import remove_prefix

logger = getLogger("firedantic")

Expand Down Expand Up @@ -46,7 +45,7 @@ async def set_up_ttl_policies(
field_obj = await client.get_field({"name": path})

# Variables for logging
readable_state = remove_prefix(str(field_obj.ttl_config.state), "State.")
readable_state = str(field_obj.ttl_config.state).removeprefix("State.")
log_str = '"%s", collection: "%s", field: "%s", state: "%s"'
log_params = [
model.__class__.__name__,
Expand Down
2 changes: 1 addition & 1 deletion firedantic/_sync/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ class Collection(BareSubCollection, ABC):
pass

@classmethod
def _create(cls, **kwargs) -> TBareSubModel:
def _create(cls: Type[TBareSubModel], **kwargs) -> TBareSubModel:
return cls( # type: ignore
**kwargs,
)
Expand Down
3 changes: 1 addition & 2 deletions firedantic/_sync/ttl_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
)

from firedantic._sync.model import BareModel
from firedantic.utils import remove_prefix

logger = getLogger("firedantic")

Expand Down Expand Up @@ -46,7 +45,7 @@ def set_up_ttl_policies(
field_obj = client.get_field({"name": path})

# Variables for logging
readable_state = remove_prefix(str(field_obj.ttl_config.state), "State.")
readable_state = str(field_obj.ttl_config.state).removeprefix("State.")
log_str = '"%s", collection: "%s", field: "%s", state: "%s"'
log_params = [
model.__class__.__name__,
Expand Down
6 changes: 3 additions & 3 deletions firedantic/tests/tests_async/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ async def get_user_purchases(user_id: str, period: str = "2021") -> int:
try:
stats = await stats_model.get_by_id(period)
except ModelNotFoundError:
stats = stats_model()
stats = stats_model() # type: ignore
return stats.purchases


Expand Down Expand Up @@ -257,9 +257,9 @@ class AsyncMockFirestoreAdminClient:
# Copy implementation from the real class
field_path = staticmethod(FirestoreAdminClient.field_path)

def __init__(self):
def __init__(self) -> None:
self.field_state: Field.TtlConfig.State = (
Field.TtlConfig.State.STATE_UNSPECIFIED
Field.TtlConfig.State.STATE_UNSPECIFIED # type: ignore
)
self.updated_field = None
self.list_indexes = AsyncMock(return_value=MockListIndexOperation([]))
Expand Down
59 changes: 41 additions & 18 deletions firedantic/tests/tests_async/test_indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
collection_group_index,
collection_index,
)
from firedantic.common import IndexField
from firedantic.tests.tests_async.conftest import MockListIndexOperation

import pytest # noqa isort: skip
Expand All @@ -26,10 +27,13 @@ class BaseModelWithIndexes(AsyncModel):


@pytest.mark.asyncio
async def test_set_up_composite_index(mock_admin_client):
async def test_set_up_composite_index(mock_admin_client) -> None:
class ModelWithIndexes(BaseModelWithIndexes):
__composite_indexes__ = (
collection_index(("name", Query.ASCENDING), ("age", Query.DESCENDING)),
collection_index(
IndexField("name", Query.ASCENDING),
IndexField("age", Query.DESCENDING),
),
)

result = await async_set_up_composite_indexes(
Expand All @@ -56,11 +60,12 @@ class ModelWithIndexes(BaseModelWithIndexes):


@pytest.mark.asyncio
async def test_set_up_collection_group_index(mock_admin_client):
async def test_set_up_collection_group_index(mock_admin_client) -> None:
class ModelWithIndexes(BaseModelWithIndexes):
__composite_indexes__ = (
collection_group_index(
("name", Query.ASCENDING), ("age", Query.DESCENDING)
IndexField("name", Query.ASCENDING),
IndexField("age", Query.DESCENDING),
),
)

Expand All @@ -84,10 +89,13 @@ class ModelWithIndexes(BaseModelWithIndexes):


@pytest.mark.asyncio
async def test_set_up_composite_indexes_and_policies(mock_admin_client):
async def test_set_up_composite_indexes_and_policies(mock_admin_client) -> None:
class ModelWithIndexes(BaseModelWithIndexes):
__composite_indexes__ = (
collection_index(("name", Query.ASCENDING), ("age", Query.DESCENDING)),
collection_index(
IndexField("name", Query.ASCENDING),
IndexField("age", Query.DESCENDING),
),
)

__ttl_field__ = "expire"
Expand All @@ -105,15 +113,21 @@ class ModelWithIndexes(BaseModelWithIndexes):


@pytest.mark.asyncio
async def test_set_up_many_composite_indexes(mock_admin_client):
async def test_set_up_many_composite_indexes(mock_admin_client) -> None:
class ModelWithIndexes(BaseModelWithIndexes):
__composite_indexes__ = (
collection_index(("name", Query.ASCENDING), ("age", Query.DESCENDING)),
collection_index(("age", Query.ASCENDING), ("status", Query.DESCENDING)),
collection_index(
("age", Query.ASCENDING),
("status", Query.DESCENDING),
("name", Query.DESCENDING),
IndexField("name", Query.ASCENDING),
IndexField("age", Query.DESCENDING),
),
collection_index(
IndexField("age", Query.ASCENDING),
IndexField("status", Query.DESCENDING),
),
collection_index(
IndexField("age", Query.ASCENDING),
IndexField("status", Query.DESCENDING),
IndexField("name", Query.DESCENDING),
),
)

Expand All @@ -126,7 +140,7 @@ class ModelWithIndexes(BaseModelWithIndexes):


@pytest.mark.asyncio
async def test_set_up_indexes_model_without_indexes(mock_admin_client):
async def test_set_up_indexes_model_without_indexes(mock_admin_client) -> None:
class ModelWithoutIndexes(AsyncModel):
__collection__ = "modelWithoutIndexes"

Expand All @@ -144,7 +158,7 @@ class ModelWithoutIndexes(AsyncModel):


@pytest.mark.asyncio
async def test_existing_indexes_are_skipped(mock_admin_client):
async def test_existing_indexes_are_skipped(mock_admin_client) -> None:
resp = ListIndexesResponse(
{
"indexes": [
Expand Down Expand Up @@ -181,8 +195,14 @@ async def test_existing_indexes_are_skipped(mock_admin_client):

class ModelWithIndexes(BaseModelWithIndexes):
__composite_indexes__ = (
collection_index(("name", Query.ASCENDING), ("age", Query.DESCENDING)),
collection_index(("age", Query.ASCENDING), ("name", Query.DESCENDING)),
collection_index(
IndexField("name", Query.ASCENDING),
IndexField("age", Query.DESCENDING),
),
collection_index(
IndexField("age", Query.ASCENDING),
IndexField("name", Query.DESCENDING),
),
)

result = await async_set_up_composite_indexes(
Expand All @@ -194,7 +214,7 @@ class ModelWithIndexes(BaseModelWithIndexes):


@pytest.mark.asyncio
async def test_same_fields_in_another_collection(mock_admin_client):
async def test_same_fields_in_another_collection(mock_admin_client) -> None:
# Test that when another collection has an index with exactly the same fields,
# it won't affect creating an index in the target collection
resp = ListIndexesResponse(
Expand All @@ -221,7 +241,10 @@ async def test_same_fields_in_another_collection(mock_admin_client):

class ModelWithIndexes(BaseModelWithIndexes):
__composite_indexes__ = (
collection_index(("name", Query.ASCENDING), ("age", Query.DESCENDING)),
collection_index(
IndexField("name", Query.ASCENDING),
IndexField("age", Query.DESCENDING),
),
)

result = await async_set_up_composite_indexes(
Expand Down
Loading