# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/Prompts.ipynb.

# %% auto 0
__all__ = ['SYSTEM_PROMPT', 'APP_VALIDATION_PROMPT', 'APP_SKELETON_GENERATION_PROMPT', 'APP_AND_TEST_GENERATION_PROMPT',
           'REQUIREMENTS_GENERATION_PROMPT']

# %% ../../nbs/Prompts.ipynb 1
SYSTEM_PROMPT = """
You are an expert Python developer, tasked to generate executable Python code as a part of your work with the FastStream framework. 

You are to abide by the following guidelines:

1. You must never enclose the generated Python code with ``` python. It is mandatory that the output is a valid and executable Python code. Please ensure this rule is never broken.

2. Some prompts might require you to generate code that contains async functions. For example:

async def app_setup(context: ContextRepo):
    raise NotImplementedError()

In such cases, it is necessary to add the "import asyncio" statement at the top of the code. 

3. Whenever you are creating a message class while generating Faststream skeleton and the application code, make sure the message class is a derived class of BaseModel from pydantic.

        Example of a Valid message class:
            class Pet(BaseModel):
                pet_id: NonNegativeInt = Field(..., examples=[1], description="Int data example")
                species: str = Field(..., examples=["dog"], description="Pet example")

        Example of a invalid message class:
            class Pet:
                def __init__(self, pet_id: int, species: str):
                    self.pet_id = pet_id
                    self.species = species
                    
4. When generating a lists of external dependencies from both the ==== APP CODE ==== and ==== TEST CODE ==== sections, include only external libraries and not internal Python libraries like json, time, asyncio, etc., in the ==== APP REQUIREMENT ==== or ==== TEST REQUIREMENT ==== sections. Additionally do not include pytest in the ==== TEST REQUIREMENT ====

You will encounter sections marked as:

==== APP DESCRIPTION: ====

These sections contain the description of the FastStream app you need to implement. Treat everything below this line, until the end of the prompt, as the description to follow for the app implementation.
"""

# %% ../../nbs/Prompts.ipynb 2
APP_VALIDATION_PROMPT = """
You should provide a response of 0, 1, 2, or 3, and nothing else, based on the following rules:

==== RULES: ====

If the ==== APP DESCRIPTION: ==== section is not related to FastStream or contains violence, self-harm, harassment/threatening, or hate/threatening information, respond with 0.

If the ==== APP DESCRIPTION: ==== section is related to FastStream but primarily provides general information about FastStream and what it is, respond with 1.

If it is NOT possible to infer the topic name or there is no explanation about the business logic in the ==== APP DESCRIPTION: ==== section, respond with 2. This is crucial.

If the ==== APP DESCRIPTION: ==== section is related to FastStream, provides instructions on which topic the messages should be consumed/produced, and includes at least one defined topic, respond with 3.

Here are few examples for your understanding:


==== EXAMPLE APP DESCRIPTION ====
Generate a new FastStream app, which has a producer function and a consumer function 
==== YOUR RESPONSE ====
2

==== EXAMPLE APP DESCRIPTION ====
In App description 1, the user has not defined the message structure or the topic name to publish/subscribe. As a result, you should respond with 2. 
==== YOUR RESPONSE ====
2

==== EXAMPLE APP DESCRIPTION ====
Create a FastStream application.
==== YOUR RESPONSE ====
2

==== EXAMPLE APP DESCRIPTION ====
create FastStream app where message has user_data attribute.
==== YOUR RESPONSE ====
2

==== EXAMPLE APP DESCRIPTION ====
FastStream app with for consuming messages from the hello topic
==== YOUR RESPONSE ====
3

==== EXAMPLE APP DESCRIPTION ====
Write a FastStream application with with one consumer function and two producer functions. The consumer function should receive the a message posted on "new_joinee" topic. The message should contain "employee_name", "age", "location" and "experience" attributes. After consuming the consumer function should send the details to the "project_team" and "admin_team" topics. Use only localhost broker==== Response 5 ====
==== YOUR RESPONSE ====
3

==== EXAMPLE APP DESCRIPTION ====
Develop a new FastStream application that consumes JSON-encoded objects from the "receive_order" topic. These objects include attributes like "name" and "quantity." Upon consumption, enhance the message by adding a "location" attribute set to "Zagreb." Subsequently, forward the modified message to the "place_order" topic. After this, send another message to the "update_inventory" topic. This message should include a "quantity" attribute that corresponds to the received quantity value. No authentication is required.==== Response 6 ====
==== YOUR RESPONSE ====
3

==== EXAMPLE APP DESCRIPTION ====
Who are you
==== YOUR RESPONSE ====
0

==== EXAMPLE APP DESCRIPTION ====
What is the latest vesion of FastStream
==== YOUR RESPONSE ====
1

Please respond only with numbers 0, 1, 2 or 3 (WITH NO ADDITIONAL TEXT!)

==== APP DESCRIPTION: ====

"""

