For some of your web apps you develop in python, you will want to run them on the cloud so that your script can run 24/7. For some of your smaller applications, you may want to find the right free python hosting service so you don’t have to worry about the per month charges. These web applications might be a website written in flask, or using another web framework, it might be other types of python apps that runs in the background and runs your automation. This is where you can consider some of the hosting services that have a free plan and are still very easy to setup.
To find the right hosting platforms that fits your needs, you want to consider a few things:
- Ease of access to upload projects
- What type of support they provide
- What specifications that virtual server environment has to offer
One such new platform is called deta.sh. Deta is a free hosting service that can be used to provide web hosting for deploying python web applications or other types of python applications that run in the background.
The deta service, as of mid-2022, is still in the development stage and is expected to have a permanent free python hosting service so that online python applications can be setup and deployed quickly and easily. Deta is a relatively new service but is a service that is intended to compete with pythonanywhere, heroku, and similar services to run python on web servers. The service lets you host python script online without fuss directly from a command line, much like how you can check in code to github. Although it is new, it has the potential to be one of the best free python hosting there is in order to get your python online.
The platform provides you mini virtual environments (called ‘micros’) where you can host your python scripts. These can be separated into workspaces called ‘projects’ so that you can also more easily manage your environments. The way you can access/upload your code is with the command line through a password Access Token.

We will go through step by step how to run your python online. For this article, we will guide you on using deta to host a simple flask based web page so that you can have python as a webserver.
Signing up for Deta.sh
Deta.sh is effectively a cloud python hosting service which sits on top of AWS and allows you to deploy your python code into a virtual machine (called a deta micro), store files (called data drive) and also store data (called deta base). Unlike AWS or other hosting services, you can quickly host and run your script without going through the hassle of setting up server, security configurations etc.
The Deta.sh team offers the service for free in order to allow developers to monetize the solutions where deta.sh will be able to share some of that revenue. To date, there are no paid Deta.sh hosting plans for python hosting and no intention. So you can continue to run python code online forever.
To begin with, head over to the website https://deta.sh to first create an account.

Once you have submitted, go to your email and click on the verify link.

After you click on sign-in, enter the same username and password, and you will be taken to the default page where you will have the ability to “See My Key”

Click on the “See My Key” to see your secret password. You will only be able to see it once and will not be able to see it ever again.
This is what they project key will look like:

You need both the key and the project id.
Think of the key like a password and the “Project ID” as a password. When you want to access your deta.sh to upload programs, make changes, you will need to use your project key to access your space.
If you lose your project id/key, you will not be able to recover it. However, you can create a new one with Settings->Create Key option.

