Example #1
0
    def __init__(self,
                 project_id,
                 write_key=None,
                 read_key=None,
                 persistence_strategy=None):
        """ Initializes a KeenClient object.

        :param project_id: the Keen IO project ID
        :param write_key: a Keen IO Scoped Key for Writes
        :param read_key: a Keen IO Scoped Key for Reads
        :param persistence_strategy: optional, the strategy to use to persist
        the event
        """
        super(KeenClient, self).__init__()

        # do some validation
        self.check_project_id(project_id)

        # Set up an api client to be used for querying and optionally passed
        # into a default persistence strategy.
        self.api = KeenApi(project_id, write_key=write_key, read_key=read_key)

        if persistence_strategy:
            # validate the given persistence strategy
            if not isinstance(persistence_strategy, BasePersistenceStrategy):
                raise exceptions.InvalidPersistenceStrategyError()
        if not persistence_strategy:
            # setup a default persistence strategy
            persistence_strategy = persistence_strategies \
                .DirectPersistenceStrategy(self.api)

        self.project_id = project_id
        self.persistence_strategy = persistence_strategy
class DirectPersistenceStrategy(BasePersistenceStrategy):
    """
    A persistence strategy that saves directly to Keen and bypasses any local
    cache.
    """

    def __init__(self, project_id, write_key, read_key):
        """ Initializer for DirectPersistenceStrategy.

        :param api: the Keen Api object used to communicate with the Keen API
        """
        self.project_id = project_id
        self.write_key = write_key
        self.read_key = read_key
        self.make_api()
        super(DirectPersistenceStrategy, self).__init__()

    def make_api(self):
        self.api = KeenApi(self.project_id, write_key=self.write_key, read_key=self.read_key)

    def persist(self, event):
        """ Posts the given event directly to the Keen API.

        :param event: an Event to persist
        """
        self.api.post_event(event)
    def all(self):
        """
        Gets all saved queries for a project from the Keen IO API.
        Master key must be set.
        """
        keen_api = KeenApi(self.project_id, master_key=self.master_key)
        self._check_for_master_key()
        url = "{0}/{1}/projects/{2}/queries/saved".format(
            keen_api.base_url, keen_api.api_version, self.project_id)
        response = keen_api.fulfill("get", url, headers=self._headers())

        return response.json()
    def all(self):
        """
        Gets all saved queries for a project from the Keen IO API.
        Master key must be set.
        """
        keen_api = KeenApi(self.project_id, master_key=self.master_key)
        self._check_for_master_key()
        url = "{0}/{1}/projects/{2}/queries/saved".format(
            keen_api.base_url, keen_api.api_version, self.project_id
        )
        response = keen_api.fulfill("get", url, headers=self._headers())

        return response.json()
    def delete(self, query_name):
        """
        Deletes a saved query from a project with a query name.
        Master key must be set.
        """
        keen_api = KeenApi(self.project_id, master_key=self.master_key)
        self._check_for_master_key()
        url = "{0}/{1}/projects/{2}/queries/saved/{3}".format(
            keen_api.base_url, keen_api.api_version, self.project_id,
            query_name)
        response = keen_api.fulfill("delete", url, headers=self._headers())
        keen_api._error_handling(response)

        return True
    def delete(self, query_name):
        """
        Deletes a saved query from a project with a query name.
        Master key must be set.
        """
        keen_api = KeenApi(self.project_id, master_key=self.master_key)
        self._check_for_master_key()
        url = "{0}/{1}/projects/{2}/queries/saved/{3}".format(
            keen_api.base_url, keen_api.api_version, self.project_id, query_name
        )
        response = keen_api.fulfill("delete", url, headers=self._headers())
        keen_api._error_handling(response)

        return True
    def get(self, query_name):
        """
        Gets a single saved query for a project from the Keen IO API given a
        query name.
        Master key must be set.
        """
        keen_api = KeenApi(self.project_id, master_key=self.master_key)
        self._check_for_master_key()
        url = "{0}/{1}/projects/{2}/queries/saved/{3}".format(
            keen_api.base_url, keen_api.api_version, self.project_id,
            query_name)
        response = keen_api.fulfill("get", url, headers=self._headers())
        keen_api._error_handling(response)

        return response.json()
    def create(self, query_name, saved_query):
        """
        Creates the saved query via a PUT request to Keen IO Saved Query endpoint. Master key must be set.
        """
        keen_api = KeenApi(self.project_id, master_key=self.master_key)
        self._check_for_master_key()
        url = "{0}/{1}/projects/{2}/queries/saved/{3}".format(
            keen_api.base_url, keen_api.api_version, self.project_id, query_name
        )
        response = keen_api.fulfill(
            "put", url, headers=self._headers(), data=saved_query
        )
        keen_api._error_handling(response)

        return response.json()
    def get(self, query_name):
        """
        Gets a single saved query for a project from the Keen IO API given a
        query name.
        Master key must be set.
        """
        keen_api = KeenApi(self.project_id, master_key=self.master_key)
        self._check_for_master_key()
        url = "{0}/{1}/projects/{2}/queries/saved/{3}".format(
            keen_api.base_url, keen_api.api_version, self.project_id, query_name
        )
        response = keen_api.fulfill("get", url, headers=self._headers())
        keen_api._error_handling(response)

        return response.json()
