0

How can more than one client share the same generator? For the sake of simplicity I have just created a function that yields every lower letter. In my project I have a class that is being called that yields something else.

Folder structure:

app
---views
--------views.py
__init__.py

Suppose I have following __init__.py in app/.

from flask import Flask

def create_app():
    
    app = Flask(__name__)

    from .views.views import public as public_bp
    app.register_blueprint(public_bp)

    return app

And the simple view i app/views/views.py

import time, string

from flask import Blueprint, render_template, Response


public = Blueprint('views', __name__)


def generate():
    letters = list(string.ascii_lowercase)
    for l in letters:
        yield f"var: {l} \r\n"
        time.sleep(1)

@public.route('/')
def feed():
    return Response(
        generate(),
        content_type='text/event-stream',
        mimetype='multipart/x-mixed-replace'
    )

Fire up the dev server and visit localhost:5000 and you will see the lower letter printed out. How can another client enter where another client already is and not start the generate() from the beginning?

rzlvmp
  • 7,512
  • 5
  • 16
  • 45
destinychoice
  • 45
  • 3
  • 15
  • What is "client"? – buran May 16 '23 at 09:12
  • @buran client 1 enters website, fires up the generator, client 2 enters website, generator is already running and that client picks up where the generator is at. – destinychoice May 16 '23 at 09:21
  • Hello @destinychoice becouse that's how generator works it *"`yield` maintains the state between function calls, and resumes from where it left off when we call the `next()` method again. So if `yield` is called in the **generator**, then the next time the same generator is called we'll pick right back up after the last `yield` statement."*. You can read more from [here](https://stackabuse.com/python-generators/) – Ankit Tiwari May 16 '23 at 09:25
  • Every time you access `feed()` you return (as part from the `Response`) a new generator. If I get what you want, you need single generator object and return one/next value from it in the `Response`. Now, you have to tell what you will do when generator is exhausted, i.e. there are 26 letters. Also why the `sleep()`? – buran May 16 '23 at 10:17

1 Answers1

0

Short answer:

from flask import Flask

app = Flask(__name__)

def gen(lst: list):
  for el in lst:
    yield el

GEN = gen(["1", "2", "3", "4", "5"])

@app.route('/')
def hello():
    return f'next number is {next(GEN)}'

will do what you want.

Detailed answer:

If you plan to run your app with production-ready application server like uvicorn or uwsgi your application will be multiprocessing application. Let's say that you have 2 processes. Every process will create own GEN object so here will be 2 separated generators, and that is not correct. In other words, you have to create single global variable accessible from multiple processes. However, your generator should be created only once when first process is started, and other processes should use already created GEN object. So, you have to implement concurrency checking logic. And here are many other pitfalls hidden at first sight.

So, my recommendation is: just store last generated value somewhere in DB or in-memory storage (like redis) and implement simple logic how to respond value next to value saved in DB. That will be simple and common way.

If your application is extremely small and you are using single server you may store last value inside text file that may be read by multiple processes:

from flask import Flask
import os

app = Flask(__name__)

LIST = ["1", "2", "3", "4", "5"]

@app.route('/')
def hello():
    next_idx = 0
    if os.path.isfile('temp_file.txt'):
        with open('temp_file.txt', 'r') as f:
            current_val = f.read()
        for idx, el in enumerate(LIST):
            if el == current_val:
                next_idx = idx + 1
                break
    with open('temp_file.txt', 'w') as f:
        f.write(LIST[next_idx])
    return f'next number is {LIST[next_idx]}'

This code allows simply share same data between processes and even applications, but you still have to handle risk of concurrent file access.

rzlvmp
  • 7,512
  • 5
  • 16
  • 45