Skip to content

Commit

Permalink
Merge pull request #1 from ilkersigirci/feature/more-tmdb-utils
Browse files Browse the repository at this point in the history
Feature/more tmdb utils
  • Loading branch information
ilkersigirci authored Jan 18, 2025
1 parent 3a7608a commit de8e7f6
Show file tree
Hide file tree
Showing 14 changed files with 463 additions and 220 deletions.
18 changes: 9 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ repos:
- id: check-added-large-files
args: ["--maxkb=1000"]

# - repo: https://github.com/astral-sh/uv-pre-commit
# # uv version.
# rev: 0.4.20
# hooks:
# # Update the uv lockfile
# - id: uv-lock
# # Run the pip compile
# - id: pip-compile
# args: [requirements.in, -o, requirements.txt]
- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
rev: 0.5.10
hooks:
# Update the uv lockfile
- id: uv-lock
# Export to requirements.txt
- id: uv-export
args: [--quiet, --frozen, --no-hashes, --no-group, dev, --no-group, doc, --no-group, test, --format, requirements-txt, --output-file, requirements.txt]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
Expand Down
43 changes: 43 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Simple App",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": [
"api.gui.simple_app:app",
"--host",
"0.0.0.0",
"--port",
"5001",
// "--reload"
],
"justMyCode": true,
// "jinja": true,
"env": {
"PYTHONPATH": "${workspaceFolder}"
}
},
{
"name": "Debug Game App",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": [
"api.gui.game_app:app",
"--host",
"0.0.0.0",
"--port",
"5001",
// "--reload"
],
"justMyCode": true,
// "jinja": true,
"env": {
"PYTHONPATH": "${workspaceFolder}"
}
}
]
}
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,9 @@ profile-builtin: ## Profile the file with cProfile and shows the report in the t
docker-build: ## Build docker image
docker build --tag ${DOCKER_IMAGE} --file docker/Dockerfile --target ${DOCKER_TARGET} .

export-requirements: ## Export requirements to requirements.txt
@uv export --frozen --no-hashes --no-group dev --no-group doc --no-group test --format requirements-txt --output-file requirements.txt

run-fasthtml: ## Run fasthtml app
uv run uvicorn api.gui.game_app:app --host 0.0.0.0 --port 5001
# uv run --module api/gui/game_app
uv run uvicorn api.gui.game_app:app --host 0.0.0.0 --port 5002

run-fasthtml-simple: ## Run simple fasthtml app
uv run uvicorn api.gui.simple_app:app --host 0.0.0.0 --port 5002
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ make run-fasthtml

3. Open your browser and navigate to:
```
http://localhost:5001
http://localhost:5002
```

## Game Rules
Expand Down
2 changes: 1 addition & 1 deletion api/gui/fasthtml_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def post(query: str = ""):
if not query:
return Div("Start typing to search movies...", id="search-results")

results = fuzzy_search_movies(query)
results = fuzzy_search_movies(query=query, include_backdrops=True)

if not results:
return Div("No movies found", id="search-results")
Expand Down
144 changes: 109 additions & 35 deletions api/gui/game_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,55 @@
Form,
Img,
Input,
Option,
P,
Select,
Style,
Titled,
fast_app,
)

from api.utils.movie import fuzzy_search_movies, get_random_movie_with_details

app, rt = fast_app()

# Store current game state
current_game = {}
app, rt = fast_app(secret_key="your-secret-key-here") # Add secret key for session


@rt("/")
def get():
# Add top navigation with new game button
def get(session):
# Get current category from session, default to 'popular'
current_category = session.get("game", {}).get("category", "popular")