Example #10
0
    def __init__(self, project_id, write_key=None, read_key=None,
                 persistence_strategy=None, json_encoder=None):
        """ Initializes a KeenClient object.

        :param project_id: the Keen IO project ID
        :param write_key: a Keen IO Scoped Key for Writes
        :param read_key: a Keen IO Scoped Key for Reads
        :param persistence_strategy: optional, the strategy to use to persist
        the event
        :param json_encoder: optional, class for custom encoding, inherited
        from json.JSONEncoder
        """
        super(KeenClient, self).__init__()

        # do some validation
        self.check_project_id(project_id)

        # Set up an api client to be used for querying and optionally passed
        # into a default persistence strategy.
        self.api = KeenApi(project_id, write_key=write_key, read_key=read_key, json_encoder=json_encoder)

        if persistence_strategy:
            # validate the given persistence strategy
            if not isinstance(persistence_strategy, BasePersistenceStrategy):
                raise exceptions.InvalidPersistenceStrategyError()
        if not persistence_strategy:
            # setup a default persistence strategy
            persistence_strategy = persistence_strategies \
                .DirectPersistenceStrategy(self.api)

        self.project_id = project_id
        self.persistence_strategy = persistence_strategy
Example #11
0
    def create(self, query_name, saved_query):
        """
        Creates the saved query via a PUT request to Keen IO Saved Query endpoint. Master key must be set.
        """
        keen_api = KeenApi(self.project_id, master_key=self.master_key)
        self._check_for_master_key()
        url = "{0}/{1}/projects/{2}/queries/saved/{3}".format(
            keen_api.base_url, keen_api.api_version, self.project_id,
            query_name)
        response = keen_api.fulfill("put",
                                    url,
                                    headers=utilities.headers(self.master_key),
                                    data=saved_query)
        keen_api._error_handling(response)

        return response.json()
Example #12
0
    def results(self, query_name):
        """
        Gets a single saved query with a 'result' object for a project from thei
        Keen IO API given a query name.
        Read or Master key must be set.
        """
        keen_api = KeenApi(self.project_id, master_key=self.master_key)
        self._check_for_master_or_read_key()
        url = "{0}/{1}/projects/{2}/queries/saved/{3}/result".format(
            keen_api.base_url, keen_api.api_version, self.project_id,
            query_name)
        key = self.master_key if self.master_key else self.read_key
        response = keen_api.fulfill("get", url, headers=utilities.headers(key))
        keen_api._error_handling(response)

        return response.json()
    def results(self, query_name):
        """
        Gets a single saved query with a 'result' object for a project from thei
        Keen IO API given a query name.
        Read or Master key must be set.
        """
        keen_api = KeenApi(self.project_id, master_key=self.master_key)
        self._check_for_master_or_read_key()
        url = "{0}/{1}/projects/{2}/queries/saved/{3}/result".format(
            keen_api.base_url, keen_api.api_version, self.project_id, query_name
        )
        key = self.master_key if self.master_key else self.read_key
        response = keen_api.fulfill("get", url, headers={"Authorization": key })
        keen_api._error_handling(response)

        return response.json()
