Пример #1
0
    def __init__(self,
                 project_id,
                 api_key,
                 base_url=None,
                 get_timeout=None,
                 post_timeout=None,
                 max_workers=2
                 ):
        """Initializes a ConnectClient object.

        :param project_id: the Connect project id
        :param api_key: the Connect api key for the project
        :param base_url: alternate url to use for testing purposes
        :param get_timeout: timeout for get requests in seconds
        :param post_timeout: timeout for post requests in seconds
        :param max_workers: maximum number of workers for the ThreadPoolExecutor
        """

        if get_timeout is None:
            get_timeout = 60

        if post_timeout is None:
            post_timeout = 60

        self._api = ConnectApi(project_id=project_id,
                              api_key=api_key,
                              base_url=base_url,
                              post_timeout=post_timeout,
                              get_timeout=get_timeout)
                    
        executor = futures.ThreadPoolExecutor(max_workers=max_workers)    
        self._executor = executor
Пример #2
0
    def setup_method(self, method):

        batched = defaultdict(list)
        for e in MULTI_EVENT_DATA:
            e = Event(COLLECTION_NAME, e)
            batched[COLLECTION_NAME].append(e.body)

        self.multi_events = batched

        self.single_event = Event(COLLECTION_NAME, SINGLE_EVENT_DATA)
        self.connect = ConnectApi(project_id=PROJECT_ID, api_key=API_PUSH_KEY)
Пример #3
0
class ConnectClient(object):
    """
    The ConnectClient is the main object to use to push and query events in
    Connect.
    """

    def __init__(self,
                 project_id,
                 api_key,
                 base_url=None,
                 get_timeout=None,
                 post_timeout=None,
                 max_workers=2
                 ):
        """Initializes a ConnectClient object.

        :param project_id: the Connect project id
        :param api_key: the Connect api key for the project
        :param base_url: alternate url to use for testing purposes
        :param get_timeout: timeout for get requests in seconds
        :param post_timeout: timeout for post requests in seconds
        :param max_workers: maximum number of workers for the ThreadPoolExecutor
        """

        if get_timeout is None:
            get_timeout = 60

        if post_timeout is None:
            post_timeout = 60

        self._api = ConnectApi(project_id=project_id,
                              api_key=api_key,
                              base_url=base_url,
                              post_timeout=post_timeout,
                              get_timeout=get_timeout)
                    
        executor = futures.ThreadPoolExecutor(max_workers=max_workers)    
        self._executor = executor
        
    def push_event(self, collection_name, event):
        """ Validate and process single event
        then push directly to connnect

        :param collection_name: the collection name for the event in the
        project
        :param event: dict with the event data to push to connect
        :param async: boolean push event asynchronously
        
        returns Future object for api call
        """

        try:            
            processed_event = Event(collection_name, event)
        except exceptions.InvalidEventError as e:
            future = futures.Future()                         
            future.set_exception(e)
            return future
        
        return self._executor.submit(self._api.post_event, processed_event)
        
    def push_events(self, events):
        """ Validate and process multiple events
        then push to connect as a batch
       
        :param events: dict keyed by collection nane with a list of dict 
        objects as value

        returns future object
        """
        
        if not isinstance(events, (dict)):
            ex =  exceptions.InvalidEventError(
                "Events must be a list of dict objects")
            future = futures.Future()            
            future.set_exception(ex)
            return future
            
        events_by_collection = defaultdict(list)
        for collection in events:
            if isinstance(events[collection], list):
                for event in events[collection]:
                    try:                
                        e = Event(collection, event)
                    except exceptions.InvalidEventError as ex:
                        future = futures.Future()                    
                        future.set_exception(ex)
                        return future
                    events_by_collection[collection].append(e.body)
            
            else:
                ex =  exceptions.InvalidEventError(
                   "Events must be a list of dict objects")
                future = futures.Future()            
                future.set_exception(ex)
                return future
            
        return self._executor.submit(self._api.post_events, events_by_collection)

    
    def _push_processed_events(self, events):
        """ Push multi processed (validated) events
        :param events: list of Event objects
        
        returns PushBatchResponse object
        
        """
        events_by_collection = defaultdict(list)
        for e in events:
               collection_name = e.collection_name
               events_by_collection[collection_name].append(e.body)
            
        return self._api.post_events(events_by_collection)

    def _export_events(self, collection_name, parameters): # pragma: no cover
        """ Export entire events from connect based on a supplied filter.
        Returns a list of dict objects

        :param collection_name: the name of the collection where the event
        lives
        :param parameters: connect filter to retrieve specific events
        """
        results, status_code = self._api._get_export(collection_name,parameters)
        response = {"http_status_code": status_code,
                    "results": results}
        return response

    def _bulk_export(self, collection_name, parameters): # pragma: no cover
        """ Initiate bulk export from connect to S3
        returns the export_id to track the status of the export
        
        :param collection_name: name of the collection
        :param parameters: dict object of parameters for export as defined in
        the HTTP connect doco: 
        http://docs.getconnect.io/http#exporting-events
        """        
        status_url  = self._api._post_export(collection_name,parameters)
        status_url_split = status_url.replace("https://","").split("/")
        export_id = status_url_split[4]

        return export_id

    def _bulk_export_status(self, collection_name, export_id): # pragma: no cover
        """ Get the correct status of a S3 bulk export
        :param collection_name: name of collection
        :parma export_id: as supplied by bulk_export methid
        """
        results, status_code = self._api._get_export_status(collection_name, 
                                                          export_id)
        response = {"results" : results,
                    "http_status_code" : status_code}
        return response        
