Moving Functions From app.py to framework.py

Question:
So in my framework I am making I am trying to clean up app.py and move the functions to framework.py, it throws this error →

Traceback (most recent call last):
File "/home/runner/VIAL/app.py", line 2, in <mod ule>
from sre import App, route, tags, Response File "/home/runner/VIAL/src/__init__.py", line 4 , in ‹module>
from . framework import route
ImportError: cannot import name 'route' from 'src. framework' (/home/runner/VIAL/src/framework.py) 

Repl link:
https://replit.com/@SalladShooter/VIAL

app.py

# app.py
from src import App, route, tags, Response

class MyApp(App):
    def index(self, request):
        css_link = '<link rel="stylesheet" type="text/css" href="/static/styles.css">'
        js_link = '<script src="/static/script.js"></script>'

        html_content = css_link + js_link

        list_items = [
            tags.li("Item  1"),
            tags.li("Item  2"),
            tags.li("Item  3")
        ]
        html_content += str(tags.ul().add_children(*list_items))
        html_content += str(tags.p("Hello, World!").attribute('class', 'p').id('p').add_children(tags.div()))

        return Response(html_content, content_type='text/html')

framework.py

# framework.py
from werkzeug.wrappers import Request, Response
from werkzeug.routing import Map, Rule  # Adding the import for Rule

class Request(Request):
    pass

class Response(Response):
    pass

class App:
    def __init__(self):
        self.routes = {}
        self.url_map = Map()  # Creating a url_map for routing

    def route(self, rule, **kwargs):
        def decorator(f):
            kwargs['endpoint'] = f.__name__
            self.url_map.add(Rule(rule, **kwargs))
            return f
        return decorator

    def dispatch_request(self, request):
        view_func = self.routes.get(request.path)
        if view_func is not None:
            return view_func(request)
        return Response('Not Found', status=404)

    def __call__(self, environ, start_response):
        request = Request(environ)
        response = self.dispatch_request(request)
        return response(environ, start_response)

__init__.py

# Copyright (c) 2024 SalladShooter

from .framework import App, Response
from .framework import route
from .framework import dispatch_request, __call__
from .html_generator import *

__all__ = ['App', 'route', 'tags', 'Response', 'dispatch_request', '__call__'] ```
1 Like

It appears that route is a function inside a class, so:
__init__.py

# Copyright (c) 2024 SalladShooter
from .framework import App, Response
from .framework import dispatch_request, __call__
from .html_generator import *
route = App().route
__all__ = ['App', 'route', 'tags', 'Response', 'dispatch_request', '__call__']
1 Like

Hey @Fairies0feast!

Thanks it fixed the error, but now I get this one and I have no clue why →

Traceback (most recent call last):
File "/home/runner/VIAL/app.py", line 2, in ‹module> from sre import App, route, tags, Response File "/home/runner/VIAL/src/__init__.py", line 3, in <module>
from . framework import dispatch_request, -_call_ ImportError: cannot import name 'dispatch_request' fro m 'src. framework' (/home/runner/VIAL/src/framework.py) 
1 Like

Try changing your __init__.py to:

# Copyright (c) 2024 SalladShooter
from .framework import App, Response
from .framework import __call__
from .html_generator import *
route = App().route
dispatch_request = App().dispatch_request
__all__ = ['App', 'route', 'tags', 'Response', 'dispatch_request', '__call__']
If you get cannot import name __call__...
# Copyright (c) 2024 SalladShooter
from .framework import App, Response
from .html_generator import *
route = App().route
__call__ = App().__call__
dispatch_request = App().dispatch_request
__all__ = ['App', 'route', 'tags', 'Response', 'dispatch_request', '__call__']
1 Like

Hello,
may I ask why exactly you are trying to import methods of a class? They are meant to be methods, not global functions.

The correct way to use methods is to create an instance (probably just one in this case) and use it.

# __init__.py
from .framework import App, Response
from .html_generator import *  # you may want to avoid * imports

app = App()
# if you for some reason don't like prepending "app." when calling methods, you can put:
# dispatch_request = app.dispatch_request
# __call__ = app.__call__  # <- please rename
2 Likes

@NuclearPasta0 thanks, it removed my error, but nothing is happening. When I run the program it immediately stops (400ms-800ms).
__init__.py

# Copyright (c) 2024 SalladShooter
from .framework import App, Response
from .html_generator import Tag, tags
app = App()
route = app.route
__call__ = app.__call__
dispatch_request = app.dispatch_request
__all__ = ['App', 'route', 'tags', 'Response', 'dispatch_request', '__call__'] ```
1 Like

