def find_data_objects(classname=None, state=None, visibility=None,
                      name=None, name_mode='exact', properties=None,
                      typename=None, tag=None, tags=None,
                      link=None, project=None, folder=None, recurse=None,
                      modified_after=None, modified_before=None,
                      created_after=None, created_before=None,
                      describe=False, limit=None, level=None, region=None,
                      return_handler=False, first_page_size=100,
                      **kwargs):
    """
    :param classname:
        Class with which to restrict the search, i.e. one of "record",
        "file", "applet", "workflow"
    :type classname: string
    :param state: State of the object ("open", "closing", "closed", "any")
    :type state: string
    :param visibility: Visibility of the object ("hidden", "visible", "either")
    :type visibility: string
    :param name: Name of the object (also see *name_mode*)
    :type name: string
    :param name_mode: Method by which to interpret the *name* field ("exact": exact match, "glob": use "*" and "?" as wildcards, "regexp": interpret as a regular expression)
    :type name_mode: string
    :param properties: Properties (key-value pairs) that each result must have (use value True to require the property key and allow any value)
    :type properties: dict
    :param typename: Type constraint that each result must conform to
    :type typename: string or dict
    :param tag: Tag that each result must be tagged with (deprecated in favor of *tags*)
    :type tag: string
    :param tags: List of tags that each result must have ALL of
    :type tags: list of strings
    :param link: ID of an object that each result must link to
    :type link: string
    :param project: ID of a project in which each result must appear
    :type project: string
    :param folder: If *project* is given, full path to a folder in which each result must belong (default is the root folder)
    :type folder: string
    :param recurse: If *project* is given, whether to look in subfolders of *folder* as well (default is True)
    :type recurse: boolean
    :param modified_after: Timestamp after which each result was last modified (see note below for interpretation)
    :type modified_after: int or string
    :param modified_before: Timestamp before which each result was last modified (see note below for interpretation)
    :type modified_before: int or string
    :param created_after: Timestamp after which each result was last created (see note below for interpretation)
    :type created_after: int or string
    :param created_before: Timestamp before which each result was last created (see note below for interpretation)
    :type created_before: int or string
    :param describe: Controls whether to also return the output of
        calling describe() on each object. Supply False to omit describe
        output, True to obtain the default describe output, or a dict to
        be supplied as the describe call input (which may, among other
        things, be used to customize the set of fields that is returned)
    :type describe: bool or dict
    :param level: The minimum permissions level for which results should be returned (one of "VIEW", "UPLOAD", "CONTRIBUTE", or "ADMINISTER")
    :param region: Filter on result set by the given region(s).
    :type region: string or list of strings
    :type level: string
    :param limit: The maximum number of results to be returned (if not specified, the number of results is unlimited)
    :type limit: int
    :param first_page_size: The number of results that the initial API call will return. Subsequent calls will raise this by multiplying by 2 up to a maximum of 1000.
    :type first_page_size: int
    :param return_handler: If True, yields results as dxpy object handlers (otherwise, yields each result as a dict with keys "id" and "project")
    :type return_handler: boolean
    :rtype: generator

    Returns a generator that yields all data objects matching the query,
    up to *limit* objects. It transparently handles paging through the
    result set if necessary. For all parameters that are omitted, the
    search is not restricted by the corresponding field.

    .. note:: All timestamps must be supplied as one of the following:

       * A nonnegative integer, interpreted as milliseconds since the Epoch
       * A negative integer, interpreted as an offset in milliseconds relative
         to the current time
       * A string containing a negative integer with one of the suffixes
         "s", "m", "d", "w", or "y" (for seconds, minutes, days, weeks,
         or years), interpreted as an offset from the current time.

       The following examples both find all items that were created more
       than 1 week ago::

           items1 = list(find_data_objects(created_before="-1w"))
           items2 = list(find_data_objects(created_before=-7*24*60*60*1000))

    This example iterates through all files with property
    "project" set to "cancer project" and prints their object IDs:

      for result in find_data_objects(classname="file", properties={"project": "cancer project"}):
          print "Found a file with object id " + result["id"]

    """

    query = {}
    if classname is not None:
        query["class"] = classname
    if state is not None:
        query["state"] = state
    if visibility is not None:
        query["visibility"] = visibility
    if name is not None:
        if name_mode == 'exact':
            query["name"] = name
        elif name_mode == 'glob':
            query['name'] = {'glob': name}
        elif name_mode == 'regexp':
            query['name'] = {'regexp': name}
        else:
            raise DXError('find_data_objects: Unexpected value found for argument name_mode')
    if properties is not None:
        query["properties"] = properties
    if typename is not None:
        query["type"] = typename
    if tag is not None and tags is not None:
        raise DXError('find_data_objects: Arguments "tag" and "tags" cannot both be provided')
    if tag is not None:
        query["tag"] = tag
    if tags is not None:
        query["tags"] = {"$and": tags}
    if link is not None:
        query["link"] = link
    if project is not None:
        query["scope"] = {"project": project}
        if folder is not None:
            query["scope"]["folder"] = folder
        if recurse is not None:
            query["scope"]["recurse"] = recurse
    elif folder is not None or recurse is not None:
        if dxpy.WORKSPACE_ID is not None:
            query['scope'] = {'project': dxpy.WORKSPACE_ID}
            if folder is not None:
                query['scope']['folder'] = folder
            if recurse is not None:
                query['scope']['recurse'] = recurse
        else:
            raise DXError("Cannot search within a folder or recurse if a project is not specified")
    if modified_after is not None or modified_before is not None:
        query["modified"] = {}
        if modified_after is not None:
            query["modified"]["after"] = dxpy.utils.normalize_time_input(modified_after)
        if modified_before is not None:
            query["modified"]["before"] = dxpy.utils.normalize_time_input(modified_before)
    if created_after is not None or created_before is not None:
        query["created"] = {}
        if created_after is not None:
            query["created"]["after"] = dxpy.utils.normalize_time_input(created_after)
        if created_before is not None:
            query["created"]["before"] = dxpy.utils.normalize_time_input(created_before)
    if describe is not None and describe is not False:
        query["describe"] = describe
    if level is not None:
        query['level'] = level
    if region is not None:
        query['region'] = region
    if limit is not None:
        query["limit"] = limit

    return _find(dxpy.api.system_find_data_objects, query, limit, return_handler, first_page_size, **kwargs)