Skip to main content

Python client for the G Adventures REST API

Project description

===============================
G API Python Client
===============================

.. image:: https://badge.fury.io/py/gapipy.svg
:target: http://badge.fury.io/py/gapipy

.. image:: https://travis-ci.org/gadventures/gapipy.svg?branch=master
:target: https://travis-ci.org/gadventures/gapipy

A client for the G Adventures REST API (https://developers.gadventures.com)

* GitHub Repository: https://github.com/gadventures/gapipy/
* Documentation: http://gapipy.readthedocs.org.
* Free software: MIT license


Quick Start
-----------

.. code-block:: python

>>> from gapipy import Client
>>> api = Client(application_key='MY_SECRET_KEY')

>>> # Get a resource by id
>>> tour = api.tours.get(24309)
>>> tour.product_line
u'AHEH'
>>> tour.departures.count()
134
>>> dossier = tour.tour_dossier
>>> dossier.name
u'Essential India'
>>> itinerary = dossier.structured_itineraries[0]
>>> {day.day: day.summary for day in itinerary.days[:3]}
{1: u'Arrive at any time. Arrival transfer included through the G Adventures-supported Women on Wheels project.',
2: u'Take a morning walk through the city with a young adult from the G Adventures-supported New Delhi Streetkids Project. Later, visit Old Delhi, explore the spice markets, and visit Jama Masjid and Connaught Place.',
3: u"Arrive in Jaipur and explore this gorgeous 'pink city'."}

>>> # Create a new resource
>>> booking = api.bookings.create({'currency': 'CAD', 'external_id': 'abc'})

>>> # Modify an existing resource
>>> booking.external_id = 'def'
>>> booking.save()


Resources
---------

Resource objects are instantiated from python dictionaries created from JSON
data. The fields are parsed and converted to python objects as specified in the
resource class.

A nested resource will only be instantiated when its corresponding attribute is
accessed in the parent resource. These resources may be returned as a ``stub``,
and upon access of an attribute not present, will internally call ``.fetch()``
on the resource to populate it.

A field pointing to the URL for a collection of a child resources will hold a
``Query`` object for that resource. As for nested resources, it will only be
instantiated when it is first accessed.


Queries
-------

A Query for a resource can be used to fetch resources of that type (either a
single instance or an iterator over them, possibly filtered according to some
conditions). Queries are roughly analogous to Django's QuerySets.

An API client instance has a query object for each available resource
(accessible by an attribute named after the resource name)

Methods on Query objects
========================

All queries support the ``get``, ``create`` and ``options`` methods. The other methods are
only supported for queries whose resources are listable.

``options()``
Get the options for a single resource

``get(resource_id)``
Get a single resource.

``create(data)``
Create an instance of the query resource using the given data.

``all([limit=n])``
Generator over all resources in the current query. If ``limit`` is a
positive integer ``n``, then only the first ``n`` results will be returned.

``filter(field1=value1, [field2=value2, ...])``
Filter resources on the provided fields and values. Calls to ``filter`` can
be chained.

``count()``
Return the number of resources in the current query (by reading the
``count`` field on the response returned by requesting the list of
resources in the current query).

Caching
-------

``gapipy`` can be configured to use a cache to avoid having to send HTTP
requests for resources it has already seen. Cache invalidation is not
automatically handled: it is recommended to listen to G API webhooks_ to purge
resources that are outdated.

.. _webhooks: https://developers.gadventures.com/docs/webhooks.html

By default, ``gapipy`` will use the cached data to instantiate a resource, but
a fresh copy can be fetched from the API by passing ``cached=False`` to
``Query.get``. This has the side-effect of recaching the resource with the
latest data, which makes this a convenient way to refresh cached data.

Caching can be configured through the ``cache_backend`` and ``cache_options``
settings. ``cached_backend`` should be a string of the fully qualified path to
a cache backend, i.e. a subclass of ``gapipy.cache.BaseCache``. A handful of
cache backends are available out of the box:

* ``gapipy.cache.SimpleCache``
A simple in-memory cache for single process environments and is not
thread safe.

* ``gapipy.cache.RedisCache``
A key-value cache store using Redis as a backend.

* ``gapipy.cache.NullCache`` (Default)
A cache that doesn't cache.

Since the cache backend is defined by a python module path, you are free to use
a cache backend that is defined outside of this project.


Connection Pooling
------------------

We use the ``requests`` library, and you can take advantage of the provided
connection pooling options by passing in a ``'connection_pool_options'`` dict
to your client.

Values inside the ``'connection_pool_options'`` dict of interest are as
follows:

* Set ``enable`` to ``True`` to enable pooling. Defaults to ``False``.
* Use ``number`` to set the number of connection pools to cache.
Defaults to 10.
* Use ``maxsize`` to set the max number of connections in each pool.
Defaults to 10.
* Set ``block`` to ``True`` if the connection pool should block and wait
for a connection to be released when it has reached ``maxsize``. If
``False`` and the pool is already at ``maxsize`` a new connection will
be created without blocking, but it will not be saved once it is used.
Defaults to ``False``.

See also:

* http://www.python-requests.org/en/latest/api/#requests.adapters.HTTPAdapter
* http://urllib3.readthedocs.io/en/latest/reference/index.html#module-urllib3.connectionpool


Dependencies
------------

The only dependency needed to use the client is requests_.

.. _requests: http://python-requests.org

Testing
-------

Running tests is pretty simple. We use `nose` as the test runner. You can
install all requirements for testing with the following::

$ pip install -r requirements-testing.txt

Once installed, run unit tests with::

$ nosetests -A integration!=1

Otherwise, you'll want to include a GAPI Application Key so the integration
tests can successfully hit the API::

$ export GAPI_APPLICATION_KEY=MY_SECRET_KEY; nosetests

In addition to running the test suite against your local Python interpreter, you
can run tests using `Tox <http://tox.testrun.org>`_. Tox allows the test suite
to be run against multiple environments, or in this case, multiple versions of
Python. Install and run the ``tox`` command from any place in the gapipy source
tree. You'll want to export your G API application key as well::

$ export GAPI_APPLICATION_KEY=MY_SECRET_KEY
$ pip install tox
$ tox

Tox will attempt to run against all environments defined in the ``tox.ini``. It
is recommended to use a tool like `pyenv <https://github.com/yyuu/pyenv>`_ to
ensure you have multiple versions of Python available on your machine for Tox to
use.


Fields
------

* ``_model_fields`` represent dictionary fields like so:

Note: ``_model_fields = [('address', Address)]`` and ``Address`` subclasses ``BaseModel``

.. code-block:: python

"address": {
"street": "19 Charlotte St",
"city": "Toronto",
"state": {
"id": "CA-ON",
"href": "https://rest.gadventures.com/states/CA-ON",
"name": "Ontario"
},
"country": {
"id": "CA",
"href": "https://rest.gadventures.com/countries/CA",
"name": "Canada"
},
"postal_zip": "M5V 2H5"
}


* ``_model_collection_fields`` represent a list of dictionary fields like so:

Note: ``_model_collection_fields = [('emails', AgencyEmail),]`` and ``AgencyEmail`` subclasses ``BaseModel``

.. code-block:: python

"emails": [
{
"type": "ALLOCATIONS_RELEASE",
"address": "g@gadventures.com"
},
{
"type": "ALLOCATIONS_RELEASE",
"address": "g2@gadventures.com"
}
]

* ``_resource_fields`` refer to another ``Resource``

Thanks for helping!




History
=======

2.10.0 (2017-12-01)
------------------

* Add the `amount_pending` field to the `Booking` resource
* `PricePromotion` extends from the `Promotion` resource (PR 85)
* Update the `Agent` class to use BaseModel classes for the
`role` and `phone_numbers` fields.
* see https://github.com/gadventures/gapipy/releases/2.10.0 for more details


2.9.3 (2017-11-23)
------------------

* Expose `requirement_set` for `departure_services` and `activity_services`.
* *NOTE*: We have skipped `2.9.2` due to pypi upload issues.


2.9.1 (2017-11-22)
------------------

* Adds the `options` method on the Resource Query object.
A more detailed description of the issue can be found at:
* https://github.com/gadventures/gapipy/releases/2.9.1
* *NOTE*: We have skipped `2.9.0` due to pypi upload issues


2.8.2 (2017-11-14)
------------------

* Adds fields `sale_start_datetime` and `sale_finish_datetime` to the
Promotion resource. The fields mark the start/finish date-time values
for when a Promotion is applicable. The values represented are in UTC.


2.8.1 (2017-10-25)
------------------

* Add new fields to the `Agency` and `AgencyChain` resources


2.8.0 (2017-10-23)
------------------

* This release adds a behaviour change to the `.all()` method on resource Query
objects. Prior to this release, the base Resource Query object would retain
any previously added `filter` values, and be used in subsequent calls. Now
the underlying filters are reset after a `<resource>.all()` call is made.

A more detailed description of the issue and fix can be found at:
* https://github.com/gadventures/gapipy/issues/76
* https://github.com/gadventures/gapipy/pull/77

* Adds missing fields to the Agency and Flight Service resources (pull/78)


2.7.6 (2017-10-04)
------------------

* Add `agency` field to `Booking` resource.


2.7.5 (2017-09-25)
------------------

* Add test fix for Accommodation. It is listable resource as of `2.7.4`
* Add regression test for departures.addon.product model
* Ensure Addon's are instantiated to the correct underlying model.
* Prior to this release, all Addon.product resources were instantiated as `Accommodation`.


2.7.4 (2017-09-20)
------------------

* Add `videos`, `images`, and `categories` to Activity, Transport, Place, and Accommodation Dossiers.
* Add `flags` to Itinerary resource
* Add list view of `accommodations` resource


2.7.3 (2017-09-06)
------------------

* Add `type` field to `AgencyDocument` model
* Add `structured_itinerary` model collection field to `Departure` resource


2.7.2 (2017-08-18)
------------------

* Fix flight_status Reference value in FlightService resource


2.7.1 (2017-08-18)
------------------

* Fix: remove FlightStatus import reference for FlightService resource
* Add fields (fixes two broken Resource tests)
* Add `href` field for `checkins` resource
* Add `date_cancelled` field for `departures` resource
* Fix broken UpdateCreateResource tests


2.7.0 (2017-08-18)
------------------

* Remove `flight_statuses` and `flight_segments` resources.


2.6.2 (2017-08-11)
------------------

* Version bump


2.6.1 (2017-08-11)
------------------

* Adds a Deprecation warning when using the `tours` resource.


2.6.0 (2017-08-11)
------------------

* Fixed `issue 65 <https://github.com/gadventures/gapipy/issues/65>`_: only
write data into the local cache after a fetch from the API, do not write data
into the local cache when fetching from the local cache.


2.5.2 (2017-04-26)
------------------

* Added ``future`` dependency to setup.py


2.5.1 (2017-02-08)
------------------

* Fixed an issue in which modifying a nested dictionary caused gapipy to not
identify a change in the data.
* Added ``tox.ini`` for testing across Python platforms.
* Capture ``403`` Status Codes as a ``None`` object.

2.5.0 (2017-01-20)
------------------

* Provided Python 3 functionality (still Python 2 compatible)
* Removed Python 2 only tests
* Installed ``future`` module for smooth Python 2 to Python 3 migration
* Remove ``DictToModel`` class and the associated tests
* ``Dossier`` Resource(s)
* Minor field updates to: ``Customer``, ``InsuranceService``, ``DepartureService``, ``Booking``, ``FlightStatus``, ``State``

2.4.9 (2016-11-22)
------------------

* Fixed a bug with internal ``_get_uri`` function.

2.4.8 (2016-11-11)
------------------

* Adjusted ``Checkin`` resource to meet updated spec.

2.4.7 (2016-10-25)
------------------

* Added ``Checkin`` resource.

2.4.6 (2016-10-19)
------------------

* Fix broken ``Duration`` init in ``ActivityDossier`` (likely broke due to
changes that happened in 2.0.0)

2.4.5 (2016-10-13)
------------------

* Added ``Image`` resource definition and put it to use in ``Itinerary`` and ``PlaceDossier``

2.4.4 (2016-09-09)
------------------

* Added ``date_last_modified`` and ``date_created`` to ``Promotion``.

2.4.3 (2016-09-06)
------------------

* Added ``gender`` to ``Customer``.
* Added ``places_of_interest`` to ``Place``.

2.4.2 (2016-07-08)
------------------

* Added ``departure`` reference to ``DepartureComponent``

2.4.1 (2016-07-06)
------------------

* Removed use of ``.iteritems`` wherever present in favour of ``.items``
* Added ``features`` representation to ``ActivityDossier`` and ``TransportDossier``

2.4.0 (2016-06-29)
------------------

* Added ``CountryDossier`` resource.

2.3.0 (2016-06-28)
------------------

* Added ``DossierSegment`` resource.
* Added ``ServiceLevel`` resource.

2.2.2 (2016-06-08)
------------------

* Added day ``label`` field to the ``Itinerary`` resource.

2.2.1 (2016-06-06)
------------------

* Added ``audience`` field to the ``Document`` resource.

2.2.0 (2016-05-17)
------------------

* Added ``transactional_email``, and ``emails`` to ``Agency`` resource.

2.1.2 (2016-05-17)
------------------

* Added ``audience`` to ``Invoice`` resource.

2.1.1 (2016-04-29)
------------------

* Removed invalid field, ``email`` from ``AgencyChain``

2.1.0 (2016-04-25)
------------------

* Added new resource, ``AgencyChain``

2.0.0 (2016-03-11)
------------------

The global reference to the last instantiated Client has been removed. It is
now mandatory to pass in a Client instance when instantiating a Model or
Resource.

In practice, this should not introduce too much changes in codebases that are
using ``gapipy``, since resources are mostly interacted with through a Client
instance (for example, ``api.tours.get(123)``, or
``api.customers.create({...})``), instead of being instantiated independently.
The one possible exception is unit testing: in that case, ``Client.build`` can
be useful.

The global variable was causing issues with connection pooling when multiple
client with different configurations were used at the same time.

1.1.0 (2016-03-11)
------------------

* Added new resource, ``DossierFeature``

1.0.0 (2016-02-29)
------------------

* Adopted `Semantic Versioning <http://semver.org/>`_ for this project.
* Refactored how the cache key is set. This is a breaking change for any modules that implemented their own cache interface. The cache modules are no longer responsible for defining the cache value, but simply storing whatever it is given into cache. The ``Query`` object now introduces a ``query_key`` function which generates the cache key sent to the cache modules.

0.6.3 (2016-01-21)
------------------

* Added better error handling to `Client.build`. An AttributeError raised when instantiating a resource won't be shadowed by the except block anymore.


0.6.2 (2016-01-20)
------------------

* Fixed a regression bug when initializing DepartureServiceRoom model.

0.6.1 (2016-01-20)
------------------

* Fixed a regression bug when initializing services.

0.6.0 (2016-01-20)
------------------

* Fixed a bug when initializing list of resources.

0.5.5 (2016-01-08)
------------------

* Added a component of type ``ACCOMMODATION`` to ``Itineraries``.

0.5.4 (2016-01-04)
------------------

* Added ``associated_services`` to ``SingleSupplementService``

0.5.3 (2015-12-31)
------------------

* Added ``name`` to ``Departure``.
* Happy New Year!

0.5.2 (2015-12-15)
------------------

* Added ``variation_id`` to ``BaseCache`` to fix a ``TypeError`` when using the ``NullCache``

0.5.1 (2015-12-14)
------------------

* Add ``associated_agency`` to ``bookings`` resource

0.5.0 (2015-12-10)
------------------

* Minor adjusted in Query internals to ensure the ``variation_id`` of an Itinerary is handled properly.
* Added ``ItineraryHighlights`` and ``ItineraryMedia`` resources. These are sub resources of the ``Itinerary``

0.4.6 (2015-12-09)
------------------

* Added connection pool caching to ``RedisCache``. Instances of ``gapipy`` with the same cache settings (in the same Python process) will share a connection pool.

0.4.5 (2015-11-05)
------------------

* Added ``code`` field to the ``type`` of an ``Itinerary``'s listed ``details``.

0.4.4 (2015-11-04)
------------------

* Added the ``details`` field to the ``Itinerary`` resource -- a list of textual details about an itinerary.

0.4.3 (2015-11-03)
-------------------

* Added the ``tour_dossier`` field to the ``Itinerary`` resource.

0.4.2 (2015-10-28)
------------------

* Fixed a bug that would cause ``amount`` when looking at ``Promotion`` objects in the ``Departure`` to be removed from the data dict.

0.4.1 (2015-10-16)
------------------

* Moved an import of ``requests`` down from the module level. Fixes issues in CI environments.

0.4.0 (2015-10-13)
------------------

* Added connection pooling options, see docs for details on ``connection_pool_options``.

0.3.0 (2015-09-24)
------------------

* Modified how the ``Promotion`` object is loaded within ``price_bands`` on a ``Departure``. It now correctly captures the ``amount`` field.

0.2.0 (2015-09-15)
------------------

* Modified objects within ``cache`` module to handle ``variation_id``, which is exposed within the ``Itinerary`` object. Previously, the ``Itinerary`` would not be correctly stored in cache with its variant reference.

0.1.51 (2015-08-31)
-------------------

* Added the ``components`` field to the ``Departure`` resource.


0.1.50 (2015-07-28)
-------------------

* Fixed an issue with the default ``gapipy.cache.NullCache`` when ``is_cached`` was used.

0.1.49 (2015-07-23)
-------------------

* Added new fields to ``Itinerary`` revolving around variations.
* Added ``declined_reason`` to all service resources.

0.1.48 (2015-07-15)
-------------------

* Add DeclinedReason resource

0.1.47 (2015-07-08)
-------------------

* Fixed a bug in ``APIRequestor.get``. Requesting a resource with with an id of ``0`` won't raise an Exception anymore.

0.1.46 (2015-06-10)
-------------------

* Added ``associated_services`` and ``original_departure_service`` to various service resources and ``departure_services`` model respectively.

0.1.45 (2015-05-27)
-------------------

* Fixed ``products`` within the ``Promotion`` resource to properly retain ``type`` and ``sub_type`` fields after being parsed into a dictionary.

0.1.44 (2015-05-22)
-------------------

* Changed default `cache_backend` to use `gapipy.cache.NullCache`. Previously, `SimpleCache` was the default and led to confusion in production environments, specifically as to why resources were not matching the API output. Now, by default, to get any caching from gapipy you must explicitly set it.

0.1.43 (2015-04-29)
-------------------

* Fixed `Place` init with empty admin_divisions


0.1.42 (2015-04-29)
-------------------

* Added `description` to `TourCategory` resource.

0.1.41 (2015-04-14)
-------------------

* Added `DepartureComponent` resource. See the [official G API documentation for details](https://developers.gadventures.com/docs/departure_component.html)

0.1.40 (2015-04-06)
-------------------

* Added `deposit` to `DepartureService` model

0.1.39 (2015-03-31)
-------------------

* Refactor ``APIRequestor._request``. While this should not change existing functionality, it is now possible to override specific methods on ``APIRequestor`` if needed.


0.1.38 (2015-03-23)
-------------------

* Fixed: Due to inconsistencies in the G API with regards to nested resources, the `fetch` function was modified to use the raw data from the API, rather than a specific set of allowed fields.

0.1.37 (2015-03-23)
-------------------

* Fixed: Iterating over ``products`` within the ``promotions`` object now works as expected. Previously, accessing the ``products`` attribute would result in a Query object with incorrect parameters.

0.1.36 (2015-03-17)
-------------------

* Support free to amount price range formatting (e.g. Free-10CAD)

0.1.35 (2015-03-12)
-------------------

* Added `duration_min` & `duration_max` to `ActivityDossier` model

0.1.34 (2015-03-11)
-------------------

* Added `OptionalActivity` model
* All Dossiers with `details`:
* Now represented as list of `DossierDetail` models
* Added convenience methods for retrieving specific details
* `ItineraryComponent` and `ActivityDossier` use new `Duration` model
for their `duration` field/property
* Added `duration_label` and `location_label` to `ItineraryComponent`
* Added `duration_label`, `price_per_person_label`, and `price_per_group_label`
to `ActivityDossier`


0.1.33 (2015-03-02)
-------------------

* Added `name` field to the Itinerary resource.


0.1.32 (2015-02-18)
-------------------

* Changed cache key creation to account for `GAPI_LANGUAGE` when the environment variable is set.

0.1.31 (2015-02-18)
-------------------

* Fixed a bug when setting _resource_fields in ``DepartureService`` resource


0.1.30 (2015-02-11)
-------------------

* ``TourDossier.structured_itineraries`` now refers to a list of Itinerary
resources

0.1.29 (2015-02-10)
-------------------

* Added ``TransportDossier`` and ``Itinerary`` resources.

* The reference to the itinerary in a ``DepartureService`` is now a
full-fledged ``Itinerary`` resource.

0.1.28 (2015-01-22)
-------------------

* Bug fix to correctly send ``Content-Type: application/json`` in POST, PUT, or PATCH.

0.1.27 (2015-01-19)
-------------------

* Update ``DepartureService`` object to contain a reference to its ``Itinerary``

0.1.26 (2015-01-14)
-------------------

* Normalize API request headers, to promote caching.

0.1.25 (2015-01-09)
-------------------

* Added ``ActivityDossier`` and ``AccommodationDossier`` resources, as well as references to it from ``Activity`` and ``Accommodation``.

0.1.24 (2015-01-07)
-------------------

* Added ``PlaceDossier`` resource, as well as reference to it from ``Place``

0.1.22 (2014-12-12)
-------------------

* Added ``advertised_departures`` to ``TourDossier``

0.1.21 (2014-11-26)
-------------------

* Fixed a bug with promotions on a Price object. When promotions were accessed, gapipy would query for all promotions, rather than returning the inline list.

0.1.20 (2014-11-20)
-------------------

* Departure resource is now listable via filters.

0.1.19 (2014-11-17)
-------------------

* Fixed a bug with `RedisCache.is_cached` where it would not use the set `key_prefix` when checking for existence in cache. Effectively, it would always return False

0.1.18 (2014-11-12)
-------------------

* When setting a date_field, initiate it as a `datetime.date` type.

0.1.17 (2014-11-07)
-------------------

* Deprecated `RedisHashCache` from cache backends available by default. Was not well tested or reliable.

0.1.16 (2014-10-28)
---------------------

* Fixed a bug where if a model field received `null` as a value, it would fail. Now,
if the result is `null`, the model field will have an appropriate `None` value.

0.1.15 (2014-10-23)
---------------------

* Fix a bug in the DepartureRoom model. The `price_bands` attribute is now
properly set.


0.1.14 (2014-10-22)
---------------------

* Fixed a bug where AgencyDocument was not included in the code base.


0.1.13 (2014-10-21)
---------------------

* Add ``latitude``, ``longitude``, and ``documents`` to the ``Agency`` resource.

0.1.12 (2014-10-20)
---------------------

* ``date_created`` on the ``Agency`` resource is correctly parsed as a local time.

0.1.11 (2014-10-15)
---------------------

* Improve the performance of ``Resource.fetch`` by handling cache get/set.

0.1.10 (2014-10-09)
---------------------

* Fix a bug in AccommodationRoom price bands. The `season_dates` and
`blackout_dates` attributes are now properly set.


0.1.9 (2014-09-23)
---------------------

* Add `iso_639_3` and `iso_639_1` to `Language`

0.1.8 (2014-09-17)
---------------------

* Remove the `add_ons` field in `Departure`, and add `addons`.


0.1.7 (2014-08-22)
---------------------

* Fix a bug when initializing AccommodationRoom from cached data.

0.1.6 (2014-08-19)
---------------------

* Add Query.purge_cached

0.1.5 (2014-07-29)
---------------------

* Add `details` field to the list of `incomplete_requirements` in a `DepartureService`.

0.1.4 (2014-07-21)
---------------------

* Removed sending of header `X-HTTP-Method-Override: PATCH` when the update
command is called. Now, when `.save(partial=True)` is called, the
correct PATCH HTTP method will be sent with the request.

0.1.3 (2014-07-18)
------------------

* Return ``None`` instead of raising a HTTPError 404 exception when fetching a
non-existing resource by id.
* Added ability to create resources from the Query objects on the client
instance (for example, ``api.customers.create({'name': {'legal_first_name': 'Pat', ...}, ...})``)

0.1.2 (2014-07-14)
------------------

* Added Query.is_cached
* Added cache options

0.1.1 (2014-06-27)
------------------

* Use setuptools find_packages

0.1.0 (2014-06-20)
------------------

* First release on PyPI.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gapipy-2.10.0.tar.gz (65.1 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page