Skip to content

Commit 7e22a95

Browse files
committed
generated movie on gallery
1 parent 568dd58 commit 7e22a95

File tree

7 files changed

+147
-61
lines changed

7 files changed

+147
-61
lines changed

src/azurerambi/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ def movie_generate():
198198
generated_movie['id'] = f"{genre_index}_{movie1_id}_{movie2_id}_{random.randint(10000, 99999)}"
199199

200200
logger.info("Generated movie: %s", json.dumps(generated_movie, indent=2))
201+
201202
except requests.RequestException as e:
202203
logger.error("Error in calling movie_generate service: %s", e)
203204
generated_movie = {

src/azurerambi/dump_logs.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
# This script dumps the logs from the movie_gallery_svc container app
3+
# Usage: ./dump_logs.sh
4+
# Ensure you have the Azure CLI installed and logged in
5+
set -ex
6+
CONTAINERAPPS_ENVIRONMENT="azure-rambi"
7+
RESOURCE_GROUP="rg-dev-rambi"
8+
LOG_ANALYTICS_WORKSPACE_CLIENT_ID=$(az containerapp env show --name $CONTAINERAPPS_ENVIRONMENT --resource-group $RESOURCE_GROUP --query properties.appLogsConfiguration.logAnalyticsConfiguration.customerId --out tsv)
9+
az monitor log-analytics query --workspace $LOG_ANALYTICS_WORKSPACE_CLIENT_ID \
10+
--analytics-query "ContainerAppConsoleLogs_CL | where ContainerAppName_s == 'gui-svc' | project ContainerAppName_s, Log_s, TimeGenerated | sort by TimeGenerated | take 30" \
11+
--out json > logs.json
12+
cat logs.json | jq

src/movie_gallery_svc/entities.py

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,66 @@
11
import json
2-
import uuid
3-
import logging
4-
from typing import List
2+
from typing import Optional
53
from pydantic import BaseModel
64

75
class MovieRequest(BaseModel):
86
"""Request model for adding a new movie."""
97
title: str
108
description: str
119

12-
class Movie(dict):
13-
"""Movie model for storing movie details."""
14-
def __init__(self, movie_id : str, title : str, description : str):
15-
dict.__init__(self, movie_id=movie_id, title=title, description=description)
16-
17-
def __repr__(self):
18-
return f"MovieGallery(movie_id={self['movie_id']}, title={self['title']}, description={self['description']})"
19-
20-
def getattr(self, key):
21-
"""Get attribute value by key."""
22-
if key not in self:
23-
raise KeyError(f"Key '{key}' not found in Movie.")
24-
return self[key]
25-
26-
def setattr(self, key, value):
27-
"""Set attribute value by key."""
28-
if key not in self:
29-
raise KeyError(f"Key '{key}' not found in Movie.")
30-
if not isinstance(value, str):
31-
raise TypeError(f"Value for '{key}' must be a string.")
32-
self[key] = value
33-
34-
def __str__(self):
35-
return f"Movie(movie_id={self['movie_id']}, title={self['title']}, description={self['description']})"
10+
class Movie(BaseModel):
11+
"""Enhanced Movie model with all required fields"""
12+
id: str
13+
title: str
14+
plot: str
15+
poster_url: Optional[str] = None
16+
poster_description: Optional[str] = None
17+
18+
def to_dict(self) -> dict:
19+
"""Convert Movie instance to dictionary"""
20+
return self.model_dump()
21+
22+
@classmethod
23+
def from_dict(cls, data: dict) -> 'Movie':
24+
"""Create Movie instance from dictionary"""
25+
return cls(**data)
26+
27+
def __str__(self) -> str:
28+
"""Return a string representation of the Movie instance"""
29+
return f"Movie(id={self.id}, title={self.title}, plot={self.plot}, poster_url={self.poster_url}, poster_description={self.poster_description})"
30+
31+
class MoviePayload(BaseModel):
32+
"""Data class for movie generation payload"""
33+
movie1: Movie
34+
movie2: Movie
35+
genre: str
36+
37+
def __repr__(self) -> str:
38+
"""Return a string representation of the MoviePayload instance"""
39+
return f"MoviePayload(movie1={self.movie1}, movie2={self.movie2}, genre={self.genre})"
40+
41+
42+
class GeneratedMovie(Movie):
43+
"""Model for generated movie data extending the base Movie class"""
44+
prompt: str
45+
payload: MoviePayload
46+
47+
class Config:
48+
"""Pydantic model configuration"""
49+
json_encoders = {
50+
# Add custom encoders if needed
51+
}
52+
53+
@classmethod
54+
def from_json(cls, json_str: str) -> 'GeneratedMovie':
55+
"""Create a GeneratedMovie instance from a JSON string"""
56+
data = json.loads(json_str)
57+
return cls(**data)
58+
59+
def to_json(self) -> str:
60+
"""Convert the GeneratedMovie instance to a JSON string"""
61+
return self.model_dump_json()
62+
63+
def __str__(self) -> str:
64+
"""Return a string representation of the GeneratedMovie instance"""
65+
return f"GeneratedMovie(id={self.id}, title={self.title}, plot={self.plot}, poster_url={self.poster_url}, poster_description={self.poster_description}, payload={self.payload})"
3666

37-
@staticmethod
38-
def from_bytes(json_bytes : bytes) -> 'Movie':
39-
"""Convert bytes to Movie object."""
40-
item = json.loads(json_bytes.decode('utf-8'))
41-
return Movie(
42-
item["movie_id"],
43-
item["title"],
44-
item["description"]
45-
)

src/movie_gallery_svc/main.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
import json
44
import logging
55
import uvicorn
6+
import traceback
67

78
from store import MovieStore
8-
from entities import MovieRequest, Movie
9+
from entities import GeneratedMovie, MovieRequest, Movie
910

1011
from fastapi import FastAPI, Response, status
1112
from fastapi.responses import HTMLResponse
1213
from dapr.clients import DaprClient
1314
from dapr.ext.fastapi import DaprApp
14-
import traceback
15+
1516

1617
logging.basicConfig(level=logging.INFO)
1718

@@ -38,33 +39,55 @@ def root():
3839
"""
3940

4041
@app.post("/movies", status_code = status.HTTP_201_CREATED)
41-
def add_movie(movie: MovieRequest):
42+
def add_movie(movie: GeneratedMovie) -> GeneratedMovie:
4243
"""Endpoint to add a new movie."""
4344
try:
44-
logging.info("Adding new movie")
45+
logging.info("Adding new movie %s", movie)
4546
store=MovieStore(DaprClient())
46-
movie=Movie(
47-
uuid.uuid4().hex,
48-
movie.title,
49-
movie.description)
50-
logging.info("Saving new movie %s", movie)
51-
new_request=store.upsert(movie)
52-
return Response(content = json.dumps(new_request), media_type = "application/json")
47+
inserted_generated_movie=store.upsert(movie)
48+
return inserted_generated_movie
5349
except Exception as e:
5450
logging.error('Add_Movie Error: %s', e)
5551
logging.error('Call stack: %s', traceback.format_exc())
5652
return Response(content = json.dumps({'method':'add_movie','error':e}), media_type = "application/json")
5753

58-
@app.get("/movies")
59-
def list_movies():
54+
@app.get("/movies/{movie_id}", response_model=GeneratedMovie)
55+
def get_movie(movie_id: str) -> GeneratedMovie:
56+
"""Endpoint to get a movie by ID."""
57+
logging.info("Getting movie with ID: %s", movie_id)
58+
try:
59+
store = MovieStore(DaprClient())
60+
movie = store.try_find_by_id(movie_id)
61+
if movie:
62+
return movie
63+
else:
64+
return Response(content=json.dumps({}), media_type="application/json", status_code=status.HTTP_404_NOT_FOUND)
65+
except Exception as e:
66+
logging.error('Get_Movie Error: %s', e)
67+
logging.error('Call stack: %s', traceback.format_exc())
68+
return Response(content=json.dumps({'method':'get_movie','error':e}), media_type="application/json")
69+
70+
71+
@app.get("/movies", response_model=list[GeneratedMovie])
72+
def list_movies() -> list[GeneratedMovie]:
6073
"""Endpoint to list all movies."""
6174
logging.info("Listing all movies")
6275
try:
6376
logging.info('initializing Dapr client')
6477
dapr_client = DaprClient()
6578
logging.info('initializing MovieStore')
6679
movies = MovieStore(dapr_client).find_all()
67-
return Response(content=json.dumps(movies), media_type="application/json")
80+
# remove prompt from the movie
81+
for movie in movies:
82+
if isinstance(movie, GeneratedMovie):
83+
movie.prompt = None
84+
#movie.payload = None
85+
# convert to JSON
86+
d_movies = [movie.to_json() for movie in movies]
87+
logging.info('Movies JSON: %s', d_movies)
88+
# return as JSON
89+
logging.info('Returning movies as JSON')
90+
return movies
6891
except Exception as e:
6992
logging.error('RuntimeError: %s', e)
7093
return Response(content=json.dumps([]), media_type="application/json", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)

src/movie_gallery_svc/sample.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"id": "629_346698_Animation_76281",
3+
"title": "The Usual Barbies",
4+
"plot": "In the enchanting realm of Barbie Land, a close-knit group of Barbie friends—each with their own unique talents and personalities—embarks on a charming adventure to solve the mystery of the missing glitter that keeps their world vibrant. When the colors of their beloved Dreamhouse start to fade, the usual suspects band together to uncover the source of the dimming magic. Along their journey, they encounter playful puzzles, make new friends, and discover that teamwork and creativity are the keys to restoring their colorful paradise. As they navigate through whimsical landscapes, the Barbies learn valuable lessons about trust, perseverance, and the beauty of embracing each other's differences, ultimately bringing back the sparkle that makes their world shine.",
5+
"poster_url": null,
6+
"poster_description": "The poster showcases a lively lineup of five animated characters standing side by side against a backdrop of a bright, colorful sky filled with whimsical clouds and shimmering stars. Each Barbie is dressed in distinct, vibrant outfits that highlight their unique personalities and styles. Behind them stretches a fantastical cityscape with sparkling buildings and lush gardens, hinting at the magical world they inhabit. The characters are smiling and exuding a sense of camaraderie and excitement, ready for their adventure. The overall design is playful and enchanting, with a harmonious blend of pinks, blues, and yellows that create an inviting and magical atmosphere, perfectly capturing the spirit of their quest and the joyful essence of Barbie Land.",
7+
"prompt": "### Movie 1\n\n* Title: The Usual Suspects\n* Plot: Held in an L.A. interrogation room, Verbal Kint attempts to convince the feds that a mythic crime lord, Keyser Soze, not only exists, but was also responsible for drawing him and his four partners into a multi-million dollar heist that ended with an explosion in San Pedro harbor – leaving few survivors. Verbal lures his interrogators with an incredible story of the crime lord's almost supernatural prowess.\n* Poster description: \"The poster for \\\"The Usual Suspects\\\" features five men standing in a lineup, against a background that resembles a police height chart. Each person is dressed in a different style, reflecting varied personalities. The height chart behind them lists measurements from 3'0\\\" to 6'6\\\". Beneath the lineup, in bold red lettering stamped across a black background, is the title \\\"THE USUAL SUSPECTS,\\\" designed to resemble a stamp, suggesting themes of crime and investigation. The overall design evokes a sense of mystery and intrigue, central to the film's narrative.\"\n\n### Movie 2\n\n* Title: Barbie\n* Plot: Barbie and Ken are having the time of their lives in the colorful and seemingly perfect world of Barbie Land. However, when they get a chance to go to the real world, they soon discover the joys and perils of living among humans.\n* Poster description: \"The 'Barbie' movie poster features vibrant colors and a playful design. It showcases a bright blue sky as the background, setting a cheerful and sunny mood. At the top of the poster, the word \\\"Barbie\\\" is prominently displayed in large, bold, white script against a vivid pink backdrop, which draws immediate attention and hints at the iconic brand. Below, a vintage-style pink convertible is shown, capturing the quintessentially glamorous and fun essence of Barbie. The vehicle is detailed with sleek white accents, complementing its classic and stylish appearance. Two figures are seated in the convertible, exuding a sense of excitement and adventure. The figure on the left is dressed in a pink checkered outfit and accessorizes with white sunglasses and a pearl necklace and bracelet, adding to the classic Barbie aesthetic. The figure on the right wears an open-collared, pink and green striped shirt, linked to the casual and lively spirit of summer fun. The overall composition of the poster emphasizes color and style, capturing the whimsical and fashionable world associated with Barbie.\"\n\n### Additional Information\n\n* Target Genre: Animation \n\n\n\n",
8+
"payload": {
9+
"movie1": {
10+
"id": "629",
11+
"title": "The Usual Suspects",
12+
"plot": "Held in an L.A. interrogation room, Verbal Kint attempts to convince the feds that a mythic crime lord, Keyser Soze, not only exists, but was also responsible for drawing him and his four partners into a multi-million dollar heist that ended with an explosion in San Pedro harbor – leaving few survivors. Verbal lures his interrogators with an incredible story of the crime lord's almost supernatural prowess.",
13+
"poster_url": "https://image.tmdb.org/t/p/original//rWbsxdwF9qQzpTPCLmDfVnVqTK1.jpg",
14+
"poster_description": "\"The poster for \\\"The Usual Suspects\\\" features five men standing in a lineup, against a background that resembles a police height chart. Each person is dressed in a different style, reflecting varied personalities. The height chart behind them lists measurements from 3'0\\\" to 6'6\\\". Beneath the lineup, in bold red lettering stamped across a black background, is the title \\\"THE USUAL SUSPECTS,\\\" designed to resemble a stamp, suggesting themes of crime and investigation. The overall design evokes a sense of mystery and intrigue, central to the film's narrative.\""
15+
},
16+
"movie2": {
17+
"id": "346698",
18+
"title": "Barbie",
19+
"plot": "Barbie and Ken are having the time of their lives in the colorful and seemingly perfect world of Barbie Land. However, when they get a chance to go to the real world, they soon discover the joys and perils of living among humans.",
20+
"poster_url": "https://image.tmdb.org/t/p/original//iuFNMS8U5cb6xfzi51Dbkovj7vM.jpg",
21+
"poster_description": "\"The 'Barbie' movie poster features vibrant colors and a playful design. It showcases a bright blue sky as the background, setting a cheerful and sunny mood. At the top of the poster, the word \\\"Barbie\\\" is prominently displayed in large, bold, white script against a vivid pink backdrop, which draws immediate attention and hints at the iconic brand. Below, a vintage-style pink convertible is shown, capturing the quintessentially glamorous and fun essence of Barbie. The vehicle is detailed with sleek white accents, complementing its classic and stylish appearance. Two figures are seated in the convertible, exuding a sense of excitement and adventure. The figure on the left is dressed in a pink checkered outfit and accessorizes with white sunglasses and a pearl necklace and bracelet, adding to the classic Barbie aesthetic. The figure on the right wears an open-collared, pink and green striped shirt, linked to the casual and lively spirit of summer fun. The overall composition of the poster emphasizes color and style, capturing the whimsical and fashionable world associated with Barbie.\""
22+
},
23+
"genre": "Animation"
24+
}
25+
}

src/movie_gallery_svc/store.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
import json
44
import traceback
5-
from entities import Movie
5+
from entities import GeneratedMovie, Movie
66
from dapr.clients import DaprClient
77

88
logging.basicConfig(level=logging.INFO)
@@ -14,18 +14,19 @@ def __init__(self, dapr_client : DaprClient):
1414
self.dapr_client = dapr_client
1515
self.state_store_name = 'movie-gallery-svc-statetore'
1616

17-
def upsert(self, movie: Movie) -> Movie:
17+
def upsert(self, movie: GeneratedMovie) -> GeneratedMovie:
1818
"""Add a movie to the store."""
1919
logging.info("Adding movie: %s", movie)
20-
logging.info("JSON %s", json.dumps(movie))
21-
logging.info("saving movie to store %s using this key %s", self.state_store_name, movie['movie_id'])
20+
logging.info("JSON %s", movie.to_json())
21+
movie_id = movie.id
22+
logging.info("Saving movie to store %s using this key %s", self.state_store_name, movie_id)
2223
self.dapr_client.save_state(
2324
store_name=self.state_store_name,
24-
key=movie['movie_id'],
25-
value=json.dumps(movie)
25+
key=movie_id,
26+
value=movie.to_json()
2627
)
27-
logging.info("Movie %s added to store", movie['movie_id'])
28-
return self.try_find_by_id(movie['movie_id'])
28+
logging.info("Movie %s added to store", movie_id)
29+
return self.try_find_by_id(movie_id)
2930

3031
def try_find_by_id(self, movie_id : str) -> Movie:
3132
"""Find a movie by its ID."""
@@ -36,7 +37,8 @@ def try_find_by_id(self, movie_id : str) -> Movie:
3637
key=movie_id
3738
)
3839
if response.data:
39-
movie = Movie.from_bytes(response.data)
40+
logging.info("Movie found in store data: %s", response.data)
41+
movie = GeneratedMovie.from_json(response.data)
4042
logging.info("Movie found: %s", movie)
4143
return movie
4244
else:
@@ -69,8 +71,8 @@ def find_all(self) -> list[Movie]:
6971
query=query,
7072
states_metadata=states_metadata
7173
)
72-
movies = [Movie.from_bytes(item.value) for item in response.results]
73-
logging.info("Movies found: %s", movies)
74+
movies = [GeneratedMovie.from_json(item.value) for item in response.results]
75+
logging.info("GeneratedMovies found: %d", len(movies))
7476
return movies
7577
except Exception as e:
7678
logging.error("Error finding all movies: %s", e)

src/movie_generator_svc/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class GenAIMovie(Movie):
8888
prompt: str = None
8989
payload: MoviePayload = None
9090

91+
9192
class GenAiMovieService:
9293
""" Class to manage the access to OpenAI API to generate a new movie """
9394

@@ -204,6 +205,7 @@ def generate_movie(self, movie1: Movie, movie2: Movie, genre: str) -> GenAIMovie
204205
generated_movie.poster_url = None
205206
generated_movie.payload = MoviePayload(movie1=movie1, movie2=movie2, genre=genre)
206207
generated_movie.id = f"{movie1.id}_{movie2.id}_{genre}_{random.randint(10000, 99999)}"
208+
207209
logger.info("Generated movie: %s", generated_movie)
208210
return generated_movie
209211

0 commit comments

Comments
 (0)