0

Yesterday (around 10 hours prior to this post) I successfully completed the Problem Set 9 Finance as part of the CS50 Introduction to Computer Science.

I have passed all the tests multiple times, implemented some additional features as per the spec, ran the tests again multiple times (successfully) and finally submitted the application with submit50 cs50/problems/2023/x/finance

My test logs are as follows: https://submit.cs50.io/check50/4cfbef0a2307b38b3be96461761f25bc1fb7094d

What could've caused the issue: When I first unzipped the starting boilerplate into /finance directory I tried to split the app.py file into smaller files with blueprints. The idea failed because apparently the test suite doesn't like app.py being split into standalone files, so I abandoned it altogether and created a new /finance2 directory to which I copypasted the code from /finance. After the code was fully pasted into /finance2, I deleted the original directory /finance and renamed /finance2 to /finance.

I have no idea why it would create an issue, but this is the first time I have encountered it and the only difference from submitting other problem sets is me changing the directory names.

Oddly enough, my style50 command fails the style check:

Can't check your style just yet! Try running your code, fix any errors, then check its style again!

I use prettier for styling the code. Running style50 had never been an problem up until yesterday. Perhaps it is somehow connected.

Upd: added code for those experiencing the same problem as me

import os

from cs50 import SQL
from flask import Flask, flash, redirect, render_template, request, session
from flask_session import Session
from werkzeug.security import check_password_hash, generate_password_hash

from helpers import apology, login_required, lookup, usd, validate_password

# Configure application
app = Flask(__name__)

# Custom filter
app.jinja_env.filters["usd"] = usd

# Configure session to use filesystem (instead of signed cookies)
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)

# Configure CS50 Library to use SQLite database
db = SQL("sqlite:///finance.db")


@app.after_request
def after_request(response):
    """Ensure responses aren't cached"""
    response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
    response.headers["Expires"] = 0
    response.headers["Pragma"] = "no-cache"
    return response


@app.route("/")
@login_required
def index():
    """Show portfolio of stocks"""

    user_id = session["user_id"]
    user = db.execute("SELECT username FROM users WHERE id = ?", user_id)

    # Query for user's stocks
    stocks = db.execute(
        "SELECT symbol, SUM(shares) as shares_sum FROM transactions WHERE user_id = ? GROUP BY symbol HAVING shares_sum > 0",
        user_id,
    )

    # Calculate total for every stock and get prices
    holdings = []
    total_stocks = 0

    for stock in stocks:
        stock_data = lookup(stock["symbol"])
        stock_val = stock_data["price"] * stock["shares_sum"]
        total_stocks += stock_val
        holdings.append(
            {
                "symbol": stock["symbol"],
                "name": stock_data["name"],
                "shares": stock["shares_sum"],
                "price": stock_data["price"],
                "total": stock_val,
            }
        )

    cash_result = db.execute("SELECT cash FROM users WHERE id = ?", user_id)
    cash = cash_result[0]["cash"] if cash_result else 0

    return render_template(
        "index.html", holdings=holdings, cash=cash, grand_total=total_stocks + cash
    )


@app.route("/buy", methods=["GET", "POST"])
@login_required
def buy():
    """Buy shares of stock"""

    if request.method == "POST":
        symbol = request.form.get("symbol").upper()
        shares_to_buy = request.form.get("shares")

        try:
            shares_to_buy = int(request.form.get("shares"))
        except ValueError:
            return apology("shares quantity must be a positive number", 400)

        result = execute_buy(session["user_id"], symbol, shares_to_buy)
        if "error" in result:
            return apology(result["error"], result["code"])
        else:
            flash(result)
            return redirect("/")

    return render_template("buy.html")


def execute_buy(user_id: str, symbol: str, shares_to_buy: int) -> {"error": str, "code": str} or str:
    if not symbol:
        return {"error": "must provide symbol", "code": "400"}

    stock = lookup(symbol)
    if not stock:
        return {"error": "ticker symbol not found", "code": "400"}

    if shares_to_buy <= 0:
        return {"error": "shares quantity must be a positive number", "code": "400"}

    rows = db.execute("SELECT cash FROM users WHERE id = ?", user_id)
    available: int = rows[0]["cash"]
    total = stock["price"] * shares_to_buy # total purchase price

    # check cash balance
    if available < total:
        return {"error": "Not enough cash to buy", "code": "400"}

    # record the purchase
    db.execute(
        "INSERT INTO transactions (user_id, symbol, shares, price_per_share, timestamp) VALUES (?,?,?,?, CURRENT_TIMESTAMP)",
        user_id,
        symbol,
        shares_to_buy,
        stock["price"],
    )

    # update user's balance
    db.execute("UPDATE users SET cash = cash - ? WHERE id = ?", total, user_id)

    return f"Purchased {shares_to_buy} shares of {symbol}"