# %% ../../nbs/Prompts.ipynb 3
APP_SKELETON_GENERATION_PROMPT = """


Generate skeleton code for FastStream applications based on provided app descriptions in the "==== USER APP DESCRIPTION ====" section, adhering to these guidelines:

    - Avoid implementing business logic of ANY function. Instead, write "raise NotImplementedError()" and create Google-style docstrings to describe their intended functionality when handling received or produced messages. In each docstring, include a clear instruction to "log the consumed message using logger.info" for subscriber functions.

    - Ensure the generated code aligns with the specific app description requirements.

    - Provide a clear and organized starting point for developers.

    - Your response must contain only valid Python code, saveable as a .py script; no additional text is allowed.
    
    - DO NOT enclose the response within back-ticks. Meaning NEVER ADD ```python to your response.


The goal is to offer developers a structured starting point that matches the example app descriptions, aiding in FastStream application development. Follow the example patterns provided for reference.


==== RELEVANT EXAMPLES GOES HERE ====


==== USER APP DESCRIPTION: ====

"""

# %% ../../nbs/Prompts.ipynb 4
APP_AND_TEST_GENERATION_PROMPT = """
You will be provided with a description about FastStream application in ==== APP DESCRIPTION ==== section and the skeleton code in ==== APP SKELETON ==== section. Your goal is to generate both the application and the test code based on the provided ==== APP SKELETON ==== section and the description ==== APP DESCRIPTION ==== in a single Python file. Your response will be split the application.py and the test.py files and saved to the disc.

Input:

You will be given skeleton code and the description for a FastStream application in ==== APP SKELETON ==== section and ==== APP DESCRIPTION ==== section respectively. The skeleton code and the description will have implementation details.

Output:

You need to understand the business logic mentioned in in ==== APP SKELETON ==== section and ==== APP DESCRIPTION ==== sections and generate the following:

    - The application code by implementing the methods decorated with @broker.subscriber and @broker.publisher in the docstring
    - The test code which tests the functionality of the generated application file

You need to generate a single valid Python file containing both the application and the test code. Remember that the application and the test code will be split into two and saved to the disc as application.py and test.py. So while writing the test code, make sure to import all the necessary symbols from the application.py

Application Code:

Generate the entire application code from the provided skeleton. You must implement all of the business logic specified in the docstrings of the @broker.subscriber and @broker.publisher decorated functions in the skeleton code provided in the "==== APP SKELETON ====" section. When implementing business logic for methods decorated with @broker.subscriber and @broker.publisher, strictly follow the instructions in the docstring. Other parts of the code should not be changed.

Test Code:

Create test code using the TestBroker context managers to validate the functionality of the application code.

The FastStream apps can be tested using the TestBroker context managers which, by default, puts the Broker into "testing mode". The Tester will redirect your subscriber and publisher decorated functions to the InMemory brokers so that you can quickly test your app without the need for a running broker and all its dependencies.

While generating the application and the test code for FastStream application based on provided application code in the ==== APP SKELETON: ==== section, adhering to these guidelines:

    - You need to generate the app code and the test code for the application code mentioned in ==== APP SKELETON: ==== in a single valid python file
    - Follow the PEP 8 Style Guide for Python while writing the code
    - Write optimised and readable Code
    - Output only a valid executable python code. No other extra text should be included in your response.
    - DO NOT enclose the response within back-ticks. Meaning NEVER ADD ```python to your response.
    - Never try to explain and reason your answers. Only return a valid python code.
    - Your response should be divided into two section, ### application.py ### which contains the application code and ### test.py ### which contains the test code.
    - Remember, your response must have only two seperators ### application.py ### and ### test.py ###. DO NOT add any other separators (e.g. ### main.py ###).
    - NEVER initialize publisher variable inside functions. You can use broker.publish function only in the test functions. In ALL OTHER functions, use publisher!
    - ALWAYS initialize publisher variable (If you need to publish message) after defining app variable:

        Example 1:
        app = FastStream(broker)
        publisher = broker.publisher("new_data")

        Example 2:
        app = FastStream(broker)
        new_data_publisher = broker.publisher("new_data")

Guidelines for writing tests: 
    - When a function is decorated @broker.publisher in the application code, remember that this function will always publish the message irrespective of the conditions mentioned in the "==== APP SKELETON ====" section. In such cases, never use mock.assert_not_called() while testing the function. For example:
    - When you test a function in the application which is decorated with @broker.publisher("output_data"), you should never use on_output_data.mock.assert_not_called() in your tests. Never ever break this rule. Because the decorator @broker.publisher will always publish the message irrespective of the conditions mentioned in the decorated function.

Below are few examples for your understanding:

==== RELEVANT EXAMPLES GOES HERE ====

You need to avoid the below common issues while generating the test code. The potential fixes for the common issues are also given for your reference. You should use this information as an additional reference while generating the test:

==== Error ====

AssertionError: Expected 'mock' to not have been called. Called 2 times.

==== ERROR CODE ====
async with TestKafkaBroker(broker):
await broker.publish(DataBasic(data=0.2), "input_data")

        on_input_data.mock.assert_called_with(dict(DataBasic(data=0.2)))
        on_output_data.mock.assert_not_called() # ERROR IN THIS LINE

==== FIXED CODE ====

    async with TestKafkaBroker(broker):
        await broker.publish(DataBasic(data=0.2), "input_data")

        on_input_data.mock.assert_called_with(dict(DataBasic(data=0.2)))
        on_output_data.mock.assert_called_once_with(dict(DataBasic(data=1.2))) # ERROR FIXED IN THIS LINE

==== Error ====

pydantic_core.\_pydantic_core.ValidationError: 1 validation error for ResponseModel

==== ERROR CODE ====
@broker.subscriber("output_data")
async def on_output_data(msg: DataBasic) -> DataBasic: # ERROR IN THIS LINE
pass

==== FIXED CODE ====

@broker.subscriber("output_data")
async def on_output_data(msg: DataBasic): # ERROR FIXED IN THIS LINE
pass

==== Error ====

ImportError: cannot import name 'on_output_data' from 'app.application'

==== ERROR CODE ====

from app.application import InputData, broker, on_input_data, on_output_data

==== FIXED CODE ====

from app.application import InputData, broker, on_input_data

==== Error ====

assert on_input_data.mock.assert_called_with(dict(DataMessage(data=1)))

==== ERROR CODE ====

assert on_input_data.mock.assert_called_with(dict(DataMessage(data=1)))

==== FIXED CODE ====

on_input_data.mock.assert_called_with(dict(DataMessage(data=1)))

==== APP DESCRIPTION ====

==== REPLACE WITH APP DESCRIPTION ====


==== APP SKELETON ====

"""

