<commit_before>"""Module for interacting with the problems."""

from random import randint

import pymongo
from flask import current_app
from voluptuous import ALLOW_EXTRA, Range, Required, Schema

import api
from api import check, PicoException, validate
from api.cache import memoize

problem_schema = Schema(
    {
        Required("name"): check(
            ("The problem's display name must be a string.", [str])
        ),
        Required("sanitized_name"): check(
            ("The problems's sanitized name must be a string.", [str])
        ),
        Required("score"): check(
            ("Score must be a positive integer.", [int, Range(min=0)])
        ),
        Required("author"): check(("Author must be a string.", [str])),
        Required("category"): check(("Category must be a string.", [str])),
        Required("instances"): check(("The instances must be a list.", [list])),
        Required("organization"): check(("Organization must be string.", [str])),
        Required("event"): check(("Event must be string.", [str])),
        Required("unique_name"): check(
            ("The problems's unique name must be a string.", [str])
        ),
        "static_flag": check(
            ("The static_flag must be a bool.", [lambda f: type(f) == bool])
        ),
        "walkthrough": check(("The problem walkthrough must be a string.", [str])),
        "description": check(("The problem description must be a string.", [str])),
        "version": check(("A version must be a string.", [str])),
        "tags": check(("Tags must be described as a list.", [list])),
        "pkg_architecture": check(("Package architecture must be string.", [str])),
        "pkg_description": check(("Package description must be string.", [str])),
        "pkg_name": check(("Package name must be string.", [str])),
        "pkg_dependencies": check(("Package dependencies must be list.", [list])),
        "pip_requirements": check(("pip requirements must be list.", [list])),
        "pip_python_version": check(("Pip python version must be a string.", [str])),
        "pid": check(
            ("You should not specify a pid for a problem.", [lambda _: False])
        ),
        "_id": check(
            ("Your problems should not already have _ids.", [lambda id: False])
        ),
    },
    extra=ALLOW_EXTRA,
)

instance_schema = Schema(
    {
        Required("description"): check(("The description must be a string.", [str])),
        Required("hints"): check(("Hints must be a list.", [list])),
        Required("flag"): check(("The flag must be a string.", [str])),
        "port": check(("The port must be an int", [int])),
        "server": check(("The server must be a string.", [str])),
    },
    extra=True,
)


def get_all_categories():
    """
    Get the set of distinct problem categories.

    Returns:
        The set of distinct problem categories.

    """
    db = api.db.get_conn()
    # Do not return categories that only appear on disabled problems
    match = {"disabled": False}
    return db.problems.find(match).distinct("category")


def upsert_problem(problem, sid):
    """
    Add or update a problem.

    Args:
        problem: problem dict
        sid: shell server ID
    Returns:
        The created/updated problem ID.
    """
    db = api.db.get_conn()

    # Validate the problem object
    # @TODO it may make more sense to do this with e.g. Marshmallow at the
    #       routing level
    validate(problem_schema, problem)
    for instance in problem["instances"]:
        validate(instance_schema, instance)

    problem["pid"] = problem["unique_name"]

    # Initially disable problems
    problem["disabled"] = True

    # Assign instance IDs and server numbers
    server_number = api.shell_servers.get_server(sid)["server_number"]
    for instance in problem["instances"]:
        instance["iid"] = api.common.hash(
            str(instance["instance_number"]) + sid + problem["pid"]
        )
        instance["sid"] = sid
        if server_number is not None:
            instance["server_number"] = server_number

    # Docker Instance tracking
    # XXX: also track port information and TTL
    digests = []
    for i in problem["instances"]:
        if i["docker_challenge"]:
            digests.append(i["instance_digest"])

            try:
                docker_pub = current_app.config["DOCKER_PUB"]
            except KeyError:
                raise PicoException("Attempted to load a DockerChallenge but DOCKER_PUB not configured")

            # update port display style with docker host value
            for p, v in i["port_info"].items():
                v["fmt"] = v["fmt"].format(host=docker_pub)

    # track problem to image information for docker instances
    pid = problem["pid"]
    if len(digests) > 0:
        data = {"pid": pid, "digests": digests}
        db.images.update({"pid": pid}, data, upsert=True)

    if problem.get("walkthrough"):  # Falsy for None and empty string
        problem["has_walkthrough"] = True<commit_msg>Update web load to accommodate pre-docker challenges<commit_after>"""Module for interacting with the problems."""

from random import randint

import pymongo
from flask import current_app
from voluptuous import ALLOW_EXTRA, Range, Required, Schema