@app.route("/history")
@login_required
def history():
    """Show history of transactions"""

    user_id = session["user_id"]

    # Query for transactions
    transactions = db.execute(
        "SELECT symbol, shares, price_per_share, timestamp FROM transactions WHERE user_id = ?",
        user_id,
    )

    for tr in transactions:
        tr["type"] = "Buy" if tr["shares"] > 0 else "Sell"
        tr["shares"] = abs(tr["shares"])  # quantity should be positive

    return render_template("history.html", transactions=transactions)


@app.route("/login", methods=["GET", "POST"])
def login():
    """Log user in"""

    # Forget any user_id
    session.clear()

    # User reached route via POST (as by submitting a form via POST)
    if request.method == "POST":
        # Ensure username was submitted
        if not request.form.get("username"):
            return apology("must provide username", 403)

        # Ensure password was submitted
        elif not request.form.get("password"):
            return apology("must provide password", 403)

        # Query database for username
        rows = db.execute(
            "SELECT * FROM users WHERE username = ?", request.form.get("username")
        )

        # Ensure username exists and password is correct
        if len(rows) != 1 or not check_password_hash(
            rows[0]["hash"], request.form.get("password")
        ):
            return apology("invalid username and/or password", 403)

        # Remember which user has logged in
        session["user_id"] = rows[0]["id"]
        session["username"] = rows[0]["username"]

        # Redirect user to home page
        flash(f"Welcome back, {session["username"]}")
        return redirect("/")

    # User reached route via GET (as by clicking a link or via redirect)
    else:
        return render_template("login.html")


@app.route("/logout")
def logout():
    """Log user out"""

    # Forget any user_id
    session.clear()

    # Redirect user to login form
    return redirect("/")


@app.route("/quote", methods=["GET", "POST"])
@login_required
def quote():
    """Get stock quote."""

    stock_data = None

    if request.method == "POST":
        symbol = request.form.get("symbol")

        if not symbol:
            return apology("must provide symbol", 400)

        stock_data = lookup(symbol)
        if not stock_data:
            return apology("ticker symbol not found", 400)

    return render_template("quote.html", stock=stock_data)


@app.route("/register", methods=["GET", "POST"])
def register():
    if request.method == "GET":
        return render_template("register.html")

    elif request.method == "POST":
        # Ensure username was submitted
        if not request.form.get("username"):
            return apology("must provide username")

        # Ensure password was submitted
        elif not request.form.get("password"):
            return apology("must provide password")

        # Ensure password was confirmed
        elif not request.form.get("confirmation"):
            return apology("must confirm password")

        pwd_is_valid, msg = validate_password(request.form.get("password"))
        if not pwd_is_valid:
            return apology(msg, 400)

        # Check if passwords match
        elif request.form.get("password") != request.form.get("confirmation"):
            return apology("passwords do not match")

        # Query database for a matching username
        db_username = db.execute(
            "SELECT username FROM users WHERE username = ?",
            request.form.get("username"),
        )

        if db_username:
            return apology("user already exists", 400)

        db.execute(
            "INSERT INTO users (username, hash) VALUES (?, ?)",
            request.form.get("username"),
            generate_password_hash(request.form.get("password")),
        )

        user_id = db.execute(
            "SELECT id FROM users WHERE username = ?", request.form.get("username")
        )[0]["id"]
        session["user_id"] = user_id
        session["username"] = request.form.get("username")

        flash(
            "Success! You are now registered and logged in, using your new credentials"
        )
        return redirect("/")


@app.route("/sell", methods=["GET", "POST"])
@login_required
def sell():
    """Sell shares of stock"""

    if request.method == "POST":
        user_id = session["user_id"]
        symbol = request.form.get("symbol")
        shares_to_sell = (
            int(request.form.get("shares")) if request.form.get("shares") else 0
        )

        if shares_to_sell <= 0 or not shares_to_sell:
            return apology("Expected a positive integer", 400)

        result = execute_sell(user_id, symbol, shares_to_sell)

        if "error" in result:
            return apology(result["error"], result["code"])

        flash(result)
        return redirect("/")

    user_id = session["user_id"]
    stocks = db.execute(
        "SELECT symbol FROM transactions WHERE user_id = ? GROUP BY symbol HAVING SUM(shares) > 0",
        user_id,
    )

    return render_template("sell.html", stocks=stocks)


def execute_sell(user_id: str, symbol: str, shares_to_sell: int):
    if not symbol:
        return {"error": "Select a stock symbol to sell", "code": "400"}

    if shares_to_sell <= 0:
        return {"error": "Expected a positive integer", "code": "400"}

    rows = db.execute(
        "SELECT SUM(shares) as total_shares FROM transactions WHERE user_id = ? AND symbol = ? GROUP BY symbol",
        user_id,
        symbol,
    )

    if len(rows) != 1:
        return {"error": "Rows are not equal to 1", "code": "400"}

    if shares_to_sell > rows[0]["total_shares"]:
        return {"error": "Too many shares to sell", "code": "400"}


    stock_data = lookup(symbol)
    if not (stock_data):
        return {"error": "Could not find the stock price", "code": "400"}

    current_price = stock_data["price"]
    total_to_sell = current_price * shares_to_sell

    # Reflect the result of sale in user's balance
    db.execute("UPDATE users SET cash = cash + ? WHERE id = ?", total_to_sell, user_id)

    # Record the loss of user's shares in transaction
    db.execute(
        "INSERT INTO transactions (user_id, symbol, shares, price_per_share) VALUES (?,?,?,?)",
        user_id,
        symbol,
        -shares_to_sell,
        current_price,
    )

    return f"Sold {shares_to_sell} shares of {symbol}"