One thing I’d like to call out is the Project ID. This is the ID of this particular s[ace

If you have multiple programs which access deta.sh, it is best to have separate project keys. The reason is that if one of your keys are compromised, then you can simply just change that key and not have all your applications be affected.
Setting Up Your Remote Access For Deta.sh
We will first setup deta.sh in the command line interface so that you can communicate to your deta.sh space on the cloud.
You can do this with either one of:
Mac / Linux:
curl -fsSL https://get.deta.dev/cli.sh | sh
Windows:
iwr https://get.deta.dev/cli.ps1 -useb | iex
Once that’s done, what will happen is that there will be a hidden folder called $HOME/.deta that is created (specifically in the case of Mac / Linux). It’s in this directory that the deta command line application will be found.
You can type deta --help to check that the command line tool was installed correctly

Next, you will need to create an access token so that you can connect to your deta.sh account. For this you will need to create an access token. Go to your deta.sh home page (e.g. https://web.deta.sh/) and then go back to the main projects page.

Next, click on the Create Access token under settings

Once you create token, this will create an Access Token so that you don’t need to login each time.

Copy this Access Token and then, create a file called tokens in the $HOME/.deta/ directory. Steps for Mac/Linux are:
cd $HOME/.deta
nano tokens
You can then add the following json inside the tokens file:
{
"deta_access_token": "<your access token created above>"
}
Finally, you can install the python library that will be used to access the deta components with the deta library.
pip install deta
Have a Free Python Hosting Flask on Deta.sh
To create an environment to host your python code and have python web hosting, you need to create something called a “micro“. This is almost like a mini virtual server with 128mb of memory but will not be running all the time. They will wake up, execute your code, and then go back to sleep. Deta.sh is not designed for long running applications with heavy computations (use one of the public cloud providers for that!). Also, each micro has its own python online cloud private access.
To begin with, you can use the command deta new --python <micro name>. The <micro name> is the name to label the mini-virtual name.

The above command will create a directory called flask_test with a python script called main.py

The default code in the main.py is:
def app(event):
return "Hello, world!"
At the same time, this code will be uploaded to deta.sh. If you go to the dashboard page https://web.deta.sh/ you will see a sub-menu under the Micro menu. You may need to refresh your browser if you had it open.

You will notice that there’s also a URL for this deta micro which is the end point where your application output can be accessed. Think of this simply as the console output.

If you encountered any errors, in the command line, you can type deta logs to get an output of any errors from the logs.
To make a more useful application, we can create a flask application to show a more functional webpage. In order to do this, you will need to dell deta.sh to install the flask library. You cannot use pip install unfortunately, but instead you need to use the requirements.txt instead.
First, add flask into a requirements.txt file in your local directory. So your file should simply look like this:
#requirements.txt
flask
Then in your main.py code file, you add the following, again this is in your local directory
from flask import Flask
app = Flask(__name__)
@app.route('/', methods=["GET"])
def hello_world():
return "Hello Flask World"
# def app(event):
# return "Hello, world!"
In order to now upload the changes to your micro, you will need to run the command deta deploy. This will upload the files requirements.txt and updates to main.py into your micro.
deta deploy
When executed, this should upload the code and install the libraries:

Managing Flask Forms On Free Python Hosting
Now that we have a simple static web page, we can create a more complex example where there’s a form that can be submitted. Using the weather API from openweathermap API, we can show the weather for a given location.
To get the weather data, we need to install two libraries pyowm and datetime. Hence, this will need to be added to requirements.txt.
#requirements.txt
flask
pyowm
datetime
Then for the code, the following can be updated in the main.py:
from flask import Flask, request, jsonify
import pyowm, datetime
app = Flask(__name__)
@app.route('/', methods=["GET"])
def get_location():
return """<html>
<body>
<form action="weather" method="POST">
<input name="location" type="text">
<input type="submit" value="submit">
</form>
</body>
</html>"""
@app.route('/weather', methods=["POST", "GET"])
def get_weather():
api_key = '<your open weather map API ley>'
owm = pyowm.OWM( api_key ).weather_manager()
weather_data = owm.weather_at_place('Bangalore').weather
ref_time = datetime.datetime.fromtimestamp( weather_data.ref_time ).strftime('%Y-%m-%d %H:%M')
weather_str = f"<h1>Weather Report for: {request.form['location']}</h1>"
weather_str += f"<ul>"
weather_str += f"<li><b>Time:</b> { ref_time } </li>"
weather_str += f"<li><b>Overview:</b> {weather_data.detailed_status} </li>"
weather_str += f"<li><b>Wind Speed:</b> {weather_data.wind()} </li>"
weather_str += f"<li><b>Humidity:</b> {weather_data.humidity} </li>"
weather_str += f"<li><b>Temperature:</b> {weather_data.temperature('fahrenheit')} </li>"
weather_str += f"<li><b>Rain:</b> {weather_data.rain} </li>"
weather_str += f"</ul>"
return weather_str
# def app(event):
# return "Hello, world!"
Then to upload the code into deta.sh, you can use the command deploy:
deta deloy
Once deployed, you can then go to the website – this is the endpoint that was automatically generated by deta.sh above.

def get_location()Once submitted, then a call is made to OpenWeatherMap

/ url, then the function def get_weather() is called to process the form. The variable that was passed, can be access through request.form['location']. The above code works by first providing a form through the function def get_location() which generates a very simple form through HTML:
<html>
<body>
<form action="weather" method="POST">
<input name="location" type="text">
<input type="submit" value="submit">
</form>
</body>
</html>
When the submit button is pressed, the form calls the /weather URL with the field location. Once called, then the python function def get_weather() is called upon which a call to OpenWeatherMap.org is made to get the weather data for the given location.
Conclusion
This is just a tip of the iceberg of what you can do with deta. You can also run scheduled jobs, run a NoSQL database, and have file storage as well. Contact us if you’d like us to cover these areas too.
How To Use Python Decorators: A Complete Guide
Intermediate
You’ve probably seen the @ symbol above function definitions in Python code and wondered what it does. That’s a decorator — one of Python’s most powerful and elegant features. Decorators let you wrap a function with additional behavior (logging, caching, access control, rate limiting, timing) without modifying the function’s code. They’re the reason you can add authentication to a Flask route with a single line, or enable caching with @functools.lru_cache.
Decorators are a pure Python feature — no installation required. They’re built on Python’s first-class functions (functions that can be passed as arguments and returned from other functions). Once you understand how decorators work mechanically, you’ll be able to read and write the patterns used by virtually every Python framework, from Django’s @login_required to FastAPI’s @app.get() to pytest’s @pytest.fixture.
In this tutorial, you’ll learn how decorators work from first principles, how to use functools.wraps to preserve function metadata, how to write parameterized decorators (decorators that take arguments), how to stack multiple decorators, how to use class-based decorators, and how to apply these techniques in real-world scenarios like timing, retry logic, and access control.
Decorators: Quick Example
Here’s the simplest useful decorator — one that logs when a function is called:
# decorator_quick.py
import functools
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}({args}, {kwargs})")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
# This is equivalent to: add = log_calls(add)
result = add(3, 4)
print(f"Final result: {result}")
# The function's identity is preserved
print(f"Function name: {add.__name__}")
Output:
Calling add((3, 4), {})
add returned 7
Final result: 7
Function name: add
The @log_calls syntax is shorthand for add = log_calls(add). The decorator receives the original function, returns a new wrapper function that adds behavior before and after calling the original, and replaces the name add with the wrapper. The @functools.wraps(func) line copies the original function’s name, docstring, and other metadata onto the wrapper — always include this.
How Decorators Work: First Principles
To truly understand decorators, you need to understand that in Python, functions are objects — they can be passed as arguments and returned from other functions. This is called “first-class functions.” Decorators are just a syntax shortcut for a function transformation pattern.
# first_class_functions.py
# Functions can be passed as arguments
def apply_twice(func, value):
return func(func(value))
def double(x):
return x * 2
result = apply_twice(double, 3)
print(f"Apply twice: {result}") # 3 -> 6 -> 12
# Functions can be returned from other functions
def make_multiplier(n):
def multiplier(x):
return x * n
return multiplier # Returns the inner function
triple = make_multiplier(3)
print(f"Triple 5: {triple(5)}") # 15
# The decorator pattern manually, without @ syntax
def shout(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper() + "!!!"
return wrapper
def greet(name):
return f"Hello, {name}"
# Without @ syntax -- same result
greet = shout(greet)
print(greet("alice")) # HELLO, ALICE!!!
Output:
Apply twice: 12
Triple 5: 15
HELLO, ALICE!!!
The key insight: @shout above a function definition is exactly equivalent to writing greet = shout(greet) after the definition. The @ syntax just makes it more readable and places the decoration visually near the function definition where it belongs.
Always Use functools.wraps
Without @functools.wraps(func), your decorator replaces the original function’s metadata with the wrapper’s. This causes problems with debugging, documentation, and tools that inspect function names. Always include it:
# wraps_example.py
import functools
# WITHOUT functools.wraps -- breaks function identity
def bad_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
# WITH functools.wraps -- preserves identity
def good_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@bad_decorator
def my_function_bad():
"""This function does something important."""
pass
@good_decorator
def my_function_good():
"""This function does something important."""
pass
print(f"Bad decorator name: {my_function_bad.__name__}")
print(f"Bad decorator docstr: {my_function_bad.__doc__}")
print()
print(f"Good decorator name: {my_function_good.__name__}")
print(f"Good decorator docstr: {my_function_good.__doc__}")
Output:
Bad decorator name: wrapper
Bad decorator docstr: None
Good decorator name: my_function_good
Good decorator docstr: This function does something important.
Practical Decorator Examples
Timing Functions
A timer decorator measures how long a function takes to execute — great for performance monitoring and identifying bottlenecks:
# timer_decorator.py
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f} seconds")
return result
return wrapper
@timer
def slow_function():
time.sleep(0.1)
return "done"
@timer
def sum_million():
return sum(range(1_000_000))
slow_function()
result = sum_million()
print(f"Sum result: {result:,}")
Output:
slow_function took 0.1002 seconds
sum_million took 0.0312 seconds
Sum result: 499,999,500,000
Retry Logic
A retry decorator automatically re-runs a function if it raises an exception — essential for network calls, database operations, and any code that can fail transiently:
# retry_decorator.py
import functools
import time
import random
def retry(max_attempts=3, delay=1.0, exceptions=(Exception,)):
"""Decorator factory: retries a function up to max_attempts times."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
last_error = e
print(f"Attempt {attempt}/{max_attempts} failed: {e}")
if attempt < max_attempts:
time.sleep(delay)
raise last_error
return wrapper
return decorator
# Simulated unreliable function (fails 70% of the time)
call_count = 0
@retry(max_attempts=5, delay=0.1, exceptions=(ValueError,))
def unreliable_api_call():
global call_count
call_count += 1
if random.random() < 0.7:
raise ValueError(f"API timeout on call #{call_count}")
return f"Success on call #{call_count}"
random.seed(42)
result = unreliable_api_call()
print(f"Final result: {result}")
Output:
Attempt 1/5 failed: API timeout on call #1
Attempt 2/5 failed: API timeout on call #2
Attempt 3/5 failed: API timeout on call #3
Final result: Success on call #4
Notice the decorator factory pattern: retry(max_attempts=5, delay=0.1) returns a decorator, which then returns a wrapper. This is a three-level nesting -- outer function configures, middle function receives the function to decorate, inner function is what actually runs. This is the standard pattern for parameterized decorators.
Parameterized Decorators
When your decorator needs configuration (like the number of retries in the example above), you add one more level of nesting -- a "decorator factory" that takes the parameters and returns the actual decorator:
# parameterized_decorator.py
import functools
def repeat(n):
"""Call the decorated function n times."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(n):
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
@repeat(3)
def say_hello(name):
return f"Hello, {name}!"
results = say_hello("Alice")
for r in results:
print(r)
Output:
Hello, Alice!
Hello, Alice!
Hello, Alice!
Stacking Multiple Decorators
You can apply multiple decorators to the same function by stacking them. They apply from bottom to top (closest to the function first):
# stacking_decorators.py
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
print(f" [timer] {func.__name__}: {time.perf_counter()-start:.4f}s")
return result
return wrapper
def log_result(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(f" [log] {func.__name__} returned: {result}")
return result
return wrapper
# Applied bottom-up: log_result wraps the original,
# then timer wraps log_result's wrapper
@timer
@log_result
def compute(x, y):
return x ** y
result = compute(2, 10)
print(f"Final result: {result}")
Output:
[log] compute returned: 1024
[timer] compute: 0.0001s
Final result: 1024
Real-Life Example: Access Control Decorators
Here's a practical access control system using decorators -- the same pattern used by web frameworks for route authentication:
# access_control.py
import functools
# Simulated current user session
current_user = {'name': 'alice', 'roles': ['user', 'editor'], 'logged_in': True}
def login_required(func):
"""Decorator that requires the user to be logged in."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
if not current_user.get('logged_in'):
print(f"Access denied: login required for {func.__name__}")
return None
return func(*args, **kwargs)
return wrapper
def require_role(role):
"""Decorator factory: requires the user to have a specific role."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if role not in current_user.get('roles', []):
print(f"Access denied: '{role}' role required for {func.__name__}")
return None
return func(*args, **kwargs)
return wrapper
return decorator
@login_required
def view_dashboard():
return f"Dashboard for {current_user['name']}"
@login_required
@require_role('admin')
def delete_user(user_id):
return f"Deleted user {user_id}"
@login_required
@require_role('editor')
def publish_post(post_id):
return f"Published post {post_id}"
# Alice is logged in and has 'editor' but not 'admin'
print(view_dashboard())
print(delete_user(42))
print(publish_post(101))
# Simulate a logged-out user
current_user['logged_in'] = False
print(view_dashboard())
Output:
Dashboard for alice
Access denied: 'admin' role required for delete_user
Published post 101
Access denied: login required for view_dashboard
This is the exact pattern used by Flask's @login_required and Django's @permission_required. The decorators are reusable across any number of functions -- add access control to a new function by adding one line above its definition. The stacked @login_required @require_role('admin') means the user must pass both checks: logged in AND has the required role.
Frequently Asked Questions
When should I use a decorator instead of a helper function?
Use a decorator when you want to add the same cross-cutting behavior (logging, timing, validation, caching) to multiple functions without repeating the logic. If you find yourself writing the same "before" and "after" code in many functions, that's a strong signal to extract it into a decorator. For one-off or highly specific behavior, a regular helper function is simpler.
Can I use a class as a decorator?
Yes -- any callable can be a decorator. A class with a __call__ method works as a decorator. Class-based decorators are useful when you need to maintain state between calls (like call counts or cached results). Define __init__(self, func) to receive the function and __call__(self, *args, **kwargs) to wrap it. The @functools.wraps(func) approach works on __call__ too.
Do decorators work on class methods?
Yes, but with one caveat: the first argument of instance methods is self. Since decorators use *args, **kwargs, this is handled automatically. However, @staticmethod and @classmethod are themselves decorators. When stacking with them, always place @staticmethod or @classmethod outermost (closest to the def).
What is @functools.lru_cache and when should I use it?
@functools.lru_cache(maxsize=128) memoizes a function's return values -- if the function is called again with the same arguments, it returns the cached result instead of recomputing. Use it for pure functions (no side effects) that are called repeatedly with the same inputs. It's especially powerful for recursive functions like Fibonacci where the same sub-problems repeat many times.
Why does my IDE show wrong type hints after applying a decorator?
Without @functools.wraps, the decorated function's signature shows as (*args, **kwargs) -- losing the original type hints. With @functools.wraps, the function identity is preserved, but the signature the type checker sees is still the wrapper's. For full type hint preservation in decorated functions, use typing.ParamSpec and typing.Concatenate (Python 3.10+) to annotate the wrapper correctly.
Conclusion
Decorators are one of Python's most powerful code-reuse mechanisms. In this tutorial, you learned how Python's first-class functions make decorators possible, why @functools.wraps(func) is essential in every decorator, how to write practical decorators for timing, retry logic, and logging, how to create parameterized decorators using a decorator factory pattern, how to stack multiple decorators on a single function, and how the access control pattern mirrors real framework implementations.
The access control project is a foundation you can extend: add role inheritance, time-based access restrictions, or rate limiting. Every web framework you'll encounter -- Flask, Django, FastAPI -- relies heavily on decorators for its most important features.
For deeper coverage, see the functools module documentation and PEP 318 which introduced decorator syntax to Python.
Related Articles
Further Reading: For more details, see the Python virtual environments documentation.
Frequently Asked Questions
Is Deta still free for hosting Python apps?
Deta Space offers a free tier for personal use. The original Deta.sh Micros service has evolved. For free Python hosting alternatives, consider Railway, Render, PythonAnywhere, or Google Cloud Run’s free tier.
What are the best free Python hosting alternatives?
PythonAnywhere offers a free tier for web apps. Render provides free static sites and web services. Railway has a free trial. Google Cloud Run and AWS Lambda have generous free tiers for serverless deployments.
How do I deploy a Python Flask app for free?
Use Render (connect GitHub repo), PythonAnywhere (upload directly), or Railway (deploy from GitHub). Each provides different advantages for hobby and small-scale projects.
What should I consider when choosing Python hosting?
Consider free tier limits, sleep/cold-start behavior, database availability, custom domain support, deployment method, Python version support, and scaling options.
Can I host a Python bot or script for free?
Yes. PythonAnywhere allows always-on tasks. Google Cloud Functions and AWS Lambda handle event-driven scripts. For Discord/Telegram bots, Railway and Render offer free tiers suitable for small bots.