Name | gena JSON |
Version |
1.7.0
JSON |
| download |
home_page | None |
Summary | Generate APIs from Database Models |
upload_time | 2024-04-13 23:59:12 |
maintainer | None |
docs_url | None |
author | Binh Vu |
requires_python | <4.0,>=3.8 |
license | MIT |
keywords |
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# GENA · [](https://pypi.org/project/gena) [](https://www.npmjs.com/package/react)
Framework to help to build (web) application faster.
1. [`gena`](): generate restful APIs from peewee models (i.e., specification of database table).
2. [`gena-app`](): provides basic structure for frontend application (state management, routing).
For demo, see the [todolist folder](/examples/todolist).
# Getting started
We are going to build a simple todolist app to demonstrate how to use this framework. See the [todolist folder](/todolist) for the complete source code.
### Setup the project:
1. create the python project (server code): `poetry new todolist` (you need [Poetry](https://python-poetry.org/) to run this command)
2. move inside the project folder: `cd todolist`
3. add our backend library: `poetry add gena`
4. create a `www` directory containing your frontend code using [`create-react-app`](https://create-react-app.dev/docs/adding-typescript/): (`yarn create react-app www --template typescript`)
5. add our frontend library: `cd www; yarn add gena-app; cd ..` -- this will modify your [`www/package.json`](/todolist/www/package.json) file.
6. modify the build script in [`www/package.json`](/todolist/www/package.json) to tell `create-react-app` to build static files into [`todolist/www`](/todolist/todolist/www) directory inside the python package: `"build": "BUILD_PATH='../todolist/www' react-scripts build"`. This allows us to distribute both frontend and backend in a single python package.
7. add `"proxy": "http://localhost:5000"` to the [`www/package.json`](/todolist/www/package.json). This allows us to send the request to the server during development.
After this step, you will have the following folder structure:
todolist
├── todolist # our backend code
│ ├── __init__.py
├── pyproject.toml
├── www # our frontend code
### Backend
#### Define database schema
We use [`peewee`](https://docs.peewee-orm.com/), an ORM library, to define our database schema.
First, let's create a file containing our schema: [`todolist/models.py`](/todolist/todolist/models.py). In this simple todo list, we only have one table `todo_list`, which has two fields: `checked` and `todo`.
```python
import os
from peewee import SqliteDatabase, Model, BooleanField, TextField
db = SqliteDatabase(os.environ["DBFILE"])
class TodoList(Model):
class Meta:
database = db
db_table = "todo_list"
checked = BooleanField()
todo = TextField()
```
(Optionally) We add the following code to the same file to insert some dummy data to our database for testing
```python
if not os.path.exists(os.environ['DBFILE']):
db.create_tables([TodoList], safe=True)
TodoList.insert_many([
{"checked": False, "todo": "go grocery"},
{"checked": False, "todo": "do laundry"},
]).execute()
```
#### Create APIs
With our backend library, creating APIs is as simple as calling a function `generate_api` with the ORM model `TodoList`:
```python
import os
from gena import generate_app, generate_api
from todolist.models import TodoList
app = generate_app(
controllers=[generate_api(model) for model in [TodoList]],
pkg_dir=os.path.dirname(__file__)
)
```
Under the hood, [`generate_api`](/gena/api_generator.py) uses the specification in the model to automatically generate a blueprint containing the following endpoints (sometimes are called views or controllers):
1. `GET /{table_name}`: querying records matched a query.
2. `GET /{table_name}/<id>`: get a record by id
3. `HEAD /{table_name}/<id>`: whether a record with the given id exists.
4. `POST /{table_name}/find_by_ids`: querying list of records by their ids
5. `POST /{table_name}`: create a record
6. `PUT /{table_name}/<id>`: update a record
7. `DELETE /{table_name}/<id>`: delete a record
8. `DELETE /{table_name}`: truncate the whole table, only available if the `enable_truncate_table` parameter is set to be `True`.
You can extend the blueprint to add additional endpoints or overwrite some if you needed. Inputs from the users are automatically validate based on the type of the field, or you can provide your own deserializer (whose job is also validate the value) for a particular field via the `deserializer` parameter. You can also provide your own `serializer` to control how the data should be serialize.
The outer function `generate_app` will returns the Flask application. It takes three parameters
- `controllers`: a list of [Flask's blueprints](https://flask.palletsprojects.com/en/2.0.x/blueprints/) or a python package, in which the blueprints are discovered automatically. This is useful if you organize your blueprints in separated files. All endpoints defined in the blueprints have url prefix `/api` (`/{table_name}` becomes `/api/{table_name}`).
- `pkg_dir`: the path to our `todolist` package. Our app will serve the static files from `{pkg_dir}/www` folder.
- `log_sql_queries`: whether to log sql queries when we are in development mode.
With the given app, we finally can start our server with: `DBFILE=./todolist.db FLASK_APP=todolist/app.py flask run`. Note: remember to activate your virtual environment first with `poetry shell`.
### Frontend
#### Create Stores
Our frontend library uses [React](https://reactjs.org/) and provides state management with [MobX](https://mobx.js.org/)) and routing with [ReactRouter](https://reactrouter.com/) out of the box. In addition, most of the functions are typed so that refactoring and maintaining is easier.
To start, we create stores which contains all of the data of our application. A store also contains other functions to create, update, delete, and query records from the server. If you haven't familiar with MobX, please check out their [documentation](https://mobx.js.org/the-gist-of-mobx.html).
Let's create a file [`www/src/models/TodoListStore.ts`](/todolist/www/src/models/TodoListStore.ts) and paste the following code:
```typescript
import { SimpleCRUDStore } from "gena-app";
import { makeObservable, action } from "mobx";
export interface Todo {
id: number;
checked: boolean;
todo: string;
}
export class TodoListStore extends SimpleCRUDStore<number, Todo> {
constructor() {
super(`/api/todo_list`);
makeObservable(this, {
toggle: action,
});
}
toggle(item: Todo) {
item.checked = !item.checked;
}
}
```
In the above code, we create an interface `Todo` matched with the definition in our database schema. Since we didn't provide custom deserializer, the fields in our frontend is matched exactly with the fields in our database in the backend. Then, we define a store `TodoListStore` extending the `SimpleCRUDStore` from our frontend library `gena-app`. This gives us several useful functions out of the box such as `create`, `delete`, `update`, and `query` the records from the server. We also define an additional function that will toggle the `checked` value of an `Todo` to show how to extend the store if needed.
To use the stores with [React Hooks](https://reactjs.org/docs/hooks-intro.html), we need to following code in [`www/src/models/index.ts`](/todolist/www/src/models/index.ts)
```typescript
import React from "react";
import { TodoListStore } from "./TodoListStore";
export const stores = {
todolistStore: new TodoListStore(),
};
export type IStore = Readonly<typeof stores>;
export const StoreContext = React.createContext<IStore>(stores);
export function useStores(): IStore {
return React.useContext(StoreContext);
}
export { TodoListStore as VariableStore };
export type { Todo } from "./TodoListStore";
```
#### Routing & Application
Routing specifies which component the application should render for a particular URL. This makes it much easier to keep the application logic separated especially when you have lots of pages. Our frontend library enables us to define all the routing in a single place with strong type annotation, so that it's easier to maintain the application's routing and is harder to make mistake.
Let's create a file `www/routes.tsx` and define our first route:
```typescript
import { NoArgsPathDef } from "gena-app";
import { HomePage } from "./pages/HomePage";
export const routes = {
home: new NoArgsPathDef({
component: HomePage,
pathDef: "/",
exact: true,
}),
};
```
In this route, we say when user open `/`, we will render a component [`HomePage`](/todolist/www/src/pages/HomePage.tsx). For now, let's just use a dummy `HomePage` for now:
```typescript
export const HomePage = () => {
return <p>Hello world</p>;
};
```
With the specified routes and a dummy `HomePage` component, the final piece is to render and attach our component to the DOM. In [`index.tsx`](/todolist/www/src/index.tsx):
```typescript
import ReactDOM from "react-dom";
import { App } from "gena-app";
import "./index.css";
import { StoreContext, stores } from "./models";
import reportWebVitals from "./reportWebVitals";
import { routes } from "./routes";
ReactDOM.render(
<StoreContext.Provider value={stores}>
<App routes={routes} />
</StoreContext.Provider>,
document.getElementById("root")
);
```
`StoreContext.Provider` component enables us to use `useStores` hook we defined earlier in any component, and `App` component will render the component that matched with the current URL as specified in our routes.
#### Create TodoList component
It's now time to complete our [`HomePage`](/todolist/www/src/pages/HomePage.tsx) component to show the TodoList.
We first use the stores within the component and wrapped it with `observer` so that it will react whenever the state changes
```typescript
import { observer } from "mobx-react";
export const HomePage = observer(() => {
const { todolistStore } = useStores();
...
});
```
We are going to display the todo list as a list. So we use [List component](https://ant.design/components/list).
```typescript
export const HomePage = observer(() => {
const { todolistStore } = useStores();
useEffect(() => {
todolistStore.fetch({ limit: 1000, offset: 0 });
}, []);
const items = todolistStore.list.map((item) => {
return (
<List.Item key={item.id}>
<Checkbox
checked={item.checked}
onChange={(e) => {
item.checked = e.target.checked;
todolistStore.update(item);
}}
>
<Typography.Paragraph
style={{ marginBottom: 0 }}
editable={{
onChange: (text) => {
item.todo = text;
todolistStore.update(item);
},
}}
>
{item.todo}
</Typography.Paragraph>
</Checkbox>
<Button
type="primary"
danger={true}
onClick={() => {
todolistStore.delete(item.id);
}}
>
Delete
</Button>
</List.Item>
);
});
const addItem = () => todolistStore.create({ checked: false, todo: "" });
return (
<Space direction="vertical" style={{ width: "100%" }}>
<List bordered={true}>{items}</List>
<Button type="primary" onClick={addItem}>
Add
</Button>
</Space>
);
});
```
Now, we are able to display the list of todo items from the database, and check/uncheck item that is done. We can extend the UI to add other functionalities such as add, delete, or update the content of todo items. You can check out the complete code in [here](/todolist/www/src/pages/HomePage.tsx).
Raw data
{
"_id": null,
"home_page": null,
"name": "gena",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.8",
"maintainer_email": null,
"keywords": null,
"author": "Binh Vu",
"author_email": "binh@toan2.com",
"download_url": "https://files.pythonhosted.org/packages/52/6a/66f23f4e4bd8e75e794062c014b7cff21aa28867dc85e422193d87cdfa24/gena-1.7.0.tar.gz",
"platform": null,
"description": "# GENA · [](https://pypi.org/project/gena) [](https://www.npmjs.com/package/react)\n\nFramework to help to build (web) application faster.\n\n1. [`gena`](): generate restful APIs from peewee models (i.e., specification of database table).\n2. [`gena-app`](): provides basic structure for frontend application (state management, routing).\n\nFor demo, see the [todolist folder](/examples/todolist).\n\n# Getting started\n\nWe are going to build a simple todolist app to demonstrate how to use this framework. See the [todolist folder](/todolist) for the complete source code.\n\n### Setup the project:\n\n1. create the python project (server code): `poetry new todolist` (you need [Poetry](https://python-poetry.org/) to run this command)\n2. move inside the project folder: `cd todolist`\n3. add our backend library: `poetry add gena`\n4. create a `www` directory containing your frontend code using [`create-react-app`](https://create-react-app.dev/docs/adding-typescript/): (`yarn create react-app www --template typescript`)\n5. add our frontend library: `cd www; yarn add gena-app; cd ..` -- this will modify your [`www/package.json`](/todolist/www/package.json) file.\n6. modify the build script in [`www/package.json`](/todolist/www/package.json) to tell `create-react-app` to build static files into [`todolist/www`](/todolist/todolist/www) directory inside the python package: `\"build\": \"BUILD_PATH='../todolist/www' react-scripts build\"`. This allows us to distribute both frontend and backend in a single python package.\n7. add `\"proxy\": \"http://localhost:5000\"` to the [`www/package.json`](/todolist/www/package.json). This allows us to send the request to the server during development.\n\nAfter this step, you will have the following folder structure:\n\n todolist\n \u251c\u2500\u2500 todolist # our backend code\n \u2502 \u251c\u2500\u2500 __init__.py\n \u251c\u2500\u2500 pyproject.toml\n \u251c\u2500\u2500 www # our frontend code\n\n### Backend\n\n#### Define database schema\n\nWe use [`peewee`](https://docs.peewee-orm.com/), an ORM library, to define our database schema.\n\nFirst, let's create a file containing our schema: [`todolist/models.py`](/todolist/todolist/models.py). In this simple todo list, we only have one table `todo_list`, which has two fields: `checked` and `todo`.\n\n```python\nimport os\nfrom peewee import SqliteDatabase, Model, BooleanField, TextField\n\n\ndb = SqliteDatabase(os.environ[\"DBFILE\"])\n\n\nclass TodoList(Model):\n class Meta:\n database = db\n db_table = \"todo_list\"\n\n checked = BooleanField()\n todo = TextField()\n```\n\n(Optionally) We add the following code to the same file to insert some dummy data to our database for testing\n\n```python\nif not os.path.exists(os.environ['DBFILE']):\n db.create_tables([TodoList], safe=True)\n TodoList.insert_many([\n {\"checked\": False, \"todo\": \"go grocery\"},\n {\"checked\": False, \"todo\": \"do laundry\"},\n ]).execute()\n```\n\n#### Create APIs\n\nWith our backend library, creating APIs is as simple as calling a function `generate_api` with the ORM model `TodoList`:\n\n```python\nimport os\nfrom gena import generate_app, generate_api\nfrom todolist.models import TodoList\n\napp = generate_app(\n controllers=[generate_api(model) for model in [TodoList]],\n pkg_dir=os.path.dirname(__file__)\n)\n```\n\nUnder the hood, [`generate_api`](/gena/api_generator.py) uses the specification in the model to automatically generate a blueprint containing the following endpoints (sometimes are called views or controllers):\n\n1. `GET /{table_name}`: querying records matched a query.\n2. `GET /{table_name}/<id>`: get a record by id\n3. `HEAD /{table_name}/<id>`: whether a record with the given id exists.\n4. `POST /{table_name}/find_by_ids`: querying list of records by their ids\n5. `POST /{table_name}`: create a record\n6. `PUT /{table_name}/<id>`: update a record\n7. `DELETE /{table_name}/<id>`: delete a record\n8. `DELETE /{table_name}`: truncate the whole table, only available if the `enable_truncate_table` parameter is set to be `True`.\n\nYou can extend the blueprint to add additional endpoints or overwrite some if you needed. Inputs from the users are automatically validate based on the type of the field, or you can provide your own deserializer (whose job is also validate the value) for a particular field via the `deserializer` parameter. You can also provide your own `serializer` to control how the data should be serialize.\n\nThe outer function `generate_app` will returns the Flask application. It takes three parameters\n\n- `controllers`: a list of [Flask's blueprints](https://flask.palletsprojects.com/en/2.0.x/blueprints/) or a python package, in which the blueprints are discovered automatically. This is useful if you organize your blueprints in separated files. All endpoints defined in the blueprints have url prefix `/api` (`/{table_name}` becomes `/api/{table_name}`).\n- `pkg_dir`: the path to our `todolist` package. Our app will serve the static files from `{pkg_dir}/www` folder.\n- `log_sql_queries`: whether to log sql queries when we are in development mode.\n\nWith the given app, we finally can start our server with: `DBFILE=./todolist.db FLASK_APP=todolist/app.py flask run`. Note: remember to activate your virtual environment first with `poetry shell`.\n\n### Frontend\n\n#### Create Stores\n\nOur frontend library uses [React](https://reactjs.org/) and provides state management with [MobX](https://mobx.js.org/)) and routing with [ReactRouter](https://reactrouter.com/) out of the box. In addition, most of the functions are typed so that refactoring and maintaining is easier.\n\nTo start, we create stores which contains all of the data of our application. A store also contains other functions to create, update, delete, and query records from the server. If you haven't familiar with MobX, please check out their [documentation](https://mobx.js.org/the-gist-of-mobx.html).\n\nLet's create a file [`www/src/models/TodoListStore.ts`](/todolist/www/src/models/TodoListStore.ts) and paste the following code:\n\n```typescript\nimport { SimpleCRUDStore } from \"gena-app\";\nimport { makeObservable, action } from \"mobx\";\n\nexport interface Todo {\n id: number;\n checked: boolean;\n todo: string;\n}\n\nexport class TodoListStore extends SimpleCRUDStore<number, Todo> {\n constructor() {\n super(`/api/todo_list`);\n\n makeObservable(this, {\n toggle: action,\n });\n }\n\n toggle(item: Todo) {\n item.checked = !item.checked;\n }\n}\n```\n\nIn the above code, we create an interface `Todo` matched with the definition in our database schema. Since we didn't provide custom deserializer, the fields in our frontend is matched exactly with the fields in our database in the backend. Then, we define a store `TodoListStore` extending the `SimpleCRUDStore` from our frontend library `gena-app`. This gives us several useful functions out of the box such as `create`, `delete`, `update`, and `query` the records from the server. We also define an additional function that will toggle the `checked` value of an `Todo` to show how to extend the store if needed.\n\nTo use the stores with [React Hooks](https://reactjs.org/docs/hooks-intro.html), we need to following code in [`www/src/models/index.ts`](/todolist/www/src/models/index.ts)\n\n```typescript\nimport React from \"react\";\nimport { TodoListStore } from \"./TodoListStore\";\n\nexport const stores = {\n todolistStore: new TodoListStore(),\n};\nexport type IStore = Readonly<typeof stores>;\nexport const StoreContext = React.createContext<IStore>(stores);\n\nexport function useStores(): IStore {\n return React.useContext(StoreContext);\n}\n\nexport { TodoListStore as VariableStore };\nexport type { Todo } from \"./TodoListStore\";\n```\n\n#### Routing & Application\n\nRouting specifies which component the application should render for a particular URL. This makes it much easier to keep the application logic separated especially when you have lots of pages. Our frontend library enables us to define all the routing in a single place with strong type annotation, so that it's easier to maintain the application's routing and is harder to make mistake.\n\nLet's create a file `www/routes.tsx` and define our first route:\n\n```typescript\nimport { NoArgsPathDef } from \"gena-app\";\nimport { HomePage } from \"./pages/HomePage\";\n\nexport const routes = {\n home: new NoArgsPathDef({\n component: HomePage,\n pathDef: \"/\",\n exact: true,\n }),\n};\n```\n\nIn this route, we say when user open `/`, we will render a component [`HomePage`](/todolist/www/src/pages/HomePage.tsx). For now, let's just use a dummy `HomePage` for now:\n\n```typescript\nexport const HomePage = () => {\n return <p>Hello world</p>;\n};\n```\n\nWith the specified routes and a dummy `HomePage` component, the final piece is to render and attach our component to the DOM. In [`index.tsx`](/todolist/www/src/index.tsx):\n\n```typescript\nimport ReactDOM from \"react-dom\";\nimport { App } from \"gena-app\";\nimport \"./index.css\";\nimport { StoreContext, stores } from \"./models\";\nimport reportWebVitals from \"./reportWebVitals\";\nimport { routes } from \"./routes\";\n\nReactDOM.render(\n <StoreContext.Provider value={stores}>\n <App routes={routes} />\n </StoreContext.Provider>,\n document.getElementById(\"root\")\n);\n```\n\n`StoreContext.Provider` component enables us to use `useStores` hook we defined earlier in any component, and `App` component will render the component that matched with the current URL as specified in our routes.\n\n#### Create TodoList component\n\nIt's now time to complete our [`HomePage`](/todolist/www/src/pages/HomePage.tsx) component to show the TodoList.\n\nWe first use the stores within the component and wrapped it with `observer` so that it will react whenever the state changes\n\n```typescript\nimport { observer } from \"mobx-react\";\n\nexport const HomePage = observer(() => {\n const { todolistStore } = useStores();\n ...\n});\n```\n\nWe are going to display the todo list as a list. So we use [List component](https://ant.design/components/list).\n\n```typescript\nexport const HomePage = observer(() => {\n const { todolistStore } = useStores();\n useEffect(() => {\n todolistStore.fetch({ limit: 1000, offset: 0 });\n }, []);\n\n const items = todolistStore.list.map((item) => {\n return (\n <List.Item key={item.id}>\n <Checkbox\n checked={item.checked}\n onChange={(e) => {\n item.checked = e.target.checked;\n todolistStore.update(item);\n }}\n >\n <Typography.Paragraph\n style={{ marginBottom: 0 }}\n editable={{\n onChange: (text) => {\n item.todo = text;\n todolistStore.update(item);\n },\n }}\n >\n {item.todo}\n </Typography.Paragraph>\n </Checkbox>\n <Button\n type=\"primary\"\n danger={true}\n onClick={() => {\n todolistStore.delete(item.id);\n }}\n >\n Delete\n </Button>\n </List.Item>\n );\n });\n\n const addItem = () => todolistStore.create({ checked: false, todo: \"\" });\n\n return (\n <Space direction=\"vertical\" style={{ width: \"100%\" }}>\n <List bordered={true}>{items}</List>\n <Button type=\"primary\" onClick={addItem}>\n Add\n </Button>\n </Space>\n );\n});\n```\n\nNow, we are able to display the list of todo items from the database, and check/uncheck item that is done. We can extend the UI to add other functionalities such as add, delete, or update the content of todo items. You can check out the complete code in [here](/todolist/www/src/pages/HomePage.tsx).\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Generate APIs from Database Models",
"version": "1.7.0",
"project_urls": null,
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "dd05b84d3065e6c9203fff6bd5fe20b21dda719721f28f652694cbe646a621f0",
"md5": "95004d425f0c93bec4c9c3b36d953a7e",
"sha256": "3ff03ecc9671c56d8b7c7d955ea9534a63e06ff4c532987557a225c0b70a0f58"
},
"downloads": -1,
"filename": "gena-1.7.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "95004d425f0c93bec4c9c3b36d953a7e",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.8",
"size": 22296,
"upload_time": "2024-04-13T23:59:10",
"upload_time_iso_8601": "2024-04-13T23:59:10.311634Z",
"url": "https://files.pythonhosted.org/packages/dd/05/b84d3065e6c9203fff6bd5fe20b21dda719721f28f652694cbe646a621f0/gena-1.7.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "526a66f23f4e4bd8e75e794062c014b7cff21aa28867dc85e422193d87cdfa24",
"md5": "c01d6f5a7104b595d04e8f499fa7aebc",
"sha256": "3874fb013f4c252c646e19c998eaaca1cfd1bcf86d746759a90f2087ddc48702"
},
"downloads": -1,
"filename": "gena-1.7.0.tar.gz",
"has_sig": false,
"md5_digest": "c01d6f5a7104b595d04e8f499fa7aebc",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.8",
"size": 22850,
"upload_time": "2024-04-13T23:59:12",
"upload_time_iso_8601": "2024-04-13T23:59:12.173172Z",
"url": "https://files.pythonhosted.org/packages/52/6a/66f23f4e4bd8e75e794062c014b7cff21aa28867dc85e422193d87cdfa24/gena-1.7.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-04-13 23:59:12",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "gena"
}