コード例 #1
0
    def generate_integer(self, integer_spec, spec_name_stack=None):
        """
        Generate an integer from the given specification.

        :param integer_spec: An integer specification
        :param spec_name_stack: A specification name stack, for reference loop
            detection.  Unused but included for API compatibility with
            object/array generators.
        :return: An int
        :raises stix2generator.exceptions.ObjectGenerationError: If a
            generation error occurs
        """
        min_, is_min_exclusive, max_, is_max_exclusive = \
            _process_numeric_min_max_properties(
                integer_spec,
                self.config.number_min,
                self.config.is_number_min_exclusive,
                self.config.number_max,
                self.config.is_number_max_exclusive
            )

        # Guess I won't assume the user expressed the bounds as ints, so I
        # need to convert to ints and check the resulting bounds.  The
        # call above to process min/max properties doesn't assume we require
        # ints.
        if int(min_) == min_:
            min_ = int(min_)
            if is_min_exclusive:
                min_ += 1
        else:
            min_ = int(math.ceil(min_))

        if int(max_) == max_:
            max_ = int(max_)
            if is_max_exclusive:
                max_ -= 1
        else:
            max_ = int(math.floor(max_))

        if min_ > max_:
            raise ObjectGenerationError(
                "no integers exist in the specified interval", "integer")

        return random.randint(min_, max_)
コード例 #2
0
    def __generate_semantic(self, spec, value_constraint):
        """
        Generate from a semantic-type spec.

        :param spec: The spec
        :param value_constraint: A ValueConstraint instance representing some
            additional constraint to be honored by the generator.  This is
            derived from a value co-constraint expression.  If None, there is
            no additional constraint.
        :return: The generated value
        :raises stix2generator.exceptions.SemanticValueTypeMismatchError: If the
            semantic produces a value which doesn't agree with the spec's
            declared type.
        :raises stix2generator.exceptions.ObjectGenerationError: If the
            semantic name isn't found in any of this generator's semantic
            providers
        """
        semantic = spec[
            stix2generator.generation.semantics.SEMANTIC_PROPERTY_NAME
        ]

        if semantic in self.__semantics:
            provider = self.__semantics[semantic]

            value = provider.create_semantic(spec, self, value_constraint)

            # Should check that the implementation created the right type of
            # value.
            actual_type = _json_type_from_python_type(type(value))

            if actual_type != spec["type"]:
                raise SemanticValueTypeMismatchError(
                    semantic, actual_type, value, spec["type"]
                )

        else:
            raise ObjectGenerationError(
                "unrecognized semantic: " + semantic
            )

        return value
コード例 #3
0
def _get_properties_to_include(object_spec, optional_property_probability,
                               minimize_ref_properties):
    """
    Determine which object properties to include, based on required/optional
    choices and any defined presence co-constraints.

    :param object_spec: The object spec
    :param optional_property_probability: The probability an optional property
        should be included.  Must be a number from 0 to 1.
    :param minimize_ref_properties: True if we should minimize optional
        reference properties.  False if they should receive no special
        treatment.
    :return: The property names, as a set of strings
    :raises stix2generator.exceptions.PresenceCoconstraintError: If an invalid
        presence co-constraint is found
    :raises stix2generator.exceptions.UndefinedPropertyError: If a reference to an
        undefined property or group is found in the "required" or "optional"
        property value of the spec
    :raises stix2generator.exceptions.ObjectGenerationError: If a reference to a
        grouped property is found
    """

    prop_specs = object_spec.get("properties", {})
    required_names = object_spec.get("required")
    optional_names = object_spec.get("optional")

    if required_names is not None and optional_names is not None:
        raise ObjectGenerationError(
            '"required" and "optional" can\'t both be present')

    # If neither optional nor required names are specified, all
    # properties/groups will be required.
    elif required_names is None and optional_names is None:
        # empty optional set = all required
        optional_names = set()

    # Convert to sets to remove dupes
    elif required_names is not None:
        required_names = set(required_names)
    elif optional_names is not None:
        optional_names = set(optional_names)

    group_coconstraints, dependency_coconstraints = \
        _get_presence_coconstraints(object_spec)

    # Detect errors in the required/optional prop list: all must be
    # defined, and grouped properties must not be referenced
    req_or_opt = required_names if required_names is not None \
        else optional_names
    defined_prop_names = prop_specs.keys()
    defined_group_names = group_coconstraints.keys()
    grouped_property_names = set(
        itertools.chain.from_iterable(
            coco.property_names for coco in group_coconstraints.values()))

    undef_name_errors = req_or_opt - defined_prop_names - defined_group_names
    if undef_name_errors:
        raise UndefinedPropertyError(undef_name_errors)

    grouped_prop_errors = req_or_opt & grouped_property_names
    if grouped_prop_errors:
        raise ObjectGenerationError(
            "Property(s) are grouped and cannot be referenced"
            " individually: {}".format(", ".join(
                "{}".format(p) for p in grouped_prop_errors)))

    # Include all ungrouped property names and property group names in
    # the same "pool" of names one can specify as required or optional.
    name_pool = (defined_prop_names - grouped_property_names) \
        | defined_group_names

    # Get set of optional names (whether they specified "required" or
    # "optional" in the spec).
    effectively_optional_names = optional_names if optional_names is not None \
        else name_pool - required_names

    # Start out the set of names to include with all required ones.
    names_to_include = required_names if required_names is not None \
        else name_pool - effectively_optional_names

    # And then maybe add some optional ones.
    for name in effectively_optional_names:

        is_group = name in defined_group_names
        is_ref = name.endswith("_ref") or name.endswith("_refs")

        can_include = False
        if minimize_ref_properties:
            if is_group:
                if group_coconstraints[name].can_satisfy_without_refs():
                    can_include = True
            elif not is_ref:
                can_include = True

        else:
            can_include = True

        if can_include and random.random() < optional_property_probability:
            names_to_include.add(name)

    # Incorporate the "dependencies": add any other properties we
    # require
    for dep_key, dep_names in dependency_coconstraints.items():
        if dep_key in names_to_include:
            names_to_include.update(dep_names)

    # For any names which are property groups, expand them to the
    # component properties according to their co-constraints
    # ... can't modify a set as you iterate!  So need a temp set.
    temp_set = set()
    for name in names_to_include:
        if name in group_coconstraints:
            temp_set.update(group_coconstraints[name].choose_properties(
                optional_property_probability, minimize_ref_properties))
        else:
            temp_set.add(name)

    names_to_include = temp_set

    return names_to_include
コード例 #4
0
    def generate_from_spec(self,
                           spec,
                           expected_type=None,
                           spec_name_stack=None,
                           value_constraint=None):
        """
        Generate a value based on the given specification, which need not exist
        under any particular name in this generator's registry.

        :param spec: The specification, as parsed JSON
        :param expected_type: If the spec should be for a particular JSON type,
            that type.  If it doesn't matter, pass None.
        :param spec_name_stack: A stack of previously-visited specification
            names, used for reference loop detection.  Pass None to start a
            new stack.
        :param value_constraint: A ValueConstraint instance representing some
            additional constraint to be honored by the generator.  This is
            derived from a value co-constraint expression.  If None, there is
            no additional constraint.
        :return: The generated value
        :raises stix2generator.exceptions.UnrecognizedJSONTypeError: If given a
            non-const dict spec whose declared type is not recognized as a
            JSON type, or if expected_type is given and not a recognized JSON
            type.
        :raises stix2generator.exceptions.TypeMismatchError: If expected_type is
            given and the spec type doesn't match.
        :raises stix2generator.exceptions.ObjectGenerationError: For various
            ways the given spec is invalid.  Other types of errors are also
            wrapped/chained from this exception type (if possible) so that
            we get decoration with extra info from higher stack frames, which
            is useful for diagnosing where those problems occur.
        """

        spec_type = _get_spec_type(spec)

        if expected_type:
            if expected_type not in _JSON_TYPES:
                raise UnrecognizedJSONTypeError(expected_type)

            # There really should be some flexibility for numeric types: if
            # number is expected, integers should be accepted too...
            if spec_type != expected_type:
                raise TypeMismatchError(expected_type, spec_type)

        # If not a dict, the spec IS the desired value.  It's an easy way to
        # produce fixed values.
        if not isinstance(spec, dict):
            value = spec

        # The other way: use "const", like in json-schema.
        elif "const" in spec:
            value = spec["const"]

        else:

            semantic_name = spec.get(
                stix2generator.generation.semantics.SEMANTIC_PROPERTY_NAME)

            try:

                if semantic_name:
                    value = self.__generate_semantic(spec, value_constraint)

                else:
                    value = self.__generate_plain(spec, spec_name_stack,
                                                  value_constraint)

            except ObjectGenerationError as e:
                # In a recursive context, set this at the deepest nesting
                # level only.  Also, I think it's better to use the semantic
                # name as the type name in error messages, for semantic specs.
                if not e.spec_type:
                    e.spec_type = semantic_name or spec_type
                raise

            except Exception as e:
                raise ObjectGenerationError(
                    "An error occurred during generation: {}: {}".format(
                        type(e).__name__, str(e)), semantic_name
                    or spec_type) from e

        return value
コード例 #5
0
def _process_numeric_min_max_properties(spec, default_min,
                                        is_default_min_exclusive, default_max,
                                        is_default_max_exclusive):
    """
    Factors out a rather large chunk of code for validating and processing
    the min/max properties on numbers and integers.  Maybe we need a
    JSON-Schema for specifications and validate against that, to reduce the
    amount of hand-written validation code we need to write...

    :param spec: A number or integer spec
    :param default_min: If the spec doesn't specify a minimum, use this as
        the default.
    :param is_default_min_exclusive: Whether default_min, if it is used,
        is an exclusive bound.
    :param default_max: If the spec doesn't specify a maximum, use this as
        the default.
    :param is_default_max_exclusive: Whether default_max, if it is used,
        is an exclusive bound.
    :return: A (num, bool, num, bool) 4-tuple giving the bounds and whether
        each bound is exclusive or not:
            (min, is_min_exclusive, max, is_max_exclusive)
    :raises stix2generator.exceptions.ObjectGenerationError: For various types
        of problems with numeric specifications
    """
    if "minimum" in spec and "exclusiveMinimum" in spec:
        raise ObjectGenerationError(
            "minimum and exclusiveMinimum can't both be present")

    if "maximum" in spec and "exclusiveMaximum" in spec:
        raise ObjectGenerationError(
            "maximum and exclusiveMaximum can't both be present")

    min_given = any(p in spec for p in ("minimum", "exclusiveMinimum"))
    max_given = any(p in spec for p in ("maximum", "exclusiveMaximum"))

    # I think this check is necessary since user-specified min/max could well
    # be out of order w.r.t. defaults, producing unexpected errors.  What would
    # users expect the other bound to be anyway, if they only gave one bound?
    if (min_given and not max_given) or (max_given and not min_given):
        raise ObjectGenerationError(
            "can't give minimum without a maximum, or vice versa")

    if "minimum" in spec:
        min_ = spec["minimum"]
        is_min_exclusive = False
    elif "exclusiveMinimum" in spec:
        min_ = spec["exclusiveMinimum"]
        is_min_exclusive = True
    else:
        min_ = default_min
        is_min_exclusive = is_default_min_exclusive

    if "maximum" in spec:
        max_ = spec["maximum"]
        is_max_exclusive = False
    elif "exclusiveMaximum" in spec:
        max_ = spec["exclusiveMaximum"]
        is_max_exclusive = True
    else:
        max_ = default_max
        is_max_exclusive = is_default_max_exclusive

    if min_ > max_:
        raise ObjectGenerationError("minimum can't be greater than maximum")
    elif min_ == max_ and (is_max_exclusive or is_min_exclusive):
        raise ObjectGenerationError(
            "In an open or half-open interval, minimum must be strictly "
            "less than maximum")

    return min_, is_min_exclusive, max_, is_max_exclusive