# Add top navigation with category selector and new game button
top_nav = Div(
Form(
Select(
Option(
"Popular Movies",
value="popular",
selected=current_category == "popular",
),
Option(
"Top Rated Movies",
value="top_rated",
selected=current_category == "top_rated",
),
Option(
"Now Playing",
value="now_playing",
selected=current_category == "now_playing",
),
Option(
"Upcoming Movies",
value="upcoming",
selected=current_category == "upcoming",
),
name="category",
hx_post="/new-game",
hx_target="body",
hx_trigger="change",
),
style="display: inline-block; margin-right: 1rem;",
),
Button("New Game", hx_post="/new-game", hx_target="body"),
style="text-align: right; margin-bottom: 1rem;",
)
Expand All @@ -42,6 +73,12 @@ def get():
object-fit: cover;
border-radius: 8px;
}
/* Add title styling */
h1 {
text-align: center;
margin: 2rem 0;
}
/* Rest of existing styles */
.search-results {
margin-top: 1rem;
}
Expand Down Expand Up @@ -77,39 +114,52 @@ def get():
.guess-used {
background-color: #666;
}
.search-form {
display: flex;
gap: 1rem;
align-items: center;
}
.search-form input[type="search"] {
flex: 1;
margin: 0;
}
.search-form button {
margin: 0;
}
"""),
Div(
Input(
type="search",
name="query",
placeholder="Search for a movie...",
placeholder="Guess the movie...",
hx_post="/search",
hx_trigger="keyup changed delay:500ms",
hx_trigger="input changed delay:200ms",
hx_target="#search-results",
autocomplete="off", # To prevent browser autocomplete from interfering
),
Button(
"Submit Guess",
hx_post="/guess",
hx_include="#search-form",
hx_target="#search-results",
),
cls="search-form",
),
id="search-form",
)

# Initialize new game if not exists
if not current_game:
# Initialize new game if not exists in session
if "game" not in session:
movie = get_random_movie_with_details()
# Start with just the first backdrop
current_backdrop = movie["backdrops"][0] if movie["backdrops"] else None
current_game.update(
{
"movie": movie,
"current_backdrop_index": 0,
"shown_backdrops": [current_backdrop] if current_backdrop else [],
"guesses_remaining": 5, # Add guess counter
}
)
session["game"] = {
"movie": movie,
"current_backdrop_index": 0,
"shown_backdrops": [current_backdrop] if current_backdrop else [],
"guesses_remaining": 5,
}

current_game = session["game"] # Get game state from session

backdrop = None
if current_game["shown_backdrops"]:
Expand Down Expand Up @@ -143,7 +193,8 @@ def get():

results_div = Div(id="search-results")

# Add top_nav to the Container
# Add top_nav to the Container with reordered elements:
# Move search_box and results_div together, before the backdrop
return Titled(
"Movie Guess Game",
Container(top_nav, backdrop, guess_indicators, search_box, results_div),
Expand All @@ -152,45 +203,48 @@ def get():

@rt("/search")
def post(query: str = ""):
if not query:
return Div("Start typing to search for movies...", id="search-results")
MIN_CHARS = 2

results = fuzzy_search_movies(query)
if not query or len(query) < MIN_CHARS:
return Div("Start typing to search for movies...", id="search-results")

results = fuzzy_search_movies(query=query, limit=3, include_backdrops=False)
if not results:
return Div("No movies found", id="search-results")

# Create clickable list items that populate the search input
# Create clickable list items that populate the search input and automatically submit
movie_items = [
Div(
f"{movie['title']} ({movie['release_date'][:4]})",
# Fixed string escaping by using format() instead of f-string
onclick='document.querySelector(\'[name="query"]\').value = "{}";'.format(
movie["title"].replace('"', '\\"')
),
onclick="""
document.querySelector('[name="query"]').value = "{}";
document.querySelector('#search-form button').click();
""".format(movie["title"].replace('"', '\\"')),
cls="search-item",
)
for movie in results[:5] # Limit to 5 results
for movie in results[:3]
]

return Div(*movie_items, id="search-results", cls="search-results")


@rt("/guess")
def post(query: str = ""):
def post(query: str = "", session=None): # Add session parameter
if not query:
return Div("Please select a movie to guess", id="search-results")

results = fuzzy_search_movies(query)
results = fuzzy_search_movies(query=query, limit=3, include_backdrops=False)
if not results:
return Div("No movies found", id="search-results")

current_game = session["game"] # Get game state from session
current_movie = current_game["movie"]
movie = results[0] # Use the best match
is_correct = movie["id"] == current_movie["id"]

# Decrease remaining guesses
current_game["guesses_remaining"] = current_game.get("guesses_remaining", 5) - 1
session["game"] = current_game # Save updated game state back to session

# Update guess counter display
updated_counter = Div(
Expand Down Expand Up @@ -248,6 +302,7 @@ def post(query: str = ""):
]
# Replace the current backdrop with the new one
current_game["shown_backdrops"] = [next_backdrop]
session["game"] = current_game # Save updated game state back to session
backdrop_url = f"https://image.tmdb.org/t/p/w1280{next_backdrop}"
return (
Div(
Expand Down Expand Up @@ -277,10 +332,29 @@ def post(query: str = ""):


@rt("/new-game")
def post():
current_game.clear()
return get()
def post(category: str = "popular", session=None):
if "game" in session:
del session["game"]

# Get new movie from selected category
movie = get_random_movie_with_details(category=category)
current_backdrop = movie["backdrops"][0] if movie["backdrops"] else None

session["game"] = {
"movie": movie,
"current_backdrop_index": 0,
"shown_backdrops": [current_backdrop] if current_backdrop else [],
"guesses_remaining": 5,
"category": category, # Store selected category
}

return get(session)


# FIXME: Doesn't work since it can't find the `api` module
# if __name__ == "__main__":
# serve()
# import uvicorn

# # serve()

# uvicorn.run("__main__:app", host="0.0.0.0", port=5002, reload=True, log_config=None)
Loading

0 comments on commit de8e7f6

Please sign in to comment.