A post was split to a new topic: Can’t Connect GitHub to Replit

I am not entirely sure, but it looks like your code simply does not do anything.
You aren’t using __call__ or route or dispatch_request of __init__.py anywhere, and you aren’t using MyApp of app.py anywhere.
Also, I am still not entirely sure of the reason for creating an App() and grabbing methods from it. It would be best to use methods from the instance instead of an imported bound method.

2 Likes

I saw that you have defined a route method in your App class which adds rules to the url_map . But, as @NuclearPasta0 pasta said, it seems that dispatch_request method is trying to get the view function directly from self.routes which you never populate.

Your current setup does not map the endpoint names to their corresponding functions. You need to store this mapping when you add a rule in the route decorator too.

You could change your framework.py to incorporate those things:

from werkzeug.wrappers import Request, Response
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, NotFound

class App:
    def __init__(self):
        self.url_map = Map()
        self.view_functions = {}

    def route(self, rule, **options):
        def decorator(f):
            self.url_map.add(Rule(rule, endpoint=f.__name__))
            self.view_functions[f.__name__] = f
            return f
        return decorator

    def dispatch_request(self, request):
        adapter = self.url_map.bind_to_environ(request.environ)
        try:
            endpoint, values = adapter.match()
            return self.view_functions[endpoint](request, **values)
        except HTTPException as e:
            return e

    def __call__(self, environ, start_response):
        request = Request(environ)
        response = self.dispatch_request(request)
        return response(environ, start_response)

    def run(self, host='127.0.0.1', port=5000):
        from werkzeug.serving import run_simple
        run_simple(host, port, self)

Ps.: I threw self.url_map.bind_to_environ(environ) to create a URL map adapter

2 Likes

@WindLother I was looking through and that isn’t really the error I’m facing of it not running. It is because I forgot the run function in app.py but for some reason it is having trouble importing →

Traceback (most recent call last):
File "/home/runner/VIAL/app.py", line 2, in <m odule>
from sre import App, route, tags, Response,
run
File "/home/runner/VIAL/src/_init__•py", line
2, in ‹module>
from .framework import App, Response, run ImportError: cannot import name 'run' from 'src. framework' (/home/runner/VIAL/src/framework.py) 

__init__.py

# Copyright (c) 2024 SalladShooter
from .framework import App, Response, run
from .html_generator import Tag, tags
app = App()
route = app.route
__all__ = ['App', 'route', 'tags', 'Response', 'run'] 

app.py

# app.py
from src import App, route, tags, Response, run

class MyApp(App):
    def index(self, request):
        css_link = '<link rel="stylesheet" type="text/css" href="/static/styles.css">'
        js_link = '<script src="/static/script.js"></script>'

        html_content = css_link + js_link

        list_items = [
            tags.li("Item  1"),
            tags.li("Item  2"),
            tags.li("Item  3")
        ]
        html_content += str(tags.ul().add_children(*list_items))
        html_content += str(tags.p("Hello, World!").attribute('class', 'p').id('p').add_children(tags.div()))

        return Response(html_content, content_type='text/html')

if __name__ == "__main__":
    app = MyApp()
    run('0.0.0.0', 5000, app) 

You define run as a method of the App class (in your framework.py), you don’t need to (and shouldn’t) import it separately, you just need to create an instance of App and then call the run method on that instance.

So here:

You don’t need to do this, just:

