{"id":18541276,"url":"https://github.com/fernaper/kascade-orm","last_synced_at":"2025-05-15T03:35:03.949Z","repository":{"id":182861458,"uuid":"669207770","full_name":"fernaper/kascade-orm","owner":"fernaper","description":"Python ORM for SQL Databases based on Pydantic","archived":false,"fork":false,"pushed_at":"2023-07-23T15:52:12.000Z","size":16,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-17T08:33:27.234Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fernaper.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-21T15:54:24.000Z","updated_at":"2023-07-21T17:42:39.000Z","dependencies_parsed_at":"2023-07-21T22:28:14.652Z","dependency_job_id":"fb9fe8e6-c9a2-4a63-8a0b-42a7cc26586e","html_url":"https://github.com/fernaper/kascade-orm","commit_stats":null,"previous_names":["fernaper/kascade-orm"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fernaper%2Fkascade-orm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fernaper%2Fkascade-orm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fernaper%2Fkascade-orm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fernaper%2Fkascade-orm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fernaper","download_url":"https://codeload.github.com/fernaper/kascade-orm/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254266522,"owners_count":22042123,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T20:04:30.906Z","updated_at":"2025-05-15T03:35:03.927Z","avatar_url":"https://github.com/fernaper.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cbr /\u003e\n\n\u003cdiv align=\"center\"\u003e\n    \u003ch1\u003eKascade ORM\u003c/h1\u003e\n    \u003cp\u003e\u003ch3 align=\"center\"\u003ePython ORM for SQL Databases based on Pydantic\u003c/h3\u003e\u003c/p\u003e\n    \u003cdiv align=\"center\"\u003e\n    \u003c/div\u003e\n\u003c/div\u003e\n\n\u003chr\u003e\n\n## What is Kascade ORM?\n\nKascade ORM is the next-geneneration ORM built on top of Pydantic in order to have the best integration with frameworks like FastAPI.\n\n---\n\n## Roadmap\n\nOur plan is to give the users maximum access to SQL Databases inside Python code without making them hard to understand.\nTo do so, we plan to structure everything in `Objects`.\n\n### Notes about Callable:\n\nThis util callables could be created directly in SQL or via Python.\nIf is it possible it is always created on SQL.\n\n- cuid: Depends\n- uuid: Depends\n- random: Depends\n- autoincrement: SQL Always\n- utcnow: SQL Always\n- now: SQL Always\n- custom: Python Always\n\n### Things to store per column:\n\n- Type: `Type Hint`\n- Name: `str`\n- Unique: `bool`\n- Optional: `bool`\n- IsId: `bool`\n- Default: `Any` or `Callable`\n- OnUpdate: `None` or `Callable`\n\n### Things to store per Relation:\n- Table1: `Table`\n- Table2: `Table`\n- Table1Columns: `List[Column]`\n- Table2Columns: `List[Column]`\n\n### Things to store per Table:\n\n- Columns: `List[Column]`\n- CompoundUniques: `List[List[Column]]`\n- Indexes: `List[Column]`\n- Relations: `List[Relation]`\n\n---\n\n## Extra features planned to be added\n\n1. If we have two tables we plan to substract them in order to detect differences between them. This whay we can easily manage `apply`s to update the tables.\n2. Users should be capable to create fast Type Hints from their tables in order to allow returning for example an `User` without the `password` in FastAPI without needing to create a custom Schema that is just a duplication of the `User` schema without this field.\n3. Important: Allow to generate the Python file with the current Database schema.\n\n## This is an example of how we plan to create tables\n\n```python\nfrom pydantic import EmailStr\nfrom kascade import ForeignKey, Table, Column, column_defaults\n\nclass Item(Table):\n    # Note that if it is called ID and is an int,\n    # this configuration is equivalente to the\n    # `User` table configuration\n    id: int\n    name: str\n    user_id: int\n\nclass User(Table):\n    name: str\n    email: EmailStr\n    password: str\n    id: Column = Column(\n        type=int,\n        unique=True,\n        default=column_defaults.autoincrement,\n    )\n    avatar: Optional[bytes] = None\n    items: ForeignKey = ForeignKey(\n        table=Item,\n        column='user_id',\n    )\n```\n\nThis is just an idea, we could change it. Also, we are still thinking on the best way to manage relationships.\n\nOn this example the generated Python code (internally) will look similar to:\n\n```python\n\nfrom pydantic import EmailStr\nfrom kascade import ForeignKey, Table, Column, column_defaults\n\nclass Item(Table):\n    id: int\n    name: str\n    user_id: int    \n\n    @property\n    def user(self):\n        # Code to get user dynamically (or explicitly)\n        pass\n\nclass User(Table):\n    name: str\n    email: EmailStr\n    password: str\n    id: int\n    avatar: Optional[bytes] = None\n    \n    @property\n    def items(self):\n        # Code to get items dynamically (or explicitly)\n        pass\n\n```\n\nThis class will have also other methods based on Table ones.\n\n---\n\nAlso, this is an example on how an end user will use this tables:\n\n```python\nimport asyncio\n\nfrom kascade import Kascade\n\n\nasync def main():\n    async with Kascade() as db:\n        user = await db.User.create({\n            name='Fernando Pérez',\n            email='fernaperg@gmail.com',\n            password='super-secure-kascade-password',\n        })\n\n        item = await db.Item.create({\n            name='Laptop',\n            # Only one of the following is needed\n            user_id=user.id,\n            user=user,\n        })\n\n        all_users_with_avatar = await db.User.find_many({\n            'where': {\n                'avatar': {\n                    'not': None,\n                }\n            }\n        })\n\n        # In this case we know that for some reasson we need\n        # to query all items for each user,\n        # Therefore, in order to improve performance and\n        # avoid unexpected loads when querying items from\n        # each user with the code: all_kascade_emails[0].items\n        all_kascade_emails = await db.User.find_many({\n            'where': {\n                'email': {\n                    'ends_with': '@kascade.com',\n                }\n            }\n            'include': {\n                'items': True,\n            }\n        })\n\n# Example on how to define Type Hints\ndef item_example() -\u003e Kascade.Item:\n    pass\n\n\n# Example on how to skip some parameters\ndef user_example() -\u003e kascade.User.exclude('password'):\n    pass\n\nif __name__ == '__main__':\n    asyncio.run(main())\n\n```\n\nIn order to suppor include or exclude we are going to solve it in a way similar to:\n\n```python\nfrom typing import get_type_hints\nfrom pydantic import BaseModel, create_model\n\n# `create_model` \u003c--- this is the solution\n# https://chat.openai.com/share/cfa989a8-7ac4-4508-abd8-f39ca5a17602\n\nclass Table(BaseModel):\n    a: int = 1\n    b: int = 2\n\n    @classmethod\n    def exclude(cls, *field_names):\n        annotations = get_type_hints(cls)\n        field_names = set(field_names)\n        for field_name in field_names:\n            annotations.pop(field_name, None)\n        fields_dict = cls.model_fields\n        new_fields = {\n            name: field for name, field in fields_dict.items() if name not in field_names\n        }\n        namespace = {'__annotations__': annotations}\n        new_class = type(cls.__name__, (BaseModel,), namespace)\n        new_class.__fields__ = new_fields\n        return new_class\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffernaper%2Fkascade-orm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffernaper%2Fkascade-orm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffernaper%2Fkascade-orm/lists"}