Пример #4
0
class TestConnectAPI:
    def setup_method(self, method):

        batched = defaultdict(list)
        for e in MULTI_EVENT_DATA:
            e = Event(COLLECTION_NAME, e)
            batched[COLLECTION_NAME].append(e.body)

        self.multi_events = batched

        self.single_event = Event(COLLECTION_NAME, SINGLE_EVENT_DATA)
        self.connect = ConnectApi(project_id=PROJECT_ID, api_key=API_PUSH_KEY)

    def test_init(self, post):
        assert PROJECT_ID == self.connect._project_id
        assert API_PUSH_KEY == self.connect._api_key
        assert "https://api.getconnect.io" == self.connect._base_url
        assert 60 == self.connect._get_timeout
        assert 60 == self.connect._post_timeout
        assert isinstance(self.connect._session, Session)

        connect = ConnectApi(
            project_id=PROJECT_ID, api_key=API_PUSH_KEY, base_url="myurl", post_timeout=10, get_timeout=5
        )
        assert connect._base_url == "myurl"
        assert connect._post_timeout == 10
        assert connect._get_timeout == 5

    def test_post_event(self, post):
        # 200 - empty response
        mocked_response = mocked_connect_response(200, None)
        post.return_value = mocked_response
        result = self.connect.post_event(self.single_event)
        assert isinstance(result, responses.PushResponse)
        post.reset_mock()

        with patch("connect.api.ConnectApi._build_response") as build_response:
            self.connect.post_event(self.single_event)
            url = "{0}/events/{1}".format(BASE_URL, COLLECTION_NAME)
            data = json.dumps(self.single_event.body)
            post.assert_called_once_with(url=url, data=data, timeout=60)
            build_response.assert_called_once_with(
                response_body=None, raw_event=self.single_event.body, status_code=200
            )
            build_response.reset_mock()

            # Non-empty response (!= 200)
            body = {"errorMessage": "Maximum event size of 64kb exceeded."}
            mocked_response = mocked_connect_response(413, body)
            post.return_value = mocked_response
            self.connect.post_event(self.single_event)
            build_response.assert_called_once_with(
                response_body=body, raw_event=self.single_event.body, status_code=413
            )

    def test_post_events(self, post):

        events = []
        expected_events = defaultdict(list)
        for e in MULTI_EVENT_DATA:
            events.append(Event(COLLECTION_NAME, e))
            expected_events[COLLECTION_NAME].append(e)

        body = {
            COLLECTION_NAME: [
                {"event": events[0].body, "success": True},
                {
                    "event": events[1].body,
                    "success": False,
                    "message": "An error occured inserting the event please try again.",
                },
            ]
        }
        mocked_response = mocked_connect_response(200, body)
        post.return_value = mocked_response

        result = self.connect.post_events(self.multi_events)
        url = "{0}/events".format(BASE_URL)
        data = json.dumps(self.multi_events)
        post.assert_called_with(url=url, data=data, timeout=60)
        assert isinstance(result, responses.PushBatchResponse)

        with patch("connect.api.ConnectApi._build_batch_response") as build_batch_response:
            self.connect.post_events(self.multi_events)
            build_batch_response.assert_called_once_with(
                response_body=body, events_by_collection=self.multi_events, status_code=200
            )

    def test__build_response(self, post):
        single_event = Event(COLLECTION_NAME, SINGLE_EVENT_DATA)

        r = self.connect._build_response(None, single_event.body, 200)
        assert isinstance(r, responses.PushResponse)
        assert r.error_message is None
        assert r.http_status_code == 200
        assert r.event == single_event.body

        r = self.connect._build_response(None, single_event.body, 401)
        assert isinstance(r, responses.PushResponse)
        assert r.error_message == "Unauthorised. Please check your Project Id and API Key"
        assert r.event == single_event.body
        assert r.http_status_code == 401

        response_body = {"errors": [{"field": "fieldName", "description": "There was an error with this field."}]}

        r = self.connect._build_response(response_body, single_event.body, 422)
        assert isinstance(r, responses.PushResponse)
        assert r.error_message == [{"field": "fieldName", "description": "There was an error with this field."}]
        assert r.event == single_event.body
        assert r.http_status_code == 422

        response_body = {"errorMessage": "Maximum event size of 64kb exceeded."}

        r = self.connect._build_response(response_body, single_event.body, 413)
        assert isinstance(r, responses.PushResponse)
        assert r.error_message == "Maximum event size of 64kb exceeded."
        assert r.event == single_event.body
        assert r.http_status_code == 413

    def test__build_batch_response(self, post):
        response_body = {
            COLLECTION_NAME: [
                {"success": True},
                {"success": False, "message": "An error occurred inserting the event please try again."},
            ],
            "my_collection2": [{"success": True}],
        }
        events = defaultdict(list)
        events["my_collection2"].append(SINGLE_EVENT_DATA)
        events[COLLECTION_NAME] = [e for e in MULTI_EVENT_DATA]
        r = self.connect._build_batch_response(response_body, events, 200)

        assert isinstance(r, responses.PushBatchResponse)
        assert r.http_status_code == 200
        assert r.error_message is None

        # to do: assert event body

        for collection in r.results:
            collection_results = r.results[collection]

            for i in range(0, len(collection_results)):
                assert isinstance(collection_results[i], responses.PushResponse)
                assert collection_results[i].http_status_code is None

                if i == 1:
                    assert collection_results[i].success == False
                    assert (
                        collection_results[i].error_message == "An error occurred inserting the event please try again."
                    )
                    assert collection_results[i].event == MULTI_EVENT_DATA[1]
                else:
                    assert collection_results[i].success == True
                    assert collection_results[i].error_message is None

        response_body = {"errorMessage": "An error occurred while processing your request"}
        r = self.connect._build_batch_response(response_body, events, 500)

        assert r.http_status_code == 500
        assert r.error_message == "An error occurred while processing your request"
        assert r.results == {}

    def test_create_session(self, post):
        s = self.connect._create_session()
        assert isinstance(s, Session)
        assert s.headers["Content-Type"] == "application/json"
        assert s.headers["X-Api-Key"] == API_PUSH_KEY
        assert s.headers["X-Project-Id"] == PROJECT_ID