@app.route("/change_password", methods=["GET", "POST"])
@login_required
def change_password():
    if request.method == "POST":
        user_id = session["user_id"]

        rows = db.execute("SELECT hash FROM users WHERE id = ?", user_id)

        old_pwd_hash = rows[0]["hash"]
        old_pwd = request.form.get("old_password")
        new_pwd = request.form.get("new_password")
        confirmation = request.form.get("confirm_password")

        if not check_password_hash(old_pwd_hash, old_pwd):
            return apology("Old password is incorrect", 403)

        if new_pwd != confirmation:
            return apology("Old and new passwords don't match", 400)

        new_pwd_hash = generate_password_hash(new_pwd)
        db.execute("UPDATE users SET hash = ? WHERE id = ?", new_pwd_hash, user_id)

        flash("Password changed successfully")
        return redirect("/")

    return render_template("change_password.html")


@app.route("/trade", methods=["GET", "POST"])
@login_required
def trade():
    if request.method == "POST":
        user_id = session["user_id"]
        symbol = request.form.get("symbol").upper()
        shares_qty = request.form.get("shares")
        action = request.form.get("action").lower()

        try:
            shares_qty = int(shares_qty)
            if shares_qty <= 0:
                return apology("Expected be a positive number", 400)
        except ValueError:
            return apology("Expected a positive number", 400)

        if action == "sell":
            result = execute_sell(user_id, symbol, shares_qty)

            if "error" in result:
                return apology(result["error"], result["code"])

            flash(result)
            return redirect("/")

        elif action == "buy":
            result = execute_buy(user_id, symbol, shares_qty)

            if "error" in result:
                return apology(result["error"], result["code"])

            flash(result)
            return redirect("/")

        else:
            return apology("Invalid action", 400)

    return redirect("/")
8
  • I will also add that apparently my submission is reported with "No results": ``` Submitted 10 minutes ago, Monday, December 11, 2023 5:54 PM EET No results tar.gz • zip ```
    – superpav
    Commented Dec 11, 2023 at 16:05
  • try running style50 on the code directly from inside the IDE. id be willing to bet you made an error when copy pasting everything together. This should be a good lesson to you not to break things apart that are instructed not to. I have no idea what you mean by a smaller file with blueprints? your error has nothing to do with changing directory names, your errors have to do with incorrect code would be my guess.
    – UpAndAdam
    Commented Dec 12, 2023 at 22:06
  • having provided NO code we cant do anything more than speculate.
    – UpAndAdam
    Commented Dec 12, 2023 at 22:06
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking.
    – Community Bot
    Commented Dec 12, 2023 at 22:07
  • @UpAndAdam there was nothing said in the problem set description about NOT splitting code into separate files. In fact, it's a good practice. I tried it, the app worked well, but the tests didn't. I found here that other people had the same problem with the tests, although no particular mechanism was described as to why they were failing even though the app was working well. So, at that rate, I was expecting problems from anywhere. Since you figured out that style50 was the culprit hours after I posted a solution, I assume my problem desc was not so bad after all.
    – superpav
    Commented Dec 13, 2023 at 5:22

1 Answer 1

1

Apparently style50 was causing the issue. In my particular case I had two styling problems: (1) I used double quotes inside a formated string flash(f"Welcome back, {session['username']}") which is something style50 did not flag. The original line looked like this: flash(f"Welcome back, {session["username"]}"). And (2) I had a one-liner that style50 wanted to break into 3 lines.

After a full rebuild, I identified the first problem after I downloaded the finance.zip to my machine and opened it in a local environment where the Black formatter extension was installed.

Resolving the styling issues fixed it for me.

3
  • thats nuts to me that a style50 issue caused it to go from working to not working, style50 is supposed to be just formatting rules, I'm pretty surprised that the change in quotes made a difference... could you clarify what the original code was vs. the fixed code?
    – UpAndAdam
    Commented Dec 13, 2023 at 15:34
  • ibb.co/BwFxG3B This is the one style50 didn't see. It was only seen locally in VSCode. In VSCode I had both Black Formatter. It was either it or the VSCode default python analyzer.
    – superpav
    Commented Dec 19, 2023 at 3:43
  • Another styling issue was this: ibb.co/tcsF7ZQ This one was seen by style50
    – superpav
    Commented Dec 19, 2023 at 3:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .