{"id":18736595,"url":"https://github.com/gadventures/gapipy","last_synced_at":"2026-03-16T20:33:52.121Z","repository":{"id":553816,"uuid":"20815950","full_name":"gadventures/gapipy","owner":"gadventures","description":"Python client for the G Adventures REST API (https://developers.gadventures.com)","archived":false,"fork":false,"pushed_at":"2024-05-30T18:14:50.000Z","size":671,"stargazers_count":2,"open_issues_count":6,"forks_count":1,"subscribers_count":24,"default_branch":"master","last_synced_at":"2024-10-06T17:18:11.032Z","etag":null,"topics":["api","clients","gapi","python"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gadventures.png","metadata":{"files":{"readme":"README.rst","changelog":"HISTORY.rst","contributing":"CONTRIBUTING.rst","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-06-13T19:50:18.000Z","updated_at":"2024-05-30T18:14:54.000Z","dependencies_parsed_at":"2023-01-13T10:28:40.736Z","dependency_job_id":null,"html_url":"https://github.com/gadventures/gapipy","commit_stats":null,"previous_names":[],"tags_count":68,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gadventures%2Fgapipy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gadventures%2Fgapipy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gadventures%2Fgapipy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gadventures%2Fgapipy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gadventures","download_url":"https://codeload.github.com/gadventures/gapipy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223486879,"owners_count":17153241,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","clients","gapi","python"],"created_at":"2024-11-07T15:21:39.312Z","updated_at":"2025-10-28T06:07:28.898Z","avatar_url":"https://github.com/gadventures.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"G API Python Client\n===================\n\n.. image:: https://badge.fury.io/py/gapipy.svg\n    :target: http://badge.fury.io/py/gapipy\n\nA client for the G Adventures REST API (https://developers.gadventures.com)\n\n* GitHub Repository: https://github.com/gadventures/gapipy/\n* Documentation: http://gapipy.readthedocs.org.\n* Free software: MIT License\n\n\nQuick Start\n-----------\n\n.. code-block:: python\n\n   \u003e\u003e\u003e from gapipy import Client\n   \u003e\u003e\u003e api = Client(application_key='MY_SECRET_KEY')\n\n   \u003e\u003e\u003e # Get a resource by id\n   \u003e\u003e\u003e tour_dossier = api.tour_dossiers.get(24309)\n   \u003e\u003e\u003e tour_dossier.product_line\n   u'AHEH'\n   \u003e\u003e\u003e tour_dossier.departures.count()\n   134\n   \u003e\u003e\u003e tour_dossier.name\n   u'Essential India'\n   \u003e\u003e\u003e itinerary = tour_dossier.structured_itineraries[0]\n   \u003e\u003e\u003e {day.day: day.summary for day in itinerary.days[:3]}\n   {1: u'Arrive at any time. Arrival transfer included through the G Adventures-supported Women on Wheels project.',\n   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.',\n   3: u\"Arrive in Jaipur and explore this gorgeous 'pink city'.\"}\n\n   \u003e\u003e\u003e # Create a new resource\n   \u003e\u003e\u003e booking = api.bookings.create({'currency': 'CAD', 'external_id': 'abc'})\n\n   \u003e\u003e\u003e # Modify an existing resource\n   \u003e\u003e\u003e booking.external_id = 'def'\n   \u003e\u003e\u003e booking.save()\n\n\nSince `2.25.0 (2020-01-02)`_\n\n.. code-block:: python\n\n   \u003e\u003e\u003e # since 2.25.0 reference stubs that fail to fetch will return a\n\n   \u003e\u003e\u003e # subclass of requests.HTTPError (See: https://github.com/gadventures/gapipy/pull/119)\n   \u003e\u003e\u003e # This can also be done on Query.get by passing a Falsy value for the\n   \u003e\u003e\u003e # httperrors_mapped_to_none kwarg.\n   \u003e\u003e\u003e\n   \u003e\u003e\u003e dep = api.departures.get('404_404', httperrors_mapped_to_none=None)\n   ... # omitted stacktrace\n   HTTPError: 404 Client Error: {\"http_status_code\":404,\"message\":\"Not found.\",\"errors\":[],\"time\":\"2020-01-02T19:46:07Z\",\"error_id\":\"gapi_asdf1234\"} for url: https://rest.gadventures.com/departures/404_404\n\n   \u003e\u003e\u003e dep = api.departures.get('404404')\n   \u003e\u003e\u003e dep.start_address.country\n   \u003cCountry: BR (stub)\u003e\n\n   \u003e\u003e\u003e # lets have GAPI return a _404_ error here for the country stub `fetch`\n   \u003e\u003e\u003e # when we attempt to retrieve the continent attribute\n\n   \u003e\u003e\u003e dep.start_address.country.continent  # reference/stub forces a fetch\n\n   \u003e\u003e\u003e # pre 2.25.0 behaviour\n   ... # omitted stacktrace\n   AttributeError: 'Country' has no field 'continent' available\n\n   \u003e\u003e\u003e # post 2.25.0 behaviour\n   ... # omitted stacktrace\n   HTTPError: 404 Client Error: {\"http_status_code\":404,\"message\":\"Not found.\",\"errors\":[],\"time\":\"2020-01-02T19:46:07Z\",\"error_id\":\"gapi_qwer5678\"} for url: https://rest.gadventures.com/countries/BR\n\n\nResources\n---------\n\nResource objects are instantiated from python dictionaries created from JSON\ndata. The fields are parsed and converted to python objects as specified in the\nresource class.\n\nA nested resource will only be instantiated when its corresponding attribute is\naccessed in the parent resource. These resources may be returned as a ``stub``,\nand upon access of an attribute not present, will internally call ``.fetch()``\non the resource to populate it.\n\nA field pointing to the URL for a collection of a child resources will hold a\n``Query`` object for that resource. As for nested resources, it will only be\ninstantiated when it is first accessed.\n\n\nQueries\n-------\n\nA Query for a resource can be used to fetch resources of that type (either a\nsingle instance or an iterator over them, possibly filtered according to  some\nconditions). Queries are roughly analogous to Django's QuerySets.\n\nAn API client instance has a query object for each available resource\n(accessible by an attribute named after the resource name)\n\n\nMethods on Query objects\n========================\n\nAll queries support the ``get``, ``create`` and ``options`` methods. The other\nmethods are only supported for queries whose resources are listable.\n\n``options()``\n   Get the options for a single resource\n\n``get(resource_id, [headers={}])``\n   Get a single resource; optionally passing in a dictionary of header\n   values.\n\n``create(data)``\n   Create an instance of the query resource using the given data.\n\n``all([limit=n])``\n   Generator over all resources in the current query. If ``limit`` is a\n   positive integer ``n``, then only the first ``n`` results will be returned.\n\n   * A ``TypeError`` will be raised if limit is not ``None`` or ``int`` type\n   * A ``ValueError`` will be raised if ``limit \u003c= 0``\n\n``filter(field1=value1, [field2=value2, ...])``\n\n``filter(**{\"nested.field\": \"value\"})``\n   Filter resources on the provided fields and values. Calls to ``filter`` can\n   be chained. The method will return a clone of the ``Query`` object and must\n   be stored in a separate variable in order to have access to **stacked**\n   filters.\n\n``count()``\n   Return the number of resources in the current query (by reading the\n   ``count`` field on the response returned by requesting the list of\n   resources in the current query).\n\n\nCaching\n-------\n\n``gapipy`` can be configured to use a cache to avoid having to send HTTP\nrequests for resources it has already seen. Cache invalidation is not\nautomatically handled: it is recommended to listen to G API webhooks_ to purge\nresources that are outdated.\n\n.. _webhooks: https://developers.gadventures.com/docs/webhooks.html\n\nBy default, ``gapipy`` will use the cached data to instantiate a resource, but\na fresh copy can be fetched from the API by passing ``cached=False`` to\n``Query.get``. This has the side-effect of recaching the resource with the\nlatest data, which makes this a convenient way to refresh cached data.\n\nCaching can be configured through the ``cache_backend`` and ``cache_options``\nsettings. ``cached_backend`` should be a string of the fully qualified path to\na cache backend, i.e. a subclass of ``gapipy.cache.BaseCache``. A handful of\ncache backends are available out of the box:\n\n``gapipy.cache.SimpleCache``\n   A simple in-memory cache for single process environments and is not\n   thread safe.\n\n``gapipy.cache.RedisCache``\n   A key-value cache store using Redis as a backend.\n\n``gapipy.cache.NullCache`` (Default)\n   A cache that doesn't cache.\n\n``gapipy.cache.DjangoCache`` (requires Django)\n   A cache which uses Django's cache settings for configuration. Requires there\n   be a ``gapi`` entry in ``settings.CACHES``.\n\nSince the cache backend is defined by a python module path, you are free to use\na cache backend that is defined outside of this project.\n\n\nConnection Pooling\n------------------\n\nWe use the ``requests`` library, and you can take advantage of the provided\nconnection pooling options by passing in a ``'connection_pool_options'`` dict\nto your client.\n\nValues inside the ``'connection_pool_options'`` dict of interest are as\nfollows:\n\n* Set ``enable`` to ``True`` to enable pooling. Defaults to ``False``.\n* Use ``number`` to set the number of connection pools to cache.\n  Defaults to 10.\n* Use ``maxsize`` to set the max number of connections in each pool.\n  Defaults to 10.\n* Set ``block`` to ``True`` if the connection pool should block and wait\n  for a connection to be released when it has reached ``maxsize``. If\n  ``False`` and the pool is already at ``maxsize`` a new connection will\n  be created without blocking, but it will not be saved once it is used.\n  Defaults to ``False``.\n\nSee also:\n---------\n\n* http://www.python-requests.org/en/latest/api/#requests.adapters.HTTPAdapter\n* http://urllib3.readthedocs.io/en/latest/reference/index.html#module-urllib3.connectionpool\n\n\nDependencies\n------------\n\nThe only dependency needed to use the client is requests_.\n\n.. _requests: http://python-requests.org\n\n\nTesting\n-------\n\nRunning tests is pretty simple. We use `nose` as the test runner. You can\ninstall all requirements for testing with the following::\n\n   $ pip install -r requirements-testing.txt\n\nOnce installed, run unit tests with::\n\n   $ nosetests -A integration!=1\n\nOtherwise, you'll want to include a GAPI Application Key so the integration\ntests can successfully hit the API::\n\n   $ export GAPI_APPLICATION_KEY=MY_SECRET_KEY; nosetests\n\nIn addition to running the test suite against your local Python interpreter, you\ncan run tests using `Tox \u003chttp://tox.testrun.org\u003e`_. Tox allows the test suite\nto be run against multiple environments, or in this case, multiple versions of\nPython. Install and run the ``tox`` command from any place in the gapipy source\ntree. You'll want to export your G API application key as well::\n\n   $ export GAPI_APPLICATION_KEY=MY_SECRET_KEY\n   $ pip install tox\n   $ tox\n\nTox will attempt to run against all environments defined in the ``tox.ini``. It\nis recommended to use a tool like `pyenv \u003chttps://github.com/yyuu/pyenv\u003e`_ to\nensure you have multiple versions of Python available on your machine for Tox to\nuse.\n\n\nFields\n------\n\n* ``_model_fields`` represent dictionary fields.\n\n.. note::\n\n   * ``_model_fields = [('address', Address)]`` AND\n   * ``Address`` subclasses ``BaseModel``\n\n.. code-block:: python\n\n   {\n      \"address\": {\n         \"street\": \"19 Charlotte St\",\n         \"city\": \"Toronto\",\n         \"state\": {\n            \"id\": \"CA-ON\",\n            \"href\": \"https://rest.gadventures.com/states/CA-ON\",\n            \"name\": \"Ontario\"\n         },\n         \"country\": {\n            \"id\": \"CA\",\n            \"href\": \"https://rest.gadventures.com/countries/CA\",\n            \"name\": \"Canada\"\n         },\n         \"postal_zip\": \"M5V 2H5\"\n      }\n   }\n\n\n* ``_model_collection_fields`` represent a list of dictionary fields.\n\n.. note::\n\n   * ``_model_collection_fields = [('emails', AgencyEmail),]`` AND\n   * ``AgencyEmail`` subclasses ``BaseModel``\n\n.. code-block:: python\n\n   {\n      \"emails\": [\n         {\n            \"type\": \"ALLOCATIONS_RELEASE\",\n            \"address\": \"g@gadventures.com\"\n         },\n         {\n            \"type\": \"ALLOCATIONS_RELEASE\",\n            \"address\": \"g2@gadventures.com\"\n         }\n      ]\n   }\n\n* ``_resource_fields`` refer to another ``Resource``\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgadventures%2Fgapipy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgadventures%2Fgapipy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgadventures%2Fgapipy/lists"}