# __init__.py
from .framework import App, Response
from .html_generator import Tag, tags
app = App()
route = app.route
__all__ = ['App', 'route', 'tags', 'Response']

Same thing here:

Just instantiate MyApp and then call the run method on this instance.

# app.py
from src import App, route, tags, Response

class MyApp(App):

if __name__ == "__main__":
    app = MyApp()
    app.run('0.0.0.0', 5000)
1 Like

@WindLother I did what you said to do, but when I run it all the info it gives is 404 errors. For some reason it won’t tell me why I’m getting the errors, and I haven’t been able to fix them and proceed with my Framework.

I’ve been trying to fix the random 404 errors through brute force and AI. I can’t figure out the issue as it wont give me any context in the Webview or Console. I am seeking help/advice as I want to complete my Framework.

Port :5000 opened on {..replit.dev}

WARNING: This is a development server. Do not use it in a production deployment. Use a productio
n WSGI server instead.
* Running on all addresses (0.0.0.0 )
* Running on http://127.0.0.1:5000* Running on http://172.31.196.3:5000
Press CTRL+C to quit
172.31.196.3 - - [18/Feb/2024 00:22:14] "GET / H
TTP/1.1" 404 -
172.31.196.3 - - [18/Feb/2024 00:22:15] "GET / H
TTP/1.1" 404 - 

Why are you defining a new class instead of defining the routes?

I’ve forked and changed some of the code.

# app.py
from src import App, Tags, Response

app = App()


@app.route('/')
def index(request) -> Response:
    css_link = '<link rel="stylesheet" type="text/css" href="/static/styles.css">'
    js_link = '<script src="/static/script.js"></script>'

    html_content = css_link + js_link

    list_items = [
        Tags.Li('Item  1'),
        Tags.Li('Item  2'),
        Tags.Li('Item  3')
    ]
    html_content += str(Tags.Ul().add_children(*list_items))
    html_content += str(Tags.P('Hello, World!').attribute('class', 'p').id('p').add_children(Tags.Div()))

    return Response(html_content, content_type='text/html')


if __name__ == '__main__':
    app.run('0.0.0.0', 5000)

1 Like

Hey @QwertyQwerty88!

Thank you so much it fixed it, but now it throws 404 errors trying to GET / the CSS and JS files.

It doesn’t seem that you are actually serving the static files.

1 Like

@SalladShooter like, you need to tell the server that there are files in the static folder that need to be served. Let me try to whip something up in my Repl. Would you like to be invited, so you can see what I do?

1 Like

Yes please, I’m sorry that I am being a bother, I’m just getting into this kind of stuff.

1 Like

Okay, so I got it working using werkzeug shared data middleware:

from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.middleware.shared_data import SharedDataMiddleware  # New
from werkzeug.routing import Map, Rule
from werkzeug.serving import run_simple
from werkzeug.wrappers import Request, Response


class App:

    def __init__(self):
        self.url_map = Map()
        self.view_functions = {}

    def route(self, rule, **options):

        def decorator(function):
            self.url_map.add(Rule(rule, endpoint=function.__name__))
            self.view_functions[function.__name__] = function
            return function

        return decorator

    def dispatch_request(self, request):
        adapter = self.url_map.bind_to_environ(request.environ)
        try:
            endpoint, values = adapter.match()
            return self.view_functions[endpoint](request, **values)
        except HTTPException as e:
            return e

    def __call__(self, environ, start_response):
        request = Request(environ)
        response = self.dispatch_request(request)
        return response(environ, start_response)

    def run(self, host='0.0.0.0', port=5000):
        app = SharedDataMiddleware(self, {'/static': 'static'})  # New
        run_simple(host, port, app)

Lines I’ve added have got a # New comment next to them. You can, of course, modify the code as you see fits (move the shared data middleware out of the run function, allow the user to serve different static folders, etc.)

3 Likes

@QwertyQwerty88 one quick thing, when I change my CSS it isn’t updating the looks of the HTML elements, do I just need to refresh and wait or something else.