{"id":15308589,"url":"https://github.com/monkenwu/pythonflaskdevarchitecture","last_synced_at":"2026-04-27T12:02:42.213Z","repository":{"id":223079023,"uuid":"758486738","full_name":"monkenWu/PythonFlaskDevArchitecture","owner":"monkenWu","description":"基於 Flask 的 API 伺服器開發架構示範","archived":false,"fork":false,"pushed_at":"2024-02-18T05:16:23.000Z","size":49,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-27T14:47:05.316Z","etag":null,"topics":["flask","flask-api","flask-application","flask-restful","flask-sqlalchemy"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/monkenWu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2024-02-16T12:33:50.000Z","updated_at":"2024-02-18T05:15:47.000Z","dependencies_parsed_at":"2024-02-22T21:01:14.948Z","dependency_job_id":null,"html_url":"https://github.com/monkenWu/PythonFlaskDevArchitecture","commit_stats":null,"previous_names":["monkenwu/pythonflaskdevarchitecture"],"tags_count":1,"template":true,"template_full_name":null,"purl":"pkg:github/monkenWu/PythonFlaskDevArchitecture","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monkenWu%2FPythonFlaskDevArchitecture","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monkenWu%2FPythonFlaskDevArchitecture/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monkenWu%2FPythonFlaskDevArchitecture/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monkenWu%2FPythonFlaskDevArchitecture/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/monkenWu","download_url":"https://codeload.github.com/monkenWu/PythonFlaskDevArchitecture/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monkenWu%2FPythonFlaskDevArchitecture/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32335297,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T23:26:28.701Z","status":"online","status_checked_at":"2026-04-27T02:00:06.769Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["flask","flask-api","flask-application","flask-restful","flask-sqlalchemy"],"created_at":"2024-10-01T08:17:06.539Z","updated_at":"2026-04-27T12:02:42.182Z","avatar_url":"https://github.com/monkenWu.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Python Flask Dev Architecture\n\n這是一個基於 Flask 的 API 伺服器開發架構示範。\n\n在這個示範中整合了一些常用的開發工具，並且提供了一個基本的開發環境。包含以下特性：\n\n* 使用 `docker-compose` 部署開發環境\n* 使用 `sqllib` 進行資料庫操作\n* 使用 `alembic` 進行資料庫 Migration\n* 使用 `SQLAlchemy` 作為 ORM，並且提供基本的 Model 設計\n* 使用 `unittest` 進行單元測試，提供基本的測試行為與獨立的測試環境組態設定\n* 使用 `flask` 進行 API 開發，並且進行 API 版本控制\n* 使用 `logging` 進行日誌記錄，提供統一的日誌管理介面\n* 使用 `flask` 內建的錯誤處理機制，並且提供統一的錯誤處理介面\n* 建立 Controller-Service-DAO 的開發架構\n\n## Feature\n\n在專案內已包含了一些 APIs 與相關的測試，你可以透過在啟動開發環境後進行基本的測試。你可以在專案根目錄中找到 `APIs.postman_collection.json` 檔案，這是一個 Postman Collection 檔案，你可以透過 Postman 進行 API 測試，這些 API 在設計上依循 RESTful API 的設計原則。\n\n在專案內提供以下的功能模組：\n* 使用者\n    * 註冊\n    * 登入 - 取得 Access Token\n    * 取得使用者資訊\n* 任務\n    * 新增任務\n    * 取得任務列表\n    * 取得任務資訊\n    * 更新任務 - 完整更新\n    * 更新任務 - 部分更新\n    * 刪除任務\n\n## Environment\n\n本專案將透過 docker-compose.yml 部署開發環境\n\n### 初次部署\n\n* 建構環境\n    ```bash\n    docker-compose build\n    ```\n    \u003e 如果需要重頭建構 image 可以同時在指令後面使用 `--no-cache` 選項\n* 執行容器\n    ```bash\n    docker-compose up\n    ```\n    \u003e 如果需要背景執行可以使用 `-d` 選項\n* 進入容器\n    ```bash\n    docker-compose exec app bash\n    ```\n    \u003e 你也可以透過 VSCode 的 Remote - Containers 擴充套件進入容器開發\n\n* 將 `env.example` 複製成 `.env` 並調整組態設定使其符合需求\n* 初始化資料庫\n    ```bash\n    alembic upgrade head\n    ```\n* 執行開發伺服器\n    ```bash\n    python main.py\n    ```\n\n### 更新 pip 依賴\n\n當你在專案中新增了新的依賴，你需要更新 `requirements.txt` 檔案。\n\n```bash\npip freeze \u003e requirements.txt\n```\n\n再次建構環境\n\n```bash\ndocker-compose build\n```\n\n## Database Migration\n\n資料庫 Migration 使用 `alembic`，並且使用 `SQLAlchemy` 作為 ORM。 \n`alembic` 會自動掃描 `models.orm` Package 下的所有 Model 並且生成對應的 Migration。\n\n### 更新 Migration Versiom\n\n若對於資料庫的 Schema 進行了修改，則需要更新 Migration Version。\n\n```bash\nalembic revision --autogenerate -m \"message\"\n```\n\n### 執行 Migration\n\n```bash\nalembic upgrade head\n```\n\n## Controller and API Version Control\n\n所有 `controllers` 底下的資料夾都會被自動地掃描並且註冊成 API 路由。\n\nAPI 將透過 `controllers` 底下的資料夾進行版本控制，每個資料夾代表一個版本的 API。\n\n### 新增 API 版本\n\n* 新增一個資料夾，並且命名為 `v{version}`，例如 `v1`\n* 在 main.py 中調整以下程式\n    ```python\n    ## auto require all controller\n    versions = ['v1', 'v2', 'v{version}']\n    ```\n* 在新的 `v{version}` 資料夾中開始新增新的 Controller\n\n### 新增 Controller\n\n在 `v{version}` 資料夾中新增一個新的 Controller，例如 `IndexController.py`\n\n```python\nfrom flask import Blueprint, jsonify\nfrom system.LogManager import LogManager\n\ndefi = Blueprint('v1_index_api', __name__)\n\n@defi.route('/', methods=['GET'])\ndef index_page():\n    access_log = LogManager.get_logger('access_log', LogManager.LOG_INFO)\n    access_log.write('Access index page', LogManager.LOG_INFO)\n    return jsonify(status=200, msg={\n        \"message\": \"Server is running. This is version 1 API.\"\n    })\n```\n\ndefi 是一個 Flask Blueprint 實體，他將會被註冊成一個 API 路由。你一定得透過 defi 作為 Blueprint('v1_index_api', __name__) 的 Blueprint 名稱，這樣才能被 `main.py` 正確地自動註冊。\n\n### 路由規則\n\n所有 Controller `v{version}` 底下的路由都會以 `/api/{version}` 開頭。舉個例子：\n\n置於 `{project_root}/controllers/v2/IndexController.py` 的以下路由\n\n```python\n@defi.route('/', methods=['GET'])\ndef index_page():\n    return jsonify(status=200, msg={\n        \"message\": \"Server is running, This is version 2 API.\"\n})\n```\n\n將會被註冊成 `/api/v2/` 。\n\n## 執行單元測試\n\n專案使用 `unittest` 作為單元測試框架，所有的單元測試都會置於 `tests` 資料夾底下。在執行單元測試前，請確保根目錄下的 `.testing.env` 與 `alembic.testing.ini` 檔案已經設定好。\n\n### 執行測試\n\n* 執行單個測試\n    ```bash\n    python -m unittest tests/v1/TestUserCase.py\n    ```\n* 執行全部測試\n    ```bash\n    python -m unittest discover -s ./tests/v1 -p 'Test*.py'\n    ```\n\n### 建立新的測試\n\n一個測試檔案應該繼承 `unittest.TestCase` 並且在 `setUp` 方法中初始化測試環境，並且在 `tearDown` 方法中清理測試環境。\n\n請於合適的地方建立一個以 `Test` 開頭的測試檔案，並讓他看起來像是以下的程式碼：\n\n```python\nfrom tests.BaseTest import initialize_database, clear_database\nimport unittest\nfrom main import app\n\nclass TestUserCase(unittest.TestCase):\n\n    def setUp(self):\n        self.client = app.test_client()\n        self.ctx = app.app_context()\n        self.ctx.push()\n        initialize_database()\n        \n    def tearDown(self):\n        self.ctx.pop()\n        clear_database()\n\n```\n\n上述程式碼中的 `initialize_database` 與 `clear_database` 是用來初始化與清理測試資料庫的方法，這個測試用資料庫將會依賴 `.testing.env` 與 `alembic.testing.ini` 檔案中的相關設定。\n\n## 錯誤處理\n\n專案使用 `flask` 內建的錯誤處理機制，並且透過 `logging` 模組進行日誌的記錄。\n\n### 錯誤處理\n\n錯誤處理的程式碼位於 `main.py` 中，並且透過 `@app.errorhandler` 裝飾器進行註冊。若在程式中出現無法處理的錯誤，那麼將會統一拋出 HTTP 500 錯誤。\n\n當 `.env` 中的 `APP_ENV` 設定為 `development` 時，錯誤處理將會回傳詳細的錯誤訊息，否則將會回傳一個簡單的錯誤訊息。\n\n舉個例子：\n\n* development 環境響應的錯誤資訊\n    ```json\n    {\n        \"details\": [\n            \"Traceback (most recent call last):\",\n            \"  File \\\"/usr/local/lib/python3.9/site-packages/flask/app.py\\\", line 870, in full_dispatch_request\",\n            \"    rv = self.dispatch_request()\",\n            \"  File \\\"/usr/local/lib/python3.9/site-packages/flask/app.py\\\", line 855, in dispatch_request\",\n            \"    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]\",\n            \"  File \\\"/app/controllers/v1/UserController.py\\\", line 10, in register\",\n            \"    sdfds\",\n            \"NameError: name 'sdfds' is not defined\"\n        ],\n        \"message\": \"name 'sdfds' is not defined\"\n    }\n    ```\n* production 環境響應的錯誤資訊\n    ```json\n    {\n        \"message\": \"Internal Server Error\"\n    }\n    ```\n\n### 拋出錯誤\n\n你可以透過 `system.Exceptions` 底下的類別在你的 Controller 或是 Service 中拋出錯誤。框架會自動地將這些錯誤訊息轉換成 HTTP 響應。\n\n舉了一個例子：\n\n```python\nfrom system.Exceptions import ValidationError\n\ndef create_task(self, user_id, name):\n    task = self.task_dao.find_by_name(user_id, name)\n    if task:\n        raise ValidationError(f'Task {name} already exists')\n```\n\n此時，當 `create_task` 方法被呼叫時，若 `task` 已經存在，則會拋出一個 HTTP 400 錯誤，並且響應以下的訊息：\n\n```json\n{\n    \"message\": \"Task {name} already exists\"\n}\n```\n\n## 日誌\n\n專案內的日誌統一由 `system.LogManager` 進行管理，他是一個 Singleton 類別，並且透過 `logging` 模組進行日誌的記錄。所有的 Log 將統一被寫入到 `writable/logs` 資料夾底下，相同的日誌最多留存 3 天。\n\n### 系統預設錯誤日誌\n\n系統錯誤將會被記錄在 `writable/logs/app_error.log`。當無法預期的錯誤發生時，將會被記錄在這個日誌中。就像這樣：\n\n```log\n2024-02-18 01:00:38,773 - ERROR - name 'sdfds' is not defined\nTraceback (most recent call last):\n\n  File \"/usr/local/lib/python3.9/site-packages/flask/app.py\", line 870, in full_dispatch_request\n    rv = self.dispatch_request()\n\n  File \"/usr/local/lib/python3.9/site-packages/flask/app.py\", line 855, in dispatch_request\n    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]\n\n  File \"/app/controllers/v1/UserController.py\", line 10, in register\n    sdfds\n\nNameError: name 'sdfds' is not defined\n```\n\n### 建立你自己的日誌\n\n你可以透過 `system.LogManager` 類別建立你自己的日誌，使用 `get_logger` 方法取得 Logger 實體。舉個例子：\n\n```python\nfrom flask import Blueprint, jsonify\nfrom system.LogManager import LogManager\n\ndefi = Blueprint('v1_index_api', __name__)\n\n@defi.route('/', methods=['GET'])\ndef index_page():\n    access_log = LogManager.get_logger('access_log', LogManager.LOG_INFO)\n    access_log.write('Access index page', LogManager.LOG_INFO)\n    return jsonify(status=200, msg={\n        \"message\": \"Server is running. This is version 1 API.\"\n    })\n```\n\n上述程式碼中的 `access_log` 將會記錄在 `writable/logs/access_log.log` 中。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonkenwu%2Fpythonflaskdevarchitecture","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmonkenwu%2Fpythonflaskdevarchitecture","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonkenwu%2Fpythonflaskdevarchitecture/lists"}