class LRSResponse(Base):
    """Creates a new LRSResponse object, either from a dict, another object, or from kwargs

    :param success: True if the LRS return a successful status (sometimes includes 404), False otherwise
    :type success: bool
    :param request: HTTPRequest object that was sent to the LRS
    :type request: HTTPRequest
    :param response: HTTPResponse object that was received from the LRS
    :type response: HTTPResponse
    :param data: Body of the HTTPResponse
    :type data: unicode
    :param content: Parsed content received from the LRS
    """

    _props_req = [
        'success',
        'request',
        'response',
        'data',
    ]

    _props = [
        'content',
    ]

    _props.extend(_props_req)

    def __init__(self, *args, **kwargs):
        self._success = None
        self._request = None
        self._response = None
        self._data = None
        self._content = None

        super(LRSResponse, self).__init__(*args, **kwargs)

    @property
    def success(self):
        """The LRSResponse's success. True if the LRS return a
        successful status (sometimes includes 404), False otherwise.

        :setter: Tries to convert to boolean
        :setter type: bool
        :rtype: bool
        """
        return self._success

    @success.setter
    def success(self, value):
        self._success = bool(value)

    @property
    def request(self):
        """The HTTPRequest object that was sent to the LRS

        :setter: Tries to convert to an HTTPRequest object
        :setter type: :class:`tincan.http_request.HTTPRequest`
        :rtype: :class:`tincan.http_request.HTTPRequest`
        """
        return self._request

    @request.setter
    def request(self, value):
        if value is not None and not isinstance(value, HTTPRequest):
            value = HTTPRequest(value)

        self._request = value

    @property
    def response(self):
        """The HTTPResponse object that was sent to the LRS

        :setter: Must be an HTTPResponse object
        :setter type: :class:`httplib.HTTPResponse`
        :rtype: :class:`httplib.HTTPResponse`
        """
        return self._response

    @response.setter
    def response(self, value):
        if value is not None and not isinstance(value, HTTPResponse):
            raise TypeError(
                f"Property 'response' in 'tincan.{self.__class__.__name__}' must be set with an HTTPResponse object"
            )
        self._response = value

    @property
    def data(self):
        return self._data

    @data.setter
    def data(self, value):
        """Setter for the _data attribute. Should be set from response.read()

        :param value: The body of the response object for the LRSResponse
        :type value: unicode
        """
        if value is not None and isinstance(value, (bytes, bytearray)):
            value = value.decode('utf-8')
        self._data = value

    @property
    def content(self):
        """Parsed content received from the LRS
        """
        return self._content

    @content.setter
    def content(self, value):
        self._content = value

    @content.deleter
    def content(self):
        del self._content class HTTPRequest(Base):
    """Creates a new HTTPRequest object, either from a dict, another object, or from kwargs

    :param method: Method for the HTTP connection ("GET", "POST", "DELETE", etc.)
    :type method: unicode
    :param resource: Resource for the LRS HTTP connection ("about", "statements", "activities/state", etc.)
    :type resource: unicode
    :param headers: Headers for the HTTP connection ("If-Match", "Content-Type", etc.)
    :type headers: dict(unicode:unicode)
    :param query_params: Query parameters for the HTTP connection ("registration", "since", "statementId", etc.)
    :type query_params: dict(unicode:unicode)
    :param content: Content body for the HTTP connection. Valid json string.
    :type content: unicode
    :param ignore404: True if this request should consider a 404 response successful, False otherwise
    :type ignore404: bool
    """

    _props_req = [
        'method',
        'resource',
        'headers',
        'query_params',
    ]

    _props = [
        'content',
        'ignore404',
    ]

    _props.extend(_props_req)

    def __init__(self, *args, **kwargs):
        self._method = None
        self._resource = None
        self._headers = None
        self._query_params = None
        self._content = None
        self._ignore404 = None

        super(HTTPRequest, self).__init__(*args, **kwargs)

    @property
    def method(self):
        """Method for the HTTP connection ("GET", "POST", "DELETE", etc.)

        :setter: Tries to convert to unicode
        :setter type: str | unicode
        :rtype: unicode
        """
        return self._method

    @method.setter
    def method(self, value):
        if not isinstance(value, str) and value is not None:
            str(value)
        self._method = value

    @property
    def resource(self):
        """Resource for the LRS HTTP connection ("about", "statements", "activities/state", etc.)

        :setter: Tries to convert to unicode
        :setter type: str | unicode
        :rtype: unicode
        """
        return self._resource

    @resource.setter
    def resource(self, value):
        if not isinstance(value, str) and value is not None:
            str(value)
        self._resource = value

    @property
    def headers(self):
        """Headers for the HTTP connection ("If-Match", "Content-Type", etc.)

        :setter: Accepts a dict or an object
        :setter type: dict
        :rtype: dict
        """
        return self._headers

    @headers.setter
    def headers(self, value):
        val_dict = {}
        if value is not None:
            val_dict.update(value if isinstance(value, dict) else vars(value))
        self._headers = val_dict

    @property
    def query_params(self):
        """Query parameters for the HTTP connection ("registration", "since", "statementId", etc.)

        :setter: Accepts a dict or an object
        :setter type: dict
        :rtype: dict
        """
        return self._query_params

    @query_params.setter
    def query_params(self, value):
        val_dict = {}
        if value is not None:
            val_dict.update(value if isinstance(value, dict) else vars(value))
        self._query_params = val_dict

    @property
    def content(self):
        """Content body for the HTTP connection. Valid json string.

        :setter: Tries to convert to unicode
        :setter type: str | unicode
        :rtype: unicode
        """
        return self._content

    @content.setter
    def content(self, value):
        if not isinstance(value, str) and value is not None:
            value = value.decode("utf-8")
        self._content = value

    @content.deleter
    def content(self):
        del self._content

    @property
    def ignore404(self):
        """True if this request should consider a 404 response successful, False otherwise

        :setter: Tries to convert to boolean
        :setter type: bool
        :rtype: bool
        """
        return self._ignore404

    @ignore404.setter
    def ignore404(self, value):
        self._ignore404 = bool(value)

    @ignore404.deleter
    def ignore404(self):
        del self._ignore404 class StatementList(TypedList):
    _cls = Statement class Agent(SerializableBase):
    _props_req = [
        "object_type"
    ]

    _props = [
        "name",
        "mbox",
        "mbox_sha1sum",
        "openid",
        "account"
    ]

    _props.extend(_props_req)

    def __init__(self, *args, **kwargs):
        self._object_type = None
        self._name = None
        self._mbox = None
        self._mbox_sha1sum = None
        self._openid = None
        self._account = None

        super(Agent, self).__init__(*args, **kwargs)

    @property
    def object_type(self):
        """Object Type for Agent. Will always be 'Agent'

        :setter: Tries to convert to unicode
        :setter type: unicode
        :rtype: unicode

        """
        return self._object_type

    @object_type.setter
    def object_type(self, _):
        self._object_type = 'Agent'

    @property
    def name(self):
        """Name for Agent

        :setter: Tries to convert to unicode
        :setter type: unicode
        :rtype: unicode

        """
        return self._name

    @name.setter
    def name(self, value):
        if value is not None:
            if value == '':
                raise ValueError("Property name can not be set to an empty string")
            elif not isinstance(value, str):
                value = str(value)
        self._name = value

    @name.deleter
    def name(self):
        del self._name

    @property
    def mbox(self):
        """Mbox for Agent

        :setter: Tries to convert to unicode
        :setter type: unicode
        :rtype: unicode

        """
        return self._mbox

    @mbox.setter
    def mbox(self, value):
        if value is not None:
            if value == '':
                raise ValueError("Property mbox can not be set to an empty string")
            elif not isinstance(value, str):
                value = str(value)
        if not value.startswith("mailto:"):
            value = "mailto:" + value
        self._mbox = value

    @mbox.deleter
    def mbox(self):
        del self._mbox

    @property
    def mbox_sha1sum(self):
        """Mbox_sha1sum for Agent

        :setter: Tries to convert to unicode
        :setter type: unicode
        :rtype: unicode

        """
        return self._mbox_sha1sum

    @mbox_sha1sum.setter
    def mbox_sha1sum(self, value):
        if value is not None:
            if value == '':
                raise ValueError("Property mbox_sha1sum can not be set to an empty string")
            elif not isinstance(value, str):
                value = str(value)
        self._mbox_sha1sum = value

    @mbox_sha1sum.deleter
    def mbox_sha1sum(self):
        del self._mbox_sha1sum

    @property
    def openid(self):
        """Openid for Agent

        :setter: Tries to convert to unicode
        :setter type: unicode
        :rtype: unicode

        """
        return self._openid

    @openid.setter
    def openid(self, value):
        if value is not None:
            if value == '':
                raise ValueError("Property openid can not be set to an empty string")
            elif not isinstance(value, str):
                value = str(value)
        self._openid = value

    @openid.deleter
    def openid(self):
        del self._openid

    @property
    def account(self):
        """Account for Agent

        :setter: Tries to convert to :class:`tincan.AgentAccount`
        :setter type: :class:`tincan.AgentAccount`
        :rtype: :class:`tincan.AgentAccount`

        """
        return self._account

    @account.setter
    def account(self, value):
        if value is not None and not isinstance(value, AgentAccount):
            value = AgentAccount(value)
        self._account = value

    @account.deleter
    def account(self):
        del self._account class Statement(StatementBase):
    _UUID_REGEX = re.compile(
        r'^[a-f0-9]{8}-'
        r'[a-f0-9]{4}-'
        r'[1-5][a-f0-9]{3}-'
        r'[89ab][a-f0-9]{3}-'
        r'[a-f0-9]{12}$'
    )

    _props_req = [
        "id",
        "stored",
        "authority"
    ]

    _props = [
        "result",
        "version"
    ]

    _props.extend(StatementBase._props)
    _props.extend(_props_req)

    def __init__(self, *args, **kwargs):
        self._id = None
        self._stored = None
        self._authority = None
        self._result = None
        self._version = Version.latest

        super(Statement, self).__init__(*args, **kwargs)

    @property
    def id(self):
        """Id for Statement

        :setter: Tries to unicode
        :setter type: unicode
        :rtype: unicode

        """
        return self._id

    @id.setter
    def id(self, value):
        if value is not None and not isinstance(value, uuid.UUID):
            if isinstance(value, str) and not self._UUID_REGEX.match(value):
                raise ValueError("Invalid UUID string")
            value = uuid.UUID(value)
        self._id = value

    @id.deleter
    def id(self):
        del self._id

    @property
    def object(self):
        """Object for Statement

        :setter: Sets the object
        :setter type: :class:`tincan.Agent` | :class:`tincan.Group` | :class:`tincan.StatementRef` |
                      :class:`tincan.SubStatement` | :class:`tincan.Activity`
        :rtype: :class:`tincan.Agent` | :class:`tincan.Group` | :class:`tincan.StatementRef` |
                :class:`tincan.SubStatement` | :class:`tincan.Activity`

        """
        return self._object

    @object.setter
    def object(self, value):
        if value is not None:
            if not isinstance(value, Agent) and \
                    not isinstance(value, Group) and \
                    not isinstance(value, SubStatement) and \
                    not isinstance(value, StatementRef) and \
                    not isinstance(value, Activity):
                if isinstance(value, dict):
                    if 'object_type' in value or 'objectType' in value:
                        if 'objectType' in value:
                            value['object_type'] = value['objectType']
                            value.pop('objectType')
                        if value['object_type'] == 'Agent':
                            value = Agent(value)
                        elif value['object_type'] == 'SubStatement':
                            value = SubStatement(value)
                        elif value['object_type'] == 'StatementRef':
                            value = StatementRef(value)
                        elif value['object_type'] == 'Activity':
                            value = Activity(value)
                        elif value['object_type'] == 'Group':
                            value = Group(value)
                        else:
                            value = Activity(value)
                    else:
                        value = Activity(value)
        self._object = value

    @object.deleter
    def object(self):
        del self._object

    @property
    def stored(self):
        """Storage time. Tries to convert to :class:`datetime.datetime`.
        If no timezone is given, makes a naive `datetime.datetime`.

        Strings will be parsed as ISO 8601 timestamps.

        If a number is provided, it will be interpreted as a UNIX
        timestamp, which by definition is UTC.

        If a `dict` is provided, does `datetime.datetime(**value)`.

        If a `tuple` or a `list` is provided, does
        `datetime.datetime(*value)`. Uses the timezone in the tuple or
        list if provided.

        :setter type: :class:`datetime.datetime` | unicode | str | int | float | dict | tuple | list | None
        :rtype: :class:`datetime.datetime`
        """
        return self._stored

    @stored.setter
    def stored(self, value):
        if value is None or isinstance(value, datetime):
            self._stored = value
            return

        try:
            self._stored = make_datetime(value)
        except TypeError as e:
            message = (
                f"Property 'stored' in a 'tincan.{self.__class__.__name__}' "
                f"object must be set with a "
                f"datetime.datetime, str, unicode, int, float, dict "
                f"or None.\n\n{repr(e)}"
            )
            raise TypeError(message) from e


    @stored.deleter
    def stored(self):
        del self._stored

    @property
    def authority(self):
        """Authority for Statement

        :setter: Tries to convert to :class:`tincan.Agent`
        :setter type: :class:`tincan.Agent`
        :rtype: :class:`tincan.Agent`

        """
        return self._authority

    @authority.setter
    def authority(self, value):
        if value is not None and not isinstance(value, Agent):
            value = Agent(value)
        self._authority = value

    @authority.deleter
    def authority(self):
        del self._authority

    @property
    def result(self):
        """Result for Statement

        :setter: Tries to convert to :class:`tincan.Result`
        :setter type: :class:`tincan.Result`
        :rtype: :class:`tincan.Result`

        """
        return self._result

    @result.setter
    def result(self, value):
        if value is not None and not isinstance(value, Result):
            value = Result(value)
        self._result = value

    @result.deleter
    def result(self):
        del self._result

    @property
    def version(self):
        """Version for Statement

        :setter: Tries to convert to unicode
        :setter type: unicode
        :rtype: unicode

        """
        return self._version

    @version.setter
    def version(self, value):
        if value is not None:
            if value == '':
                raise ValueError("Property version can not be set to an empty string")
            elif not isinstance(value, str):
                value = str(value)
        self._version = value

    @version.deleter
    def version(self):
        del self._version class Activity(SerializableBase, StatementTargetable):
    _props_req = [
        'id',
        'object_type'
    ]

    _props = [
        'definition',
    ]

    _props.extend(_props_req)

    def __init__(self, *args, **kwargs):
        self._id = None
        self._object_type = None
        self._definition = None

        super(Activity, self).__init__(*args, **kwargs)

    @property
    def id(self):
        """Id for Activity

        :setter: Sets the id
        :setter type: unicode
        :rtype: unicode

        """
        return self._id

    @id.setter
    def id(self, value):
        if value is not None:
            if value == '':
                raise ValueError(
                    f"Property 'id' in 'tincan.{self.__class__.__name__}' object must be not empty."
                )
        self._id = None if value is None else str(value)

    @property
    def object_type(self):
        """Object type for Activity. Will always be "Activity"

        :setter: Tries to convert to unicode
        :setter type: unicode
        :rtype: unicode

        """
        return self._object_type

    @object_type.setter
    def object_type(self, _):
        self._object_type = 'Activity'

    @property
    def definition(self):
        """Definition for Activity

        :setter: Tries to convert to :class:`tincan.ActivityDefinition`
        :setter type: :class:`tincan.ActivityDefinition`
        :rtype: :class:`tincan.ActivityDefinition`

        """
        return self._definition

    @definition.setter
    def definition(self, value):
        if value is not None and not isinstance(value, ActivityDefinition):
            value = ActivityDefinition(value)
        self._definition = value

    @definition.deleter
    def definition(self):
        del self._definition class StatementsResult(SerializableBase):
    _props_req = [
        'statements',
        'more',
    ]

    _props = []
    _props.extend(_props_req)

    def __init__(self, *args, **kwargs):
        self._statements = None
        self._more = None

        super(StatementsResult, self).__init__(*args, **kwargs)

    @property
    def statements(self):
        """Statements for StatementsResult

        :setter: Tries to convert each element to :class:`tincan.Statement`
        :setter type: list of :class:`tincan.Statement`
        :rtype: list of :class:`tincan.Statement`

        """
        return self._statements

    @statements.setter
    def statements(self, value):
        if value is None:
            self._statements = StatementList()
            return
        try:
            self._statements = StatementList(value)
        except Exception:
            raise TypeError(f"Property 'statements' in a 'tincan.{self.__class__.__name__}' object must be set with a "
                            f"list or None."
                            f"\n\n"
                            f"Tried to set it with a '{value.__class__.__name__}' object: {repr(value)}"
                            f"\n\n")


    @property
    def more(self):
        """More for StatementsResult

        :setter: Tries to convert to unicode
        :setter type: unicode
        :rtype: unicode

        """
        return self._more

    @more.setter
    def more(self, value):
        if value is None or isinstance(value, str):
            self._more = value
            return
        try:
            self._more = str(value)
        except Exception as e:
            msg = (
                f"Property 'more' in a 'tincan.{self.__class__.__name__}' object must be set with a "
                f"str or None."
            )
            msg += repr(e)
            raise TypeError(msg) from e class About(SerializableBase):
    """Stores info about this installation of `tincan`.

    :param version: The versions supported. This attribute is required.
    :type version: list of unicode
    :param extensions: Custom user data. This attribute is optional.
    :type extensions: :class:`tincan.Extensions`
    """

    _props_req = [
        'version',
    ]
    _props = [
        'extensions',
    ]
    _props.extend(_props_req)

    def __init__(self, *args, **kwargs):
        self._version = None
        self._extensions = None

        super(About, self).__init__(*args, **kwargs)

    @property
    def version(self):
        """Version for About

        :setter: Sets the version. If None is provided, defaults to
        `[tincan.Version.latest]`. If a string is provided,
        makes a 1-element list containing the string.
        :setter type: list | tuple | str | unicode | None
        :rtype: list
        """
        return self._version

    @version.setter
    def version(self, value):
        def check_version(version):
            """Checks a single version string for validity. Raises
            if invalid.

            :param version: the version string to check
            :type version: list of str or tuple of str or basestring or unicode
            :raises: ValueError
            """
            if version in ['1.0.3', '1.0.2', '1.0.1', '1.0.0', '0.95', '0.9']:
                return

            # Construct the error message
            if isinstance(value, (list, tuple)):
                value_str = repr(version) + ' in ' + repr(value)
            else:
                value_str = repr(version)

            msg = (
                f"Tried to set property 'version' in a 'tincan.{self.__class__.__name__}' object "
                f"with an invalid value: {value_str}\n"
                f"Allowed versions are: {', '.join(map(repr, Version.supported))}"
            )

            raise ValueError(msg)

        if value is None:
            self._version = [Version.latest]
        elif isinstance(value, str):
            check_version(value)
            self._version = [value]
        elif isinstance(value, (list, tuple)):
            for v in value:
                check_version(v)
            self._version = list(value)
        else:
            raise TypeError(
                f"Property 'version' in a 'tincan.{self.__class__.__name__}' object must be set with a "
                f"list, tuple, str, unicode or None. Tried to set it with: {repr(value)}"
            )

    @property
    def extensions(self):
        """Extensions for About

        :setter: Tries to convert to :class:`tincan.Extensions`. If None is provided,
        sets to an empty :class:`tincan.Extensions` dict.
        :setter type: :class:`tincan.Extensions` | dict | None
        :rtype: :class:`tincan.Extensions`
        """
        return self._extensions

    @extensions.setter
    def extensions(self, value):
        if isinstance(value, Extensions):
            self._extensions = value
        elif value is None:
            self._extensions = Extensions()
        else:
            try:
                self._extensions = Extensions(value)
            except Exception as e:
                msg = (
                    f"Property 'extensions' in a 'tincan.{self.__class__.__name__} object must be set with a "
                    f"tincan.Extensions, dict, or None.\n\n"
                )
                msg += repr(e)
                raise TypeError(msg)

    @extensions.deleter
    def extensions(self):
        del self._extensions class Version(object):
    supported = [
        '1.0.3',
        '1.0.2',
        '1.0.1',
        '1.0.0',
    ]
    latest = supported[0] class Base(object):
    _props = []

    def __init__(self, *args, **kwargs):
        """Initializes an object by checking the provided arguments
        against lists defined in the individual class. If required
        properties are defined, this method will set them to None by default.
        Optional properties will be ignored if they are not provided. The
        class may provide custom setters for properties, in which case those
        setters (see __setattr__ below).

        """
        if hasattr(self, '_props_req') and self._props_req:
            list(map(lambda k: setattr(self, k, None), self._props_req))

        new_kwargs = {}
        for obj in args:
            new_kwargs.update(obj if isinstance(obj, dict) else vars(obj))

        new_kwargs.update(kwargs)

        for key, value in new_kwargs.items():
            setattr(self, key, value)

    def __setattr__(self, attr, value):
        """Makes sure that only allowed properties are set. This method will
        call the proper attribute setter as defined in the class to provide
        additional error checking

        :param attr: the attribute being set
        :type attr: str
        :param value: the value to set

        """
        if attr.startswith('_') and attr[1:] in self._props:
            super(Base, self).__setattr__(attr, value)
        elif attr not in self._props:
            raise AttributeError(
                f"Property '{attr}' cannot be set on a 'tincan.{self.__class__.__name__}' object."
                f"Allowed properties: {', '.join(self._props)}"
            )
        else:
            super(Base, self).__setattr__(attr, value)

    def __eq__(self, other):
        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ class StateDocument(Document):
    """Extends :class:`tincan.Document` with Agent, Activity, and Registration fields; can be created from a dict,
    another :class:`tincan.Document`, or from kwargs.

    :param id: The id of this document
    :type id: unicode
    :param content_type: The content_type of the content of this document
    :type content_type: unicode
    :param content: The content of this document
    :type content: bytearray
    :param etag: The etag of this document
    :type etag: unicode
    :param timestamp: The time stamp of this document
    :type timestamp: :class:`datetime.datetime`
    :param agent: The agent object of this document
    :type agent: :class:`tincan.Agent`
    :param activity: The activity object of this document
    :type activity: :class:`Activity`
    :param registration: The registration id of the state for this document
    :type registration: unicode
    """

    _props_req = list(Document._props_req)

    _props_req.extend([
        'agent',
        'activity',
        'registration',
    ])

    _props = list(Document._props)

    _props.extend(_props_req)

    def __init__(self, *args, **kwargs):
        self._agent = None
        self._activity = None
        self._registration = None

        super(StateDocument, self).__init__(*args, **kwargs)

    @property
    def agent(self):
        """The Document's agent object

        :setter: Tries to convert to :class:`tincan.Agent`
        :setter type: :class:`tincan.Agent`
        :rtype: :class:`tincan.Agent`
        """
        return self._agent

    @agent.setter
    def agent(self, value):
        if not isinstance(value, Agent) and value is not None:
            try:
                value = Agent(value)
            except:
                raise TypeError(
                    f"Property 'agent' in 'tincan.{self.__class__.__name__}' must be set with a type "
                    f"that can be constructed into a tincan.Agent object."
                )
        self._agent = value

    @property
    def activity(self):
        """The Document's activity object

        :setter: Tries to convert to activity
        :setter type: :class:`tincan.Activity`
        :rtype: :class:`tincan.Activity`
        """
        return self._activity

    @activity.setter
    def activity(self, value):
        if not isinstance(value, Activity) and value is not None:
            try:
                value = Activity(value)
            except:
                raise TypeError(
                    f"Property 'activity' in 'tincan.{self.__class__.__name__}' must be set with a type "
                    f"that can be constructed into a tincan.Activity object."
                )
        self._activity = value

    @property
    def registration(self):
        """The Document registration id

        :setter: Tries to convert to unicode
        :setter type: str | unicode | :class:`uuid.UUID`
        :rtype: unicode
        """
        return self._registration

    @registration.setter
    def registration(self, value):
        if not isinstance(value, str) and value is not None:
            str(value)

        self._registration = value class ActivityProfileDocument(Document):
    """Extends :class:`tincan.Document` with an Activity field, can be created from a dict, another
    :class:`tincan.Document`, or from kwargs.

    :param id: The id of this document
    :type id: unicode
    :param content_type: The content_type of the content of this document
    :type content_type: unicode
    :param content: The content of this document
    :type content: bytearray
    :param etag: The etag of this document
    :type etag: unicode
    :param timestamp: The timestamp of this document
    :type timestamp: :class:`datetime.datetime`
    :param activity: The activity object of this document
    :type activity: :class:`tincan.Activity`
    """

    _props_req = list(Document._props_req)

    _props_req.extend([
        'activity',
    ])

    _props = list(Document._props)

    _props.extend(_props_req)

    def __init__(self, *args, **kwargs):
        self._activity = None
        super(ActivityProfileDocument, self).__init__(*args, **kwargs)

    @property
    def activity(self):
        """The Document's activity object

        :setter: Tries to convert to :class:`tincan.Activity`
        :setter type: :class:`tincan.Activity`
        :rtype: :class:`tincan.Activity`
        """
        return self._activity

    @activity.setter
    def activity(self, value):
        if not isinstance(value, Activity) and value is not None:
            try:
                value = Activity(value)
            except:
                raise TypeError(
                    f"Property 'activity' in 'tincan.{self.__class__.__name__}' must be set with a type "
                    f"that can be constructed into an tincan.Activity object."
                )
        self._activity = value class AgentProfileDocument(Document):
    """Extends :class:`tincan.Document` with an Agent field, can be created from a dict,
    another :class:`tincan.Document`, or from kwargs.

    :param id: The id of this document
    :type id: unicode
    :param content_type: The content_type of the content of this document
    :type content_type: unicode
    :param content: The content of this document
    :type content: bytearray
    :param etag: The etag of this document
    :type etag: unicode
    :param timestamp: The timestamp of this document
    :type timestamp: :class:`datetime.datetime`
    :param agent: The agent object of this document
    :type agent: :class:`tincan.Agent`
    """

    _props_req = list(Document._props_req)

    _props_req.extend([
        'agent',
    ])

    _props = list(Document._props)

    _props.extend(_props_req)

    def __init__(self, *args, **kwargs):
        self._agent = None
        super(AgentProfileDocument, self).__init__(*args, **kwargs)

    @property
    def agent(self):
        """The Document's agent object

        :setter: Tries to convert to :class:`tincan.Agent`
        :setter type: :class:`tincan.Agent`
        :rtype: :class:`tincan.Agent`
        """
        return self._agent

    @agent.setter
    def agent(self, value):
        if not isinstance(value, Agent) and value is not None:
            try:
                value = Agent(value)
            except:
                raise TypeError(
                    f"Property 'agent' in 'tincan.{self.__class__.__name__}' must be set with a type "
                    f"that can be constructed into an tincan.Agent object."
                )
        self._agent = value
