Compare commits

...

10 Commits

Author SHA1 Message Date
5b0cc0d87d fix: fixed tests 2025-01-24 19:10:36 +01:00
ecfc0fc167 fix: fixed issue with new mixin 2025-01-24 19:04:07 +01:00
eb62c87679 feat: added experimental init and annotation mixins 2025-01-24 18:58:39 +01:00
2ad7700f72 Merge pull request #30 from creyD/renovate/stripe-11.x
feat(deps): update dependency stripe to v11
2025-01-24 18:39:30 +01:00
1d7b767623 Merge pull request #31 from vikynoah/pagination_fix 2025-01-24 15:29:52 +01:00
vikynoah
f1f29e84c2 fix: Pagination Fix 2025-01-24 15:23:08 +01:00
dcb9afb8f2 fix: added btree_gist extension option 2025-01-24 11:01:48 +01:00
creyD
8c98e001f9 Adjusted files for isort & autopep 2025-01-24 07:49:05 +00:00
959a746e4f fix: fixed issue with new date format 2025-01-24 08:48:35 +01:00
renovate[bot]
10c1ea5411 feat(deps): update dependency stripe to v11 2025-01-21 21:13:13 +00:00
7 changed files with 70 additions and 31 deletions

View File

@@ -1 +1,2 @@
from .base import * # noqa
from .mixins import * # noqa

View File

@@ -1,4 +1,5 @@
import uuid
from datetime import datetime
from sqlalchemy import Column, DateTime, String
from sqlalchemy.dialects.postgresql import UUID
@@ -6,18 +7,16 @@ from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import as_declarative
from sqlalchemy.sql import func
from .mixins import AutoAnnotateMixin, AutoInitMixin
@as_declarative()
class Base:
class Base(AutoAnnotateMixin, AutoInitMixin):
__abstract__ = True
# Primary key as uuid
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
created_by_id = Column(String)
__name__: str

View File

@@ -0,0 +1,36 @@
from sqlalchemy import Column
from sqlalchemy.orm import Mapped
class AutoAnnotateMixin:
@classmethod
def __init_subclass__(cls) -> None:
super().__init_subclass__()
annotations = {}
for key, value in cls.__dict__.items():
if isinstance(value, Column):
annotations[key] = Mapped[value.type.python_type]
cls.__annotations__ = annotations
class AutoInitMixin:
@classmethod
def __init_subclass__(cls) -> None:
super().__init_subclass__()
init_params = []
for key, value in cls.__dict__.items():
if isinstance(value, Column):
if not value.nullable and not value.default and not value.server_default:
init_params.append((key, value.type.python_type))
def __init__(self, **kwargs):
super(cls, self).__init__()
for key, _ in init_params:
if key not in kwargs:
raise TypeError(f"Missing required argument: {key}")
setattr(self, key, kwargs[key])
for key, value in kwargs.items():
if key not in init_params and hasattr(self.__class__, key):
setattr(self, key, value)
cls.__init__ = __init__

View File

@@ -1,27 +1,26 @@
from contextlib import suppress
from math import ceil
from typing import Any, Generic, Optional, Self, Sequence, TypeVar, Union, overload
from contextlib import suppress
from pydantic import BaseModel
from fastapi_pagination import Params
from fastapi_pagination.bases import AbstractPage, AbstractParams
from fastapi import Query
from fastapi_pagination.api import apply_items_transformer, create_page
from fastapi_pagination.bases import AbstractPage, AbstractParams, RawParams
from fastapi_pagination.ext.sqlalchemy import create_paginate_query
from fastapi_pagination.types import (
AdditionalData,
AsyncItemsTransformer,
GreaterEqualOne,
GreaterEqualZero,
AdditionalData,
SyncItemsTransformer,
AsyncItemsTransformer,
ItemsTransformer,
SyncItemsTransformer,
)
from fastapi_pagination.api import create_page, apply_items_transformer
from fastapi_pagination.utils import verify_params
from fastapi_pagination.ext.sqlalchemy import create_paginate_query
from fastapi_pagination.bases import AbstractParams, RawParams
from pydantic import BaseModel
from pydantic.json_schema import SkipJsonSchema
from sqlalchemy.sql.selectable import Select
from sqlalchemy.orm.session import Session
from sqlalchemy import select, func
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession, async_scoped_session
from fastapi import Query
from sqlalchemy.orm.session import Session
from sqlalchemy.sql.selectable import Select
from sqlalchemy.util import await_only, greenlet_spawn
T = TypeVar("T")
@@ -29,7 +28,7 @@ T = TypeVar("T")
class PaginationParams(BaseModel, AbstractParams):
page: int = Query(1, ge=1, description="Page number")
size: int = Query(50, ge=1, le=100, description="Page size")
size: int = Query(50, ge=1, description="Page size")
pagination: bool = Query(True, description="Toggle pagination")
def to_raw_params(self) -> RawParams:
@@ -62,7 +61,7 @@ class Page(AbstractPage[T], Generic[T]):
total: Optional[int] = None,
**kwargs: Any,
) -> Self:
if not isinstance(params, Params):
if not isinstance(params, PaginationParams):
raise TypeError("Page should be used with Params")
size = params.size or total or len(items)
@@ -170,9 +169,9 @@ def _paginate(
total = connection.scalar(count_query)
if params.pagination is False and total > 0:
params = Params(page=1, size=total)
params = PaginationParams(page=1, size=total)
else:
params = Params(page=params.page, size=params.size)
params = PaginationParams(page=params.page, size=params.size)
query = create_paginate_query(query, params)
items = connection.execute(query).all()

View File

@@ -3,7 +3,7 @@ import unittest
from typing import Type
from httpx import ASGITransport, AsyncClient, Response
from sqlalchemy import create_engine
from sqlalchemy import create_engine, text
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy_utils import create_database, database_exists, drop_database
@@ -23,7 +23,9 @@ class AbstractTestAPI(unittest.IsolatedAsyncioTestCase):
print("setting up abstract")
@classmethod
def setup_database(cls, sync_db_url: str, async_db_url: str, base: Type[Base]):
def setup_database(
cls, sync_db_url: str, async_db_url: str, base: Type[Base], btree_gist: bool = False
):
cls.engine_s = create_engine(
sync_db_url,
echo=False,
@@ -34,6 +36,10 @@ class AbstractTestAPI(unittest.IsolatedAsyncioTestCase):
drop_database(cls.engine_s.url)
create_database(cls.engine_s.url)
if btree_gist:
with cls.engine_s.begin() as conn:
conn.execute(text("CREATE EXTENSION IF NOT EXISTS btree_gist"))
# Migrate
base.metadata.create_all(cls.engine_s)

View File

@@ -1 +1 @@
stripe==10.12.0 # Stripe
stripe==11.4.1 # Stripe

View File

@@ -7,9 +7,7 @@ from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from creyPY.fastapi.app import generate_unique_id
from creyPY.fastapi.crud import (
get_object_or_404,
)
from creyPY.fastapi.crud import get_object_or_404
from creyPY.fastapi.models.base import Base
@@ -65,7 +63,7 @@ class TestMyFunction(unittest.TestCase):
def test_get_object_or_404_existing_object(self):
# Arrange
obj_id = UUID("123e4567-e89b-12d3-a456-426614174000")
obj = MockDBClass(obj_id)
obj = MockDBClass(id=obj_id)
self.db.add(obj)
self.db.commit()