Skip to content

Commit

Permalink
feat: automate tablename generation from camelCase
Browse files Browse the repository at this point in the history
This commit introduces a method to automatically convert the class name from camelCase to snake_case for database table names. This enhancement addresses inconsistencies in naming conventions and improves maintainability, allowing developers to focus on writing class definitions without worrying about table naming.

- Implemented `__tablename__` using `camel2snake` for automatic conversion
- Added utility functions for converting between camelCase and snake_case
- Created a test to verify the correct generation of table names

Generated-by: aiautocommit
  • Loading branch information
iloveitaly committed Nov 15, 2024
1 parent 33905a9 commit 193f839
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 19 deletions.
10 changes: 7 additions & 3 deletions activemodel/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import typing as t

import sqlalchemy as sa
from sqlalchemy.orm import declared_attr
from sqlmodel import SQLModel

from activemodel.utils.camelcase import camel2snake

from .query_wrapper import QueryWrapper

Expand All @@ -28,9 +32,9 @@ def after_update(self):
pass

# TODO snake case tables automatically
# @declared_attr
# def __tablename__(cls) -> str:
# return cls.__name__.lower()
@declared_attr
def __tablename__(cls) -> str:
return camel2snake(cls.__name__)

@classmethod
def select(cls, *args):
Expand Down
10 changes: 8 additions & 2 deletions activemodel/query_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from typing import Generic
from typing import Generic, TypeVar

from activemodel.timestamps import compile_sql
from sqlmodel.sql.expression import SelectOfScalar

WrappedModelType = TypeVar("WrappedModelType")


def compile_sql(target: SelectOfScalar):
return str(target.compile(get_engine().connect()))


class QueryWrapper(Generic[WrappedModelType]):
Expand Down
14 changes: 0 additions & 14 deletions activemodel/timestamps.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
from datetime import datetime
from typing import TypeVar

import sqlalchemy as sa

# TODO raw sql https://github.com/tiangolo/sqlmodel/discussions/772
from sqlmodel import Field
from sqlmodel.sql.expression import SelectOfScalar

from .database import get_engine

# TODO Stripe-style prefixed ID? https://stackoverflow.com/questions/62400011/how-can-i-create-a-serial-id-with-as-a-string-with-common-prefix-ie-tag-1-tag


def compile_sql(target: SelectOfScalar):
return str(target.compile(get_engine().connect()))


WrappedModelType = TypeVar("WrappedModelType")


# @classmethod
# def select(cls):
Expand Down
29 changes: 29 additions & 0 deletions activemodel/utils/camelcase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
Lifted from: https://github.com/fastapiutils/fastapi-utils/blob/master/fastapi_utils/camelcase.py
"""

import re


def snake2camel(snake: str, start_lower: bool = False) -> str:
"""
Converts a snake_case string to camelCase.
The `start_lower` argument determines whether the first letter in the generated camelcase should
be lowercase (if `start_lower` is True), or capitalized (if `start_lower` is False).
"""

camel = snake.title()
camel = re.sub("([0-9A-Za-z])_(?=[0-9A-Z])", lambda m: m.group(1), camel)
if start_lower:
camel = re.sub("(^_*[A-Z])", lambda m: m.group(1).lower(), camel)
return camel


def camel2snake(camel: str) -> str:
"""
Converts a camelCase string to snake_case.
"""
snake = re.sub(r"([a-zA-Z])([0-9])", lambda m: f"{m.group(1)}_{m.group(2)}", camel)
snake = re.sub(r"([a-z0-9])([A-Z])", lambda m: f"{m.group(1)}_{m.group(2)}", snake)
return snake.lower()
9 changes: 9 additions & 0 deletions pytest/table_name_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from activemodel import BaseModel


class TestTable(BaseModel):
id: int


def test_table_name():
assert TestTable.__tablename__ == "test_table"

0 comments on commit 193f839

Please sign in to comment.