# Fastango: Python Web Framework

![Logo](https://i.ibb.co/zZ9nrd0/image-2024-07-07-16-49-59-removebg-preview.png)

![purpose](https://img.shields.io/badge/purpose-learning-green)

![PyPI - Version](https://img.shields.io/pypi/v/fastango-v1)

Fastango is a Python web framework built for learning purposes.

It's a WSGI framework and can be used with any WSGI application server such as Gunicorn.

## Installation

```shell
pip install fastango-v1
```

## How to use it

### Basic usage:

```python
from fastango_v1.app import FastangoApp
from fastango_v1.middleware import Middleware

app = FastangoApp()


@app.route('/home')
def home(request, response):
    response.text = "Hello from the Home Page"


@app.route('/about', allowed_methods=['put'])
def about(request, response):
    response.text = "Hello from the About Page"


@app.route('/hello/{name}')
def greating(request, response, name):
    response.text = f"Hello {name}"


@app.route('/class/')
class TestView:

    def get(self, request, response, **kwargs):
        response.text = "Class Method GET"

    def post(self, request, response, **kwargs):
        response.text = "Class Method POST"


def gret_route(request, response, name):
    response.text = f"Hello New route"


app.add_route("hello-new-route", gret_route)


@app.route("/json")
def get_template(request, respose):
    respose.json = {"key": "some json"}


```

### Unit Tests

    The recommend way of writing unit tests is with [pytest]:

```python
import pytest
from fastango_v1.middleware import Middleware


def test_basic_route_adding(app):
    @app.route("/home")
    def home(req, res):
        res.text = "Hello from home"


def test_duplicate_routes_throws_exception(app):
    @app.route("/home")
    def home(req, res):
        res.text = "Hello from home"

    with pytest.raises(AssertionError):
        @app.route("/home")
        def home2(req, res):
            res.text = "Hello from home"


def test_requests_can_be_sent_by_test_client(app, test_client):
    @app.route("/home")
    def home(req, res):
        res.text = "Hello from home"

    response = test_client.get('http://testserver/home')

    assert response.text == "Hello from home"


def test_parametrized_routing(app, test_client):
    @app.route('/hello/{name}')
    def greating(request, response, name):
        response.text = f"Hello {name}"

    assert test_client.get('http://testserver/hello/Mirmux').text == "Hello Mirmux"
    assert test_client.get('http://testserver/hello/Jack').text == "Hello Jack"


def test_default_response(test_client):
    response = test_client.get('http://testserver/noneexists')

    assert response.text == 'Not found!'
    assert response.status_code == 404


def test_class_based_get(app, test_client):
    @app.route("/books")
    class Books:
        def get(self, req, res):
            res.text = "Books page"

    assert test_client.get("http://testserver/books").text == "Books page"


def test_class_based_post(app, test_client):
    @app.route("/books")
    class Books:
        def post(self, req, res):
            res.text = "Created book!"

    assert test_client.post("http://testserver/books").text == "Created book!"


def test_class_based_not_allowed_method(app, test_client):
    @app.route("/books")
    class Books:
        def post(self, req, res):
            res.text = "Created book!"

    assert test_client.get("http://testserver/books").text == "Method Not Allowed."


def test_add_another_method_route(app, test_client):
    def gret_route(request, response):
        response.text = f"Hello New route"

    app.add_route("/hello_route", gret_route)

    assert test_client.get("http://testserver/hello_route").text == "Hello New route"


def test_template_handler(app, test_client):
    @app.route("/test-template")
    def get_template(request, respose):
        respose.body = app.template(
            "home.html",
            context={"new_title": "Best Title", "new_body": "Best Body"}
        )

    response = test_client.get('http://testserver/test-template')

    assert "Best Title" in response.text
    assert "Best Body" in response.text

    assert "text/html" in response.headers['Content-Type']


def test_custom_exception_handler(app, test_client):
    @app.route("/exception")
    def exception_throwing_handler(req, resp):
        raise AssertionError('some exception')

    def on_exception(req, resp, exc):
        resp.text = "Something went wrong!"

    app.add_exception_handler(on_exception)

    response = test_client.get('http://testserver/exception')

    assert response.text == "Something went wrong!"


def test_non_existent_static_file(test_client):
    assert test_client.get("http://testserver/static/nonexistent.css").status_code == 404


def test_serving_static_file(test_client):
    response = test_client.get("http://testserver/static/test.css")

    assert response.text == "body {background-color: chocolate; }"


def test_middleware_methods_are_called(app, test_client):
    process_request_called = False
    process_response_called = False

    class SimpleMiddleware(Middleware):
        def __init__(self, app):
            super(SimpleMiddleware, self).__init__(app)

        def process_request(self, req):
            nonlocal process_request_called
            process_request_called = True

        def process_response(self, req, resp):
            nonlocal process_response_called
            process_response_called = True

    app.add_middleware(SimpleMiddleware)

    @app.route("/home")
    def index(req, resp):
        resp.text = 'from handler'

    test_client.get("http://testserver/home")

    assert process_request_called is True
    assert process_response_called is True


def test_allowed_methods_for_function_based_handlers(app, test_client):
    @app.route("/home", allowed_methods=['post'])
    def home(req, resp):
        resp.text = 'Hello Allowed Method'

    resp = test_client.get("http://testserver/home")

    assert resp.status_code == 405
    assert resp.text == 'Method Not Allowed.'


def test_json_response_helper(app, test_client):
    @app.route("/json")
    def json_handler(req, resp):
        resp.json = {"name": "Fastango"}

    resp = test_client.get("http://testserver/json")
    response_data = resp.json()

    assert resp.headers['Content-Type'] == 'application/json'
    assert response_data['name'] == 'Fastango'


def test_text_response_helper(app, test_client):
    @app.route("/text")
    def text_handler(req, resp):
        resp.text = 'plain text'

    resp = test_client.get("http://testserver/text")

    assert "text/plain" in resp.headers['Content-Type']
    assert resp.text == 'plain text'


def test_html_response_helper(app, test_client):
    @app.route("/html")
    def html_handler(req, resp):
        resp.html = app.template(
            "home.html",
            context={"new_title": "Best Title", "new_body": "Best Body"}
        )

    resp = test_client.get("http://testserver/html")

    assert "Best Title" in resp.text
    assert "Best Body" in resp.text

    assert "text/html" in resp.headers['Content-Type']

```

## Templates

The default folder for templates is `templates`. You can change it when initializing the main `API()` class:

```python
from fastango_v1.app import FastangoApp

app = FastangoApp(template_dir="templates")


@app.route("/template")
def get_template(request, respose):
    respose.html = app.template(
        "home.html",
        context={"new_title": "Best Title", "new_body": "Best Body"}
    )
```

## Middleware

    You can add middleware like this

```python
from fastango_v1.app import FastangoApp
from fastango_v1.middleware import Middleware

app = FastangoApp()


class LoginMiddleware(Middleware):
    def process_request(self, req):
        print("request is being called")

    def process_response(self, req, resp):
        print("response is being called")


app.add_middleware(LoginMiddleware)

```

## Exception Handler

```python
from fastango_v1.app import FastangoApp

app = FastangoApp()


@app.route("/exception")
def exception_throwing_handler(req, resp):
    raise AssertionError('some exception')


def on_exception(req, resp, exc):
    resp.text = str(exc)


app.add_exception_handler(on_exception)
```