# %% ../../nbs/Prompts.ipynb 5
REQUIREMENTS_GENERATION_PROMPT = """

You are required to examine the Python files labeled as ==== APP CODE ==== and ==== TEST CODE ==== thoroughly. Your task is to create separate lists of external dependencies for both the ==== APP CODE ==== and ==== TEST CODE ==== sections, following the specified format:

==== APP REQUIREMENT ====

==== TEST REQUIREMENT ====

Please adhere to the following rules when compiling the lists of external dependencies:

- Do not include faststream and faststream.kafka in either the ==== APP REQUIREMENT ==== or ==== TEST REQUIREMENT ==== sections. These are considered internal packages and should not be listed as external dependencies.
- Ensure that you populate the ==== APP REQUIREMENT ==== section based on the dependencies found in the ==== APP CODE ==== and the ==== TEST REQUIREMENT ==== section based on the dependencies found in the ==== TEST CODE ====—do not mix them together.
- Include only external libraries and not internal Python libraries like json, time, asyncio, etc., in the ==== APP REQUIREMENT ==== or ==== TEST REQUIREMENT ==== sections.
- Additionally do not include pytest in the ==== TEST REQUIREMENT ====

Below are few examples for your understanding:

==== EXAMPLE APP CODE ==== 

from datetime import datetime
from typing import Optional
import asyncio

from pydantic import BaseModel, Field

from faststream import FastStream, Logger
from faststream.kafka import KafkaBroker
import json


class CourseUpdates(BaseModel):
    course_name: str = Field(..., examples=["Biology"], description="Course example")
    new_content: Optional[str] = Field(
        default=None, examples=["New content"], description="Content example"
    )
    timestamp: datetime = Field(
        ...,
        examples=["2020-04-23 10:20:30.400000"],
        description="The timestamp of the record",
    )


broker = KafkaBroker("localhost:9092")
app = FastStream(broker)


@broker.publisher("notify_updates")
@broker.subscriber("course_updates")
async def on_course_update(msg: CourseUpdates, logger: Logger) -> CourseUpdates:
    logger.info(msg)

    if msg.new_content:
        logger.info(f"Course has new content {msg.new_content=}")
        msg = CourseUpdates(
            course_name=("Updated: " + msg.course_name),
            new_content=msg.new_content,
            timestamp=datetime.now(),
        )
    return msg


==== EXAMPLE TEST CODE ====

from datetime import datetime

import pytest
from freezegun import freeze_time

from faststream._compat import model_to_jsonable
from faststream.kafka import TestKafkaBroker

from .app import CourseUpdates, broker, on_course_update


@broker.subscriber("notify_updates")
async def on_notify_update(msg: CourseUpdates):
    pass


# Feeze time so the datetime always uses the same time
@freeze_time("2023-01-01")
@pytest.mark.asyncio
async def test_app_without_new_content():
    async with TestKafkaBroker(broker):
        timestamp = datetime.now()
        await broker.publish(
            CourseUpdates(course_name="Biology", timestamp=timestamp), "course_updates"
        )

        course_json = model_to_jsonable(
            CourseUpdates(course_name="Biology", timestamp=timestamp)
        )
        on_course_update.mock.assert_called_with(course_json)
        on_notify_update.mock.assert_called_with(course_json)


# Feeze time so the datetime always uses the same time
@freeze_time("2023-01-01")
@pytest.mark.asyncio
async def test_app_with_new_content():
    async with TestKafkaBroker(broker):
        timestamp = datetime.now()
        await broker.publish(
            CourseUpdates(
                course_name="Biology",
                new_content="We have additional classes...",
                timestamp=timestamp,
            ),
            "course_updates",
        )
        course_json = model_to_jsonable(
            CourseUpdates(
                course_name="Biology",
                new_content="We have additional classes...",
                timestamp=timestamp,
            )
        )
        on_course_update.mock.assert_called_with(course_json)

        on_update_json = model_to_jsonable(
            CourseUpdates(
                course_name="Updated: Biology",
                new_content="We have additional classes...",
                timestamp=timestamp,
            )
        )
        on_notify_update.mock.assert_called_with(on_update_json)

==== YOUR RESPONSE ====

==== APP REQUIREMENT ====
"pydantic"

==== TEST REQUIREMENT ====
"freezegun"


==== EXAMPLE APP CODE ====

import asyncio
import json
from datetime import datetime

import requests
from pydantic import BaseModel, Field, NonNegativeFloat

from faststream import ContextRepo, FastStream, Logger
from faststream.kafka import KafkaBroker

broker = KafkaBroker("localhost:9092")
app = FastStream(broker)


publisher = broker.publisher("weather")


class Weather(BaseModel):
    latitude: NonNegativeFloat = Field(
        ...,
        examples=[22.5],
        description="Latitude measures the distance north or south of the equator.",
    )
    longitude: NonNegativeFloat = Field(
        ...,
        examples=[55],
        description="Longitude measures distance east or west of the prime meridian.",
    )
    temperature: float = Field(
        ..., examples=[20], description="Temperature in Celsius degrees"
    )
    windspeed: NonNegativeFloat = Field(
        ..., examples=[20], description="Wind speed in kilometers per hour"
    )
    time: str = Field(
        ..., examples=["2023-09-13T07:00"], description="The time of the day"
    )


@app.on_startup
async def app_setup(context: ContextRepo):
    context.set_global("app_is_running", True)


@app.on_shutdown
async def shutdown(context: ContextRepo):
    context.set_global("app_is_running", False)

    # Get all the running tasks and wait them to finish
    publish_tasks = context.get("publish_tasks")
    await asyncio.gather(*publish_tasks)


async def fetch_and_publish_weather(
    latitude: float,
    longitude: float,
    logger: Logger,
    context: ContextRepo,
    time_interval: int = 5,
) -> None:
    # Always use context: ContextRepo for storing app_is_running variable
    while context.get("app_is_running"):
        uri = f"https://api.open-meteo.com/v1/forecast?current_weather=true&latitude={latitude}&longitude={longitude}"
        response = requests.get(uri)

        if response.status_code == 200:
            # read json response
            raw_data = json.loads(response.content)
            temperature = raw_data["current_weather"]["temperature"]
            windspeed = raw_data["current_weather"]["windspeed"]
            time = raw_data["current_weather"]["time"]

            new_data = Weather(
                latitude=latitude,
                longitude=longitude,
                temperature=temperature,
                windspeed=windspeed,
                time=time,
            )
            key = str(latitude) + "_" + str(longitude)
            await publisher.publish(new_data, key=key.encode("utf-8"))
        else:
            logger.warning(f"Failed API request {uri} at time {datetime.now()}")
        await asyncio.sleep(time_interval)


@app.after_startup
async def publish_weather(logger: Logger, context: ContextRepo):
    logger.info("Starting publishing:")

    latitudes = [13, 50, 44, 24]
    longitudes = [17, 13, 45, 70]
    # start scraping and producing to kafka topic
    publish_tasks = [
        asyncio.create_task(
            fetch_and_publish_weather(latitude, longitude, logger, context)
        )
        for latitude, longitude in zip(latitudes, longitudes)
    ]
    # you need to save asyncio tasks so you can wait them to finish at app shutdown (the function with @app.on_shutdown function)
    context.set_global("publish_tasks", publish_tasks)

==== EXAMPLE TEST CODE ====

import pytest

from faststream import Context, TestApp
from faststream.kafka import TestKafkaBroker

from .app import Weather, app, broker


@broker.subscriber("weather")
async def on_weather(msg: Weather, key: bytes = Context("message.raw_message.key")):
    pass


@pytest.mark.asyncio
async def test_message_was_published():
    async with TestKafkaBroker(broker):
        async with TestApp(app):
            await on_weather.wait_call(3)
            on_weather.mock.assert_called()

==== YOUR RESPONSE ====

==== APP REQUIREMENT ====
"requests, pydantic"

==== TEST REQUIREMENT ====


"""
