-
-
Notifications
You must be signed in to change notification settings - Fork 371
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
# Pull Request Adds First Class Python Support [Web Examples] Part of #3928 ## Description Web Examples for Python `1-hello-flask`, `2-todo-flask`, `3-hello-django`, `4-todo-django` Examples ## Related Issues - Link to related issue #3928. ## Checklist - [x] 1-hello-flask - [x] 2-todo-flask - [x] 3-hello-django - [x] 4-todo-django - [x] Updated Documentation ## Status Require Review!!!
- Loading branch information
1 parent
7cea88c
commit d361ace
Showing
47 changed files
with
2,202 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
= Python Web Project Examples | ||
:page-aliases: Python_Web_Examples.adoc | ||
|
||
include::partial$gtag-config.adoc[] | ||
|
||
This page provides examples of using Mill as a build tool for Python web applications. | ||
It includes setting up a basic "Hello, World!" application and developing a fully | ||
functional https://todomvc.com/[TodoMVC] app with Flask and Django, showcasing best practices | ||
for project organization, scalability, and maintainability. | ||
|
||
== Flask Hello World App | ||
|
||
include::partial$example/pythonlib/web/1-hello-flask.adoc[] | ||
|
||
== Flask TodoMVC App | ||
|
||
include::partial$example/pythonlib/web/2-todo-flask.adoc[] | ||
|
||
== Django Hello World App | ||
|
||
include::partial$example/pythonlib/web/3-hello-django.adoc[] | ||
|
||
== Django TodoMVC App | ||
|
||
include::partial$example/pythonlib/web/4-todo-django.adoc[] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// This example uses Mill to manage a Flask app that serves `"Hello, Mill!"` | ||
// at the root URL (`/`), with Flask installed as a dependency | ||
// and tests enabled using `unittest`. | ||
package build | ||
import mill._, pythonlib._ | ||
|
||
object foo extends PythonModule { | ||
|
||
def mainScript = Task.Source { millSourcePath / "src/foo.py" } | ||
|
||
def pythonDeps = Seq("flask==3.1.0") | ||
|
||
object test extends PythonTests with TestModule.Unittest | ||
|
||
} | ||
|
||
// Running these commands will test and run the Flask server with desired outputs. | ||
|
||
// The app is ready to serve at `http://localhost:5000`. | ||
|
||
/** Usage | ||
|
||
> ./mill foo.test | ||
... | ||
test_hello_flask (test.TestScript...) | ||
Test the '/' endpoint. ... ok | ||
... | ||
Ran 1 test... | ||
OK | ||
... | ||
|
||
> ./mill foo.runBackground | ||
|
||
> curl http://localhost:5000 | ||
...<h1>Hello, Mill!</h1>... | ||
|
||
> ./mill clean foo.runBackground | ||
|
||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from flask import Flask | ||
|
||
app = Flask(__name__) | ||
|
||
|
||
@app.route("/") | ||
def hello_world(): | ||
return "<h1>Hello, Mill!</h1>" | ||
|
||
|
||
if __name__ == "__main__": | ||
app.run(debug=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import unittest | ||
from foo import app # type: ignore | ||
|
||
|
||
class TestScript(unittest.TestCase): | ||
def setUp(self): | ||
"""Set up the test client before each test.""" | ||
self.app = app.test_client() # Initialize the test client | ||
self.app.testing = True # Enable testing mode for better error handling | ||
|
||
def test_hello_flask(self): | ||
"""Test the '/' endpoint.""" | ||
response = self.app.get("/") # Simulate a GET request to the root endpoint | ||
self.assertEqual(response.status_code, 200) # Check the HTTP status code | ||
self.assertIn( | ||
b"Hello, Mill!", response.data | ||
) # Check if the response contains the expected text | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// This is a `Flask`-based https://todomvc.com/[TodoMVC] application managed and built using `Mill`. | ||
// It allows users to `add`, `edit`, `delete`, and `view` tasks stored in a Python Data Structure. | ||
// Tasks can be filtered as `all`, `active`, or `completed` based on their state. | ||
// The application demonstrates dynamic rendering using Flask's routing and templating features. | ||
package build | ||
import mill._, pythonlib._ | ||
|
||
object todo extends PythonModule { | ||
|
||
def mainScript = Task.Source { millSourcePath / "src/app.py" } | ||
|
||
def pythonDeps = Seq("flask==3.1.0", "Flask-SQLAlchemy==3.1.1", "Flask-WTF==1.2.2") | ||
|
||
object test extends PythonTests with TestModule.Unittest | ||
object itest extends PythonTests with TestModule.Unittest | ||
|
||
} | ||
|
||
// Apart from running a web server, this example demonstrates: | ||
|
||
// - **Serving HTML templates** using **Jinja2** (Flask's default templating engine). | ||
// - **Managing static files** such as JavaScript, CSS, and images. | ||
// - **Filtering and managing tasks** in-memory using Python data structures. | ||
// - **Unit testing** using **unittest** for testing task operations. | ||
// - **Integration testing** using **unittest** for end-to-end application behavior. | ||
|
||
// This example also utilizes **Mill** for managing `dependencies`, `builds`, and `tests`, | ||
// offering an efficient development workflow. | ||
|
||
// The app is ready to serve at `http://localhost:5001`. | ||
|
||
/** Usage | ||
|
||
> ./mill todo.test | ||
... | ||
test_add_todo (test.TestTodoApp...) ... ok | ||
test_delete_todo (test.TestTodoApp...) ... ok | ||
test_edit_todo (test.TestTodoApp...) ... ok | ||
test_filter_todos (test.TestTodoApp...) ... ok | ||
test_toggle_all (test.TestTodoApp...) ... ok | ||
test_toggle_todo (test.TestTodoApp...) ... ok | ||
...Ran 6 tests... | ||
OK | ||
... | ||
|
||
> ./mill todo.itest | ||
... | ||
test_add_and_list_todos (test.TestTodoAppIntegration...) ... ok | ||
test_delete_todo (test.TestTodoAppIntegration...) ... ok | ||
test_edit_and_list_todos (test.TestTodoAppIntegration...) ... ok | ||
test_toggle_all_todos (test.TestTodoAppIntegration...) ... ok | ||
test_toggle_and_list_todos (test.TestTodoAppIntegration...) ... ok | ||
...Ran 5 tests... | ||
OK | ||
... | ||
|
||
> ./mill todo.runBackground | ||
|
||
> curl http://localhost:5001 | ||
...What needs to be done... | ||
|
||
> ./mill clean todo.runBackground | ||
|
||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import unittest | ||
from app import app, todos | ||
|
||
|
||
class TestTodoAppIntegration(unittest.TestCase): | ||
@classmethod | ||
def setUpClass(cls): | ||
# Set up the test client for the app | ||
cls.client = app.test_client() | ||
|
||
def setUp(self): | ||
# Clear the todos list before each test | ||
global todos | ||
todos.clear() | ||
|
||
def test_add_and_list_todos(self): | ||
# Test adding a todo and listing all todos | ||
response = self.client.post("/add/all", data="Test Todo") | ||
self.assertEqual(response.status_code, 200) | ||
# Fetch the todos list and verify the item was added | ||
response = self.client.post("/list/all", data="") | ||
self.assertIn(b"Test Todo", response.data) | ||
|
||
def test_toggle_and_list_todos(self): | ||
# Test adding a todo, toggling it, and listing active/completed todos | ||
self.client.post("/add/all", data="Test Todo") | ||
response = self.client.post("/toggle/all/0", data="") | ||
# Check if the todo is toggled | ||
self.assertEqual(response.status_code, 200) | ||
# Now, test filtering todos based on active/completed state | ||
response = self.client.post("/list/active", data="") | ||
self.assertNotIn(b"Test Todo", response.data) | ||
response = self.client.post("/list/completed", data="") | ||
self.assertIn(b"Test Todo", response.data) | ||
|
||
def test_edit_and_list_todos(self): | ||
# Test adding a todo, editing it, and then verifying the updated text | ||
self.client.post("/add/all", data="Test Todo") | ||
response = self.client.post("/edit/all/0", data="Updated Todo") | ||
# Check that the todo was updated | ||
response = self.client.post("/list/all", data="") | ||
self.assertIn(b"Updated Todo", response.data) | ||
self.assertNotIn(b"Test Todo", response.data) | ||
|
||
def test_delete_todo(self): | ||
# Test adding and deleting a todo | ||
self.client.post("/add/all", data="Test Todo") | ||
response = self.client.post("/delete/all/0", data="") | ||
# Verify that the todo was deleted | ||
response = self.client.post("/list/all", data="") | ||
self.assertNotIn(b"Test Todo", response.data) | ||
|
||
def test_toggle_all_todos(self): | ||
# Test toggling all todos | ||
self.client.post("/add/all", data="Todo 1") | ||
self.client.post("/add/all", data="Todo 2") | ||
response = self.client.post("/toggle-all/all", data="") | ||
response = self.client.post("/list/completed", data="") | ||
self.assertIn(b"Todo 1", response.data) | ||
self.assertIn(b"Todo 2", response.data) | ||
response = self.client.post("/toggle-all/all", data="") | ||
response = self.client.post("/list/active", data="") | ||
self.assertIn(b"Todo 1", response.data) | ||
self.assertIn(b"Todo 2", response.data) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
from flask import Flask, render_template, request | ||
from dataclasses import dataclass | ||
from typing import List | ||
|
||
app = Flask(__name__, static_folder="../static", template_folder="../templates") | ||
|
||
|
||
@dataclass | ||
class Todo: | ||
checked: bool | ||
text: str | ||
|
||
|
||
todos: List[Todo] = [] | ||
|
||
|
||
@app.route("/") | ||
def index(): | ||
return render_template("base.html", todos=todos, state="all") | ||
|
||
|
||
def render_body(state: str): | ||
filtered_todos = { | ||
"all": todos, | ||
"active": [todo for todo in todos if not todo.checked], | ||
"completed": [todo for todo in todos if todo.checked], | ||
}[state] | ||
return render_template("index.html", todos=filtered_todos, state=state) | ||
|
||
|
||
def filter_todos(state): | ||
"""Filter todos based on the state (all, active, completed).""" | ||
if state == "all": | ||
return todos | ||
elif state == "active": | ||
return [todo for todo in todos if not todo.checked] | ||
elif state == "completed": | ||
return [todo for todo in todos if todo.checked] | ||
|
||
|
||
@app.route("/edit/<state>/<int:index>", methods=["POST"]) | ||
def edit_todo(state, index): | ||
"""Edit the text of a todo.""" | ||
global todos | ||
updated_text = request.data.decode("utf-8") | ||
# Update the text attribute of the Todo object | ||
todos[index].text = updated_text | ||
filtered_todos = filter_todos(state) | ||
return render_template("index.html", todos=filtered_todos, state=state) | ||
|
||
|
||
@app.route("/list/<state>", methods=["POST"]) | ||
def list_todos(state): | ||
return render_body(state) | ||
|
||
|
||
@app.route("/add/<state>", methods=["POST"]) | ||
def add_todo(state): | ||
todos.insert(0, Todo(checked=False, text=request.data.decode("utf-8"))) | ||
return render_body(state) | ||
|
||
|
||
@app.route("/delete/<state>/<int:index>", methods=["POST"]) | ||
def delete_todo(state, index): | ||
if 0 <= index < len(todos): | ||
todos.pop(index) | ||
return render_body(state) | ||
|
||
|
||
@app.route("/toggle/<state>/<int:index>", methods=["POST"]) | ||
def toggle(state, index): | ||
if 0 <= index < len(todos): | ||
todos[index].checked = not todos[index].checked | ||
return render_body(state) | ||
|
||
|
||
@app.route("/clear-completed/<state>", methods=["POST"]) | ||
def clear_completed(state): | ||
global todos | ||
todos = [todo for todo in todos if not todo.checked] | ||
return render_body(state) | ||
|
||
|
||
@app.route("/toggle-all/<state>", methods=["POST"]) | ||
def toggle_all(state): | ||
global todos | ||
|
||
all_checked = all(todo.checked for todo in todos) | ||
for todo in todos: | ||
todo.checked = not all_checked | ||
|
||
return render_body(state) | ||
|
||
|
||
if __name__ == "__main__": | ||
app.run(debug=True, port=5001) |
Oops, something went wrong.