import http.client
import json
import base64
from urllib.parse import urlparse, urlencode
from tincan.lrs_response import LRSResponse
from tincan.http_request import HTTPRequest
from tincan.statement_list import StatementList
from tincan.agent import Agent
from tincan.statement import Statement
from tincan.activity import Activity
from tincan.statements_result import StatementsResult
from tincan.about import About
from tincan.version import Version
from tincan.base import Base
from tincan.documents import (
    StateDocument,
    ActivityProfileDocument,
    AgentProfileDocument
)

        if lrs_response.success:
            lrs_response.content = Statement.from_json(lrs_response.data)

        return lrs_response

    def query_statements(self, query):
        """Query the LRS for statements with specified parameters

        :param query: Dictionary of query parameters and their values
        :type query: dict
        :return: LRS Response object with the returned StatementsResult object as content
        :rtype: :class:`tincan.lrs_response.LRSResponse`

        .. note::
           Optional query parameters are\n
               **statementId:** (*str*) ID of the Statement to fetch
               **voidedStatementId:** (*str*) ID of the voided Statement to fetch
               **agent:** (*Agent* |*Group*) Filter to return Statements for which the
               specified Agent or Group is the Actor
               **verb:** (*Verb id IRI*) Filter to return Statements matching the verb id
               **activity:** (*Activity id IRI*) Filter to return Statements for which the
               specified Activity is the Object
               **registration:** (*UUID*) Filter to return Statements matching the specified registration ID
               **related_activities:** (*bool*) Include Statements for which the Object,
               Context Activities or any Sub-Statement
               properties match the specified Activity
               **related_agents:** (*bool*) Include Statements for which the Actor, Object,
               Authority, Instructor, Team, or any Sub-Statement properties match the specified Agent
               **since:** (*datetime*) Filter to return Statements stored since the specified datetime
               **until:** (*datetime*) Filter to return Statements stored at or before the specified datetime
               **limit:** (*positive int*) Allow <limit> Statements to be returned. 0 indicates the
               maximum supported by the LRS
               **format:** (*str* {"ids"|"exact"|"canonical"}) Manipulates how the LRS handles
               importing and returning the statements
               **attachments:** (*bool*) If true, the LRS will use multipart responses and include
               all attachment data per Statement returned.
               Otherwise, application/json is used and no attachment information will be returned
               **ascending:** (*bool*) If true, the LRS will return results in ascending order of
               stored time (oldest first)
        """
        params = {}

        param_keys = [
            "registration",
            "since",
            "until",
            "limit",
            "ascending",
            "related_activities",
            "related_agents",
            "format",
            "attachments",
        ]

        for k, v in query.items():
            if v is not None:
                if k == "agent":
                    params[k] = v.to_json(self.version)
                elif k == "verb" or k == "activity":
                    params[k] = v.id
                elif k in param_keys:
                    params[k] = v

        request = HTTPRequest(
            method="GET",
            resource="statements"
        )
        request.query_params = params

        lrs_response = self._send_request(request)

        if lrs_response.success:
            lrs_response.content = StatementsResult.from_json(lrs_response.data)

        return lrs_response

    def more_statements(self, more_url):
        """Query the LRS for more statements

        :param more_url: URL from a StatementsResult object used to retrieve more statements
        :type more_url: str | unicode
        :return: LRS Response object with the returned StatementsResult object as content
        :rtype: :class:`tincan.lrs_response.LRSResponse`
        """
        if isinstance(more_url, StatementsResult):
            more_url = more_url.more

        more_url = self.get_endpoint_server_root() + more_url

        request = HTTPRequest(
            method="GET",
            resource=more_url
        )

        lrs_response = self._send_request(request)

        if lrs_response.success:
            lrs_response.content = StatementsResult.from_json(lrs_response.data)

        return lrs_response

    def retrieve_state_ids(self, activity, agent, registration=None, since=None):
        """Retrieve state id's from the LRS with the provided parameters

        :param activity: Activity object of desired states
        :type activity: :class:`tincan.activity.Activity`
        :param agent: Agent object of desired states
        :type agent: :class:`tincan.agent.Agent`
        :param registration: Registration UUID of desired states
        :type registration: str | unicode
        :param since: Retrieve state id's since this time
        :type since: str | unicode
        :return: LRS Response object with the retrieved state id's as content
        :rtype: :class:`tincan.lrs_response.LRSResponse`
        """
        if not isinstance(activity, Activity):
            activity = Activity(activity)

        if not isinstance(agent, Agent):

            agent = Agent(agent)