Example #14
0
class KeenClient(object):

    """ The Keen Client is the main object to use to interface with Keen. It
    requires a project ID and one or both of write_key and read_key.

    Optionally, you can also specify a persistence strategy to elect how
    events are handled when they're added. The default strategy is to send
    the event directly to Keen, in-line. This may not always be the best
    idea, though, so we support other strategies (such as persisting
    to a local Redis queue for later processing).
    """

    def __init__(self, project_id, write_key=None, read_key=None,
                 persistence_strategy=None, json_encoder=None):
        """ Initializes a KeenClient object.

        :param project_id: the Keen IO project ID
        :param write_key: a Keen IO Scoped Key for Writes
        :param read_key: a Keen IO Scoped Key for Reads
        :param persistence_strategy: optional, the strategy to use to persist
        the event
        :param json_encoder: optional, class for custom encoding, inherited
        from json.JSONEncoder
        """
        super(KeenClient, self).__init__()

        # do some validation
        self.check_project_id(project_id)

        # Set up an api client to be used for querying and optionally passed
        # into a default persistence strategy.
        self.api = KeenApi(project_id, write_key=write_key, read_key=read_key, json_encoder=json_encoder)

        if persistence_strategy:
            # validate the given persistence strategy
            if not isinstance(persistence_strategy, BasePersistenceStrategy):
                raise exceptions.InvalidPersistenceStrategyError()
        if not persistence_strategy:
            # setup a default persistence strategy
            persistence_strategy = persistence_strategies \
                .DirectPersistenceStrategy(self.api)

        self.project_id = project_id
        self.persistence_strategy = persistence_strategy

    if sys.version_info[0] < 3:
        @staticmethod
        def check_project_id(project_id):
            ''' Python 2.x-compatible string typecheck. '''

            if not project_id or not isinstance(project_id, basestring):
                raise exceptions.InvalidProjectIdError(project_id)
    else:
        @staticmethod
        def check_project_id(project_id):
            ''' Python 3.x-compatible string typecheck. '''

            if not project_id or not isinstance(project_id, str):
                raise exceptions.InvalidProjectIdError(project_id)

    def add_event(self, event_collection, event_body, timestamp=None):
        """ Adds an event.

        Depending on the persistence strategy of the client,
        this will either result in the event being uploaded to Keen
        immediately or will result in saving the event to some local cache.

        :param event_collection: the name of the collection to insert the
        event to
        :param event_body: dict, the body of the event to insert the event to
        :param timestamp: datetime, optional, the timestamp of the event
        """
        event = Event(self.project_id, event_collection, event_body,
                      timestamp=timestamp)
        self.persistence_strategy.persist(event)

    def add_events(self, events):
        """ Adds a batch of events.

        Depending on the persistence strategy of the client,
        this will either result in the event being uploaded to Keen
        immediately or will result in saving the event to some local cache.

        :param events: dictionary of events
        """
        self.persistence_strategy.batch_persist(events)

    def generate_image_beacon(self, event_collection, event_body, timestamp=None):
        """ Generates an image beacon URL.

        :param event_collection: the name of the collection to insert the
        event to
        :param event_body: dict, the body of the event to insert the event to
        :param timestamp: datetime, optional, the timestamp of the event
        """
        event = Event(self.project_id, event_collection, event_body,
                      timestamp=timestamp)
        event_json = event.to_json(json_encoder=self.api.json_encoder)
        return "{0}/{1}/projects/{2}/events/{3}?api_key={4}&data={5}".format(
            self.api.base_url, self.api.api_version, self.project_id, self._url_escape(event_collection),
            self.api.write_key.decode(sys.getdefaultencoding()), self._base64_encode(event_json)
        )

    def _base64_encode(self, string_to_encode):
        """ Base64 encodes a string, with either Python 2 or 3.

        :param string_to_encode: the string to encode
        """
        try:
            # python 2
            return base64.b64encode(string_to_encode)
        except TypeError:
            # python 3
            encoding = sys.getdefaultencoding()
            base64_bytes = base64.b64encode(bytes(string_to_encode, encoding))
            return base64_bytes.decode(encoding)

    def _url_escape(self, url):
        try:
            import urllib
            return urllib.quote(url)
        except AttributeError:
            import urllib.parse
            return urllib.parse.quote(url)

    def count(self, event_collection, timeframe=None, timezone=None, interval=None, filters=None, group_by=None):
        """ Performs a count query

        Counts the number of events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection, timeframe=timeframe, timezone=timezone,
                                 interval=interval, filters=filters, group_by=group_by)
        return self.api.query("count", params)

    def sum(self, event_collection, target_property, timeframe=None, timezone=None, interval=None, filters=None,
            group_by=None):
        """ Performs a sum query

        Adds the values of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection, timeframe=timeframe, timezone=timezone,
                                 interval=interval, filters=filters, group_by=group_by, target_property=target_property)
        return self.api.query("sum", params)

    def minimum(self, event_collection, target_property, timeframe=None, timezone=None, interval=None, filters=None,
                group_by=None):
        """ Performs a minimum query

        Finds the minimum value of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection, timeframe=timeframe, timezone=timezone,
                                 interval=interval, filters=filters, group_by=group_by, target_property=target_property)
        return self.api.query("minimum", params)

    def maximum(self, event_collection, target_property, timeframe=None, timezone=None, interval=None, filters=None,
                group_by=None):
        """ Performs a maximum query

        Finds the maximum value of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection, timeframe=timeframe, timezone=timezone,
                                 interval=interval, filters=filters, group_by=group_by, target_property=target_property)
        return self.api.query("maximum", params)

    def average(self, event_collection, target_property, timeframe=None, timezone=None, interval=None, filters=None,
                group_by=None):
        """ Performs a average query

        Finds the average of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection, timeframe=timeframe, timezone=timezone,
                                 interval=interval, filters=filters, group_by=group_by, target_property=target_property)
        return self.api.query("average", params)

    def count_unique(self, event_collection, target_property, timeframe=None, timezone=None, interval=None,
                     filters=None, group_by=None):
        """ Performs a count unique query

        Counts the unique values of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection, timeframe=timeframe, timezone=timezone,
                                 interval=interval, filters=filters, group_by=group_by, target_property=target_property)
        return self.api.query("count_unique", params)

    def select_unique(self, event_collection, target_property, timeframe=None, timezone=None, interval=None,
                      filters=None, group_by=None):
        """ Performs a select unique query

        Returns an array of the unique values of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection, timeframe=timeframe, timezone=timezone,
                                 interval=interval, filters=filters, group_by=group_by, target_property=target_property)
        return self.api.query("select_unique", params)

    def extraction(self, event_collection, timeframe=None, timezone=None, filters=None, latest=None, email=None):
        """ Performs a data extraction

        Returns either a JSON object of events or a response
         indicating an email will be sent to you with data.

        :param event_collection: string, the name of the collection to query
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param latest: int, the number of most recent records you'd like to return
        :param email: string, optional string containing an email address to email results to

        """
        params = self.get_params(event_collection=event_collection, timeframe=timeframe, timezone=timezone,
                                 filters=filters, latest=latest, email=email)
        return self.api.query("extraction", params)

    def funnel(self, steps, timeframe=None, timezone=None):
        """ Performs a Funnel query

        Returns an object containing the results for each step of the funnel.

        :param steps: array of dictionaries, one for each step. example:
        [{"event_collection":"signup","actor_property":"user.id"},
        {"event_collection":"purchase","actor_property:"user.id"}]
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds

        """
        params = self.get_params(steps=steps, timeframe=timeframe, timezone=timezone)
        return self.api.query("funnel", params)

    def multi_analysis(self, event_collection, analyses, timeframe=None,
                       interval=None, timezone=None, filters=None, group_by=None):
        """ Performs a multi-analysis query

        Returns a dictionary of analysis results.

        :param event_collection: string, the name of the collection to query
        :param analyses: dict, the types of analyses you'd like to run.  example:
        {"total money made":{"analysis_type":"sum","target_property":"purchase.price",
        "average price":{"analysis_type":"average","target_property":"purchase.price"}
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(
            event_collection=event_collection,
            timeframe=timeframe,
            interval=interval,
            timezone=timezone,
            filters=filters,
            group_by=group_by,
            analyses=analyses)

        return self.api.query("multi_analysis", params)

    def get_params(self, event_collection=None, timeframe=None, timezone=None, interval=None, filters=None,
                   group_by=None, target_property=None, latest=None, email=None, analyses=None, steps=None):
        params = {}
        if event_collection:
            params["event_collection"] = event_collection
        if timeframe:
            if isinstance(timeframe, dict):
                params["timeframe"] = json.dumps(timeframe)
            else:
                params["timeframe"] = timeframe
        if timezone:
            params["timezone"] = timezone
        if interval:
            params["interval"] = interval
        if filters:
            params["filters"] = json.dumps(filters)
        if group_by:
            if isinstance(group_by, list):
                params["group_by"] = json.dumps(group_by)
            else:
                params["group_by"] = group_by
        if target_property:
            params["target_property"] = target_property
        if latest:
            params["latest"] = latest
        if email:
            params["email"] = email
        if analyses:
            params["analyses"] = json.dumps(analyses)
        if steps:
            params["steps"] = json.dumps(steps)

        return params
Example #15
0
class KeenClient(object):
    """ The Keen Client is the main object to use to interface with Keen. It
    requires a project ID and one or both of write_key and read_key.

    Optionally, you can also specify a persistence strategy to elect how
    events are handled when they're added. The default strategy is to send
    the event directly to Keen, in-line. This may not always be the best
    idea, though, so we support other strategies (such as persisting
    to a local Redis queue for later processing).
    """
    def __init__(self,
                 project_id,
                 write_key=None,
                 read_key=None,
                 persistence_strategy=None):
        """ Initializes a KeenClient object.

        :param project_id: the Keen IO project ID
        :param write_key: a Keen IO Scoped Key for Writes
        :param read_key: a Keen IO Scoped Key for Reads
        :param persistence_strategy: optional, the strategy to use to persist
        the event
        """
        super(KeenClient, self).__init__()

        # do some validation
        self.check_project_id(project_id)

        # Set up an api client to be used for querying and optionally passed
        # into a default persistence strategy.
        self.api = KeenApi(project_id, write_key=write_key, read_key=read_key)

        if persistence_strategy:
            # validate the given persistence strategy
            if not isinstance(persistence_strategy, BasePersistenceStrategy):
                raise exceptions.InvalidPersistenceStrategyError()
        if not persistence_strategy:
            # setup a default persistence strategy
            persistence_strategy = persistence_strategies \
                .DirectPersistenceStrategy(self.api)

        self.project_id = project_id
        self.persistence_strategy = persistence_strategy

    if sys.version_info[0] < 3:

        @staticmethod
        def check_project_id(project_id):
            ''' Python 2.x-compatible string typecheck. '''

            if not project_id or not isinstance(project_id, basestring):
                raise exceptions.InvalidProjectIdError(project_id)
    else:

        @staticmethod
        def check_project_id(project_id):
            ''' Python 3.x-compatible string typecheck. '''

            if not project_id or not isinstance(project_id, str):
                raise exceptions.InvalidProjectIdError(project_id)

    def add_event(self, event_collection, event_body, timestamp=None):
        """ Adds an event.

        Depending on the persistence strategy of the client,
        this will either result in the event being uploaded to Keen
        immediately or will result in saving the event to some local cache.

        :param event_collection: the name of the collection to insert the
        event to
        :param event_body: dict, the body of the event to insert the event to
        :param timestamp: datetime, optional, the timestamp of the event
        """
        event = Event(self.project_id,
                      event_collection,
                      event_body,
                      timestamp=timestamp)
        self.persistence_strategy.persist(event)

    def add_events(self, events):
        """ Adds a batch of events.

        Depending on the persistence strategy of the client,
        this will either result in the event being uploaded to Keen
        immediately or will result in saving the event to some local cache.

        :param events: dictionary of events
        """
        self.persistence_strategy.batch_persist(events)

    def generate_image_beacon(self,
                              event_collection,
                              event_body,
                              timestamp=None):
        """ Generates an image beacon URL.

        :param event_collection: the name of the collection to insert the
        event to
        :param event_body: dict, the body of the event to insert the event to
        :param timestamp: datetime, optional, the timestamp of the event
        """
        event = Event(self.project_id,
                      event_collection,
                      event_body,
                      timestamp=timestamp)
        event_json = event.to_json()
        return "{0}/{1}/projects/{2}/events/{3}?api_key={4}&data={5}".format(
            self.api.base_url, self.api.api_version, self.project_id,
            self._url_escape(event_collection),
            self.api.write_key.decode(sys.getdefaultencoding()),
            self._base64_encode(event_json))

    def _base64_encode(self, string_to_encode):
        """ Base64 encodes a string, with either Python 2 or 3.

        :param string_to_encode: the string to encode
        """
        try:
            # python 2
            return base64.b64encode(string_to_encode)
        except TypeError:
            # python 3
            encoding = sys.getdefaultencoding()
            base64_bytes = base64.b64encode(bytes(string_to_encode, encoding))
            return base64_bytes.decode(encoding)

    def _url_escape(self, url):
        try:
            import urllib
            return urllib.quote(url)
        except AttributeError:
            import urllib.parse
            return urllib.parse.quote(url)

    def count(self,
              event_collection,
              timeframe=None,
              timezone=None,
              interval=None,
              filters=None,
              group_by=None):
        """ Performs a count query

        Counts the number of events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection,
                                 timeframe=timeframe,
                                 timezone=timezone,
                                 interval=interval,
                                 filters=filters,
                                 group_by=group_by)
        return self.api.query("count", params)

    def sum(self,
            event_collection,
            target_property,
            timeframe=None,
            timezone=None,
            interval=None,
            filters=None,
            group_by=None):
        """ Performs a sum query

        Adds the values of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection,
                                 timeframe=timeframe,
                                 timezone=timezone,
                                 interval=interval,
                                 filters=filters,
                                 group_by=group_by,
                                 target_property=target_property)
        return self.api.query("sum", params)

    def minimum(self,
                event_collection,
                target_property,
                timeframe=None,
                timezone=None,
                interval=None,
                filters=None,
                group_by=None):
        """ Performs a minimum query

        Finds the minimum value of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection,
                                 timeframe=timeframe,
                                 timezone=timezone,
                                 interval=interval,
                                 filters=filters,
                                 group_by=group_by,
                                 target_property=target_property)
        return self.api.query("minimum", params)

    def maximum(self,
                event_collection,
                target_property,
                timeframe=None,
                timezone=None,
                interval=None,
                filters=None,
                group_by=None):
        """ Performs a maximum query

        Finds the maximum value of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection,
                                 timeframe=timeframe,
                                 timezone=timezone,
                                 interval=interval,
                                 filters=filters,
                                 group_by=group_by,
                                 target_property=target_property)
        return self.api.query("maximum", params)

    def average(self,
                event_collection,
                target_property,
                timeframe=None,
                timezone=None,
                interval=None,
                filters=None,
                group_by=None):
        """ Performs a average query

        Finds the average of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection,
                                 timeframe=timeframe,
                                 timezone=timezone,
                                 interval=interval,
                                 filters=filters,
                                 group_by=group_by,
                                 target_property=target_property)
        return self.api.query("average", params)

    def count_unique(self,
                     event_collection,
                     target_property,
                     timeframe=None,
                     timezone=None,
                     interval=None,
                     filters=None,
                     group_by=None):
        """ Performs a count unique query

        Counts the unique values of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection,
                                 timeframe=timeframe,
                                 timezone=timezone,
                                 interval=interval,
                                 filters=filters,
                                 group_by=group_by,
                                 target_property=target_property)
        return self.api.query("count_unique", params)

    def select_unique(self,
                      event_collection,
                      target_property,
                      timeframe=None,
                      timezone=None,
                      interval=None,
                      filters=None,
                      group_by=None):
        """ Performs a select unique query

        Returns an array of the unique values of a target property for events that meet the given criteria.

        :param event_collection: string, the name of the collection to query
        :param target_property: string, the name of the event property you would like use
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection,
                                 timeframe=timeframe,
                                 timezone=timezone,
                                 interval=interval,
                                 filters=filters,
                                 group_by=group_by,
                                 target_property=target_property)
        return self.api.query("select_unique", params)

    def extraction(self,
                   event_collection,
                   timeframe=None,
                   timezone=None,
                   filters=None,
                   latest=None,
                   email=None):
        """ Performs a data extraction

        Returns either a JSON object of events or a response
         indicating an email will be sent to you with data.

        :param event_collection: string, the name of the collection to query
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param latest: int, the number of most recent records you'd like to return
        :param email: string, optional string containing an email address to email results to

        """
        params = self.get_params(event_collection=event_collection,
                                 timeframe=timeframe,
                                 timezone=timezone,
                                 filters=filters,
                                 latest=latest,
                                 email=email)
        return self.api.query("extraction", params)

    def funnel(self, steps, timeframe=None, timezone=None):
        """ Performs a Funnel query

        Returns an object containing the results for each step of the funnel.

        :param steps: array of dictionaries, one for each step. example:
        [{"event_collection":"signup","actor_property":"user.id"},
        {"event_collection":"purchase","actor_property:"user.id"}]
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds

        """
        params = self.get_params(steps=steps,
                                 timeframe=timeframe,
                                 timezone=timezone)
        return self.api.query("funnel", params)

    def multi_analysis(self,
                       event_collection,
                       analyses,
                       timeframe=None,
                       interval=None,
                       timezone=None,
                       filters=None,
                       group_by=None):
        """ Performs a multi-analysis query

        Returns a dictionary of analysis results.

        :param event_collection: string, the name of the collection to query
        :param analyses: dict, the types of analyses you'd like to run.  example:
        {"total money made":{"analysis_type":"sum","target_property":"purchase.price",
        "average price":{"analysis_type":"average","target_property":"purchase.price"}
        :param timeframe: string or dict, the timeframe in which the events
        happened example: "previous_7_days"
        :param interval: string, the time interval used for measuring data over
        time example: "daily"
        :param timezone: int, the timezone you'd like to use for the timeframe
        and interval in seconds
        :param filters: array of dict, contains the filters you'd like to apply to the data
        example: {["property_name":"device", "operator":"eq", "property_value":"iPhone"}]
        :param group_by: string or array of strings, the name(s) of the properties you would
        like to group you results by.  example: "customer.id" or ["browser","operating_system"]

        """
        params = self.get_params(event_collection=event_collection,
                                 timeframe=timeframe,
                                 interval=interval,
                                 timezone=timezone,
                                 filters=filters,
                                 group_by=group_by,
                                 analyses=analyses)

        return self.api.query("multi_analysis", params)

    def get_params(self,
                   event_collection=None,
                   timeframe=None,
                   timezone=None,
                   interval=None,
                   filters=None,
                   group_by=None,
                   target_property=None,
                   latest=None,
                   email=None,
                   analyses=None,
                   steps=None):
        params = {}
        if event_collection:
            params["event_collection"] = event_collection
        if timeframe:
            if type(timeframe) is dict:
                params["timeframe"] = json.dumps(timeframe)
            else:
                params["timeframe"] = timeframe
        if timezone:
            params["timezone"] = timezone
        if interval:
            params["interval"] = interval
        if filters:
            params["filters"] = json.dumps(filters)
        if group_by:
            if type(group_by) is list:
                params["group_by"] = json.dumps(group_by)
            else:
                params["group_by"] = group_by
        if target_property:
            params["target_property"] = target_property
        if latest:
            params["latest"] = latest
        if email:
            params["email"] = email
        if analyses:
            params["analyses"] = json.dumps(analyses)
        if steps:
            params["steps"] = json.dumps(steps)

        return params
 def make_api(self):
     self.api = KeenApi(self.project_id, write_key=self.write_key, read_key=self.read_key)