import api
from api import check, PicoException, validate
from api.cache import memoize

problem_schema = Schema(
    {
        Required("name"): check(
            ("The problem's display name must be a string.", [str])
        ),
        Required("sanitized_name"): check(
            ("The problems's sanitized name must be a string.", [str])
        ),
        Required("score"): check(
            ("Score must be a positive integer.", [int, Range(min=0)])
        ),
        Required("author"): check(("Author must be a string.", [str])),
        Required("category"): check(("Category must be a string.", [str])),
        Required("instances"): check(("The instances must be a list.", [list])),
        Required("organization"): check(("Organization must be string.", [str])),
        Required("event"): check(("Event must be string.", [str])),
        Required("unique_name"): check(
            ("The problems's unique name must be a string.", [str])
        ),
        "static_flag": check(
            ("The static_flag must be a bool.", [lambda f: type(f) == bool])
        ),
        "walkthrough": check(("The problem walkthrough must be a string.", [str])),
        "description": check(("The problem description must be a string.", [str])),
        "version": check(("A version must be a string.", [str])),
        "tags": check(("Tags must be described as a list.", [list])),
        "pkg_architecture": check(("Package architecture must be string.", [str])),
        "pkg_description": check(("Package description must be string.", [str])),
        "pkg_name": check(("Package name must be string.", [str])),
        "pkg_dependencies": check(("Package dependencies must be list.", [list])),
        "pip_requirements": check(("pip requirements must be list.", [list])),
        "pip_python_version": check(("Pip python version must be a string.", [str])),
        "pid": check(
            ("You should not specify a pid for a problem.", [lambda _: False])
        ),
        "_id": check(
            ("Your problems should not already have _ids.", [lambda id: False])
        ),
    },
    extra=ALLOW_EXTRA,
)

instance_schema = Schema(
    {
        Required("description"): check(("The description must be a string.", [str])),
        Required("hints"): check(("Hints must be a list.", [list])),
        Required("flag"): check(("The flag must be a string.", [str])),
        "port": check(("The port must be an int", [int])),
        "server": check(("The server must be a string.", [str])),
    },
    extra=True,
)


def get_all_categories():
    """
    Get the set of distinct problem categories.

    Returns:
        The set of distinct problem categories.

    """
    db = api.db.get_conn()
    # Do not return categories that only appear on disabled problems
    match = {"disabled": False}
    return db.problems.find(match).distinct("category")


def upsert_problem(problem, sid):
    """
    Add or update a problem.

    Args:
        problem: problem dict
        sid: shell server ID
    Returns:
        The created/updated problem ID.
    """
    db = api.db.get_conn()

    # Validate the problem object
    # @TODO it may make more sense to do this with e.g. Marshmallow at the
    #       routing level
    validate(problem_schema, problem)
    for instance in problem["instances"]:
        validate(instance_schema, instance)

    problem["pid"] = problem["unique_name"]

    # Initially disable problems
    problem["disabled"] = True

    # Assign instance IDs and server numbers
    server_number = api.shell_servers.get_server(sid)["server_number"]
    for instance in problem["instances"]:
        instance["iid"] = api.common.hash(
            str(instance["instance_number"]) + sid + problem["pid"]
        )
        instance["sid"] = sid
        if server_number is not None:
            instance["server_number"] = server_number

    # Docker Instance tracking
    # XXX: also track port information and TTL
    digests = []
    for i in problem["instances"]:
        if "docker_challenge" in i and i["docker_challenge"]:
            digests.append(i["instance_digest"])

            try:
                docker_pub = current_app.config["DOCKER_PUB"]
            except KeyError:
                raise PicoException("Attempted to load a DockerChallenge but DOCKER_PUB not configured")

            # update port display style with docker host value
            for p, v in i["port_info"].items():
                v["fmt"] = v["fmt"].format(host=docker_pub)

    # track problem to image information for docker instances
    pid = problem["pid"]
    if len(digests) > 0:
        data = {"pid": pid, "digests": digests}
        db.images.update({"pid": pid}, data, upsert=True)

    if problem.get("walkthrough"):  # Falsy for None and empty string
        problem["has_walkthrough"] = True
{'fraction_non_alphanumeric': 0.1353167935158644, 'fraction_numerical': 0.0003953741227636651, 'mean_word_length': 2.7308259587020647, 'pattern_counts': {'":': 38, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6509066', 'n_tokens_mistral': 2827, 'n_tokens_neox': 2754, 'n_words': 949}