Пример #1
0
    def needs_posted(self, sub, games):
        """Taking a subreddit object and list of games, decide which threads need to be posted"""
        env = SandboxedEnvironment()
        ret = []
        for thread in sub.config['threads']:
            self.logger.debug("Testing games against expression %s for sub %r",
                              thread['post_condition'], sub)
            post_cond_fun = env.compile_expression(thread['post_condition'])
            for game in games:
                self.logger.debug("Testing %s for thread %s for %r", game,
                                  thread['id'], sub)
                if self.already_posted(sub, thread, game):
                    # This could prove expensive, but it's easy for now.
                    # Simple optimization: fetch all already_posted threads for non_archived games outside the loop
                    self.logger.debug("Game already posted, skipping")
                    continue

                try:
                    ctx = make_context(game, sub.config)
                except NotReadyException as e:
                    self.logger.debug("Not ready to decide whether to post %s",
                                      game)
                    continue
                except Exception as e:
                    self.logger.exception(
                        "Error generating context for game %s", game)
                    continue
                post_decision = post_cond_fun(ctx)
                self.logger.debug("Decision for %r: %s", game, post_decision)
                needs_posted = False
                if isinstance(post_decision, datetime):
                    needs_posted = post_decision < now()
                elif isinstance(post_decision, bool):
                    needs_posted = post_decision
                else:
                    self.logger.warning(
                        "post_decision for %s in %s not datetime or bool: %r",
                        thread['id'], sub, post_decision)
                if needs_posted:
                    self.logger.info("Posting thread %s for game %s in %s",
                                     thread['id'], game, sub)
                    ret.append((thread, game))
        return ret
def validate_question(mspec, spec):
    if not spec.get("id"):
        raise ValidationError("module questions",
                              "A question is missing an id.")

    def invalid(msg):
        raise ValidationError("question %s" % spec['id'], msg)

    # clone dict before updating
    spec = OrderedDict(spec)

    # Since question IDs become Jinja2 identifiers, they must be valid
    # Jinaj2 identifiers. http://jinja.pocoo.org/docs/2.9/api/#notes-on-identifiers
    if not re.match("^[a-zA-Z_][a-zA-Z0-9_]*$", spec["id"]):
        invalid(
            "The question ID may only contain ASCII letters, numbers, and underscores, and the first character must be a letter or underscore."
        )

    # Perform type conversions, validation, and fill in some defaults in the YAML
    # schema so that the values are ready to use in the database.
    if spec.get("type") == "multiple-choice":
        # validate and type-convert min and max

        spec["min"] = spec.get("min", 0)
        if not isinstance(spec["min"], int) or spec["min"] < 0:
            invalid("min must be a positive integer")

        spec["max"] = None if ("max" not in spec) else spec["max"]
        if spec["max"] is not None:
            if not isinstance(spec["max"], int) or spec["max"] < 0:
                invalid("max must be a positive integer")

    elif spec.get("type") in ("module", "module-set"):
        if "module-id" in spec:
            # Resolve the relative module ID to an absolute path relative
            # to the root of this app. It's optional because a protocol
            # can be specified instead.
            spec["module-id"] = resolve_relative_module_id(
                mspec, spec.get("module-id"))
        elif "protocol" in spec:
            pass
        else:
            invalid("Question must have a module-id or protocol field.")

    elif spec.get("type") == None:
        invalid("Question is missing a type.")

    # Check that required fields are present.
    if spec.get("prompt") is None:
        # Prompts are optional in project and system modules but required elsewhere.
        if mspec.get("type") not in ("project", "system-project"):
            invalid("Question prompt is missing.")

    # Check that the prompt, placeholder, and default are valid Jinja2 templates.
    for field in ("prompt", "placeholder", "default"):
        if field not in spec: continue
        if not isinstance(spec.get(field), str):
            invalid("Question %s must be a string, not a %s." %
                    (field, str(type(spec.get(field)))))
        try:
            render_content({
                "format": "markdown",
                "template": spec[field],
            }, None, "PARSE_ONLY", "(question %s)" % field)
        except ValueError as e:
            invalid("Question %s is an invalid Jinja2 template: %s" %
                    (field, e))

    # Validate impute conditions.
    imputes = spec.get("impute", [])
    if not isinstance(imputes, list):
        invalid("Impute's value must be a list.")
    for i, rule in enumerate(imputes):

        def invalid_rule(msg):
            raise ValidationError(
                mspec['id'] + " question %s, impute condition %d" %
                (spec['id'], i + 1), msg)

        # Check that the condition is a string, and that it's a valid Jinja2 expression.
        from jinja2.sandbox import SandboxedEnvironment
        env = SandboxedEnvironment()
        if "condition" in rule:
            if not isinstance(rule.get("condition"), str):
                invalid_rule("Impute condition must be a string, not a %s." %
                             str(type(rule["condition"])))
            try:
                env.compile_expression(rule["condition"])
            except Exception as e:
                invalid_rule(
                    "Impute condition %s is an invalid Jinja2 expression: %s."
                    % (repr(rule["condition"]), str(e)))

        # Check that the value is valid. If the value-mode is raw, which
        # is the default, then any Python/YAML value is valid. We only
        # check expression values.
        if rule.get("value-mode") == "expression":
            try:
                env.compile_expression(rule["value"])
            except Exception as e:
                invalid_rule(
                    "Impute condition value %s is an invalid Jinja2 expression: %s."
                    % (repr(rule["value"]), str(e)))
        if rule.get("value-mode") == "template":
            try:
                env.from_string(rule["value"])
            except Exception as e:
                invalid_rule(
                    "Impute condition value %s is an invalid Jinja2 template: %s."
                    % (repr(rule["value"]), str(e)))

    return spec