├── .coveragerc ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── SUPPORT.md ├── images │ ├── dependencies.svg │ └── support.svg └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── CHANGELOG.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── amadeus ├── __init__.py ├── airline │ ├── __init__.py │ └── _destinations.py ├── airport │ ├── __init__.py │ ├── _direct_destinations.py │ ├── _predictions.py │ └── predictions │ │ ├── __init__.py │ │ └── _on_time.py ├── amadeus.py ├── analytics │ ├── __init__.py │ └── _itinerary_price_metrics.py ├── booking │ ├── __init__.py │ ├── _flight_order.py │ ├── _flight_orders.py │ ├── _hotel_bookings.py │ └── _hotel_orders.py ├── client │ ├── __init__.py │ ├── access_token.py │ ├── decorator.py │ ├── direction.py │ ├── errors.py │ ├── hotel.py │ ├── location.py │ ├── request.py │ └── response.py ├── e_reputation │ ├── __init__.py │ └── _hotel_sentiments.py ├── media │ └── _files.py ├── mixins │ ├── __init__.py │ ├── http.py │ ├── pagination.py │ ├── parser.py │ └── validator.py ├── namespaces │ ├── __init__.py │ ├── _airline.py │ ├── _airport.py │ ├── _analytics.py │ ├── _booking.py │ ├── _e_reputation.py │ ├── _ordering.py │ ├── _reference_data.py │ ├── _schedule.py │ ├── _shopping.py │ ├── _travel.py │ └── core.py ├── ordering │ ├── __init__.py │ ├── _transfer_order.py │ ├── _transfer_orders.py │ └── transfer_orders │ │ ├── __init__.py │ │ ├── _transfers.py │ │ └── transfers │ │ ├── __init__.py │ │ └── _cancellation.py ├── reference_data │ ├── __init__.py │ ├── _airlines.py │ ├── _location.py │ ├── _locations.py │ ├── _recommended_locations.py │ ├── _urls.py │ ├── locations │ │ ├── __init__.py │ │ ├── _airports.py │ │ ├── _cities.py │ │ ├── _hotel.py │ │ ├── _hotels.py │ │ └── hotels │ │ │ ├── __init__.py │ │ │ ├── _by_city.py │ │ │ ├── _by_geocode.py │ │ │ └── _by_hotels.py │ └── urls │ │ ├── __init__.py │ │ └── _checkin_links.py ├── schedule │ ├── __init__.py │ └── _flights.py ├── shopping │ ├── __init__.py │ ├── _activities.py │ ├── _activity.py │ ├── _availability.py │ ├── _flight_dates.py │ ├── _flight_destinations.py │ ├── _flight_offers.py │ ├── _flight_offers_search.py │ ├── _hotel_offer_search.py │ ├── _hotel_offers_search.py │ ├── _seatmaps.py │ ├── _transfer_offers.py │ ├── activities │ │ ├── __init__.py │ │ └── _by_square.py │ ├── availability │ │ ├── __init__.py │ │ └── _flight_availabilities.py │ └── flight_offers │ │ ├── __init__.py │ │ ├── _prediction.py │ │ ├── _pricing.py │ │ └── _upselling.py ├── travel │ ├── __init__.py │ ├── _analytics.py │ ├── _predictions.py │ ├── analytics │ │ ├── __init__.py │ │ ├── _air_traffic.py │ │ └── air_traffic │ │ │ ├── __init__.py │ │ │ ├── _booked.py │ │ │ ├── _busiest_period.py │ │ │ └── _traveled.py │ └── predictions │ │ ├── __init__.py │ │ ├── _flight_delay.py │ │ └── _trip_purpose.py └── version.py ├── docs ├── _static │ └── .gitkeep ├── conf.py └── index.rst ├── requirements.txt ├── setup.cfg ├── setup.py ├── specs ├── client │ ├── test_access_token.py │ ├── test_request.py │ └── test_response.py ├── mixins │ ├── test_errors.py │ ├── test_http.py │ ├── test_pagination.py │ ├── test_parser.py │ └── test_validator.py ├── namespaces │ └── test_namespaces.py ├── test_client.py └── test_version.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = ./amadeus 3 | branch = True 4 | 5 | [report] 6 | fail_under = 95 -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at developer@amadeus.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Development and Testing 2 | 3 | To run the project locally, clone the repository, and then create a virtual environment and install the dependencies. 4 | ```sh 5 | git clone https://github.com/amadeus4dev/amadeus-python.git 6 | cd amadeus-python 7 | ``` 8 | 9 | First, make sure your pyenv is initialized for each environment (`pyenv init `). 10 | If you want to have it loaded automatically, add the following to ~/.zshrc: 11 | 12 | ```sh 13 | eval "$(pyenv init -)" 14 | ``` 15 | 16 | Second, ensure you have a version of every Python we support installed. Your versions may differ. 17 | 18 | ```sh 19 | pyenv install 3.8.0 20 | pyenv install 3.9.4 21 | pyenv install 3.10.3 22 | pyenv global 3.8.0 3.9.4 3.10.3 23 | ``` 24 | 25 | Next ensure you create a virtual environment. 26 | 27 | ```sh 28 | virtualenv venv 29 | source venv/bin/activate 30 | pip install -r requirements.txt 31 | ``` 32 | 33 | ### Running tests 34 | 35 | To run the tests against every supported Python version, use `tox`. 36 | 37 | ```sh 38 | tox 39 | ``` 40 | 41 | Alternatively, to run tests just against a specific Python environment run: 42 | 43 | ```sh 44 | tox -e py 45 | ``` 46 | 47 | In order to see the code coverage results, open the `index.html` file in the `htmlcov` folder. 48 | 49 | ### Using a library locally 50 | 51 | To install a library locally, use `pip` to install the library in editable mode. 52 | 53 | ```sh 54 | pip install -e . 55 | ``` 56 | 57 | This will make the current code available for editing and using in live scripts, for example. 58 | 59 | ```py 60 | from amadeus import Client 61 | ``` 62 | 63 | ### Releasing 64 | 65 | - [ ] Update the version in `amadeus/version.py` using semver rules 66 | - [ ] Update the `CHANGELOG.rst` with the new version 67 | - [ ] Push all changes and ensure all tests pass on GitHub Actions 68 | - [ ] Draft a new [release](https://github.com/amadeus4dev/amadeus-java/releases/new) by creating a tag and copying the description from the `CHANGELOG.rst` 69 | 70 | ## How to contribute to the Amadeus Python SDK 71 | 72 | #### **Did you find a bug?** 73 | 74 | * **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/amadeus4dev/amadeus-python/issues). 75 | 76 | * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/amadeus4dev/amadeus-python/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. 77 | 78 | #### **Did you write a patch that fixes a bug?** 79 | 80 | * Open a new GitHub pull request with the patch. 81 | 82 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 83 | 84 | #### **Do you intend to add a new feature or change an existing one?** 85 | 86 | * Suggest your change [in a new issue](https://github.com/amadeus4dev/amadeus-python/issues/new) and start writing code. 87 | 88 | * Make sure your new code does not break any tests and include new tests. 89 | 90 | * With good code comes good documentation. Try to copy the existing documentation and adapt it to your needs. 91 | 92 | * Close the issue or mark it as inactive if you decide to discontinue working on the code. 93 | 94 | #### **Do you have questions about the source code?** 95 | 96 | * Ask any question about how to use the library by [raising a new issue](https://github.com/amadeus4dev/amadeus-python/issues/new). 97 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Describe the issue] 4 | 5 | ## Steps to Reproduce 6 | 7 | 1. [First step] 8 | 2. [Second step] 9 | 3. [and so on...] 10 | 11 | __Expected Behavior:__ [What you expect to happen] 12 | 13 | __Actual Behavior:__ [What actually happens] 14 | 15 | __Stable Behavior?__ [What percentage of the time does it reproduce?] 16 | 17 | ## Versions 18 | 19 | What version of Python/Pip are you running? What Operating System are you on? 20 | 21 | ## Checklist 22 | 23 | Please make sure you checked the following: 24 | 25 | * Which version of Python are you using? 26 | * Did you download the latest version of this package? 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | ## Changes for this pull request 4 | 5 | ## Checklist 6 | 7 | Remove this if you have done all of these: 8 | 9 | * Ensure all tests pass and linting 10 | * Add any changes to the README 11 | * Add any changes or new comments to SDK methods 12 | * Ensure this PR only changes what it is intended to change 13 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Amadeus Support 2 | 3 | Our [developer support team](https://developers.amadeus.com/support) is here to 4 | help you. You can find us on 5 | [StackOverflow](https://stackoverflow.com/questions/tagged/amadeus) and 6 | [email](mailto:developers@amadeus.com). 7 | -------------------------------------------------------------------------------- /.github/images/dependencies.svg: -------------------------------------------------------------------------------- 1 | dependenciesdependencies00 2 | -------------------------------------------------------------------------------- /.github/images/support.svg: -------------------------------------------------------------------------------- 1 | contactcontactsupportsupport 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | build: 9 | runs-on: ubuntu-24.04 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | python-version: ['3.8', '3.9', '3.10'] 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | python -m pip install -r requirements.txt 24 | - name: Test with tox 25 | run: | 26 | tox -e py 27 | - name: Before deploy 28 | run: | 29 | pip install -e . 30 | pip install --upgrade setuptools 31 | make docs 32 | - name: Build binary wheel and a source tarball 33 | if: ${{ matrix.python-version == '3.10' }} 34 | run: python setup.py sdist -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | on: 3 | push: 4 | # Sequence of patterns matched against refs/tags 5 | tags: 6 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 7 | jobs: 8 | build: 9 | runs-on: Ubuntu-20.04 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | python-version: ['3.10'] 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | python -m pip install -r requirements.txt 25 | - name: Test with tox 26 | run: | 27 | tox -e py 28 | - name: Before deploy 29 | run: | 30 | pip install -e . 31 | pip install --upgrade setuptools 32 | make docs 33 | - name: Build binary wheel and a source tarball 34 | run: python setup.py sdist 35 | - name: Publish a Python distribution to PyPI 36 | uses: pypa/gh-action-pypi-publish@release/v1 37 | with: 38 | user: __token__ 39 | password: ${{ secrets.PYPI_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | venv 3 | __pycache__ 4 | test.py 5 | *.pyc 6 | .python-version 7 | .tox 8 | .pytest_cache 9 | htmlcov 10 | .coverage 11 | _docs 12 | dist 13 | build 14 | .vscode 15 | env -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 12.0.0 - 2025-04-11 4 | -------------------- 5 | Decommissioned the Points of Interest and Location Score APIs 6 | 7 | Updated the Ubuntu 24.04 for GitHub Actions 8 | 9 | Big thanks to `Siddhartha Dutta `_ for his contribution to the above implementations! 10 | 11 | 11.0.0 - 2024-10-02 12 | -------------------- 13 | Decommissioned the Trip Parser API 14 | 15 | 10.1.0 - 2024-06-20 16 | -------------------- 17 | Add support for the `Hotel Booking v2 API `_. Big thanks to `Siddhartha Dutta `_ for his contribution 18 | 19 | 10.0.0 - 2024-04-17 20 | -------------------- 21 | Decommissioned the Safe Place API 22 | 23 | Fixed the List Type Query Parameter for Hotel List API, Big thanks to `Siddhartha Dutta `_ for his contribution! 24 | 25 | Updated sphinx version from 3.4.1 to 5.0.0 26 | 27 | 9.0.0 - 2023-09-04 28 | -------------------- 29 | Decommissioned the Travel Restrictions API v2 30 | 31 | 8.1.0 - 2023-06-22 32 | -------------------- 33 | Add support for the `Transfer Search API `_ 34 | 35 | Add support for the `Transfer Booking API `_ 36 | 37 | Add support for the `Transfer Management API `_ 38 | 39 | Big thanks to `Siddhartha Dutta `_ for his contribution to the above implementations! 40 | 41 | 8.0.0 - 2023-01-30 42 | -------------------- 43 | Decommissioned Travel Restrictions API v1 44 | 45 | Decommissioned Hotel Search API v2 46 | 47 | Upgraded Python v3.8+ support and old dependencies 48 | 49 | Upgraded testing by using `pytest` and `mock` 50 | 51 | Fixed #175 Replace type() with instance() 52 | 53 | Fixed #177 Update the default value {} as an argument 54 | 55 | Minor updates in How to Release and running test in contribution guide 56 | 57 | 7.1.0 - 2022-11-04 58 | -------------------- 59 | Add support for `Travel Restrictions v2 API `_ 60 | 61 | Bug fix in pagination 62 | 63 | Add SonarCloud support 64 | 65 | 7.0.0 - 2022-07-20 66 | -------------------- 67 | Add support for `Trip Parser v3 API `_ and remove Trip Parser v2 68 | 69 | Add support for `City Search API `_ 70 | 71 | Add support for `Airline Routes API `_ and `Hotel Name Autocomplete API `_. Big thanks to `Siddhartha Dutta `_ for his contribution! 72 | 73 | Implement the coverage report generation at CI time 74 | 75 | 6.0.1 - 2022-05-23 76 | -------------------- 77 | Removing all references to the unused media namespace 78 | 79 | 6.0.0 - 2022-05-23 80 | -------------------- 81 | Add support for `Hotel List API `_ 82 | 83 | Add support for `Hotel Search v3 `_ 84 | 85 | Add support for the X-HTTP-Method-Override header 86 | 87 | Remove the AI-Generated Photos API 88 | 89 | 5.3.1 - 2022-02-24 90 | -------------------- 91 | Update release workflow 92 | 93 | 5.3.0 - 2022-02-24 94 | -------------------- 95 | Add support for `Travel Restrictions API `_ 96 | Add support for `Airport Routes API `_ 97 | 98 | 5.2.0 - 2021-11-29 99 | -------------------- 100 | Migrate CI/CD to GitHub Actions 101 | 102 | 5.1.0 - 2021-05-12 103 | -------------------- 104 | Add support for the `Flight Availabilities Search API `_ 105 | 106 | Add support for the `Branded Fares Upsell API `_ 107 | 108 | 5.0.0 - 2021-02-02 109 | -------------------- 110 | Remove support for Python 2. The SDK requires Python 3.4+ 111 | 112 | Fix unwanted exception on DELETE method of Flight Order Management API 113 | 114 | 4.5.0 - 2020-11-05 115 | -------------------- 116 | Add support for the `Flight Price Analysis API `_ 117 | 118 | 4.4.0 - 2020-10-09 119 | -------------------- 120 | Add support for the `Tours and Activities API `_ 121 | 122 | 4.3.0 - 2020-09-10 123 | -------------------- 124 | Add support for the `On-Demand Flight Status API `_ 125 | 126 | 4.2.0 - 2020-08-05 127 | -------------------- 128 | Add support for the `Travel Recommendations API `_ 129 | 130 | Moved the code examples directory to a dedicated `code examples repository `_ 131 | 132 | 4.1.0 - 2020-06-11 133 | -------------------- 134 | Add support for the `Safe Place API `_ 135 | 136 | 4.0.0 - 2020-04-27 137 | -------------------- 138 | Add support for the `Flight Choice Prediction v2 `_ 139 | 140 | The input of Flight Choice Prediction v2 is the result of Flight Offers Search API - in v1 the input was the result of Flight Low-Fare Search 141 | 142 | Add support for the Retrieve (3rd) endpoint of `Points Of Interest API `_ 143 | 144 | Remove support for Flight Choice Prediction v1 145 | 146 | Remove support for Flight Low-Fare Search: decommission on May 28, 2020 and mandatory migration to Flight Offers Search 147 | 148 | Remove support for Most Searched Destinations 149 | 150 | Add Trip Parser, Flight Create Orders and Flight Order Management executable examples 151 | 152 | 3.5.0 - 2020-02-13 153 | -------------------- 154 | Add support for the `SeatMap Display `_ 155 | 156 | SeatMap Display API allows you to get information to display airplane cabin plan from a Flight Offer in order for the traveler to be able to choose his seat during the flight booking flow thanks to POST method. In addition GET method allows you to display airplane cabin plan from an existing Flight Order. 157 | 158 | 3.4.0 - 2020-01-28 159 | -------------------- 160 | Add support for the `Hotel Booking `_ 161 | 162 | The Amadeus Hotel Booking API lets you complete bookings at over 150,000 hotels and accommodations around the world. To complete bookings, you must first use the Amadeus Hotel Search API to search for hotel deals, select the desired offer and confirm the final price and availability. You can then use the Hotel Booking API to complete the reservation by providing an offer id, guest information and payment information. 163 | 164 | Add support for the `Flight Order Management `_ 165 | 166 | The Flight Order Management API lets you consult bookings created through the Flight Create Orders API. Using the booking ID generated by Flight Create Orders, Flight Order Management returns the last-updated version of the booking record with any post-booking modifications including but not limited to ticket information, form of payment or other remarks. 167 | 168 | Add support for the `Flight Create Orders `_ 169 | 170 | The Flight Create Order API is a flight booking API that lets you perform the final booking for a desired flight and ancillary products (additional bags, extra legroom, etc.). The API returns a unique ID for the flight order and reservation details. This API is used to perform the final booking on confirmed fares returned by the Flight Offers Price API. 171 | 172 | Add support for the `Flight Offers Price `_ 173 | 174 | The Flight Offers Price API confirms the flight price (including taxes and fees) and availability for a given flight returned by the Flight Offers Search API. The API also returns pricing for ancillary products (additional bags, extra legroom, etc.) and the payment information details needed for booking. 175 | 176 | Add support for the `Flight Offers Search `_ 177 | 178 | The Flight Offers Search API is a flight search API that returns cheap flights between two airports for a given number of passengers and for a given date or date range. The API returns airline name, price and fare details, as well as additional information like baggage allowance, prices for additional baggage and departure terminal. 179 | 180 | Add support for the `Trip Parser `_ 181 | 182 | The Trip Parser API parses information from various booking confirmation emails and returns a standardized, structured travel itinerary. The API can extract relevant information from a wide variety of flight, hotel, rental car and rail providers’ confirmation emails by first identifying the provider and then using a database of provider-specific email structures to determine which information to extract. The API then returns a link to the JSON structure of the itinerary. 183 | 184 | Add self-containing executable examples for the existing supported endpoints. 185 | 186 | 3.3.0 - 2019-12-04 187 | -------------------- 188 | Add support for the `AI-Generated Photos` 189 | 190 | The AI-Generated Photos API returns a link to download a rendered image of a landscape. The image size is 512x512 pixels and the currently available image categories are BEACH and MOUNTAIN. The link to download the AI-generated picture is valid for 24 hours. This API is an experimental project created by the Amadeus AI Lab using the Nvidia StyleGAN framework. This API is free to use and we welcome any feedback you may have about improvements. 191 | 192 | Add support for the `Flight Delay Prediction `_ 193 | 194 | The Flight Delay Prediction API returns the probability that a given flight will be delayed by four possible delay lengths: less than 30 minutes, 30-60 minutes, 60-120 minutes and over 120 minutes/cancellation. The API receives flight information and applies a machine-learning model trained with Amadeus historical data to determine the probability of flight delay. 195 | 196 | Release of the `Airport On-Time Performance `_ 197 | 198 | The Airport On-Time Performance API returns the estimated percentage of on-time flight departures for a given airport and date. The API receives the 3-letter IATA airport code and departure date and applies a machine-learning model trained with Amadeus historical data to estimate the overall airport on-time performance. This API is in currently in beta and only returns accurate data for airports located in the U.S. 199 | 200 | 3.2.0 - 2019-11-07 201 | -------------------- 202 | Add support for the `Trip Purpose Prediction API `_ 203 | 204 | The Trip Purpose Prediction API returns the probability of whether a round-trip flight itinerary is for business or leisure travel. The API takes flight dates, departure city and arrival city and then applies a machine-learning model trained with Amadeus historical data to determine the probability that the itinerary is for business or leisure travel. This API is useful for gaining insight and optimizing the search and shopping experience. 205 | 206 | Add support for the `Hotel Ratings API `_ 207 | 208 | The Hotel Ratings API provides hotel ratings based on automated sentiment analysis algorithm applied on the online reviews. Apart from an overall rating for a hotel also provides ratings for different categories of each (e.g.: staff, pool, internet, location). This provides a key content information for decision making during a shopping experience being able to compare how good a hotel is compared to others, sort hotels by ratings, filter by categories or recommend a hotel based on the trip context. 209 | 210 | Release of the `Flight Choice Prediction API `_ 211 | 212 | The Flight Choice Prediction API allows developers to forecast traveler choices in the context of search & shopping. Exposing machine learning & AI services for travel, this API consumes the output of the Flight Low-fare Search API and returns augmented content with probabilities of choices for each flight offers. 213 | 214 | 3.1.0 - 2019-03-25 215 | -------------------- 216 | Release of the `Points Of Interest API `_ 217 | 218 | The Points Of Interest API, powered by AVUXI TopPlace, is a search API that returns a list of popular places for a particular location. The location can be defined as area bound by four coordinates or as a geographical coordinate with a radius. The popularity of a place or 'point of interest' is determined by AVUXI's proprietary algorithm that considers factors such as ratings, check-ins, category scores among other factors from a host of online media sources. 219 | 220 | 221 | 3.0.0 - 2019-01-22 222 | -------------------- 223 | ** Hotel Search v2 has been deployed (Hotel Search v1 is now deprecated) ** 224 | 225 | ** General ** 226 | - Remove support of Hotel Search v1 227 | - URLs for all three endpoints have been simplified for ease-of-use and consistency 228 | ** Find Hotels - 1st endpoint ** 229 | - The parameter `hotels` has been renamed to `hotelIds` 230 | ** View Hotel Rooms - 2nd endpoint ** 231 | - Update from `amadeus.shopping.hotel('IALONCHO').hotel_offers.get` to `amadeus.shopping.hotel_offers_by_hotel.get(hotelId: 'IALONCHO')` 232 | - Now get all images in ‘View Hotels Rooms’ endpoint using the view parameter as `FULL_ALL_IMAGES` 233 | ** View Room Details - 3rd endpoint ** 234 | - Updated from `amadeus.shopping.hotel('IALONCHO').offer('XXX').get` to `amadeus.shopping.hotel_offer('XXX').get` 235 | - Image category added under Media in the response 236 | - Hotel distance added in the response 237 | - Response now refers to the common HotelOffer object model 238 | 239 | 2.0.1 - 2019-01-17 240 | -------------------- 241 | 242 | Fix pagination URL encoding parameters 243 | 244 | 2.0.0 - 2018-10-14 245 | -------------------- 246 | 247 | `Flight Most Searched Destinations `_: Redesign of the API - Split the previous endpoint in 2 endpoints: 248 | 249 | - 1st endpoint to find the most searched destinations 250 | - 2nd endpoint to have more data about a dedicated origin & destination 251 | 252 | `Flight Most Booked Destinations `_: 253 | 254 | - Rename origin to originCityCode 255 | 256 | `Flight Most Traveled Destinations `_: 257 | 258 | - Rename origin in originCityCode 259 | 260 | `Flight Check-in Links `_: 261 | 262 | - Rename airline to airlineCode 263 | 264 | `Airport & City Search `_: 265 | 266 | - Remove parameter onlyMajor 267 | 268 | `Airport Nearest Relevant `_: 269 | 270 | - Add radius as parameter 271 | 272 | `Airline Code Lookup `_: 273 | 274 | - Regroup parameters *IATACode* and *ICAOCode* under the same name *airlineCodes* 275 | 276 | 1.1.0 - 2018-08-01 277 | -------------------- 278 | 279 | Release 1.1.0 280 | 281 | 1.0.0 - 2018-04-20 282 | -------------------- 283 | 284 | Release 1.0.0 285 | 286 | 1.0.0b8 - 2018-04-19 287 | -------------------- 288 | 289 | Update namespace for `air_traffic/traveled` path. 290 | 291 | 1.0.0b7 - 2018-04-09 292 | -------------------- 293 | 294 | Fix an issue where UTF8 was not properly decoded. 295 | 296 | 1.0.0b6 - 2018-04-05 297 | -------------------- 298 | 299 | Set logging to silent by default 300 | 301 | 1.0.0b5 - 2018-04-05 302 | -------------------- 303 | 304 | Adds easier to read error messages 305 | 306 | 1.0.0b4 - 2018-04-04 307 | -------------------- 308 | 309 | Bug fix for install from PyPi 310 | 311 | 1.0.0b3 - 2018-04-05 312 | -------------------- 313 | 314 | - Renamed back to “amadeus” 315 | 316 | 1.0.0b2 - 2018-04-05 317 | -------------------- 318 | 319 | - Updated README for PyPi 320 | 321 | 1.0.0b1 - 2018-04-05 322 | -------------------- 323 | 324 | - Initial Beta Release 325 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2018 Amadeus IT Group SA 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 7 | persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 10 | Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 13 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 15 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE CHANGELOG.rst 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCES=amadeus specs setup.py 2 | DOC_SOURCES=amadeus docs README.rst 3 | 4 | test: 5 | mamba --format=documentation --enable-coverage 6 | 7 | coverage: 8 | coverage html 9 | open htmlcov/index.html 10 | 11 | watch: 12 | make run 13 | make coverage 14 | fswatch -o ${SOURCES} | xargs -n1 -I{} make run 15 | 16 | run: 17 | make lint 18 | make test 19 | coverage html 20 | 21 | lint: 22 | flake8 $(SOURCES) --exit-zero 23 | 24 | docs: 25 | rm -rf _docs 26 | sphinx-build -b html docs _docs 27 | 28 | clean: 29 | rm -rf _docs build dist 30 | 31 | build: 32 | python setup.py sdist bdist_wheel 33 | 34 | watchdocs: 35 | make docs 36 | open _docs/index.html 37 | fswatch -o ${DOC_SOURCES} | xargs -n1 -I{} make docs 38 | 39 | .PHONY: test coverage watch run lint docs clean build 40 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Amadeus Python SDK 2 | ================== 3 | 4 | |Module Version| |Build Status| |Maintainability| |Dependencies| |Discord| 5 | 6 | 7 | Amadeus provides a rich set of APIs for the travel industry. For more details, check out the `Amadeus for Developers Portal `__ or the `SDK class reference `__. 8 | 9 | Installation 10 | ------------ 11 | 12 | This SDK requires Python 3.8+. You can install it directly with pip: 13 | 14 | .. code:: sh 15 | 16 | pip install amadeus 17 | 18 | OR, add it to your `requirements.txt` file and install using: 19 | 20 | .. code:: sh 21 | 22 | pip install -r requirements.txt 23 | 24 | 25 | Getting Started 26 | --------------- 27 | 28 | To make your first API call, you will need to `register `__ for an Amadeus Developer Account and `set up your first 29 | application `__. 30 | 31 | .. code:: py 32 | 33 | from amadeus import Client, ResponseError 34 | 35 | amadeus = Client( 36 | client_id='REPLACE_BY_YOUR_API_KEY', 37 | client_secret='REPLACE_BY_YOUR_API_SECRET' 38 | ) 39 | 40 | try: 41 | response = amadeus.shopping.flight_offers_search.get( 42 | originLocationCode='MAD', 43 | destinationLocationCode='ATH', 44 | departureDate='2024-11-01', 45 | adults=1) 46 | print(response.data) 47 | except ResponseError as error: 48 | print(error) 49 | 50 | Examples 51 | -------------------------- 52 | You can find all the endpoints in self-contained `code examples `_. 53 | 54 | Initialization 55 | -------------- 56 | 57 | The client can be initialized directly. 58 | 59 | .. code:: py 60 | 61 | # Initialize using parameters 62 | amadeus = Client(client_id='REPLACE_BY_YOUR_API_KEY', client_secret='REPLACE_BY_YOUR_API_SECRET') 63 | 64 | Alternatively, it can be initialized without any parameters if the environment variables ``AMADEUS_CLIENT_ID`` and ``AMADEUS_CLIENT_SECRET`` are present. 65 | 66 | .. code:: py 67 | 68 | amadeus = Client() 69 | 70 | Your credentials can be found on the `Amadeus dashboard `__. 71 | 72 | By default, the SDK environment is set to ``test`` environment. To switch to a production (pay-as-you-go) environment, please switch the hostname as follows: 73 | 74 | .. code:: py 75 | 76 | amadeus = Client(hostname='production') 77 | 78 | Documentation 79 | ------------- 80 | 81 | Amadeus has a large set of APIs, and our documentation is here to get you started today. Head over to our `reference documentation `__ for in-depth information about every SDK method, as well as its arguments and return types. 82 | 83 | - `Initialize the SDK `__ 84 | - `Find an Airport `__ 85 | - `Find a Flight `__ 86 | - `Get Flight Inspiration `__ 87 | 88 | Making API calls 89 | ---------------- 90 | 91 | This library conveniently maps every API path to a similar path. 92 | 93 | For example, ``GET /v2/reference-data/urls/checkin-links?airlineCode=BA`` would be: 94 | 95 | .. code:: py 96 | 97 | amadeus.reference_data.urls.checkin_links.get(airlineCode='BA') 98 | 99 | Similarly, to select a resource by ID, you can pass in the ID to the singular path. 100 | 101 | For example, ``GET /v2/shopping/hotel-offers/XZY`` would be: 102 | 103 | .. code:: py 104 | 105 | amadeus.shopping.hotel_offer('XYZ').get() 106 | 107 | You can make any arbitrary API call directly with the ``.get`` method as well: 108 | 109 | .. code:: py 110 | 111 | amadeus.get('/v2/reference-data/urls/checkin-links', airlineCode='BA') 112 | 113 | Or, with ``POST`` method: 114 | 115 | .. code:: py 116 | 117 | amadeus.post('/v1/shopping/flight-offers/pricing', body) 118 | 119 | Response 120 | -------- 121 | 122 | Every API call returns a ``Response`` object. If the API call contained a JSON response it will parse the JSON into the ``.result`` attribute. If this data also contains a ``data`` key, it will make that available as the ``.data`` attribute. The raw body of the response is always available as the ``.body`` attribute. 123 | 124 | .. code:: py 125 | 126 | from amadeus import Location 127 | 128 | response = amadeus.reference_data.locations.get( 129 | keyword='LON', 130 | subType=Location.ANY 131 | ) 132 | 133 | print(response.body) #=> The raw response, as a string 134 | print(response.result) #=> The body parsed as JSON, if the result was parsable 135 | print(response.data) #=> The list of locations, extracted from the JSON 136 | 137 | Pagination 138 | ---------- 139 | 140 | If an API endpoint supports pagination, the other pages are available under the ``.next``, ``.previous``, ``.last`` and ``.first`` methods. 141 | 142 | .. code:: py 143 | 144 | from amadeus import Location 145 | 146 | response = amadeus.reference_data.locations.get( 147 | keyword='LON', 148 | subType=Location.ANY 149 | ) 150 | 151 | amadeus.next(response) #=> returns a new response for the next page 152 | 153 | If a page is not available, the method will return ``None``. 154 | 155 | Logging & Debugging 156 | ------------------- 157 | 158 | The SDK makes it easy to add your own logger. 159 | 160 | .. code:: py 161 | 162 | import logging 163 | 164 | logger = logging.getLogger('your_logger') 165 | logger.setLevel(logging.DEBUG) 166 | 167 | amadeus = Client( 168 | client_id='REPLACE_BY_YOUR_API_KEY', 169 | client_secret='REPLACE_BY_YOUR_API_SECRET', 170 | logger=logger 171 | ) 172 | 173 | Additionally, to enable more verbose logging, you can set the appropriate level on your own logger. The easiest way would be to enable debugging via a parameter during initialization, or using the ``AMADEUS_LOG_LEVEL`` environment variable. 174 | 175 | .. code:: py 176 | 177 | amadeus = Client( 178 | client_id='REPLACE_BY_YOUR_API_KEY', 179 | client_secret='REPLACE_BY_YOUR_API_SECRET', 180 | log_level='debug' 181 | ) 182 | 183 | List of supported endpoints 184 | --------------------------- 185 | 186 | .. code:: py 187 | 188 | # Flight Inspiration Search 189 | amadeus.shopping.flight_destinations.get(origin='MAD') 190 | 191 | # Flight Cheapest Date Search 192 | amadeus.shopping.flight_dates.get(origin='MAD', destination='MUC') 193 | 194 | # Flight Offers Search GET 195 | amadeus.shopping.flight_offers_search.get(originLocationCode='SYD', destinationLocationCode='BKK', departureDate='2022-11-01', adults=1) 196 | # Flight Offers Search POST 197 | amadeus.shopping.flight_offers_search.post(body) 198 | 199 | # Flight Offers Price 200 | flights = amadeus.shopping.flight_offers_search.get(originLocationCode='SYD', destinationLocationCode='BKK', departureDate='2022-11-01', adults=1).data 201 | amadeus.shopping.flight_offers.pricing.post(flights[0]) 202 | amadeus.shopping.flight_offers.pricing.post(flights[0:2], include='credit-card-fees,other-services') 203 | 204 | # Flight Create Orders 205 | amadeus.booking.flight_orders.post(flights[0], traveler) 206 | 207 | # Flight Order Management 208 | # The flight ID comes from the Flight Create Orders (in test environment it's temporary) 209 | # Retrieve the order based on it's ID 210 | flight_booking = amadeus.booking.flight_orders.post(body).data 211 | amadeus.booking.flight_order(flight_booking['id']).get() 212 | # Delete the order based on it's ID 213 | amadeus.booking.flight_order(flight_booking['id']).delete() 214 | 215 | # Flight SeatMap Display GET 216 | amadeus.shopping.seatmaps.get(**{"flight-orderId": "orderid"}) 217 | # Flight SeatMap Display POST 218 | amadeus.shopping.seatmaps.post(body) 219 | 220 | # Flight Availabilities POST 221 | amadeus.shopping.availability.flight_availabilities.post(body) 222 | 223 | # Branded Fares Upsell 224 | amadeus.shopping.flight_offers.upselling.post(body) 225 | 226 | # Flight Choice Prediction 227 | body = amadeus.shopping.flight_offers_search.get( 228 | originLocationCode='MAD', 229 | destinationLocationCode='NYC', 230 | departureDate='2022-11-01', 231 | adults=1).result 232 | amadeus.shopping.flight_offers.prediction.post(body) 233 | 234 | # Flight Checkin Links 235 | amadeus.reference_data.urls.checkin_links.get(airlineCode='BA') 236 | 237 | # Airline Code Lookup 238 | amadeus.reference_data.airlines.get(airlineCodes='U2') 239 | 240 | # Airport and City Search (autocomplete) 241 | # Find all the cities and airports starting by 'LON' 242 | amadeus.reference_data.locations.get(keyword='LON', subType=Location.ANY) 243 | # Get a specific city or airport based on its id 244 | amadeus.reference_data.location('ALHR').get() 245 | 246 | # City Search 247 | amadeus.reference_data.locations.cities.get(keyword='PAR') 248 | 249 | # Airport Nearest Relevant Airport (for London) 250 | amadeus.reference_data.locations.airports.get(longitude=0.1278, latitude=51.5074) 251 | 252 | # Flight Most Booked Destinations 253 | amadeus.travel.analytics.air_traffic.booked.get(originCityCode='MAD', period='2017-08') 254 | 255 | # Flight Most Traveled Destinations 256 | amadeus.travel.analytics.air_traffic.traveled.get(originCityCode='MAD', period='2017-01') 257 | 258 | # Flight Busiest Travel Period 259 | amadeus.travel.analytics.air_traffic.busiest_period.get(cityCode='MAD', period='2017', direction='ARRIVING') 260 | 261 | # Hotel Search v3 262 | # Get list of available offers by hotel ids 263 | amadeus.shopping.hotel_offers_search.get(hotelIds='RTPAR001', adults='2') 264 | # Check conditions of a specific offer 265 | amadeus.shopping.hotel_offer_search('XXX').get() 266 | 267 | # Hotel List 268 | # Get list of hotels by hotel id 269 | amadeus.reference_data.locations.hotels.by_hotels.get(hotelIds='ADPAR001') 270 | # Get list of hotels by city code 271 | amadeus.reference_data.locations.hotels.by_city.get(cityCode='PAR') 272 | # Get list of hotels by a geocode 273 | amadeus.reference_data.locations.hotels.by_geocode.get(longitude=2.160873,latitude=41.397158) 274 | 275 | # Hotel Name Autocomplete 276 | amadeus.reference_data.locations.hotel.get(keyword='PARI', subType=[Hotel.HOTEL_GDS, Hotel.HOTEL_LEISURE]) 277 | 278 | # Hotel Booking v2 279 | # The offerId comes from the hotel_offer above 280 | amadeus.booking.hotel_orders.post( 281 | guests=guests, 282 | travel_agent=travel_agent, 283 | room_associations=room_associations, 284 | payment=payment) 285 | 286 | # Hotel Booking v1 287 | # The offerId comes from the hotel_offer above 288 | amadeus.booking.hotel_bookings.post(offerId, guests, payments) 289 | 290 | # Hotel Ratings 291 | # What travelers think about this hotel? 292 | amadeus.e_reputation.hotel_sentiments.get(hotelIds = 'ADNYCCTB') 293 | 294 | # Trip Purpose Prediction 295 | amadeus.travel.predictions.trip_purpose.get(originLocationCode='ATH', destinationLocationCode='MAD', departureDate='2022-11-01', returnDate='2022-11-08') 296 | 297 | # Flight Delay Prediction 298 | amadeus.travel.predictions.flight_delay.get(originLocationCode='NCE', destinationLocationCode='IST', departureDate='2022-08-01', \ 299 | departureTime='18:20:00', arrivalDate='2022-08-01', arrivalTime='22:15:00', aircraftCode='321', carrierCode='TK', flightNumber='1816', duration='PT31H10M') 300 | 301 | # Airport On-Time Performance 302 | amadeus.airport.predictions.on_time.get(airportCode='JFK', date='2022-11-01') 303 | 304 | # Airport Routes 305 | amadeus.airport.direct_destinations.get(departureAirportCode='BLR') 306 | 307 | # Travel Recommendations 308 | amadeus.reference_data.recommended_locations.get(cityCodes='PAR', travelerCountryCode='FR') 309 | 310 | # Retrieve status of a given flight 311 | amadeus.schedule.flights.get(carrierCode='AZ', flightNumber='319', scheduledDepartureDate='2022-09-13') 312 | 313 | # Tours and Activities 314 | # What are the popular activities in Madrid (based a geo location and a radius) 315 | amadeus.shopping.activities.get(latitude=40.41436995, longitude=-3.69170868) 316 | # What are the popular activities in Barcelona? (based on a square) 317 | amadeus.shopping.activities.by_square.get(north=41.397158, west=2.160873, 318 | south=41.394582, east=2.177181) 319 | # Returns a single activity from a given id 320 | amadeus.shopping.activity('4615').get() 321 | 322 | # Returns itinerary price metrics 323 | amadeus.analytics.itinerary_price_metrics.get(originIataCode='MAD', destinationIataCode='CDG', 324 | departureDate='2021-03-21') 325 | 326 | # Airline Routes 327 | amadeus.airline.destinations.get(airlineCode='BA') 328 | 329 | # Transfer Search 330 | amadeus.shopping.transfer_offers.post(body) 331 | 332 | # Transfer Booking 333 | amadeus.ordering.transfer_orders.post(body, offerId='1000000000') 334 | 335 | # Transfer Management 336 | amadeus.ordering.transfer_order('ABC').transfers.cancellation.post(body, confirmNbr=123) 337 | 338 | Development & Contributing 339 | -------------------------- 340 | 341 | Want to contribute? Read our `Contributors 342 | Guide <.github/CONTRIBUTING.md>`__ for guidance on installing and 343 | running this code in a development environment. 344 | 345 | License 346 | ------- 347 | 348 | This library is released under the `MIT License `__. 349 | 350 | Help 351 | ---- 352 | 353 | You can find us on `StackOverflow `__ or join our developer community on `Discord `__. 354 | 355 | .. |Module Version| image:: https://badge.fury.io/py/amadeus.svg 356 | :target: https://pypi.org/project/amadeus/ 357 | .. |Build Status| image:: https://github.com/amadeus4dev/amadeus-python/actions/workflows/build.yml/badge.svg 358 | :target: https://github.com/amadeus4dev/amadeus-python/actions/workflows/build.yml 359 | .. |Maintainability| image:: https://api.codeclimate.com/v1/badges/c2e19cf9628d6f4aece2/maintainability 360 | :target: https://codeclimate.com/github/amadeus4dev/amadeus-python/maintainability 361 | .. |Dependencies| image:: https://raw.githubusercontent.com/amadeus4dev/amadeus-python/master/.github/images/dependencies.svg?sanitize=true 362 | :target: https://badge.fury.io/py/amadeus 363 | .. |Discord| image:: https://img.shields.io/discord/696822960023011329?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2 364 | :target: https://discord.gg/cVrFBqx 365 | -------------------------------------------------------------------------------- /amadeus/__init__.py: -------------------------------------------------------------------------------- 1 | from .amadeus import Client 2 | from .version import version 3 | 4 | from .client.location import Location 5 | from .client.hotel import Hotel 6 | from .client.direction import Direction 7 | from .client.request import Request 8 | from .client.response import Response 9 | from .client.errors import ResponseError 10 | from .client.errors import ParserError, ServerError, AuthenticationError 11 | from .client.errors import NotFoundError, ClientError, NetworkError 12 | 13 | __all__ = [ 14 | 'Client', 'Location', 'Direction', 'version', 'ResponseError', 15 | 'ParserError', 'ServerError', 'AuthenticationError', 16 | 'NotFoundError', 'ClientError', 'Request', 'Response', 17 | 'NetworkError', 'Hotel' 18 | ] 19 | -------------------------------------------------------------------------------- /amadeus/airline/__init__.py: -------------------------------------------------------------------------------- 1 | from ._destinations import Destinations 2 | 3 | __all__ = ['Destinations'] 4 | -------------------------------------------------------------------------------- /amadeus/airline/_destinations.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Destinations(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Get airline destinations. 8 | 9 | .. code-block:: python 10 | 11 | amadeus.airline.destinations.get(airlineCode='BA') 12 | 13 | :param airlineCode: the airline code following IATA standard. 14 | Example: ``"BA"`` 15 | 16 | :rtype: amadeus.Response 17 | :raises amadeus.ResponseError: if the request could not be completed 18 | ''' 19 | return self.client.get('/v1/airline/destinations', **params) 20 | -------------------------------------------------------------------------------- /amadeus/airport/__init__.py: -------------------------------------------------------------------------------- 1 | from ._predictions import AirportOnTime 2 | from ._direct_destinations import DirectDestinations 3 | 4 | __all__ = ['AirportOnTime', 'DirectDestinations'] 5 | -------------------------------------------------------------------------------- /amadeus/airport/_direct_destinations.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class DirectDestinations(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Returns airport direct routes. 8 | 9 | .. code-block:: python 10 | 11 | amadeus.airport.direct_destinations.get( 12 | departureAirportCode='BLR') 13 | 14 | :param departureAirportCode: the departure Airport code following 15 | IATA standard. ``"BLR"``, for example for Bengaluru 16 | 17 | :rtype: amadeus.Response 18 | :raises amadeus.ResponseError: if the request could not be completed 19 | ''' 20 | return self.client.get('/v1/airport/direct-destinations', **params) 21 | -------------------------------------------------------------------------------- /amadeus/airport/_predictions.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from .predictions import AirportOnTime 3 | 4 | 5 | class Predictions(Decorator, object): 6 | def __init__(self, client): 7 | Decorator.__init__(self, client) 8 | self.on_time = AirportOnTime(client) 9 | -------------------------------------------------------------------------------- /amadeus/airport/predictions/__init__.py: -------------------------------------------------------------------------------- 1 | from ._on_time import AirportOnTime 2 | 3 | __all__ = ['AirportOnTime'] 4 | -------------------------------------------------------------------------------- /amadeus/airport/predictions/_on_time.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class AirportOnTime(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Returns a percentage of on-time flight departures 8 | 9 | .. code-block:: python 10 | 11 | amadeus.airport.predictions.on_time.get( 12 | airportCode='JFK', 13 | date='2020-09-01') 14 | 15 | :param airportCode: the City/Airport IATA code from which 16 | the flight will depart. ``"NYC"``, for example for New York 17 | 18 | :param date: the date on which to fly out, in `YYYY-MM-DD` format 19 | 20 | :rtype: amadeus.Response 21 | :raises amadeus.ResponseError: if the request could not be completed 22 | ''' 23 | return self.client.get('/v1/airport/predictions/on-time', **params) 24 | -------------------------------------------------------------------------------- /amadeus/amadeus.py: -------------------------------------------------------------------------------- 1 | from .mixins.validator import Validator 2 | from .mixins.http import HTTP 3 | from .mixins.pagination import Pagination 4 | from .namespaces import Core as Namespaces 5 | 6 | 7 | class Client(Namespaces, Pagination, Validator, HTTP, object): 8 | ''' 9 | The Amadeus client library for accessing 10 | the travel APIs. 11 | ''' 12 | 13 | # The available hosts for this API 14 | HOSTS = { 15 | 'test': 'test.api.amadeus.com', 16 | 'production': 'api.amadeus.com' 17 | } 18 | 19 | # The initialization method for the entire module 20 | def __init__(self, **options): 21 | ''' 22 | Initialize using your credentials: 23 | 24 | .. code-block:: python 25 | 26 | 27 | from amadeus import Client 28 | 29 | amadeus = Client( 30 | client_id='YOUR_CLIENT_ID', 31 | client_secret='YOUR_CLIENT_SECRET' 32 | ) 33 | 34 | Alternatively, initialize the library using the environment variables 35 | ``AMADEUS_CLIENT_ID`` and ``AMADEUS_CLIENT_SECRET``. 36 | 37 | .. code-block:: python 38 | 39 | 40 | amadeus = amadeus.Client() 41 | 42 | :param client_id: the API key used to authenticate the API 43 | :paramtype client_id: str 44 | 45 | :param client_secret: the API secret used to authenticate the API 46 | :paramtype client_secret: str 47 | 48 | :param logger: (optional) a Python compatible logger 49 | (Default: ``logging.Logger``) 50 | :paramtype logger: logging.Logger 51 | 52 | :param log_level: (optional) the log level of the client, either 53 | ``"debug"``, ``"warn"``, or ``"silent"`` mode 54 | (Default: ``"silent"``) 55 | :paramtype log_level: str 56 | 57 | :param hostname: (optional) the name of the server API calls are made 58 | to, ``"production"`` or ``"test"``. (Default: ``"test"``) 59 | :paramtype hostname: str 60 | 61 | :param host: (optional) alternatively, you can specify a full host 62 | domain name instead, e.g. ``"api.example.com"`` 63 | :paramtype host: str 64 | 65 | :param ssl: if this client is should use HTTPS (Default: ``True``) 66 | :paramtype ssl: bool 67 | 68 | :param port: the port this client should use (Default: ``80`` for HTTP 69 | and ``443`` for HTTPS) 70 | :paramtype port: int 71 | 72 | :param custom_app_id: (optional) a custom App ID to be passed in 73 | the User Agent to the server (Default: ``None``) 74 | :paramtype custom_app_id: str 75 | 76 | :param custom_app_version: (optional) a custom App Version number to 77 | be passed in the User Agent to the server (Default: ``None``) 78 | :paramtype custom_app_version: str 79 | 80 | :param http: (optional) a :func:`urllib.request.urlopen` compatible 81 | client that accepts a :class:`urllib.request.Request` compatible 82 | object (Default: ``urlopen``) 83 | :paramtype http: urllib.request.urlopen 84 | 85 | :raises ValueError: when a required param is missing 86 | ''' 87 | self._initialize_client_credentials(options) 88 | self._initialize_logger(options) 89 | self._initialize_host(options) 90 | self._initialize_custom_app(options) 91 | self._initialize_http(options) 92 | 93 | recognized_options = ['client_id', 'client_secret', 'logger', 'host', 94 | 'hostname', 'custom_app_id', 95 | 'custom_app_version', 'http', 96 | 'log_level', 'ssl', 'port'] 97 | self._warn_on_unrecognized_options(options, self.logger, 98 | recognized_options) 99 | Namespaces.__init__(self) 100 | -------------------------------------------------------------------------------- /amadeus/analytics/__init__.py: -------------------------------------------------------------------------------- 1 | from ._itinerary_price_metrics import ItineraryPriceMetrics 2 | 3 | __all__ = ['ItineraryPriceMetrics'] 4 | -------------------------------------------------------------------------------- /amadeus/analytics/_itinerary_price_metrics.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class ItineraryPriceMetrics(Decorator, object): 5 | 6 | def get(self, **params): 7 | ''' 8 | Returns itinerary price metrics by search criteria 9 | 10 | .. code-block:: python 11 | 12 | amadeus.analytics.itinerary_price_metrics.get( 13 | originIataCode='MAD', 14 | destinationIataCode='CDG', 15 | departureDate='2021-03-21') 16 | 17 | :param originIataCode: IATA code of the origin city, for 18 | example ``"BOS"`` for Boston. 19 | 20 | :param destinationIataCode: IATA code of the destination city, 21 | for example ``"ATH"`` for Athens. 22 | 23 | :param departureDate: scheduled departure date of the flight, 24 | local to the departure airport, format YYYY-MM-DD 25 | 26 | :rtype: amadeus.Response 27 | :raises amadeus.ResponseError: if the request could not be completed 28 | ''' 29 | return self.client.get('/v1/analytics/itinerary-price-metrics', **params) 30 | -------------------------------------------------------------------------------- /amadeus/booking/__init__.py: -------------------------------------------------------------------------------- 1 | from ._flight_orders import FlightOrders 2 | from ._flight_order import FlightOrder 3 | from ._hotel_bookings import HotelBookings 4 | from ._hotel_orders import HotelOrders 5 | 6 | __all__ = ['FlightOrders', 'FlightOrder', 'HotelBookings', 'HotelOrders'] 7 | -------------------------------------------------------------------------------- /amadeus/booking/_flight_order.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class FlightOrder(Decorator, object): 5 | def __init__(self, client, flight_order_id): 6 | Decorator.__init__(self, client) 7 | self.flight_order_id = flight_order_id 8 | 9 | def get(self, **params): 10 | ''' 11 | Retrieves a flight order based on its ID. 12 | 13 | .. code-block:: python 14 | 15 | amadeus.booking.flight_order('eJzTd9f3NjIJdzUGAAp%2fAiY=').get() 16 | 17 | :rtype: amadeus.Response 18 | :raises amadeus.ResponseError: if the request could not be completed 19 | ''' 20 | return self.client.get('/v1/booking/flight-orders/{0}' 21 | .format(self.flight_order_id), **params) 22 | 23 | def delete(self, **params): 24 | ''' 25 | Deletes a flight order based on its ID. 26 | 27 | .. code-block:: python 28 | 29 | amadeus.booking.flight_order('eJzTd9f3NjIJdzUGAAp%2fAiY=').delete() 30 | 31 | :rtype: amadeus.Response 32 | :raises amadeus.ResponseError: if the request could not be completed 33 | ''' 34 | return self.client.delete('/v1/booking/flight-orders/{0}' 35 | .format(self.flight_order_id), **params) 36 | -------------------------------------------------------------------------------- /amadeus/booking/_flight_orders.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class FlightOrders(Decorator, object): 5 | def post(self, flight, travelers): 6 | ''' 7 | Books a flight 8 | 9 | .. code-block:: python 10 | 11 | amadeus.booking.flight_orders.post(flight, travelers) 12 | 13 | :rtype: amadeus.Response 14 | :raises amadeus.ResponseError: if the request could not be completed 15 | ''' 16 | flight_offers = [] 17 | if not isinstance(flight, list): 18 | flight_offers.append(flight) 19 | else: 20 | flight_offers.extend(flight) 21 | travelers_info = [] 22 | if not isinstance(travelers, list): 23 | travelers_info.append(travelers) 24 | else: 25 | travelers_info.extend(travelers) 26 | body = {'data': {'type': 'flight-order', 'flightOffers': flight_offers, 27 | 'travelers': travelers_info}} 28 | return self.client.post('/v1/booking/flight-orders', body) 29 | -------------------------------------------------------------------------------- /amadeus/booking/_hotel_bookings.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class HotelBookings(Decorator, object): 5 | def post(self, hotel_offer_id, guests, payments): 6 | ''' 7 | Books a hotel 8 | 9 | .. code-block:: python 10 | 11 | amadeus.booking.hotel_bookings.post(hotel_offer_id, guests, payments) 12 | 13 | The parameters guests and payments can be passed as dictionary 14 | or list of dictionaries. If they are dictionary in this method they are 15 | converted to a list of dictionaries. 16 | 17 | :rtype: amadeus.Response 18 | :raises amadeus.ResponseError: if the request could not be completed 19 | ''' 20 | guests_info = [] 21 | payment_info = [] 22 | if not isinstance(guests, list): 23 | guests_info.append(guests) 24 | else: 25 | guests_info.extend(guests) 26 | if not isinstance(payments, list): 27 | payment_info.append(payments) 28 | else: 29 | payment_info.extend(payments) 30 | body = {'data': {'offerId': hotel_offer_id, 31 | 'guests': guests_info, 32 | 'payments': payment_info}} 33 | return self.client.post('/v1/booking/hotel-bookings', body) 34 | -------------------------------------------------------------------------------- /amadeus/booking/_hotel_orders.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class HotelOrders(Decorator, object): 5 | def post(self, 6 | guests, 7 | travel_agent, 8 | room_associations=[], 9 | payment={}, 10 | arrival_information={}): 11 | ''' 12 | Book hotel(s) via Hotel Booking API V2 13 | 14 | .. code-block:: python 15 | 16 | amadeus.booking.hotel_orders.post(guests, 17 | travel_agent, 18 | room_associations, 19 | payment, 20 | arrival_information) 21 | 22 | The parameters guests and room_associations can be passed as dictionary 23 | or list of dictionaries. If they are dictionary in this method they are 24 | converted to a list of dictionaries. 25 | 26 | :rtype: amadeus.Response 27 | :raises amadeus.ResponseError: if the request could not be completed 28 | ''' 29 | guests_info = [] 30 | room_associations_info = [] 31 | if not isinstance(guests, list): 32 | guests_info.append(guests) 33 | else: 34 | guests_info.extend(guests) 35 | if not isinstance(room_associations, list): 36 | room_associations_info.append(room_associations) 37 | else: 38 | room_associations_info.extend(room_associations) 39 | body = {'data': {'type': 'hotel-order', 40 | 'guests': guests_info, 41 | 'travelAgent': travel_agent, 42 | 'roomAssociations': room_associations_info, 43 | 'arrivalInformation': arrival_information, 44 | 'payment': payment}} 45 | return self.client.post('/v2/booking/hotel-orders', body) 46 | -------------------------------------------------------------------------------- /amadeus/client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amadeus4dev/amadeus-python/0bc7089cb2e49b5b662ee890a5fe611f59ad4332/amadeus/client/__init__.py -------------------------------------------------------------------------------- /amadeus/client/access_token.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | class AccessToken(object): 5 | # The number of seconds before the token expires, when 6 | # we will already try to refresh it 7 | TOKEN_BUFFER = 10 8 | 9 | def __init__(self, client): 10 | self.access_token = None 11 | self.expires_at = 0 12 | self.client = client 13 | 14 | # PROTECTED 15 | 16 | # The bearer token that can be used directly in API request headers 17 | def _bearer_token(self): 18 | return 'Bearer {0}'.format(self.__token()) 19 | 20 | # PRIVATE 21 | 22 | # Returns the access token if it is still valid, 23 | # or refreshes it if it is not (or about to expire) 24 | def __token(self): 25 | if self.__needs_refresh(): 26 | self.__update_access_token() 27 | return self.access_token 28 | 29 | # Checks if the token needs a refesh by checking if the token 30 | # is nil or (about to) expire(d) 31 | def __needs_refresh(self): 32 | has_access_token = self.access_token is not None 33 | current_time_window = int(time.time()) + self.TOKEN_BUFFER 34 | has_valid_token = current_time_window < self.expires_at 35 | 36 | return not (has_access_token and has_valid_token) 37 | 38 | # Fetches a new access token and stores it and its expiry date 39 | def __update_access_token(self): 40 | response = self.__fetch_access_token() 41 | self.__store_access_token(response.result) 42 | 43 | # Fetches a new access token 44 | def __fetch_access_token(self): 45 | return self.client._unauthenticated_request( 46 | 'POST', 47 | '/v1/security/oauth2/token', 48 | { 49 | 'grant_type': 'client_credentials', 50 | 'client_id': self.client.client_id, 51 | 'client_secret': self.client.client_secret 52 | } 53 | ) 54 | 55 | # Store an access token and calculates the expiry date 56 | def __store_access_token(self, data): 57 | self.access_token = data.get('access_token', None) 58 | current_time = int(time.time()) 59 | self.expires_at = current_time + data.get('expires_in', 0) 60 | -------------------------------------------------------------------------------- /amadeus/client/decorator.py: -------------------------------------------------------------------------------- 1 | class Decorator(object): 2 | def __init__(self, client): 3 | self.client = client 4 | -------------------------------------------------------------------------------- /amadeus/client/direction.py: -------------------------------------------------------------------------------- 1 | # 2 | class Direction (object): 3 | ''' 4 | A list of direction types, as used in Busiest Travel Period 5 | 6 | .. code-block:: python 7 | 8 | 9 | from amadeus import Direction 10 | 11 | client.travel.analytics.air_traffic.busiest_period.get( 12 | cityCode = 'MAD', 13 | period = '2017', 14 | direction = Direction.ARRIVING 15 | ) 16 | 17 | :cvar ARRIVING: ``"ARRIVING"`` 18 | :cvar DEPARTING: ``"DEPARTING"`` 19 | ''' 20 | # Arriving 21 | ARRIVING = 'ARRIVING' 22 | # Departing 23 | DEPARTING = 'DEPARTING' 24 | -------------------------------------------------------------------------------- /amadeus/client/errors.py: -------------------------------------------------------------------------------- 1 | from pprint import pformat 2 | 3 | 4 | class ResponseError(RuntimeError): 5 | ''' 6 | An Amadeus error 7 | 8 | :var response: The response object containing the raw HTTP response and 9 | the request used to make the API call. 10 | :vartype response: amadeus.Response 11 | 12 | :var code: A unique code for this type of error. Options include 13 | ``NetworkError``, ``ParserError``, ``ServerError``, 14 | ``AuthenticationError``, ``NotFoundError`` and ``UnknownError``. 15 | :vartype code: str 16 | ''' 17 | 18 | def __init__(self, response): 19 | self.response = response 20 | self.code = self.__determine_code() 21 | RuntimeError.__init__(self, self.description()) 22 | 23 | # PROTECTED 24 | 25 | # Log the error 26 | def _log(self, client): 27 | if (client.log_level == 'warn'): 28 | client.logger.warning( 29 | 'Amadeus %s: %s', self.code, pformat(self.description) 30 | ) 31 | 32 | # PRIVATE 33 | 34 | # Determines the description for this error, as used in in the error output 35 | def description(self): 36 | description = self.short_description(self.response) 37 | return description + self.long_description(self.response) 38 | 39 | # Determines the short description, printed after on the same line as the 40 | # error class name 41 | @staticmethod 42 | def short_description(response): 43 | if hasattr(response, 'status_code') and response.status_code: 44 | return '[{0}]'.format(response.status_code) 45 | else: 46 | return '[---]' 47 | 48 | # Determines the longer description, printed after the initial error 49 | def long_description(self, response): 50 | message = '' 51 | if not(response and response.parsed): 52 | return message 53 | if 'error_description' in response.result: 54 | message += self.error_description(self.response) 55 | if 'errors' in response.result: 56 | message += self.errors_descriptions(self.response) 57 | return message 58 | 59 | # Returns the description of a single error 60 | @staticmethod 61 | def error_description(response): 62 | message = '' 63 | if 'error' in response.result: 64 | message += '\n{0}'.format(response.result['error']) 65 | message += '\n{0}'.format(response.result['error_description']) 66 | return message 67 | 68 | # Returns the description of multiple errors 69 | def errors_descriptions(self, response): 70 | messages = map(self.errors_description, response.result['errors']) 71 | return ''.join(messages) 72 | 73 | # Returns the description of a single error in a multi error response 74 | @staticmethod 75 | def errors_description(error): 76 | message = '\n' 77 | if ('source' in error) and ('parameter' in error['source']): 78 | message += '[{0}] '.format(error['source']['parameter']) 79 | if 'detail' in error: 80 | message += error['detail'] 81 | return message 82 | 83 | # sets the error code to the name of this class 84 | def __determine_code(self): 85 | return self.__class__.__name__ 86 | 87 | 88 | class NetworkError(ResponseError): 89 | ''' 90 | This error occurs when there is some kind of error in the network 91 | ''' 92 | pass 93 | 94 | 95 | class ParserError(ResponseError): 96 | ''' 97 | This error occurs when the response type was JSOn but could not be parsed 98 | ''' 99 | pass 100 | 101 | 102 | class ServerError(ResponseError): 103 | ''' 104 | This error occurs when there is an error on the server 105 | ''' 106 | pass 107 | 108 | 109 | class ClientError(ResponseError): 110 | ''' 111 | This error occurs when the client did not provide the right parameters 112 | ''' 113 | pass 114 | 115 | 116 | class AuthenticationError(ResponseError): 117 | ''' 118 | This error occurs when the client did not provide the right credentials 119 | ''' 120 | pass 121 | 122 | 123 | class NotFoundError(ResponseError): 124 | ''' 125 | This error occurs when the path could not be found 126 | ''' 127 | pass 128 | -------------------------------------------------------------------------------- /amadeus/client/hotel.py: -------------------------------------------------------------------------------- 1 | # 2 | class Hotel(object): 3 | ''' 4 | A list of hotel sub types, as used in Hotel Name Autocomplete 5 | 6 | .. code-block:: python 7 | 8 | 9 | from amadeus import Hotel 10 | 11 | amadeus.reference_data.locations.hotel.get( 12 | keyword='PARI', 13 | subType=[Hotel.HOTEL_LEISURE, Hotel.HOTEL_GDS] 14 | ) 15 | 16 | :cvar HOTEL_LEISURE: ``"HOTEL_LEISURE"`` 17 | :cvar HOTEL_GDS: ``"HOTEL_GDS"`` 18 | ''' 19 | # Hotel Leisure 20 | HOTEL_LEISURE = 'HOTEL_LEISURE' 21 | # Hotel GDS 22 | HOTEL_GDS = 'HOTEL_GDS' 23 | -------------------------------------------------------------------------------- /amadeus/client/location.py: -------------------------------------------------------------------------------- 1 | # 2 | class Location(object): 3 | ''' 4 | A list of location types, as used in searching for locations 5 | 6 | .. code-block:: python 7 | 8 | 9 | from amadeus import Location 10 | 11 | amadeus.reference_data.locations.get( 12 | keyword='lon', 13 | subType=Location.ANY 14 | ) 15 | 16 | :cvar AIRPORT: ``"AIRPORT"`` 17 | :cvar CITY: ``"CITY"`` 18 | :cvar ANY: ``"AIRPORT,CITY"`` 19 | ''' 20 | # Airport 21 | AIRPORT = 'AIRPORT' 22 | # City 23 | CITY = 'CITY' 24 | # Any 25 | ANY = ','.join([AIRPORT, CITY]) 26 | -------------------------------------------------------------------------------- /amadeus/client/request.py: -------------------------------------------------------------------------------- 1 | # Support Python API calls without importing 3rd party library 2 | 3 | import json 4 | 5 | from urllib.request import Request as HTTPRequest 6 | from urllib.parse import urlencode 7 | 8 | 9 | class Request(object): 10 | ''' 11 | An object containing all the compiled information about the request made. 12 | 13 | :var host: The host used for this API call 14 | :vartype host: str 15 | 16 | :var port: The port for this API call. Standard set to 443. 17 | :vartype port: int 18 | 19 | :var ssl: Wether to use SSL for a call, defaults to true 20 | :vartype ssl: bool 21 | 22 | :var scheme: The scheme used to make the API call 23 | :vartype scheme: str 24 | 25 | :var params: The GET/POST params for the API call 26 | :vartype params: dict 27 | 28 | :var path: The path of the API to be called 29 | :vartype path: str 30 | 31 | :var verb: The verb used to make an API call ('GET' or 'POST') 32 | :vartype verb: str 33 | 34 | :var bearer_token: The bearer token (if any) that was used for 35 | authentication 36 | :vartype bearer_token: str 37 | 38 | :var headers: The headers used for the API call 39 | :vartype headers: dict 40 | 41 | :var client_version: The library version used for this request 42 | :vartype client_version: str 43 | 44 | :var language_version: The Python language version used for this request 45 | :vartype language_version: str 46 | 47 | :var app_id: The custom app ID passed in for this request 48 | :vartype app_id: str 49 | 50 | :var app_version: The custom app version used for this request 51 | :vartype app_version: str 52 | ''' 53 | 54 | def __init__(self, options): 55 | self.host = options['host'] 56 | self.port = options['port'] 57 | self.ssl = options['ssl'] 58 | self.scheme = 'https' if self.ssl else 'http' 59 | self.verb = options['verb'] 60 | self.path = options['path'] 61 | self.params = options['params'] 62 | self.bearer_token = options['bearer_token'] 63 | self.client_version = options['client_version'] 64 | self.language_version = options['language_version'] 65 | self.app_id = options['app_id'] 66 | self.app_version = options['app_version'] 67 | 68 | self.headers = { 69 | 'User-Agent': self.__build_user_agent(), 70 | 'Accept': 'application/json, application/vnd.amadeus+json', 71 | 'Content-Type': 'application/vnd.amadeus+json' 72 | } 73 | 74 | self.url = self.__build_url() 75 | self.http_request = self.__build_http_request() 76 | 77 | # PRIVATE 78 | 79 | # Determines the User Agent 80 | def __build_user_agent(self): 81 | user_agent = 'amadeus-python/{0}'.format(self.client_version) 82 | user_agent += ' python/{0}'.format(self.language_version) 83 | if self.app_id: 84 | user_agent += ' {0}/{1}'.format(self.app_id, self.app_version) 85 | return user_agent 86 | 87 | # The list of paths that require HTTP override in header 88 | list_httpoverride = [ 89 | '/v2/shopping/flight-offers', 90 | '/v1/shopping/seatmaps', 91 | '/v1/shopping/availability/flight-availabilities', 92 | '/v2/shopping/flight-offers/prediction', 93 | '/v1/shopping/flight-offers/pricing?', 94 | '/v1/shopping/flight-offers/upselling' 95 | ] 96 | 97 | # Builds a HTTP Request object based on the path, params, and verb 98 | def __build_http_request(self): 99 | # Requests token in case has not been set 100 | if (self.bearer_token is None): 101 | self.headers['Content-Type'] = 'application/x-www-form-urlencoded' 102 | return HTTPRequest(self.url, 103 | data=urlencode(self.params).encode(), 104 | headers=self.headers) 105 | # Adds the authentication header since the bearer token has been set 106 | self.headers['Authorization'] = self.bearer_token 107 | 108 | if self.verb == 'POST': 109 | # Adds HTTP override in Header for the list of paths required 110 | if self.path in Request.list_httpoverride: 111 | self.headers['X-HTTP-Method-Override'] = 'GET' 112 | if isinstance(self.params, dict): 113 | return HTTPRequest(self.url, headers=self.headers, method='POST', 114 | data=json.dumps(self.params).encode()) 115 | else: 116 | return HTTPRequest(self.url, headers=self.headers, method='POST', 117 | data=self.params.encode()) 118 | else: 119 | return HTTPRequest(self.url, headers=self.headers, method=self.verb) 120 | 121 | # Encodes the params before sending them 122 | def _encoded_params(self): 123 | return self._urlencode(self.params) 124 | 125 | # Builds up the full URL based on the scheme, host, path, and params 126 | def __build_url(self): 127 | full_url = '{0}://{1}'.format(self.scheme, self.host) 128 | if not self.__port_matches_scheme(): 129 | full_url = '{0}:{1}'.format(full_url, self.port) 130 | full_url = '{0}{1}'.format(full_url, self.path) 131 | if (self.verb == 'GET'): 132 | full_url += '?{0}'.format(self._encoded_params()) 133 | return full_url 134 | 135 | def __port_matches_scheme(self): 136 | return ((self.ssl and self.port == 443) or 137 | (not self.ssl and self.port == 80)) 138 | 139 | # Helper method to prepare the parameter encoding 140 | def _urlencode(self, d): 141 | return urlencode(self._flatten_keys(d, '', {}), doseq=True) 142 | 143 | # Flattens the hash keys, so page: { offset: 1 } becomes page[offet] = 1 144 | def _flatten_keys(self, d, key, out): 145 | if not isinstance(d, dict): 146 | raise TypeError('Only dicts can be encoded') 147 | 148 | for k in d: 149 | keystr = k if not key else '[{}]'.format(k) 150 | if isinstance(d[k], dict): 151 | self._flatten_keys(d[k], str(key) + str(keystr), out) 152 | else: 153 | out['{}{}'.format(key, keystr)] = d[k] 154 | return out 155 | -------------------------------------------------------------------------------- /amadeus/client/response.py: -------------------------------------------------------------------------------- 1 | from amadeus.mixins.parser import Parser 2 | 3 | 4 | class Response(Parser, object): 5 | ''' 6 | The response object returned for every API call. 7 | 8 | :var http_response: the raw http response 9 | 10 | :var request: the original Request object used to make this call 11 | :vartype request: amadeus.Request 12 | 13 | :var result: the parsed JSON received from the API, if the result was JSON 14 | :vartype result: dict 15 | 16 | :var data: the data extracted from the JSON data, if the body contained 17 | JSON 18 | :vartype data: dict 19 | 20 | :var body: the raw body received from the API 21 | :vartype body: str 22 | 23 | :var parsed: wether the raw body has been parsed into JSON 24 | :vartype parsed: bool 25 | 26 | :var status_code: The HTTP status code for the response, if any 27 | :vartype status_code: int 28 | ''' 29 | 30 | # Initialize the Response object with the 31 | # HTTPResponse object to parse, the client that made the request 32 | # and the original request made 33 | def __init__(self, http_response, request): 34 | self.http_response = http_response 35 | self.request = request 36 | 37 | # PROTECTED 38 | 39 | # Parses the response, using the client to log any errors 40 | def _parse(self, client): 41 | self._parse_status_code() 42 | self._parse_data(client) 43 | return self 44 | -------------------------------------------------------------------------------- /amadeus/e_reputation/__init__.py: -------------------------------------------------------------------------------- 1 | from ._hotel_sentiments import HotelSentiments 2 | 3 | __all__ = ['HotelSentiments'] 4 | -------------------------------------------------------------------------------- /amadeus/e_reputation/_hotel_sentiments.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class HotelSentiments(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Provides ratings and sentiments scores for hotels 8 | 9 | .. code-block:: python 10 | 11 | amadeus.e_reputation.hotel_sentiments.get(hotelIds='TELONMFS,ADNYCCTB']) 12 | 13 | :param hotelIds: comma separated string list of amadeus hotel Ids (max 14 | 3). These Ids are found in the Hotel Search response. ``"RDLON308"``, 15 | for example for the Radisson Blu Hampshire hotel. 16 | 17 | :rtype: amadeus.Response 18 | :raises amadeus.ResponseError: if the request could not be completed 19 | ''' 20 | return self.client.get('/v2/e-reputation/hotel-sentiments', **params) 21 | -------------------------------------------------------------------------------- /amadeus/media/_files.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Files(Decorator, object): 5 | def __init__(self, client): 6 | Decorator.__init__(self, client) 7 | -------------------------------------------------------------------------------- /amadeus/mixins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amadeus4dev/amadeus-python/0bc7089cb2e49b5b662ee890a5fe611f59ad4332/amadeus/mixins/__init__.py -------------------------------------------------------------------------------- /amadeus/mixins/http.py: -------------------------------------------------------------------------------- 1 | from platform import python_version 2 | from pprint import pformat 3 | from urllib.error import URLError 4 | 5 | from amadeus.version import version 6 | from amadeus.client.request import Request 7 | from amadeus.client.response import Response 8 | from amadeus.client.access_token import AccessToken 9 | 10 | 11 | # A helper module for making generic API calls. It is used by 12 | # every namespaced API method. 13 | class HTTP(object): 14 | ''' 15 | A helper module for making generic API calls. It is used by 16 | every namespaced API method. 17 | ''' 18 | 19 | def get(self, path, **params): 20 | ''' 21 | A helper function for making generic GET requests calls. It is used by 22 | every namespaced API GET method. 23 | 24 | It can be used to make any generic API call that is automatically 25 | authenticated using your API credentials: 26 | 27 | .. code-block:: python 28 | 29 | amadeus.get('/foo/bar', airline='1X') 30 | 31 | :param path: path the full path for the API call 32 | :paramtype path: str 33 | 34 | :param params: (optional) params to pass to the API 35 | :paramtype params: dict 36 | 37 | :rtype: amadeus.Response 38 | :raises amadeus.ResponseError: when the request fails 39 | ''' 40 | return self.request('GET', path, params) 41 | 42 | def post(self, path, params=None): 43 | ''' 44 | A helper function for making generic POST requests calls. It is used by 45 | every namespaced API POST method. 46 | 47 | It can be used to make any generic API call that is automatically 48 | authenticated using your API credentials: 49 | 50 | .. code-block:: python 51 | 52 | amadeus.post('/foo/bar', airline='1X') 53 | 54 | :param path: path the full path for the API call 55 | :paramtype path: str 56 | 57 | :param params: (optional) params to pass to the API 58 | :paramtype params: dict 59 | 60 | :rtype: amadeus.Response 61 | :raises amadeus.ResponseError: when the request fails 62 | ''' 63 | return self.request('POST', path, params) 64 | 65 | def delete(self, path, **params): 66 | ''' 67 | A helper function for making generic DELETE requests calls. It is used by 68 | every namespaced API DELETE method. 69 | 70 | It can be used to make any generic API call that is automatically 71 | authenticated using your API credentials: 72 | 73 | .. code-block:: python 74 | 75 | amadeus.delete('/foo/bar', airline='1X') 76 | 77 | :param path: path the full path for the API call 78 | :paramtype path: str 79 | 80 | :param params: (optional) params to pass to the API 81 | :paramtype params: dict 82 | 83 | :rtype: amadeus.Response 84 | :raises amadeus.ResponseError: when the request fails 85 | ''' 86 | return self.request('DELETE', path, params) 87 | 88 | def request(self, verb, path, params): 89 | ''' 90 | A helper function for making generic POST requests calls. It is used by 91 | every namespaced API method. It can be used to make any generic API 92 | call that is automatically authenticated using your API credentials: 93 | 94 | .. code-block:: python 95 | 96 | amadeus.request('GET', '/foo/bar', airline='1X') 97 | 98 | :param verb: the HTTP verb to use 99 | :paramtype verb: str 100 | 101 | :param path: path the full path for the API call 102 | :paramtype path: str 103 | 104 | :param params: (optional) params to pass to the API 105 | :paramtype params: dict 106 | 107 | :rtype: amadeus.Response 108 | :raises amadeus.ResponseError: when the request fails 109 | ''' 110 | return self._unauthenticated_request( 111 | verb, path, params, 112 | self.__access_token()._bearer_token() 113 | ) 114 | 115 | # PROTECTED 116 | 117 | # Builds the URI, the request object, and makes the actual API calls. 118 | # 119 | # Used by the AccessToken to fetch a new Bearer Token 120 | # 121 | # Passes the response to a Response object for further parsing. 122 | # 123 | def _unauthenticated_request(self, verb, path, params, bearer_token=None): 124 | request = self.__build_request(verb, path, params, bearer_token) 125 | self.__log(request) 126 | return self.__execute(request) 127 | 128 | # PRIVATE 129 | 130 | # Builds a HTTP request object that contains all the information about 131 | # this request 132 | def __build_request(self, verb, path, params, bearer_token): 133 | return Request({ 134 | 'host': self.host, 135 | 'verb': verb, 136 | 'path': path, 137 | 'params': params, 138 | 'bearer_token': bearer_token, 139 | 'client_version': version, 140 | 'language_version': python_version(), 141 | 'app_id': self.custom_app_id, 142 | 'app_version': self.custom_app_version, 143 | 'ssl': self.ssl, 144 | 'port': self.port 145 | }) 146 | 147 | # Executes the request and wraps it in a Response 148 | def __execute(self, request): 149 | http_response = self.__fetch(request) 150 | response = Response(http_response, request)._parse(self) 151 | self.__log(response) 152 | response._detect_error(self) 153 | return response 154 | 155 | # A memoized AccessToken object, so we don't keep reauthenticating 156 | def __access_token(self): 157 | if (not hasattr(self, 'access_token')): 158 | self.access_token = AccessToken(self) 159 | return self.access_token 160 | 161 | # Log any object 162 | def __log(self, object): 163 | if (self.log_level == 'debug'): 164 | self.logger.debug( 165 | '%s\n%s', object.__class__.__name__, pformat(object.__dict__) 166 | ) 167 | 168 | # Actually make the HTTP call, making sure to catch it in case of an error 169 | def __fetch(self, request): 170 | try: 171 | return self.http(request.http_request) 172 | except URLError as exception: 173 | return exception 174 | -------------------------------------------------------------------------------- /amadeus/mixins/pagination.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | 4 | # A set of helper methods to allow the validating of 5 | # arguments past into the Client 6 | class Pagination(object): 7 | 8 | def previous(self, response): 9 | return self.__page('previous', response) 10 | 11 | def next(self, response): 12 | return self.__page('next', response) 13 | 14 | def first(self, response): 15 | return self.__page('first', response) 16 | 17 | def last(self, response): 18 | return self.__page('last', response) 19 | 20 | # PRIVATE 21 | 22 | def __page(self, name, response): 23 | page_number = self.__page_number_for(name, response) 24 | if page_number is None: 25 | return None 26 | params = copy.deepcopy(response.request.params) 27 | if 'page' not in params: 28 | params['page'] = {} 29 | params['page']['offset'] = page_number 30 | return self.request( 31 | response.request.verb, 32 | response.request.path, 33 | params 34 | ) 35 | 36 | @staticmethod 37 | def __page_number_for(name, response): 38 | try: 39 | url = response.result['meta']['links'][name] 40 | return url.split('page%5Boffset%5D=')[1].split('&')[0] 41 | except Exception: 42 | return None 43 | -------------------------------------------------------------------------------- /amadeus/mixins/parser.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from amadeus.client.errors import ParserError, ServerError 4 | from amadeus.client.errors import AuthenticationError, NetworkError 5 | from amadeus.client.errors import NotFoundError, ClientError 6 | 7 | 8 | class Parser(object): 9 | 10 | # PROTECTED 11 | 12 | # Tries to detect for appropriate errors 13 | def _detect_error(self, client): 14 | error = self.error_for(self.status_code, self.parsed) 15 | if error is not None: 16 | self.__raise_error(error, client) 17 | 18 | @staticmethod 19 | def error_for(status_code, parsed): # noqa: C901 20 | if status_code is None: 21 | return NetworkError 22 | if status_code >= 500: 23 | return ServerError 24 | if status_code == 401: 25 | return AuthenticationError 26 | if status_code == 404: 27 | return NotFoundError 28 | if status_code >= 400: 29 | return ClientError 30 | if status_code == 204: 31 | return None 32 | if not parsed: 33 | return ParserError 34 | 35 | # Parses the HTTP status code 36 | def _parse_status_code(self): 37 | http_response = self.http_response 38 | self.status_code = getattr(http_response, 'status', None) 39 | self.status_code = getattr(http_response, 'code', self.status_code) 40 | 41 | # Tries to parse the received data from raw string to parsed data and into 42 | # a data object 43 | def _parse_data(self, client): 44 | self.parsed = False 45 | self.data = None 46 | self.body = None 47 | self.result = None 48 | self.headers = {} 49 | 50 | self.__parse_headers(self.http_response, client) 51 | 52 | # Avoid parsing body in 204 responses 53 | if self.status_code == 204: 54 | return 55 | 56 | self.__parse_body(self.http_response, client) 57 | 58 | self.result = self.__parse_json(client) 59 | if (self.result is not None): 60 | self.data = self.result.get('data', None) 61 | 62 | # PRIVATE 63 | 64 | # Logs and raises the error 65 | def __raise_error(self, error_class, client): 66 | error = error_class(self) 67 | error._log(client) 68 | raise error 69 | 70 | def __parse_headers(self, http_response, client): 71 | if hasattr(http_response, 'getheaders'): 72 | self.headers = dict(http_response.getheaders()) or self.headers 73 | if hasattr(http_response, 'info'): 74 | self.headers = http_response.info() or self.headers 75 | 76 | # Extract the body and headers 77 | def __parse_body(self, http_response, client): 78 | if hasattr(http_response, 'read'): 79 | self.body = http_response.read() 80 | if hasattr(self.body, 'decode'): 81 | self.body = self.body.decode('utf8') 82 | 83 | # Tries to parse the JSON, if there is any 84 | def __parse_json(self, client): 85 | try: 86 | if (self.__is_json()): 87 | result = json.loads(self.body) 88 | self.parsed = True 89 | return result 90 | else: 91 | return None 92 | except Exception: 93 | self.__raise_error(ParserError, client) 94 | 95 | # checks if the HTTPResponse included JSON 96 | def __is_json(self): 97 | return self.__has_json_header() and self.__has_body() 98 | 99 | # checks if the HTTPResponse has a non-empty body 100 | def __has_body(self): 101 | return self.body and len(self.body) > 0 102 | 103 | # checks if the HTTPResponse has a JSON header 104 | def __has_json_header(self): 105 | content_type = self.headers.get('Content-Type', None) 106 | if (content_type is not None): 107 | types = content_type.split(';')[0] 108 | types = ['application/json', 'application/vnd.amadeus+json'] 109 | return content_type in types 110 | else: 111 | return False 112 | -------------------------------------------------------------------------------- /amadeus/mixins/validator.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import logging 4 | 5 | from urllib.request import urlopen 6 | 7 | 8 | # A set of helper methods to allow the validating of 9 | # arguments past into the Client 10 | class Validator(object): 11 | 12 | # PROTECTED 13 | 14 | # Checks a list of options for unrecognized keys and warns the user. 15 | # This is mainly used to provide a nice experience when users make a typo 16 | # in their arguments. 17 | @staticmethod 18 | def _warn_on_unrecognized_options(options, logger, valid_options): 19 | for key in options: 20 | if (key in valid_options): 21 | continue 22 | logger.warning('Unrecognized option: {0}'.format(key)) 23 | 24 | # Initializes the credentials, requiring an ID and Secret 25 | def _initialize_client_credentials(self, options): 26 | self.client_id = self.__init_required('client_id', options) 27 | self.client_secret = self.__init_required('client_secret', options) 28 | 29 | # Initializes an optional Logger 30 | def _initialize_logger(self, options): 31 | default_logger = logging.getLogger('amadeus') 32 | default_logger.setLevel(logging.DEBUG) 33 | handler = logging.StreamHandler(sys.stdout) 34 | default_logger.addHandler(handler) 35 | 36 | self.logger = self.__init_optional('logger', options, default_logger) 37 | self.log_level = self.__init_optional('log_level', options, 'silent') 38 | 39 | # Initializes an optional host, hostname, port, and SSL requirements 40 | def _initialize_host(self, options): 41 | self.hostname = self.__init_optional('hostname', options, 'test') 42 | self.host = self.__init_optional('host', options, 43 | self.HOSTS[self.hostname]) 44 | self.ssl = self.__init_optional('ssl', options, True) 45 | self.port = self.__init_optional('port', options, 443) 46 | 47 | # Initializes an optional custom App ID and Version 48 | def _initialize_custom_app(self, options): 49 | self.custom_app_id = self.__init_optional('custom_app_id', 50 | options, None) 51 | self.custom_app_version = self.__init_optional('custom_app_version', 52 | options, None) 53 | 54 | # Initializes a custom http handler 55 | def _initialize_http(self, options): 56 | self.http = self.__init_optional('http', options, urlopen) 57 | 58 | # PRIVATE 59 | 60 | # Uses 'init_optional' to find an entry, and it that returns 61 | # nil it raises an ArgumentError 62 | # 63 | def __init_required(self, key, options): 64 | value = self.__init_optional(key, options) 65 | if (value is None): 66 | raise ValueError('Missing required argument: {0}'.format(key)) 67 | return value 68 | 69 | # Tries to find an option by string or symbol in the options hash and 70 | # in the environment variables.When it can not find it anywhere it 71 | # defaults to the provided default option. 72 | @staticmethod 73 | def __init_optional(key, options, defa_ult=None): 74 | value = options.get(key, None) 75 | if (value is None): 76 | env_key = 'AMADEUS_{0}'.format(key.upper()) 77 | value = os.environ.get(env_key, None) 78 | if (value is None): 79 | value = defa_ult 80 | return value 81 | -------------------------------------------------------------------------------- /amadeus/namespaces/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import Core 2 | 3 | __all__ = ['Core'] 4 | -------------------------------------------------------------------------------- /amadeus/namespaces/_airline.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.airline._destinations import Destinations 3 | 4 | 5 | class Airline(Decorator, object): 6 | def __init__(self, client): 7 | Decorator.__init__(self, client) 8 | self.destinations = Destinations(client) 9 | -------------------------------------------------------------------------------- /amadeus/namespaces/_airport.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.airport._predictions import Predictions 3 | from amadeus.airport._direct_destinations import DirectDestinations 4 | 5 | 6 | class Airport(Decorator, object): 7 | def __init__(self, client): 8 | Decorator.__init__(self, client) 9 | self.predictions = Predictions(client) 10 | self.direct_destinations = DirectDestinations(client) 11 | -------------------------------------------------------------------------------- /amadeus/namespaces/_analytics.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.analytics._itinerary_price_metrics import ItineraryPriceMetrics 3 | 4 | 5 | class Analytics(Decorator, object): 6 | def __init__(self, client): 7 | Decorator.__init__(self, client) 8 | self.itinerary_price_metrics = ItineraryPriceMetrics(client) 9 | -------------------------------------------------------------------------------- /amadeus/namespaces/_booking.py: -------------------------------------------------------------------------------- 1 | from amadeus.booking._flight_orders import FlightOrders 2 | from amadeus.booking._flight_order import FlightOrder 3 | from amadeus.booking._hotel_bookings import HotelBookings 4 | from amadeus.booking._hotel_orders import HotelOrders 5 | from amadeus.client.decorator import Decorator 6 | 7 | 8 | class Booking(Decorator, object): 9 | def __init__(self, client): 10 | Decorator.__init__(self, client) 11 | self.flight_orders = FlightOrders(client) 12 | self.hotel_bookings = HotelBookings(client) 13 | self.hotel_orders = HotelOrders(client) 14 | 15 | def flight_order(self, flight_order_id): 16 | return FlightOrder(self.client, flight_order_id) 17 | 18 | 19 | __all__ = ['FlightOrders', 'FlightOrder', 'HotelBookings', 'HotelOrders'] 20 | -------------------------------------------------------------------------------- /amadeus/namespaces/_e_reputation.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.e_reputation._hotel_sentiments import HotelSentiments 3 | 4 | 5 | class EReputation(Decorator, object): 6 | def __init__(self, client): 7 | Decorator.__init__(self, client) 8 | self.hotel_sentiments = HotelSentiments(client) 9 | -------------------------------------------------------------------------------- /amadeus/namespaces/_ordering.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.ordering._transfer_orders import TransferOrders 3 | from amadeus.ordering._transfer_order import TransferOrder 4 | 5 | 6 | class Ordering(Decorator, object): 7 | def __init__(self, client): 8 | Decorator.__init__(self, client) 9 | self.transfer_orders = TransferOrders(client) 10 | 11 | def transfer_order(self, order_id): 12 | return TransferOrder(self.client, order_id) 13 | 14 | 15 | __all__ = ['TransferOrders', 'TransferOrder'] 16 | -------------------------------------------------------------------------------- /amadeus/namespaces/_reference_data.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.reference_data._urls import Urls 3 | from amadeus.reference_data._location import Location 4 | from amadeus.reference_data._locations import Locations 5 | from amadeus.reference_data._airlines import Airlines 6 | from amadeus.reference_data._recommended_locations import RecommendedLocations 7 | 8 | 9 | class ReferenceData(Decorator, object): 10 | def __init__(self, client): 11 | Decorator.__init__(self, client) 12 | self.urls = Urls(client) 13 | self.locations = Locations(client) 14 | self.airlines = Airlines(client) 15 | self.recommended_locations = RecommendedLocations(client) 16 | 17 | def location(self, location_id): 18 | return Location(self.client, location_id) 19 | -------------------------------------------------------------------------------- /amadeus/namespaces/_schedule.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.schedule._flights import Flights 3 | 4 | 5 | class Schedule(Decorator, object): 6 | def __init__(self, client): 7 | Decorator.__init__(self, client) 8 | self.flights = Flights(client) 9 | -------------------------------------------------------------------------------- /amadeus/namespaces/_shopping.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.shopping._flight_dates import FlightDates 3 | from amadeus.shopping._flight_destinations import FlightDestinations 4 | from amadeus.shopping._flight_offers import FlightOffers 5 | from amadeus.shopping._flight_offers_search import FlightOffersSearch 6 | from amadeus.shopping._seatmaps import Seatmaps 7 | from amadeus.shopping._activities import Activities 8 | from amadeus.shopping._activity import Activity 9 | from amadeus.shopping._availability import Availability 10 | from amadeus.shopping._hotel_offer_search import HotelOfferSearch 11 | from amadeus.shopping._hotel_offers_search import HotelOffersSearch 12 | from amadeus.shopping._transfer_offers import TransferOffers 13 | 14 | 15 | class Shopping(Decorator, object): 16 | def __init__(self, client): 17 | Decorator.__init__(self, client) 18 | self.flight_dates = FlightDates(client) 19 | self.flight_destinations = FlightDestinations(client) 20 | self.flight_offers = FlightOffers(client) 21 | self.flight_offers_search = FlightOffersSearch(client) 22 | self.seatmaps = Seatmaps(client) 23 | self.activities = Activities(client) 24 | self.availability = Availability(client) 25 | self.hotel_offers_search = HotelOffersSearch(client) 26 | self.transfer_offers = TransferOffers(client) 27 | 28 | def hotel_offer_search(self, offer_id): 29 | return HotelOfferSearch(self.client, offer_id) 30 | 31 | def activity(self, activity_id): 32 | return Activity(self.client, activity_id) 33 | 34 | 35 | __all__ = ['FlightDates', 'FlightDestinations', 'FlightOffers', 36 | 'FlightOffersSearch', 'Availability'] 37 | -------------------------------------------------------------------------------- /amadeus/namespaces/_travel.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.travel._analytics import Analytics 3 | from amadeus.travel._predictions import Predictions 4 | 5 | 6 | class Travel(Decorator, object): 7 | def __init__(self, client): 8 | Decorator.__init__(self, client) 9 | self.analytics = Analytics(client) 10 | self.predictions = Predictions(client) 11 | -------------------------------------------------------------------------------- /amadeus/namespaces/core.py: -------------------------------------------------------------------------------- 1 | from amadeus.namespaces._reference_data import ReferenceData 2 | from amadeus.namespaces._travel import Travel 3 | from amadeus.namespaces._shopping import Shopping 4 | from amadeus.namespaces._e_reputation import EReputation 5 | from amadeus.namespaces._airport import Airport 6 | from amadeus.namespaces._booking import Booking 7 | from amadeus.namespaces._schedule import Schedule 8 | from amadeus.namespaces._analytics import Analytics 9 | from amadeus.namespaces._airline import Airline 10 | from amadeus.namespaces._ordering import Ordering 11 | 12 | 13 | class Core(object): 14 | def __init__(self): 15 | self.reference_data = ReferenceData(self) 16 | self.travel = Travel(self) 17 | self.shopping = Shopping(self) 18 | self.e_reputation = EReputation(self) 19 | self.airport = Airport(self) 20 | self.booking = Booking(self) 21 | self.schedule = Schedule(self) 22 | self.analytics = Analytics(self) 23 | self.airline = Airline(self) 24 | self.ordering = Ordering(self) 25 | -------------------------------------------------------------------------------- /amadeus/ordering/__init__.py: -------------------------------------------------------------------------------- 1 | from ._transfer_orders import TransferOrders 2 | from ._transfer_order import TransferOrder 3 | 4 | __all__ = ['TransferOrders', 'TransferOrder'] 5 | -------------------------------------------------------------------------------- /amadeus/ordering/_transfer_order.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.ordering.transfer_orders import Transfers 3 | 4 | 5 | class TransferOrder(Decorator, object): 6 | def __init__(self, client, order_id): 7 | Decorator.__init__(self, client) 8 | self.transfers = Transfers(client, order_id) 9 | -------------------------------------------------------------------------------- /amadeus/ordering/_transfer_orders.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | from urllib.parse import urlencode 4 | 5 | 6 | class TransferOrders(Decorator, object): 7 | def post(self, body, **params): 8 | ''' 9 | Performs the final booking for a chosen transfer 10 | 11 | .. code-block:: python 12 | 13 | amadeus.ordering.transfer_orders.post(body, offerId=offer_id) 14 | 15 | :rtype: amadeus.Response 16 | :raises amadeus.ResponseError: if the request could not be completed 17 | ''' 18 | url = '/v1/ordering/transfer-orders?' 19 | return self.client.post(url + urlencode(params), body) 20 | -------------------------------------------------------------------------------- /amadeus/ordering/transfer_orders/__init__.py: -------------------------------------------------------------------------------- 1 | from ._transfers import Transfers 2 | 3 | __all__ = ['Transfers'] 4 | -------------------------------------------------------------------------------- /amadeus/ordering/transfer_orders/_transfers.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.ordering.transfer_orders.transfers import Cancellation 3 | 4 | 5 | class Transfers(Decorator, object): 6 | def __init__(self, client, order_id): 7 | Decorator.__init__(self, client) 8 | self.cancellation = Cancellation(client, order_id) 9 | -------------------------------------------------------------------------------- /amadeus/ordering/transfer_orders/transfers/__init__.py: -------------------------------------------------------------------------------- 1 | from ._cancellation import Cancellation 2 | 3 | __all__ = ['Cancellation'] 4 | -------------------------------------------------------------------------------- /amadeus/ordering/transfer_orders/transfers/_cancellation.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | from urllib.parse import urlencode 4 | 5 | 6 | class Cancellation(Decorator, object): 7 | def __init__(self, client, order_id): 8 | Decorator.__init__(self, client) 9 | self.order_id = order_id 10 | 11 | def post(self, body, **params): 12 | ''' 13 | Cancels a transfer reservation 14 | 15 | .. code-block:: python 16 | 17 | amadeus.ordering.transfer_order(order_id).transfers.cancellation.post(body, 18 | confirmNbr=confirm_nbr) 19 | 20 | :rtype: amadeus.Response 21 | :raises amadeus.ResponseError: if the request could not be completed 22 | ''' 23 | url = '/v1/ordering/transfer-orders/{0}/transfers/cancellation?'.format( 24 | self.order_id 25 | ) 26 | return self.client.post(url + urlencode(params), body) 27 | -------------------------------------------------------------------------------- /amadeus/reference_data/__init__.py: -------------------------------------------------------------------------------- 1 | from ._location import Location 2 | from ._locations import Locations 3 | from ._airlines import Airlines 4 | from ._recommended_locations import RecommendedLocations 5 | 6 | __all__ = ['Location', 'Locations', 'Airlines', 'RecommendedLocations'] 7 | -------------------------------------------------------------------------------- /amadeus/reference_data/_airlines.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Airlines(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Returns the name of the airline given an IATA code. 8 | 9 | .. code-block:: python 10 | 11 | amadeus.reference_data.airlines.get(airlineCodes='U2') 12 | 13 | :param airlineCodes: the IATA or ICAO code for the airline, e.g. 14 | :``"AF"`` (Air France IATA code) 15 | :or ``"AFR"`` (Air France ICAO code) 16 | 17 | :rtype: amadeus.Response 18 | :raises amadeus.ResponseError: if the request could not be completed 19 | ''' 20 | return self.client.get('/v1/reference-data/airlines', **params) 21 | -------------------------------------------------------------------------------- /amadeus/reference_data/_location.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Location(Decorator, object): 5 | def __init__(self, client, location_id): 6 | Decorator.__init__(self, client) 7 | self.location_id = location_id 8 | 9 | def get(self, **params): 10 | ''' 11 | Returns details for a specific airport. 12 | 13 | .. code-block:: python 14 | 15 | amadeus.reference_data.location('ALHR').get() 16 | 17 | :rtype: amadeus.Response 18 | :raises amadeus.ResponseError: if the request could not be completed 19 | ''' 20 | return self.client.get('/v1/reference-data/locations/{0}' 21 | .format(self.location_id), **params) 22 | -------------------------------------------------------------------------------- /amadeus/reference_data/_locations.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.reference_data.locations._airports import Airports 3 | from amadeus.reference_data.locations._hotels import Hotels 4 | from amadeus.reference_data.locations._hotel import Hotel 5 | from amadeus.reference_data.locations._cities import Cities 6 | 7 | 8 | class Locations(Decorator, object): 9 | def __init__(self, client): 10 | Decorator.__init__(self, client) 11 | self.airports = Airports(client) 12 | self.hotels = Hotels(client) 13 | self.hotel = Hotel(client) 14 | self.cities = Cities(client) 15 | 16 | def get(self, **params): 17 | ''' 18 | Returns details for a specific airport. 19 | 20 | .. code-block:: python 21 | 22 | 23 | from amadeus import Location 24 | 25 | amadeus.reference_data.locations.get( 26 | keyword='lon', 27 | subType=Location.ANY 28 | ) 29 | 30 | :param keyword: keyword that should represent the start of 31 | a word in a city or airport name or code 32 | 33 | :param subType: a comma seperate list of location types to search 34 | for. You can use :class:`amadeus.Location` as a helper for this. 35 | 36 | :rtype: amadeus.Response 37 | :raises amadeus.ResponseError: if the request could not be completed 38 | ''' 39 | return self.client.get('/v1/reference-data/locations', **params) 40 | -------------------------------------------------------------------------------- /amadeus/reference_data/_recommended_locations.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class RecommendedLocations(Decorator, object): 5 | def __init__(self, client): 6 | Decorator.__init__(self, client) 7 | 8 | def get(self, **params): 9 | ''' 10 | Returns a list of destination recommendations 11 | 12 | .. code-block:: python 13 | 14 | 15 | client.reference_data.recommended_locations.get( 16 | cityCodes='PAR' 17 | travelerCountryCode='FR' 18 | ) 19 | 20 | :param cityCodes: city used by the algorithm to recommend new destinations 21 | For example: ``PAR`` 22 | :param travelerCountryCode: origin country following IATA standard 23 | For example: ``FR`` 24 | 25 | :rtype: amadeus.Response 26 | :raises amadeus.ResponseError: if the request could not be completed 27 | ''' 28 | return self.client.get( 29 | '/v1/reference-data/recommended-locations', **params) 30 | -------------------------------------------------------------------------------- /amadeus/reference_data/_urls.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.reference_data.urls._checkin_links import CheckinLinks 3 | 4 | 5 | class Urls(Decorator, object): 6 | def __init__(self, client): 7 | Decorator.__init__(self, client) 8 | self.checkin_links = CheckinLinks(client) 9 | -------------------------------------------------------------------------------- /amadeus/reference_data/locations/__init__.py: -------------------------------------------------------------------------------- 1 | from ._airports import Airports 2 | from ._hotel import Hotel 3 | from ._cities import Cities 4 | 5 | __all__ = ['Airports', 'Hotel', 'Cities'] 6 | -------------------------------------------------------------------------------- /amadeus/reference_data/locations/_airports.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Airports(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Returns a list of relevant airports near to a given point. 8 | 9 | .. code-block:: python 10 | 11 | 12 | amadeus.reference_data.locations.airports.get( 13 | longitude=49.0000, 14 | latitude=2.55 15 | ) 16 | 17 | :param latitude: latitude of geographic location to search around. 18 | For example: ``52.5238`` 19 | :param longitude: longitude of geographic location to search around. 20 | For example: ``13.3835`` 21 | 22 | :rtype: amadeus.Response 23 | :raises amadeus.ResponseError: if the request could not be completed 24 | ''' 25 | return self.client.get( 26 | '/v1/reference-data/locations/airports', **params) 27 | -------------------------------------------------------------------------------- /amadeus/reference_data/locations/_cities.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Cities(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Returns cities that match a specific word or letters. 8 | 9 | .. code-block:: python 10 | 11 | 12 | amadeus.reference_data.locations.cities.get( 13 | keyword='PARI' 14 | ) 15 | 16 | :param keyword: location query keyword. 17 | For example: ``PARI`` 18 | 19 | :rtype: amadeus.Response 20 | :raises amadeus.ResponseError: if the request could not be completed 21 | ''' 22 | return self.client.get( 23 | '/v1/reference-data/locations/cities', **params) 24 | -------------------------------------------------------------------------------- /amadeus/reference_data/locations/_hotel.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Hotel(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Returns a list of hotels matching a given keyword. 8 | 9 | .. code-block:: python 10 | 11 | 12 | amadeus.reference_data.locations.hotel.get( 13 | keyword='PARI', 14 | subType=[Hotel.HOTEL_LEISURE, Hotel.HOTEL_GDS] 15 | ) 16 | 17 | :param keyword: location query keyword. 18 | For example: ``PARI`` 19 | :param subType: category of search. 20 | For example: ``[Hotel.HOTEL_LEISURE, Hotel.HOTEL_GDS]`` 21 | 22 | :rtype: amadeus.Response 23 | :raises amadeus.ResponseError: if the request could not be completed 24 | ''' 25 | return self.client.get( 26 | '/v1/reference-data/locations/hotel', **params) 27 | -------------------------------------------------------------------------------- /amadeus/reference_data/locations/_hotels.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.reference_data.locations.hotels import ByCity 3 | from amadeus.reference_data.locations.hotels import ByGeocode 4 | from amadeus.reference_data.locations.hotels import ByHotels 5 | 6 | 7 | class Hotels(Decorator, object): 8 | def __init__(self, client): 9 | Decorator.__init__(self, client) 10 | self.by_hotels = ByHotels(client) 11 | self.by_geocode = ByGeocode(client) 12 | self.by_city = ByCity(client) 13 | -------------------------------------------------------------------------------- /amadeus/reference_data/locations/hotels/__init__.py: -------------------------------------------------------------------------------- 1 | from ._by_city import ByCity 2 | from ._by_geocode import ByGeocode 3 | from ._by_hotels import ByHotels 4 | 5 | __all__ = ['ByCity', 'ByGeocode', 'ByHotels'] 6 | -------------------------------------------------------------------------------- /amadeus/reference_data/locations/hotels/_by_city.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class ByCity(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Searches for hotel in a given city. 8 | 9 | .. code-block:: python 10 | 11 | 12 | amadeus.reference_data.locations.hotels.by_city.get( 13 | cityCode='PAR') 14 | 15 | :param cityCode: the City IATA code for which to find a hotel, for 16 | example ``"PAR"`` for Paris. 17 | 18 | :rtype: amadeus.Response 19 | :raises amadeus.ResponseError: if the request could not be completed 20 | ''' 21 | return self.client.get( 22 | '/v1/reference-data/locations/hotels/by-city', **params) 23 | -------------------------------------------------------------------------------- /amadeus/reference_data/locations/hotels/_by_geocode.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class ByGeocode(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Searches for hotel using a geocode. 8 | 9 | .. code-block:: python 10 | 11 | 12 | amadeus.reference_data.locations.hotels.by_geocode.get( 13 | longitude=2.160873, 14 | latitude=41.397158 15 | ) 16 | 17 | :param latitude: latitude of geographic location to search around. 18 | For example: ``41.397158`` 19 | :param longitude: longitude of geographic location to search around. 20 | For example: ``2.160873`` 21 | 22 | :rtype: amadeus.Response 23 | :raises amadeus.ResponseError: if the request could not be completed 24 | ''' 25 | return self.client.get( 26 | '/v1/reference-data/locations/hotels/by-geocode', **params) 27 | -------------------------------------------------------------------------------- /amadeus/reference_data/locations/hotels/_by_hotels.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class ByHotels(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Searches for hotel using it's unique id. 8 | 9 | .. code-block:: python 10 | 11 | 12 | amadeus.reference_data.locations.hotels.by_hotels.get( 13 | hotelIds=["ADPAR001"]) 14 | 15 | :param hotelIds: Amadeus Property Codes (8 chars) 16 | For example: ``["ADPAR001"]`` 17 | 18 | :rtype: amadeus.Response 19 | :raises amadeus.ResponseError: if the request could not be completed 20 | ''' 21 | for key, value in params.items(): 22 | if isinstance(value, list): 23 | params[key] = ','.join(value) 24 | return self.client.get( 25 | '/v1/reference-data/locations/hotels/by-hotels', **params) 26 | -------------------------------------------------------------------------------- /amadeus/reference_data/urls/__init__.py: -------------------------------------------------------------------------------- 1 | from ._checkin_links import CheckinLinks 2 | 3 | __all__ = ['CheckinLinks'] 4 | -------------------------------------------------------------------------------- /amadeus/reference_data/urls/_checkin_links.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class CheckinLinks(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Returns the checkin links for an airline, for the 8 | language of your choice 9 | 10 | .. code-block:: python 11 | 12 | amadeus.reference_data.urls.checkin_links.get(airlineCode='BA') 13 | 14 | :param airlineCode: the IATA code for the airline, e.g. ``"BA"`` 15 | :param language: the locale for the links, for example ``"en-GB"`` 16 | 17 | :rtype: amadeus.Response 18 | :raises amadeus.ResponseError: if the request could not be completed 19 | ''' 20 | return self.client.get( 21 | '/v2/reference-data/urls/checkin-links', **params) 22 | -------------------------------------------------------------------------------- /amadeus/schedule/__init__.py: -------------------------------------------------------------------------------- 1 | from ._flights import Flights 2 | 3 | __all__ = ['Flights'] 4 | -------------------------------------------------------------------------------- /amadeus/schedule/_flights.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Flights(Decorator, object): 5 | 6 | def get(self, **params): 7 | ''' 8 | Retrieves a unique flight status by search criteria 9 | 10 | .. code-block:: python 11 | 12 | amadeus.schedule.flights.get( 13 | carrierCode='AZ', 14 | flightNumber='319', 15 | scheduledDepartureDate='2021-03-13') 16 | 17 | :param carrierCode: the IATA carrier code 18 | 19 | :param flightNumber: flight number as assigned by the carrier 20 | 21 | :param scheduledDepartureDate: scheduled departure date of the flight, 22 | local to the departure airport, format YYYY-MM-DD 23 | 24 | :rtype: amadeus.Response 25 | :raises amadeus.ResponseError: if the request could not be completed 26 | ''' 27 | return self.client.get('/v2/schedule/flights', **params) 28 | -------------------------------------------------------------------------------- /amadeus/shopping/__init__.py: -------------------------------------------------------------------------------- 1 | from ._flight_dates import FlightDates 2 | from ._flight_destinations import FlightDestinations 3 | from ._flight_offers_search import FlightOffersSearch 4 | from ._hotel_offer_search import HotelOfferSearch 5 | from ._hotel_offers_search import HotelOffersSearch 6 | from ._activities import Activities 7 | from ._transfer_offers import TransferOffers 8 | 9 | __all__ = ['FlightDates', 'FlightDestinations', 10 | 'HotelOffersSearch', 'HotelOfferSearch', 11 | 'FlightOffersSearch', 'Activities', 12 | 'TransferOffers'] 13 | -------------------------------------------------------------------------------- /amadeus/shopping/_activities.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.shopping.activities._by_square \ 3 | import BySquare 4 | 5 | 6 | class Activities(Decorator, object): 7 | def __init__(self, client): 8 | Decorator.__init__(self, client) 9 | self.by_square = BySquare(client) 10 | 11 | def get(self, **params): 12 | ''' 13 | Returns activities for a given location 14 | 15 | .. code-block:: python 16 | 17 | 18 | client.shopping.activities.get( 19 | longitude=2.160873, 20 | latitude=41.397158 21 | ) 22 | 23 | :param latitude: latitude of geographic location to search around. 24 | For example: ``41.397158`` 25 | :param longitude: longitude of geographic location to search around. 26 | For example: ``2.160873`` 27 | 28 | :rtype: amadeus.Response 29 | :raises amadeus.ResponseError: if the request could not be completed 30 | ''' 31 | return self.client.get( 32 | '/v1/shopping/activities', **params) 33 | -------------------------------------------------------------------------------- /amadeus/shopping/_activity.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Activity(Decorator, object): 5 | def __init__(self, client, activity_id): 6 | Decorator.__init__(self, client) 7 | self.activity_id = activity_id 8 | 9 | def get(self, **params): 10 | ''' 11 | Returns a single activity from a given id. 12 | 13 | .. code-block:: python 14 | 15 | client.shopping.activities('4615').get() 16 | 17 | :rtype: amadeus.Response 18 | :raises amadeus.ResponseError: if the request could not be completed 19 | ''' 20 | return self.client.get('/v1/shopping/activities/{0}' 21 | .format(self.activity_id), **params) 22 | -------------------------------------------------------------------------------- /amadeus/shopping/_availability.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from .availability import FlightAvailabilities 3 | 4 | 5 | class Availability(Decorator, object): 6 | def __init__(self, client): 7 | Decorator.__init__(self, client) 8 | self.flight_availabilities = FlightAvailabilities(client) 9 | -------------------------------------------------------------------------------- /amadeus/shopping/_flight_dates.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class FlightDates(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Find the cheapest flight dates from an origin to a destination. 8 | 9 | .. code-block:: python 10 | 11 | amadeus.shopping.flight_dates.get(origin='NYC', destination='MAD') 12 | 13 | :param origin: the City/Airport IATA code from which the flight will 14 | depart. ``"NYC"``, for example for New-York. 15 | 16 | :param destination: the City/Airport IATA code to which the flight is 17 | going. ``"MAD"``, for example for Madrid. 18 | 19 | :rtype: amadeus.Response 20 | :raises amadeus.ResponseError: if the request could not be completed 21 | ''' 22 | return self.client.get('/v1/shopping/flight-dates', **params) 23 | -------------------------------------------------------------------------------- /amadeus/shopping/_flight_destinations.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class FlightDestinations(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Find the cheapest destinations where you can fly to. 8 | 9 | .. code-block:: python 10 | 11 | amadeus.shopping.flight_destinations.get(origin='LON') 12 | 13 | :param origin: the City/Airport IATA code from which the flight will 14 | depart. ``"LON"``, for example for London. 15 | 16 | :rtype: amadeus.Response 17 | :raises amadeus.ResponseError: if the request could not be completed 18 | ''' 19 | 20 | return self.client.get('/v1/shopping/flight-destinations', **params) 21 | -------------------------------------------------------------------------------- /amadeus/shopping/_flight_offers.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from amadeus.shopping.flight_offers._prediction import FlightChoicePrediction 3 | from amadeus.shopping.flight_offers._pricing import FlightOffersPrice 4 | from amadeus.shopping.flight_offers._upselling import Upselling 5 | 6 | 7 | class FlightOffers(Decorator, object): 8 | def __init__(self, client): 9 | Decorator.__init__(self, client) 10 | self.prediction = FlightChoicePrediction(client) 11 | self.pricing = FlightOffersPrice(client) 12 | self.upselling = Upselling(client) 13 | -------------------------------------------------------------------------------- /amadeus/shopping/_flight_offers_search.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class FlightOffersSearch(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Get the cheapest flights on a given journey 8 | 9 | .. code-block:: python 10 | 11 | amadeus.shopping.flight_offers_search.get( 12 | originLocationCode='MAD', 13 | destinationLocationCode='BOS', 14 | departureDate='2019-11-01', 15 | adults='1' 16 | ) 17 | 18 | :param originLocationCode: the City/Airport IATA code from which 19 | the flight will depart. ``"MAD"``, for example for Madrid. 20 | 21 | :param destinationLocationCode: the City/Airport IATA code to 22 | which the flight is going. ``"BOS"``, for example for Boston. 23 | 24 | :param departureDate: the date on which to fly out, in `YYYY-MM-DD` 25 | format 26 | 27 | :param adults: the number of adult passengers with age 12 or older 28 | 29 | :rtype: amadeus.Response 30 | :raises amadeus.ResponseError: if the request could not be completed 31 | ''' 32 | return self.client.get('/v2/shopping/flight-offers', **params) 33 | 34 | def post(self, body): 35 | ''' 36 | Get the cheapest flights on a given journey. 37 | 38 | .. code-block:: python 39 | 40 | amadeus.shopping.flight_offers_search.post(body) 41 | 42 | :param body: the parameters to send to the API 43 | 44 | :rtype: amadeus.Response 45 | :raises amadeus.ResponseError: if the request could not be completed 46 | ''' 47 | return self.client.post('/v2/shopping/flight-offers', body) 48 | -------------------------------------------------------------------------------- /amadeus/shopping/_hotel_offer_search.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class HotelOfferSearch(Decorator, object): 5 | def __init__(self, client, offer_id): 6 | Decorator.__init__(self, client) 7 | self.offer_id = offer_id 8 | 9 | def get(self, **params): 10 | ''' 11 | Returns details for a specific offer 12 | 13 | .. code-block:: python 14 | 15 | amadeus.shopping.hotel_offer_search('XXX').get 16 | 17 | :rtype: amadeus.Response 18 | :raises amadeus.ResponseError: if the request could not be completed 19 | ''' 20 | return self.client.get('/v3/shopping/hotel-offers/{0}' 21 | .format(self.offer_id), **params) 22 | -------------------------------------------------------------------------------- /amadeus/shopping/_hotel_offers_search.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class HotelOffersSearch(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Get all offers given hotels 8 | 9 | .. code-block:: python 10 | 11 | amadeus.shopping.hotel_offers_search.get(hotelIds=RTPAR001', 12 | adults='2') 13 | 14 | :param hotelId: Amadeus Property Code (8 chars), for 15 | example ``RTPAR001``. 16 | 17 | :param adults: the number of adult passengers with age 12 or older 18 | 19 | :rtype: amadeus.Response 20 | :raises amadeus.ResponseError: if the request could not be completed 21 | ''' 22 | return self.client.get('/v3/shopping/hotel-offers', **params) 23 | -------------------------------------------------------------------------------- /amadeus/shopping/_seatmaps.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Seatmaps(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Allows you to retrieve the seat map of one or several flights based 8 | on the flight-orderId returned from Flight Create Orders API Call. 9 | 10 | .. code-block:: python 11 | 12 | amadeus.shopping.seatmaps.get( 13 | flight-orderId='' 14 | ) 15 | 16 | :param flight-orderId: identifier of the order. 17 | Either a flight offer or a flight order Id. 18 | 19 | :rtype: amadeus.Response 20 | :raises amadeus.ResponseError: if the request could not be completed 21 | ''' 22 | return self.client.get('/v1/shopping/seatmaps', **params) 23 | 24 | def post(self, body): 25 | ''' 26 | Allows you to retrieve the seat map of one or several flights. 27 | Take the body of a flight offer search or flight offer price 28 | and pass it in to this method to get a seatmap 29 | 30 | .. code-block:: python 31 | 32 | amadeus.shopping.seatmaps.post(body) 33 | 34 | :param body: the parameters to send to the API 35 | 36 | :rtype: amadeus.Response 37 | :raises amadeus.ResponseError: if the request could not be completed 38 | ''' 39 | return self.client.post('/v1/shopping/seatmaps', body) 40 | -------------------------------------------------------------------------------- /amadeus/shopping/_transfer_offers.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class TransferOffers(Decorator, object): 5 | def post(self, body): 6 | ''' 7 | Get transfer offers 8 | 9 | .. code-block:: python 10 | 11 | amadeus.shopping.transfer_offers.post(body) 12 | 13 | :param body: the parameters to send to the API 14 | 15 | :rtype: amadeus.Response 16 | :raises amadeus.ResponseError: if the request could not be completed 17 | ''' 18 | return self.client.post('/v1/shopping/transfer-offers', body) 19 | -------------------------------------------------------------------------------- /amadeus/shopping/activities/__init__.py: -------------------------------------------------------------------------------- 1 | from ._by_square import BySquare 2 | __all__ = ['BySquare'] 3 | -------------------------------------------------------------------------------- /amadeus/shopping/activities/_by_square.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class BySquare(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Returns a list of activities 8 | around a defined square (4 points). 9 | 10 | .. code-block:: python 11 | 12 | 13 | client.shopping.activities.by_square.get( 14 | north=41.397158, 15 | west=2.160873, 16 | south=41.394582, 17 | east=2.177181 18 | ) 19 | 20 | :param north: latitude north of bounding box. 21 | For example: ``41.397158`` 22 | :param west: longitude west of bounding box. 23 | For example: ``2.160873`` 24 | :param south: latitude south of bounding box. 25 | For example: ``41.394582`` 26 | :param east: longitude east of bounding box. 27 | For example: ``2.177181`` 28 | 29 | :rtype: amadeus.Response 30 | :raises amadeus.ResponseError: if the request could not be completed 31 | ''' 32 | return self.client.get( 33 | '/v1/shopping/activities/by-square', **params) 34 | -------------------------------------------------------------------------------- /amadeus/shopping/availability/__init__.py: -------------------------------------------------------------------------------- 1 | from ._flight_availabilities import FlightAvailabilities 2 | 3 | __all__ = ['FlightAvailabilities'] 4 | -------------------------------------------------------------------------------- /amadeus/shopping/availability/_flight_availabilities.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class FlightAvailabilities(Decorator, object): 5 | def post(self, body): 6 | ''' 7 | Get available seats in different fare classes 8 | 9 | .. code-block:: python 10 | 11 | amadeus.shopping.availability.flight_availabilities.post(body) 12 | 13 | :param body: the parameters to send to the API 14 | 15 | :rtype: amadeus.Response 16 | :raises amadeus.ResponseError: if the request could not be completed 17 | ''' 18 | return self.client.post( 19 | '/v1/shopping/availability/flight-availabilities', body) 20 | -------------------------------------------------------------------------------- /amadeus/shopping/flight_offers/__init__.py: -------------------------------------------------------------------------------- 1 | from ._prediction import FlightChoicePrediction 2 | from ._pricing import FlightOffersPrice 3 | from ._upselling import Upselling 4 | 5 | __all__ = ['FlightChoicePrediction', 'FlightOffersPrice', 6 | 'Upselling'] 7 | -------------------------------------------------------------------------------- /amadeus/shopping/flight_offers/_prediction.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class FlightChoicePrediction(Decorator, object): 5 | def post(self, body): 6 | ''' 7 | Forecast traveler choices in the context of search & shopping. 8 | 9 | .. code-block:: python 10 | 11 | amadeus.shopping.flight_offers.prediction.post( 12 | amadeus.shopping.flight_offers_search.get( 13 | originLocationCode='SYD', 14 | destinationLocationCode='BKK', 15 | departureDate='2020-11-01', 16 | adults=1).result 17 | ) 18 | 19 | :rtype: amadeus.Response 20 | :raises amadeus.ResponseError: if the request could not be completed 21 | ''' 22 | return self.client.post('/v2/shopping/flight-offers/prediction', body) 23 | -------------------------------------------------------------------------------- /amadeus/shopping/flight_offers/_pricing.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | from urllib.parse import urlencode 4 | 5 | 6 | class FlightOffersPrice(Decorator, object): 7 | def post(self, body, **params): 8 | ''' 9 | Gets a confirmed price and availability of a flight 10 | 11 | .. code-block:: python 12 | 13 | amadeus.shopping.flight_offers.pricing.post(body, params) 14 | 15 | :rtype: amadeus.Response 16 | :raises amadeus.ResponseError: if the request could not be completed 17 | ''' 18 | url = '/v1/shopping/flight-offers/pricing?' 19 | flight_offers = [] 20 | if not isinstance(body, list): 21 | flight_offers.append(body) 22 | else: 23 | flight_offers.extend(body) 24 | return self.client.post(url + urlencode(params), 25 | {'data': 26 | {'type': 'flight-offers-pricing', 27 | 'flightOffers': flight_offers}}) 28 | -------------------------------------------------------------------------------- /amadeus/shopping/flight_offers/_upselling.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Upselling(Decorator, object): 5 | def post(self, body): 6 | ''' 7 | Get flight offers with branded fares 8 | 9 | .. code-block:: python 10 | 11 | amadeus.shopping.flight_offers.upselling.post(body) 12 | 13 | :param body: the parameters to send to the API 14 | 15 | :rtype: amadeus.Response 16 | :raises amadeus.ResponseError: if the request could not be completed 17 | ''' 18 | return self.client.post( 19 | '/v1/shopping/flight-offers/upselling', body) 20 | -------------------------------------------------------------------------------- /amadeus/travel/__init__.py: -------------------------------------------------------------------------------- 1 | from ._analytics import Analytics 2 | from ._predictions import TripPurpose, FlightDelay 3 | 4 | __all__ = ['Analytics', 'TripPurpose', 'FlightDelay'] 5 | -------------------------------------------------------------------------------- /amadeus/travel/_analytics.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from .analytics._air_traffic import AirTraffic 3 | 4 | 5 | class Analytics(Decorator, object): 6 | def __init__(self, client): 7 | Decorator.__init__(self, client) 8 | self.air_traffic = AirTraffic(client) 9 | -------------------------------------------------------------------------------- /amadeus/travel/_predictions.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from .predictions import TripPurpose, FlightDelay 3 | 4 | 5 | class Predictions(Decorator, object): 6 | def __init__(self, client): 7 | Decorator.__init__(self, client) 8 | self.trip_purpose = TripPurpose(client) 9 | self.flight_delay = FlightDelay(client) 10 | -------------------------------------------------------------------------------- /amadeus/travel/analytics/__init__.py: -------------------------------------------------------------------------------- 1 | from ._air_traffic import AirTraffic 2 | 3 | 4 | __all__ = ['AirTraffic'] 5 | -------------------------------------------------------------------------------- /amadeus/travel/analytics/_air_traffic.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | from .air_traffic._traveled import Traveled 3 | from .air_traffic._booked import Booked 4 | from .air_traffic._busiest_period import BusiestPeriod 5 | 6 | 7 | class AirTraffic(Decorator, object): 8 | def __init__(self, client): 9 | Decorator.__init__(self, client) 10 | self.booked = Booked(client) 11 | self.traveled = Traveled(client) 12 | self.busiest_period = BusiestPeriod(client) 13 | -------------------------------------------------------------------------------- /amadeus/travel/analytics/air_traffic/__init__.py: -------------------------------------------------------------------------------- 1 | from ._traveled import Traveled 2 | from ._booked import Booked 3 | from ._busiest_period import BusiestPeriod 4 | 5 | 6 | __all__ = ['Traveled', 'Booked', 'BusiestPeriod'] 7 | -------------------------------------------------------------------------------- /amadeus/travel/analytics/air_traffic/_booked.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Booked(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Returns a list of air traffic reports, based on bookings. 8 | 9 | .. code-block:: python 10 | 11 | amadeus.travel.analytics.air_traffic.booked.get( 12 | originCityCode='LHR', 13 | period='2017-01' 14 | ) 15 | 16 | :param originCityCode: IATA code of the origin city, for 17 | example ``"BOS"`` for Boston. 18 | :param query: period when consumers are traveling 19 | in ``YYYY-MM`` format 20 | 21 | :rtype: amadeus.Response 22 | :raises amadeus.ResponseError: if the request could not be completed 23 | ''' 24 | return self.client.get('/v1/travel/analytics/air-traffic/booked', 25 | **params) 26 | -------------------------------------------------------------------------------- /amadeus/travel/analytics/air_traffic/_busiest_period.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class BusiestPeriod(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Returns a list of air traffic reports, based on number of travelers. 8 | 9 | .. code-block:: python 10 | 11 | amadeus.travel.analytics.air_traffic.busiest_period.get( 12 | cityCode='MAD', 13 | period='2017', 14 | direction=Direction.ARRIVING 15 | ) 16 | 17 | :param cityCode: IATA code of the origin city, for 18 | example ``"BOS"`` for Boston. 19 | :param period: period when consumers are traveling 20 | in ``YYYY`` format 21 | :param direction: to select between 22 | arrivals and departures (default: arrivals) 23 | 24 | :rtype: amadeus.Response 25 | :raises amadeus.ResponseError: if the request could not be completed 26 | ''' 27 | return self.client.get('/v1/travel/analytics/air-traffic/busiest-period', 28 | **params) 29 | -------------------------------------------------------------------------------- /amadeus/travel/analytics/air_traffic/_traveled.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class Traveled(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Returns a list of air traffic reports, based on number of travelers. 8 | 9 | .. code-block:: python 10 | 11 | amadeus.travel.analytics.air_traffic.traveled.get( 12 | originCityCode='LHR', 13 | period='2017-01' 14 | ) 15 | 16 | :param originCityCode: IATA code of the origin city, for 17 | example ``"BOS"`` for Boston. 18 | :param period: period when consumers are traveling 19 | in ``YYYY-MM`` format 20 | 21 | :rtype: amadeus.Response 22 | :raises amadeus.ResponseError: if the request could not be completed 23 | ''' 24 | return self.client.get('/v1/travel/analytics/air-traffic/traveled', 25 | **params) 26 | -------------------------------------------------------------------------------- /amadeus/travel/predictions/__init__.py: -------------------------------------------------------------------------------- 1 | from ._trip_purpose import TripPurpose 2 | from ._flight_delay import FlightDelay 3 | 4 | 5 | __all__ = ['TripPurpose', 'FlightDelay'] 6 | -------------------------------------------------------------------------------- /amadeus/travel/predictions/_flight_delay.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class FlightDelay(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Forecast the chances for a flight to be delayed 8 | 9 | .. code-block:: python 10 | 11 | amadeus.travel.predictions.flight_delay.get(originLocationCode='NCE', 12 | destinationLocationCode='IST', 13 | departureDate='2020-08-01', 14 | departureTime='18:20:00', 15 | arrivalDate='2020-08-01', 16 | arrivalTime='22:15:00', 17 | aircraftCode='321', 18 | carrierCode='TK', 19 | flightNumber='1816', 20 | duration='PT31H10M') 21 | 22 | :param originLocationCode: the City/Airport IATA code from which 23 | the flight will depart. ``"NYC"``, for example for New York 24 | 25 | :param destinationLocationCode: the City/Airport IATA code to which 26 | the flight is going. ``"MAD"``, for example for Madrid 27 | 28 | :param departureDate: the date on which the traveler departs 29 | from the origin, in `YYYY-MM-DD` format 30 | 31 | :param departureTime: local time on which to fly out, 32 | in `HH:MM:SS` format 33 | 34 | :param arrivalDate: the date on which the traveler arrives 35 | to the destination, in `YYYY-MM-DD` format 36 | 37 | :param arrivalTime: local time on which the traveler arrives 38 | to the destination, in `HH:MM:SS` format 39 | 40 | :param aircraftCode: IATA aircraft code 41 | 42 | :param carrierCode: airline / carrier code 43 | 44 | :param flightNumber: flight number as assigned by the carrier 45 | 46 | :param duration: flight duration, 47 | in `PnYnMnDTnHnMnS` format e.g. PT2H10M 48 | 49 | :rtype: amadeus.Response 50 | :raises amadeus.ResponseError: if the request could not be completed 51 | ''' 52 | return self.client.get('/v1/travel/predictions/flight-delay', **params) 53 | -------------------------------------------------------------------------------- /amadeus/travel/predictions/_trip_purpose.py: -------------------------------------------------------------------------------- 1 | from amadeus.client.decorator import Decorator 2 | 3 | 4 | class TripPurpose(Decorator, object): 5 | def get(self, **params): 6 | ''' 7 | Predicts traveler purpose, Business or Leisure, 8 | with the probability in the context of search & shopping 9 | 10 | .. code-block:: python 11 | 12 | amadeus.travel.predictions.trip_purpose.get( 13 | originLocationCode='NYC', 14 | destinationLocationCode='MAD', 15 | departureDate='2020-08-01', 16 | returnDate='2020-08-12', 17 | searchDate='2020-06-11') 18 | 19 | :param originLocationCode: the City/Airport IATA code from which 20 | the flight will depart. ``"NYC"``, for example for New York 21 | 22 | :param destinationLocationCode: the City/Airport IATA code to which 23 | the flight is going. ``"MAD"``, for example for Madrid 24 | 25 | :param departureDate: the date on which to fly out, in `YYYY-MM-DD` format 26 | 27 | :param returnDate: the date on which the flight returns to the origin, 28 | in `YYYY-MM-DD` format 29 | 30 | :param searchDate: the date on which the traveler performs the search, 31 | in `YYYY-MM-DD` format. 32 | If it is not specified the current date will be used 33 | 34 | :rtype: amadeus.Response 35 | :raises amadeus.ResponseError: if the request could not be completed 36 | ''' 37 | return self.client.get('/v1/travel/predictions/trip-purpose', **params) 38 | -------------------------------------------------------------------------------- /amadeus/version.py: -------------------------------------------------------------------------------- 1 | version_info = (12, 0, 0) 2 | version = '.'.join(str(v) for v in version_info) 3 | -------------------------------------------------------------------------------- /docs/_static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amadeus4dev/amadeus-python/0bc7089cb2e49b5b662ee890a5fe611f59ad4332/docs/_static/.gitkeep -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/stable/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | from amadeus import version 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = 'Amadeus' 24 | copyright = '2018, Amadeus4Dev' 25 | author = 'Amadeus4Dev' 26 | 27 | # The short X.Y version 28 | version = version 29 | # The full version, including alpha/beta/rc tags 30 | release = version 31 | 32 | 33 | # -- General configuration --------------------------------------------------- 34 | 35 | # If your documentation needs a minimal Sphinx version, state it here. 36 | # 37 | # needs_sphinx = '1.0' 38 | 39 | # Add any Sphinx extension module names here, as strings. They can be 40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 41 | # ones. 42 | extensions = [ 43 | 'sphinx.ext.autodoc', 44 | 'sphinx.ext.doctest', 45 | 'sphinx.ext.coverage', 46 | 'sphinx.ext.viewcode', 47 | 'sphinx.ext.githubpages', 48 | 'sphinx.ext.intersphinx', 49 | ] 50 | 51 | intersphinx_mapping = { 52 | 'python': ('https://docs.python.org/3', None) 53 | } 54 | 55 | # Add any paths that contain templates here, relative to this directory. 56 | templates_path = ['_templates'] 57 | 58 | # The suffix(es) of source filenames. 59 | # You can specify multiple suffix as a list of string: 60 | # 61 | # source_suffix = ['.rst', '.md'] 62 | source_suffix = '.rst' 63 | 64 | # The master toctree document. 65 | master_doc = 'index' 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = None 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | # This pattern also affects html_static_path and html_extra_path . 77 | exclude_patterns = [] 78 | 79 | # The name of the Pygments (syntax highlighting) style to use. 80 | pygments_style = 'sphinx' 81 | 82 | 83 | # -- Options for HTML output ------------------------------------------------- 84 | 85 | # The theme to use for HTML and HTML Help pages. See the documentation for 86 | # a list of builtin themes. 87 | # 88 | html_theme = "sphinx_rtd_theme" 89 | 90 | # Theme options are theme-specific and customize the look and feel of a theme 91 | # further. For a list of options available for each theme, see the 92 | # documentation. 93 | # 94 | # html_theme_options = {} 95 | 96 | # Add any paths that contain custom static files (such as style sheets) here, 97 | # relative to this directory. They are copied after the builtin static files, 98 | # so a file named "default.css" will overwrite the builtin "default.css". 99 | html_static_path = ['_static'] 100 | 101 | # Custom sidebar templates, must be a dictionary that maps document names 102 | # to template names. 103 | # 104 | # The default sidebars (for documents that don't match any pattern) are 105 | # defined by theme itself. Builtin themes are using these templates by 106 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 107 | # 'searchbox.html']``. 108 | # 109 | # html_sidebars = {} 110 | 111 | 112 | # -- Options for HTMLHelp output --------------------------------------------- 113 | 114 | # Output file base name for HTML help builder. 115 | htmlhelp_basename = 'Amadeusdoc' 116 | 117 | 118 | # -- Options for LaTeX output ------------------------------------------------ 119 | 120 | latex_elements = { 121 | # The paper size ('letterpaper' or 'a4paper'). 122 | # 123 | # 'papersize': 'letterpaper', 124 | 125 | # The font size ('10pt', '11pt' or '12pt'). 126 | # 127 | # 'pointsize': '10pt', 128 | 129 | # Additional stuff for the LaTeX preamble. 130 | # 131 | # 'preamble': '', 132 | 133 | # Latex figure (float) alignment 134 | # 135 | # 'figure_align': 'htbp', 136 | } 137 | 138 | # Grouping the document tree into LaTeX files. List of tuples 139 | # (source start file, target name, title, 140 | # author, documentclass [howto, manual, or own class]). 141 | latex_documents = [ 142 | (master_doc, 'Amadeus.tex', 'Amadeus Documentation', 143 | 'Amadeus4Dev', 'manual'), 144 | ] 145 | 146 | 147 | # -- Options for manual page output ------------------------------------------ 148 | 149 | # One entry per manual page. List of tuples 150 | # (source start file, name, description, authors, manual section). 151 | man_pages = [ 152 | (master_doc, 'amadeus', 'Amadeus Documentation', 153 | [author], 1) 154 | ] 155 | 156 | 157 | # -- Options for Texinfo output ---------------------------------------------- 158 | 159 | # Grouping the document tree into Texinfo files. List of tuples 160 | # (source start file, target name, title, author, 161 | # dir menu entry, description, category) 162 | texinfo_documents = [ 163 | (master_doc, 'Amadeus', 'Amadeus Documentation', 164 | author, 'Amadeus', 'One line description of project.', 165 | 'Miscellaneous'), 166 | ] 167 | 168 | 169 | # -- Extension configuration ------------------------------------------------- 170 | 171 | html_theme_options = { 172 | 'display_version': True, 173 | } 174 | 175 | html_context = { 176 | 'display_github': True, 177 | 'github_user': 'amadeus4dev', 178 | 'github_repo': 'amadeus-python', 179 | 'github_version': 'master', 180 | 'conf_py_path': '/docs/' 181 | } 182 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Reference 2 | ********* 3 | 4 | Client 5 | ====== 6 | 7 | .. autoclass:: amadeus.Client 8 | :members: __init__, get, post, request, previous, next, first, last 9 | 10 | Response 11 | ======== 12 | 13 | .. autoclass:: amadeus.Response 14 | 15 | ResponseError 16 | ============= 17 | 18 | .. autoclass:: amadeus.ResponseError 19 | .. autoclass:: amadeus.AuthenticationError 20 | :show-inheritance: 21 | .. autoclass:: amadeus.ClientError 22 | :show-inheritance: 23 | .. autoclass:: amadeus.NetworkError 24 | :show-inheritance: 25 | .. autoclass:: amadeus.ServerError 26 | :show-inheritance: 27 | .. autoclass:: amadeus.NotFoundError 28 | :show-inheritance: 29 | .. autoclass:: amadeus.ParserError 30 | :show-inheritance: 31 | 32 | Request 33 | ======= 34 | 35 | .. autoclass:: amadeus.Request 36 | 37 | 38 | Shopping/Flights 39 | ================ 40 | 41 | .. autoclass:: amadeus.shopping.FlightDestinations 42 | :members: get 43 | 44 | .. autoclass:: amadeus.shopping.FlightDates 45 | :members: get 46 | 47 | .. autoclass:: amadeus.shopping.FlightOffersSearch 48 | :members: get 49 | 50 | .. autoclass:: amadeus.shopping.FlightOffersSearch 51 | :members: post 52 | 53 | Shopping/Hotels 54 | =============== 55 | 56 | .. autoclass:: amadeus.shopping.hotel.HotelOffersSearch 57 | :members: get 58 | 59 | .. autoclass:: amadeus.shopping.hotel.HotelOfferSearch 60 | :members: get 61 | 62 | Shopping/FlightOffers 63 | =============== 64 | 65 | .. autoclass:: amadeus.shopping.flight_offers.FlightChoicePrediction 66 | :members: post 67 | 68 | .. autoclass:: amadeus.shopping.flight_offers.FlightChoicePrice 69 | :members: post 70 | 71 | .. autoclass:: amadeus.shopping.flight_offers.Upselling 72 | :members: post 73 | 74 | Shopping/Activities 75 | =============== 76 | 77 | .. autoclass:: amadeus.shopping.Activities 78 | :members: get 79 | 80 | .. autoclass:: amadeus.shopping.activities.BySquare 81 | :members: get 82 | 83 | .. autoclass:: amadeus.shopping.Activity 84 | :members: get 85 | 86 | Shopping/Availability 87 | =============== 88 | 89 | .. autoclass:: amadeus.shopping.availability.FlightAvailabilities 90 | :members: post 91 | 92 | Shopping/Transfers 93 | ================ 94 | 95 | .. autoclass:: amadeus.shopping.TransferOffers 96 | :members: post 97 | 98 | Travel/Analytics 99 | ================ 100 | 101 | .. autoclass:: amadeus.travel.analytics.AirTraffic 102 | :members: get 103 | 104 | .. autoclass:: amadeus.travel.analytics.FareSearches 105 | :members: get 106 | 107 | Travel/Predictions 108 | ================ 109 | 110 | .. autoclass:: amadeus.travel.predictions.TripPurpose 111 | :members: get 112 | 113 | .. autoclass:: amadeus.travel.predictions.FlightDelay 114 | :members: get 115 | 116 | ReferenceData/Locations 117 | ======================= 118 | 119 | .. autoclass:: amadeus.reference_data.Location 120 | :members: get 121 | 122 | .. autoclass:: amadeus.reference_data.Locations 123 | :members: get 124 | 125 | .. autoclass:: amadeus.reference_data.locations.Airports 126 | :members: get 127 | 128 | .. autoclass:: amadeus.reference_data.Airlines 129 | :members: get 130 | 131 | ReferenceData/Urls 132 | ================== 133 | 134 | .. autoclass:: amadeus.reference_data.urls.CheckinLinks 135 | :members: get 136 | 137 | ReferenceData/RecommendedLocations 138 | ================== 139 | 140 | .. autoclass:: amadeus.reference_data.RecommendedLocations 141 | :members: get 142 | 143 | ReferenceData/Locations/Hotels 144 | ======================= 145 | 146 | .. autoclass:: amadeus.reference_data.hotels.ByHotels 147 | :members: get 148 | 149 | .. autoclass:: amadeus.reference_data.hotels.ByCity 150 | :members: get 151 | 152 | .. autoclass:: amadeus.reference_data.hotels.ByGeocode 153 | :members: get 154 | 155 | ReferenceData/Locations/Hotel 156 | ======================= 157 | 158 | .. autoclass:: amadeus.reference_data.locations.Hotel 159 | :members: get 160 | 161 | ReferenceData/Locations/Cities 162 | ======================= 163 | 164 | .. autoclass:: amadeus.reference_data.locations.Cities 165 | :members: get 166 | 167 | Helper/Location 168 | ================== 169 | 170 | .. autoclass:: amadeus.Location 171 | 172 | Airport/Predictions 173 | ================ 174 | 175 | .. autoclass:: amadeus.airport.predictions.AirportOnTime 176 | :members: get 177 | 178 | Airport/DirectDestinations 179 | ================ 180 | 181 | .. autoclass:: amadeus.airport.DirectDestinations 182 | :members: get 183 | 184 | Media/Files 185 | ================ 186 | 187 | 188 | Booking 189 | ================ 190 | 191 | .. autoclass:: amadeus.booking.FlightOrders 192 | :members: post 193 | 194 | .. autoclass:: amadeus.booking.FlightOrder 195 | :members: get 196 | 197 | .. autoclass:: amadeus.booking.FlightOrder 198 | :members: delete 199 | 200 | .. autoclass:: amadeus.booking.HotelBookings 201 | :members: post 202 | 203 | .. autoclass:: amadeus.booking.HotelOrders 204 | :members: post 205 | 206 | 207 | Schedule/Flights 208 | ================ 209 | 210 | .. autoclass:: amadeus.schedule.Flights 211 | :members: get 212 | 213 | Analytics/ItineraryPriceMetrics 214 | ================ 215 | 216 | .. autoclass:: amadeus.analytics.ItineraryPriceMetrics 217 | :members: get 218 | 219 | Airline/Destinations 220 | ================ 221 | 222 | .. autoclass:: amadeus.airline.Destinations 223 | :members: get 224 | 225 | Ordering/Transfers 226 | ================ 227 | 228 | .. autoclass:: amadeus.ordering.TransferOrders 229 | :members: post 230 | 231 | .. autoclass:: amadeus.ordering.transfer_orders.transfers.Cancellation 232 | :members: post 233 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flake8==3.8.0 2 | flake8-quotes==2.1.1 3 | tox==3.20.0 4 | sphinx==5.0.0 5 | sphinx-rtd-theme==0.5.2 6 | pytest==7.2.0 7 | pytest-cov==4.0.0 8 | Jinja2<3.1 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [metadata] 5 | license_file = LICENSE 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | 4 | from setuptools import setup, find_packages 5 | 6 | # Load the version number 7 | about = {} 8 | here = os.path.abspath(os.path.dirname(__file__)) 9 | with open(os.path.join(here, 'amadeus', 'version.py')) as f: 10 | exec(f.read(), about) 11 | 12 | # Import the README and use it as the long-description. 13 | with io.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: 14 | long_description = '\n' + f.read() 15 | 16 | setup( 17 | name='amadeus', 18 | version=about['version'], 19 | description='Python module for the Amadeus travel APIs', 20 | long_description=long_description, 21 | author='Amadeus', 22 | author_email='developers@amadeus.com', 23 | python_requires='>=3.4.8', 24 | url='https://github.com/amadeus4dev/amadeus-python', 25 | install_requires=[], 26 | packages=find_packages(), 27 | data_files=[('docs', ['README.rst', 'CHANGELOG.rst'])], 28 | include_package_data=True, 29 | license='MIT', 30 | classifiers=[ 31 | 'License :: OSI Approved :: MIT License', 32 | 'Operating System :: OS Independent', 33 | 'Programming Language :: Python', 34 | 'Programming Language :: Python :: 3.4', 35 | 'Programming Language :: Python :: 3.5', 36 | 'Programming Language :: Python :: 3.6', 37 | 'Topic :: Software Development :: Libraries :: Python Modules', 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /specs/client/test_access_token.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mock 3 | from amadeus import Client 4 | from amadeus.client.access_token import AccessToken 5 | 6 | 7 | class TokenResponse: 8 | def __init__(self, result): 9 | self.result = result 10 | 11 | 12 | @pytest.fixture 13 | def self(): 14 | self.request = mock.MagicMock( 15 | return_value=TokenResponse({'access_token': 'abc', 'expires_in': 1799})) 16 | self.client = mock.MagicMock(Client) 17 | self.client._unauthenticated_request = self.request 18 | self.client.client_id = '123' 19 | self.client.client_secret = '234' 20 | self.access_token = AccessToken(self.client) 21 | return self 22 | 23 | 24 | def test_bearer_token(self): 25 | token = self.access_token._bearer_token() 26 | assert token == 'Bearer abc' 27 | self.client._unauthenticated_request.assert_called_with( 28 | 'POST', '/v1/security/oauth2/token', 29 | { 30 | 'grant_type': 'client_credentials', 31 | 'client_id': '123', 32 | 'client_secret': '234' 33 | }) 34 | 35 | 36 | def test_cached_token_valid(self): 37 | access_token = self.access_token 38 | token = access_token._bearer_token() 39 | assert token == 'Bearer abc' 40 | self.client._unauthenticated_request.assert_called_once() 41 | token2 = access_token._bearer_token() 42 | assert token2 == 'Bearer abc' 43 | self.client._unauthenticated_request.assert_called_once() 44 | 45 | 46 | def test_cached_token_expired(self): 47 | access_token = self.access_token 48 | token = access_token._bearer_token() 49 | assert token == 'Bearer abc' 50 | access_token.expires_at = 0 51 | token2 = access_token._bearer_token() 52 | assert token2 == 'Bearer abc' 53 | assert self.client._unauthenticated_request.call_count == 2 54 | -------------------------------------------------------------------------------- /specs/client/test_request.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from amadeus import Request 3 | from urllib.request import Request as HTTPRequest 4 | 5 | 6 | @pytest.fixture 7 | def self(): 8 | self.host = 'example.com' 9 | self.verb = 'GET' 10 | self.path = '/foo/bar' 11 | self.params = {'foo': 'bar'} 12 | self.bearer_token = 'Bearer 123' 13 | self.client_version = '1.2.3' 14 | self.lang_version = '2.3.4' 15 | self.app_id = 'amadeus-cli' 16 | self.app_version = '3.4.5' 17 | self.ssl = True 18 | self.port = 443 19 | self.request = Request({ 20 | 'host': self.host, 21 | 'verb': self.verb, 22 | 'path': self.path, 23 | 'params': self.params, 24 | 'bearer_token': self.bearer_token, 25 | 'client_version': self.client_version, 26 | 'language_version': self.lang_version, 27 | 'app_id': self.app_id, 28 | 'app_version': self.app_version, 29 | 'ssl': self.ssl, 30 | 'port': self.port 31 | }) 32 | return self 33 | 34 | 35 | def test_init(self): 36 | assert self.request.host == self.host 37 | assert self.request.port == self.port 38 | assert self.request.ssl is True 39 | assert self.request.scheme == 'https' 40 | assert self.request.verb == self.verb 41 | assert self.request.path == self.path 42 | assert self.request.params == self.params 43 | assert self.request.bearer_token == self.bearer_token 44 | assert self.request.client_version == self.client_version 45 | assert self.request.language_version == self.lang_version 46 | assert self.request.app_id == self.app_id 47 | assert self.request.app_version == self.app_version 48 | 49 | 50 | def test_http_request(self): 51 | assert isinstance(self.request.http_request, HTTPRequest) 52 | assert self.request.http_request.data is None 53 | assert self.request.http_request.get_header('Content-Type', None) is None 54 | assert self.request.http_request.get_header( 55 | 'Authorization') == self.bearer_token 56 | assert self.request.http_request.get_header( 57 | 'Accept') == 'application/json, application/vnd.amadeus+json' 58 | assert self.request.http_request.get_header( 59 | 'User-agent') == 'amadeus-python/1.2.3 python/2.3.4 amadeus-cli/3.4.5' 60 | 61 | self.request = Request({ 62 | 'host': self.host, 63 | 'verb': 'POST', 64 | 'path': self.path, 65 | 'params': self.params, 66 | 'bearer_token': self.bearer_token, 67 | 'client_version': self.client_version, 68 | 'language_version': self.lang_version, 69 | 'app_id': self.app_id, 70 | 'app_version': self.app_version, 71 | 'port': self.port, 72 | 'ssl': self.ssl 73 | }) 74 | assert isinstance(self.request.http_request, HTTPRequest) 75 | url = self.request.http_request.get_full_url() 76 | assert url == 'https://example.com/foo/bar' 77 | assert self.request.http_request.data == b'{\"foo\": \"bar\"}' 78 | 79 | 80 | def test_x_http_method_override(self): 81 | for path in Request.list_httpoverride: 82 | self.request = Request({ 83 | 'host': self.host, 84 | 'verb': 'POST', 85 | 'path': path, 86 | 'params': self.params, 87 | 'bearer_token': self.bearer_token, 88 | 'client_version': self.client_version, 89 | 'language_version': self.lang_version, 90 | 'app_id': self.app_id, 91 | 'app_version': self.app_version, 92 | 'port': self.port, 93 | 'ssl': self.ssl 94 | }) 95 | assert isinstance(self.request.http_request, HTTPRequest) 96 | assert self.request.headers['X-HTTP-Method-Override'] == 'GET' 97 | 98 | 99 | def test_custom_scheme_and_port(self): 100 | self.request = Request({ 101 | 'host': self.host, 102 | 'verb': 'POST', 103 | 'path': self.path, 104 | 'params': self.params, 105 | 'bearer_token': self.bearer_token, 106 | 'client_version': self.client_version, 107 | 'language_version': self.lang_version, 108 | 'app_id': self.app_id, 109 | 'app_version': self.app_version, 110 | 'port': 8080, 111 | 'ssl': False 112 | }) 113 | url = self.request.http_request.get_full_url() 114 | assert url == 'http://example.com:8080/foo/bar' 115 | -------------------------------------------------------------------------------- /specs/client/test_response.py: -------------------------------------------------------------------------------- 1 | import mock 2 | from amadeus import Response 3 | 4 | 5 | def test_response_init(): 6 | http_response = mock.MagicMock() 7 | request = mock.MagicMock() 8 | response = Response(http_response, request) 9 | 10 | assert response.http_response == http_response 11 | assert response.request == request 12 | -------------------------------------------------------------------------------- /specs/mixins/test_errors.py: -------------------------------------------------------------------------------- 1 | from mock import MagicMock 2 | from unittest.mock import Mock 3 | 4 | from amadeus import NetworkError, Response, Client 5 | 6 | 7 | def test_ResponseError_str_representation(): 8 | # Test str(error) with no response present 9 | error = NetworkError(None) 10 | assert str(error) == '[---]' 11 | 12 | # Test str(error) with no data present 13 | response = Mock(Response) 14 | response.parsed = True 15 | response.result = {} 16 | response.status_code = 400 17 | 18 | error = NetworkError(response) 19 | assert str(error) == '[400]' 20 | 21 | # Test str(error) with errors present 22 | response = Mock(Response) 23 | response.parsed = True 24 | response.result = { 25 | 'errors': [ 26 | { 27 | 'detail': 'This field must be filled.', 28 | 'source': {'parameter': 'departureDate'}, 29 | }, 30 | { 31 | 'detail': 'This field must be filled.', 32 | 'source': {'parameter': 'origin'}, 33 | }, 34 | { 35 | 'detail': 'This field must be filled.', 36 | 'source': {'parameter': 'destination'}, 37 | }, 38 | ] 39 | } 40 | response.status_code = 401 41 | 42 | error = NetworkError(response) 43 | error = NetworkError(response) 44 | assert ( 45 | ('''[401] 46 | [departureDate] This field must be filled. 47 | [origin] This field must be filled. 48 | [destination] This field must be filled.''') 49 | ) 50 | 51 | # Test str(error) with error_description present 52 | response = Mock(Response) 53 | response.parsed = True 54 | response.result = {'error_description': 'error'} 55 | response.status_code = 401 56 | 57 | error = NetworkError(response) 58 | assert str(error) == '[401]\nerror' 59 | 60 | 61 | def test_ResponseError_code(): 62 | # Test .code with no response present 63 | error = NetworkError(None) 64 | assert error.code == 'NetworkError' 65 | 66 | 67 | def test_ResponseError_log(): 68 | # Test .log with log level set to 'warn' 69 | client = Mock(Client) 70 | client.logger = MagicMock() 71 | client.log_level = 'warn' 72 | 73 | error = NetworkError(None) 74 | error.code = 'Foo' 75 | error.description = 'Bar' 76 | error._log(client) 77 | 78 | assert client.logger.warning.called_with('Amadeus %s: %s', 'Foo', 'Bar') 79 | 80 | # Test .log with log level set to 'silent' 81 | client = Mock(Client) 82 | client.logger = MagicMock() 83 | client.log_level = 'silent' 84 | 85 | error = NetworkError(None) 86 | error._log(client) 87 | 88 | assert not client.logger.warning.called 89 | -------------------------------------------------------------------------------- /specs/mixins/test_http.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mock 3 | import unittest.mock 4 | from amadeus import Client, Response, ResponseError 5 | from urllib.error import URLError 6 | 7 | 8 | @pytest.fixture 9 | def self(): 10 | self = mock.MagicMock() 11 | self.client = Client(client_id='123', client_secret='234', log_level='silent') 12 | self.response = Response(None, None) 13 | self.request_method = mock.MagicMock(return_value=self.response) 14 | return self 15 | 16 | 17 | def test_client_get(self): 18 | self.client.request = self.request_method 19 | response = self.client.get('/foo', foo='bar') 20 | assert response == self.response 21 | self.client.request.assert_called_with('GET', '/foo', {'foo': 'bar'}) 22 | 23 | 24 | def test_client_delete(self): 25 | self.client.request = self.request_method 26 | response = self.client.delete('/foo', foo='bar') 27 | assert response == self.response 28 | self.client.request.assert_called_with( 29 | 'DELETE', '/foo', {'foo': 'bar'}) 30 | 31 | 32 | def test_client_post(self): 33 | self.client.request = self.request_method 34 | response = self.client.post('/foo', {'foo': 'bar'}) 35 | assert response == self.response 36 | self.client.request.assert_called_with('POST', '/foo', {'foo': 'bar'}) 37 | 38 | 39 | def test_client_request(self): 40 | self.response.result = {'access_token': '123'} 41 | self.client._unauthenticated_request = self.request_method 42 | response = self.client.request('POST', '/foo', {'foo': 'bar'}) 43 | assert response == self.response 44 | assert self.client._unauthenticated_request.call_args == mock.call( 45 | 'POST', '/foo', {'foo': 'bar'}, 'Bearer 123' 46 | ) 47 | 48 | 49 | def test_client_request_use_same_access_token(self): 50 | self.response.result = {'access_token': '123', 'expires_in': 2000} 51 | self.client._unauthenticated_request = self.request_method 52 | self.client.request('POST', '/foo', {'foo': 'bar'}) 53 | 54 | self.response.result = {'access_token': '234', 'expires_in': 2000} 55 | self.client._unauthenticated_request = self.request_method 56 | self.client.request('POST', '/foo', {'foo': 'bar'}) 57 | assert not self.client._unauthenticated_request.assert_called_with( 58 | 'POST', '/foo', {'foo': 'bar'}, 'Bearer 123') 59 | 60 | 61 | def test_unauthenticated_request(self): 62 | http_response = mock.MagicMock() 63 | http_response.code = 200 64 | http_response.getheaders.return_value = [('Content-Type', 'application/json')] 65 | http_response.read.return_value = '{ "data" : { "a" : 1 } }' 66 | 67 | # Patch the client's `http` method to return the mock HTTP response 68 | unittest.mock.patch.object(self.client, 'http', return_value=http_response) 69 | 70 | # Test the client's _unauthenticated_request method 71 | with pytest.raises(ResponseError): 72 | self.client._unauthenticated_request('GET', '/foo', {}, None) 73 | 74 | # Test that a HTTPError is caught 75 | self.client.http.side_effect = URLError('Error') 76 | with pytest.raises(ResponseError): 77 | self.client._unauthenticated_request('GET', '/foo', {}, None) 78 | 79 | # Test logging in debug mode 80 | with mock.patch.object(self.client, 'http', return_value=http_response): 81 | logger = mock.MagicMock() 82 | self.client.logger = logger 83 | self.client.log_level = 'debug' 84 | with pytest.raises(ResponseError): 85 | self.client._unauthenticated_request( 86 | 'GET', '/foo', {}, None) 87 | -------------------------------------------------------------------------------- /specs/mixins/test_pagination.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mock 3 | from amadeus import Client, Response, Request 4 | from amadeus.mixins.pagination import Pagination 5 | 6 | 7 | @pytest.fixture 8 | def self(): 9 | # Initialize the client and pagination object 10 | self.next_response = mock.MagicMock(Response) 11 | client = mock.MagicMock(Client) 12 | client.request = self.next_response 13 | self.client = client 14 | self.pagination = Pagination() 15 | self.pagination.request = self.client.request 16 | 17 | # Initialize the previous request made and response received 18 | self.request = mock.MagicMock(Request) 19 | self.request.verb = self.verb = 'GET' 20 | self.request.path = self.path = '/a' 21 | self.request.params = self.params = {'a': 'b'} 22 | self.response = Response(mock.MagicMock('http_response'), self.request) 23 | return self 24 | 25 | 26 | def test_create_new_request_when_previous(self): 27 | self.response.result = { 28 | 'meta': {'links': {'previous': 'https://f.co?page%5Boffset%5D=1'}} 29 | } 30 | self.pagination.previous(self.response) 31 | self.client.request.call_args.assert_called_with( 32 | 'GET', '/a', {'a': 'b', 'page': {'offset': '1'}}) 33 | 34 | 35 | def test_should_return_nil_page_not_found_with_previous(self): 36 | self.response.result = {'meta': {'links': {}}} 37 | next_response = self.pagination.previous(self.response) 38 | assert next_response is None 39 | 40 | 41 | def test_create_new_request_when_next(self): 42 | self.response.result = { 43 | 'meta': {'links': {'next': 'https://f.co?page%5Boffset%5D=1'}} 44 | } 45 | self.pagination.next(self.response) 46 | self.client.request.call_args.assert_called_with( 47 | 'GET', '/a', {'a': 'b', 'page': {'offset': '1'}}) 48 | 49 | 50 | def test_should_return_nil_page_not_found_with_next(self): 51 | self.response.result = {'meta': {'links': {}}} 52 | next_response = self.pagination.next(self.response) 53 | assert next_response is None 54 | 55 | 56 | def test_create_new_request_when_first(self): 57 | self.response.result = { 58 | 'meta': {'links': {'first': 'https://f.co?page%5Boffset%5D=1'}} 59 | } 60 | self.pagination.first(self.response) 61 | self.client.request.call_args.assert_called_with( 62 | 'GET', '/a', {'a': 'b', 'page': {'offset': '1'}}) 63 | 64 | 65 | def test_should_return_nil_page_not_found_with_first(self): 66 | self.response.result = {'meta': {'links': {}}} 67 | next_response = self.pagination.first(self.response) 68 | assert next_response is None 69 | 70 | 71 | def test_create_new_request_when_last(self): 72 | self.response.result = { 73 | 'meta': {'links': {'last': 'https://f.co?page%5Boffset%5D=1'}} 74 | } 75 | self.pagination.last(self.response) 76 | self.client.request.call_args.assert_called_with( 77 | 'GET', '/a', {'a': 'b', 'page': {'offset': '1'}}) 78 | 79 | 80 | def test_should_return_nil_page_not_found_with_last(self): 81 | self.response.result = {'meta': {'links': {}}} 82 | next_response = self.pagination.last(self.response) 83 | assert next_response is None 84 | -------------------------------------------------------------------------------- /specs/mixins/test_parser.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mock 3 | from amadeus import Response, Request, Client 4 | from amadeus import NetworkError, ServerError, AuthenticationError 5 | from amadeus import NotFoundError, ClientError, ParserError 6 | 7 | 8 | @pytest.fixture 9 | def self(): 10 | self.request = mock.MagicMock(Request) 11 | self.client = mock.MagicMock(Client) 12 | self.client.log_level = 'silent' 13 | return self 14 | 15 | 16 | @pytest.fixture 17 | def response_setup(): 18 | http_response = mock.MagicMock() 19 | request = mock.MagicMock(Request) 20 | response_setup.client = mock.MagicMock(Client) 21 | response_setup.client.log_level = 'silent' 22 | response_setup.response = Response(http_response, request) 23 | return response_setup 24 | 25 | 26 | def test_should_parse_body(self, response_setup): 27 | response_setup.response.status_code = '200' 28 | response_setup.response.headers = {'Content-Type': 'application/json'} 29 | response_setup.response.text = '{ "data" : { "a" : 1 } }' 30 | response = Response(response_setup.response, self.request) 31 | response = response._parse(response_setup.client) 32 | assert isinstance(response, Response) 33 | 34 | 35 | def test_should_raise_network_error_with_no_code(response_setup): 36 | response_setup.response.status_code = None 37 | response_setup.response.parsed = False 38 | with pytest.raises(NetworkError): 39 | response_setup.response._detect_error(response_setup.client) 40 | 41 | 42 | def test_should_raise_server_error_with_500(response_setup): 43 | response_setup.response.status_code = 500 44 | response_setup.response.parsed = False 45 | with pytest.raises(ServerError): 46 | response_setup.response._detect_error(response_setup.client) 47 | 48 | 49 | def test_should_raise_auth_error_with_401(response_setup): 50 | response_setup.response.status_code = 401 51 | response_setup.response.parsed = False 52 | with pytest.raises(AuthenticationError): 53 | response_setup.response._detect_error(response_setup.client) 54 | 55 | 56 | def test_should_raise_not_found_error_with_404(response_setup): 57 | response_setup.response.status_code = 404 58 | response_setup.response.parsed = False 59 | with pytest.raises(NotFoundError): 60 | response_setup.response._detect_error(response_setup.client) 61 | 62 | 63 | def test_should_raise_a_generic_error_with_400(response_setup): 64 | response_setup.response.status_code = 400 65 | response_setup.response.parsed = False 66 | with pytest.raises(ClientError): 67 | response_setup.response._detect_error(response_setup.client) 68 | 69 | 70 | def test_should_raise_error_if_not_parsed_with_200(response_setup): 71 | response_setup.response.status_code = 200 72 | response_setup.response.parsed = False 73 | with pytest.raises(ParserError): 74 | response_setup.response._detect_error(response_setup.client) 75 | 76 | 77 | def test_should_raise_error_when_exception(response_setup): 78 | response_setup.response.status_code = 200 79 | response_setup.response.parsed = True 80 | response_setup.response.body = None 81 | response_setup.response._detect_error(response_setup.client) 82 | 83 | 84 | def test_should_not_raise_error_body_with_204(response_setup): 85 | response_setup.response.status_code = 204 86 | response_setup.response.parsed = False 87 | assert response_setup.response.status_code == 204 88 | response_setup.response._detect_error(response_setup.client) 89 | -------------------------------------------------------------------------------- /specs/mixins/test_validator.py: -------------------------------------------------------------------------------- 1 | from amadeus import Client 2 | from os import environ 3 | from logging import Logger, getLogger 4 | import pytest 5 | from mock import MagicMock 6 | 7 | 8 | @pytest.fixture 9 | def valid_params(): 10 | return { 11 | 'client_id': '1234', 12 | 'client_secret': '4546' 13 | } 14 | 15 | 16 | def test_client_init(valid_params): 17 | client = Client(**valid_params) 18 | assert isinstance(client, Client) 19 | 20 | 21 | def test_client_init_with_env_vars(valid_params): 22 | environ['AMADEUS_CLIENT_ID'] = '123' 23 | environ['AMADEUS_CLIENT_SECRET'] = '234' 24 | 25 | client = Client() 26 | assert isinstance(client, Client) 27 | 28 | del environ['AMADEUS_CLIENT_ID'] 29 | del environ['AMADEUS_CLIENT_SECRET'] 30 | 31 | 32 | def test_client_init_with_missing_params(valid_params): 33 | # Test missing client_id 34 | valid_params_copy = valid_params.copy() 35 | del valid_params_copy['client_id'] 36 | with pytest.raises(ValueError): 37 | Client(**valid_params_copy) 38 | 39 | # Test missing client_secret 40 | valid_params_copy = valid_params.copy() 41 | del valid_params_copy['client_secret'] 42 | with pytest.raises(ValueError): 43 | Client(**valid_params_copy) 44 | 45 | 46 | def test_client_logging(valid_params): 47 | # Test default logger 48 | amadeus = Client(**valid_params) 49 | assert isinstance(amadeus.logger, Logger) 50 | assert amadeus.log_level == 'silent' 51 | 52 | # Test custom logger 53 | logger = getLogger('amadeus') 54 | valid_params['logger'] = logger 55 | amadeus = Client(**valid_params) 56 | assert amadeus.logger is logger 57 | assert amadeus.log_level == 'silent' 58 | 59 | # Test custom log level 60 | logger.setLevel(10) 61 | amadeus = Client(**valid_params) 62 | assert amadeus.logger is logger 63 | assert amadeus.logger.level == 10 64 | 65 | 66 | def test_client_options(valid_params): 67 | # Test unrecognized option warning 68 | logger = MagicMock() 69 | valid_params['logger'] = logger 70 | valid_params['foobar'] = 'test' 71 | Client(**valid_params) 72 | logger.warning.assert_called_with('Unrecognized option: foobar') 73 | 74 | # Test default host 75 | amadeus = Client(**valid_params) 76 | assert amadeus.host == Client.HOSTS['test'] 77 | 78 | # Test custom hostname 79 | valid_params['hostname'] = 'production' 80 | amadeus = Client(**valid_params) 81 | assert amadeus.host == Client.HOSTS['production'] 82 | 83 | # Test custom host 84 | host = 'https://foo.bar.com/' 85 | valid_params['host'] = host 86 | amadeus = Client(**valid_params) 87 | assert amadeus.host == host 88 | -------------------------------------------------------------------------------- /specs/namespaces/test_namespaces.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mock import MagicMock 3 | from amadeus import Client 4 | 5 | 6 | @pytest.fixture 7 | def client(): 8 | return Client(client_id='123', client_secret='234') 9 | 10 | 11 | def test_expected_paths(client): 12 | assert client.reference_data is not None 13 | assert client.reference_data.urls is not None 14 | assert client.reference_data.urls.checkin_links is not None 15 | assert client.reference_data.location is not None 16 | assert client.reference_data.locations is not None 17 | assert client.reference_data.locations.airports is not None 18 | assert client.reference_data.locations.hotels is not None 19 | assert client.reference_data.locations.hotels.by_hotels is not None 20 | assert client.reference_data.locations.hotels.by_city is not None 21 | assert client.reference_data.locations.hotels.by_geocode is not None 22 | assert client.reference_data.locations.hotel is not None 23 | assert client.reference_data.locations.cities is not None 24 | assert client.travel is not None 25 | assert client.travel.analytics is not None 26 | assert client.travel.analytics.air_traffic.traveled is not None 27 | assert client.travel.analytics.air_traffic.booked is not None 28 | assert client.travel.analytics.air_traffic.busiest_period is not None 29 | assert client.travel.predictions is not None 30 | assert client.travel.predictions.trip_purpose is not None 31 | assert client.travel.predictions.flight_delay is not None 32 | assert client.shopping is not None 33 | assert client.shopping.flight_dates is not None 34 | assert client.shopping.flight_destinations is not None 35 | assert client.shopping.flight_offers is not None 36 | assert client.shopping.flight_offers_search is not None 37 | assert client.shopping.flight_offers.pricing is not None 38 | assert client.shopping.flight_offers.upselling is not None 39 | assert client.shopping.seatmaps is not None 40 | assert client.shopping.hotel_offers_search is not None 41 | assert client.shopping.hotel_offer_search is not None 42 | assert client.shopping.activities is not None 43 | assert client.shopping.availability is not None 44 | assert client.shopping.availability.flight_availabilities is not None 45 | assert client.e_reputation.hotel_sentiments is not None 46 | assert client.airport is not None 47 | assert client.airport.predictions is not None 48 | assert client.airport.predictions.on_time is not None 49 | assert client.airport.direct_destinations is not None 50 | assert client.booking.flight_orders is not None 51 | assert client.booking.flight_order is not None 52 | assert client.booking.hotel_orders is not None 53 | assert client.schedule is not None 54 | assert client.schedule.flights is not None 55 | assert client.analytics is not None 56 | assert client.analytics.itinerary_price_metrics is not None 57 | assert client.airline.destinations is not None 58 | assert client.shopping.transfer_offers is not None 59 | assert client.ordering.transfer_orders is not None 60 | assert client.ordering.transfer_order is not None 61 | 62 | 63 | def test_expected_get_methods(client): 64 | assert client.reference_data.urls.checkin_links.get is not None 65 | assert client.reference_data.location('ALHR').get is not None 66 | assert client.reference_data.locations.get is not None 67 | assert client.reference_data.locations.airports.get is not None 68 | assert client.reference_data.recommended_locations.get is not None 69 | assert client.reference_data.locations.hotels.by_city.get is not None 70 | assert client.reference_data.locations.hotels.by_hotels.get is not None 71 | assert client.reference_data.locations.hotels.by_geocode.get is not None 72 | assert client.reference_data.locations.hotel.get is not None 73 | assert client.travel.analytics.air_traffic.traveled.get is not None 74 | assert client.travel.analytics.air_traffic.booked.get is not None 75 | assert client.travel.analytics.air_traffic.busiest_period.get is not None 76 | assert client.travel.predictions.trip_purpose.get is not None 77 | assert client.travel.predictions.flight_delay.get is not None 78 | assert client.shopping.flight_dates.get is not None 79 | assert client.shopping.flight_destinations.get is not None 80 | assert client.shopping.flight_offers_search.get is not None 81 | assert client.shopping.seatmaps.get is not None 82 | assert client.shopping.hotel_offers_search.get is not None 83 | assert client.shopping.hotel_offer_search('123').get is not None 84 | assert client.e_reputation.hotel_sentiments.get is not None 85 | assert client.airport.predictions.on_time.get is not None 86 | assert client.airport.direct_destinations.get is not None 87 | assert client.booking.flight_order('123').get is not None 88 | assert client.booking.flight_order('123').delete is not None 89 | assert client.schedule.flights.get is not None 90 | assert client.analytics.itinerary_price_metrics.get is not None 91 | assert client.airline.destinations.get is not None 92 | 93 | 94 | def test_expected_delete_methods(client): 95 | assert client.booking.flight_order('123').delete is not None 96 | assert client.reference_data.location('ALHR').get is not None 97 | assert client.reference_data.locations.get is not None 98 | 99 | 100 | @pytest.fixture 101 | def client_setup(): 102 | client = Client(client_id='123', client_secret='234') 103 | client.get = MagicMock(return_value=None) 104 | client.post = MagicMock(return_value=None) 105 | client.delete = MagicMock(return_value=None) 106 | yield client 107 | 108 | 109 | def test_reference_data_urls_checkin_links_get(client_setup): 110 | client_setup.reference_data.urls.checkin_links.get(a='b') 111 | client_setup.get.assert_called_with( 112 | '/v2/reference-data/urls/checkin-links', 113 | a='b' 114 | ) 115 | 116 | 117 | def test_reference_data_airlines_get(client_setup): 118 | client_setup.reference_data.airlines.get(a='b') 119 | client_setup.get.assert_called_with('/v1/reference-data/airlines', a='b') 120 | 121 | 122 | def test_reference_data_location_get(client_setup): 123 | client_setup.reference_data.location('ALHR').get(a='b') 124 | client_setup.get.assert_called_with( 125 | '/v1/reference-data/locations/ALHR', a='b' 126 | ) 127 | 128 | 129 | def test_reference_data_locations_get(client_setup): 130 | client_setup.reference_data.locations.get(a='b') 131 | client_setup.get.assert_called_with( 132 | '/v1/reference-data/locations', a='b' 133 | ) 134 | 135 | 136 | def test_reference_data_locations_airports_get(client_setup): 137 | client_setup.reference_data.locations.airports.get(a='b') 138 | client_setup.get.assert_called_with( 139 | '/v1/reference-data/locations/airports', a='b' 140 | ) 141 | 142 | 143 | def test_reference_data_recommended_locations_get(client_setup): 144 | client_setup.reference_data.recommended_locations.get(a='b') 145 | client_setup.get.assert_called_with( 146 | '/v1/reference-data/recommended-locations', a='b' 147 | ) 148 | 149 | 150 | def test_travel_analytics_air_traffic_traveled_get(client_setup): 151 | client_setup.travel.analytics.air_traffic.traveled.get(a='b') 152 | client_setup.get.assert_called_with( 153 | '/v1/travel/analytics/air-traffic/traveled', a='b' 154 | ) 155 | 156 | 157 | def test_travel_analytics_air_traffic_booked_get(client_setup): 158 | client_setup.travel.analytics.air_traffic.booked.get(a='b') 159 | client_setup.get.assert_called_with( 160 | '/v1/travel/analytics/air-traffic/booked', a='b' 161 | ) 162 | 163 | 164 | def test_travel_analytics_air_traffic_busiest_period_get(client_setup): 165 | client_setup.travel.analytics.air_traffic.busiest_period.get(a='b') 166 | client_setup.get.assert_called_with( 167 | '/v1/travel/analytics/air-traffic/busiest-period', a='b' 168 | ) 169 | 170 | 171 | def test_travel_predictions_trip_purpose_get(client_setup): 172 | client_setup.travel.predictions.trip_purpose.get(a='b') 173 | client_setup.get.assert_called_with( 174 | '/v1/travel/predictions/trip-purpose', a='b' 175 | ) 176 | 177 | 178 | def test_travel_predictions_flight_delay_get(client_setup): 179 | client_setup.travel.predictions.flight_delay.get(a='b') 180 | client_setup.get.assert_called_with( 181 | '/v1/travel/predictions/flight-delay', a='b' 182 | ) 183 | 184 | 185 | def test_shopping_flight_dates_get(client_setup): 186 | client_setup.shopping.flight_dates.get(a='b') 187 | client_setup.get.assert_called_with( 188 | '/v1/shopping/flight-dates', a='b' 189 | ) 190 | 191 | 192 | def test_shopping_flight_destinations_get(client_setup): 193 | client_setup.shopping.flight_destinations.get(a='b') 194 | client_setup.get.assert_called_with( 195 | '/v1/shopping/flight-destinations', a='b' 196 | ) 197 | 198 | 199 | def test_shopping_flight_offers_search_get(client_setup): 200 | client_setup.shopping.flight_offers_search.get(a='b') 201 | client_setup.get.assert_called_with( 202 | '/v2/shopping/flight-offers', a='b' 203 | ) 204 | 205 | 206 | def test_shopping_hotel_offers_search_get(client_setup): 207 | client_setup.shopping.hotel_offers_search.get( 208 | hotelIds='RTPAR001', adults=2) 209 | client_setup.get.assert_called_with( 210 | '/v3/shopping/hotel-offers', hotelIds='RTPAR001', 211 | adults=2 212 | ) 213 | 214 | 215 | def test_shopping_hotel_offer_search_get(client_setup): 216 | client_setup.shopping.hotel_offer_search('XXX').get(a='b') 217 | client_setup.get.assert_called_with( 218 | '/v3/shopping/hotel-offers/XXX', a='b' 219 | ) 220 | 221 | 222 | def test_shopping_seatmaps_get(client_setup): 223 | client_setup.shopping.seatmaps.get(**{'a': 'b'}) 224 | client_setup.get.assert_called_with( 225 | '/v1/shopping/seatmaps', a='b' 226 | ) 227 | 228 | 229 | def test_e_reputation_hotel_sentiments_get(client_setup): 230 | client_setup.e_reputation.hotel_sentiments.get(hotelIds='XKPARC12') 231 | client_setup.get.assert_called_with( 232 | '/v2/e-reputation/hotel-sentiments', hotelIds='XKPARC12' 233 | ) 234 | 235 | 236 | def test_airport_predictions_on_time_get(client_setup): 237 | client_setup.airport.predictions.on_time.get(a='b') 238 | client_setup.get.assert_called_with( 239 | '/v1/airport/predictions/on-time', a='b' 240 | ) 241 | 242 | 243 | def test_airport_direct_destinations_get(client_setup): 244 | client_setup.airport.direct_destinations.get(a='b') 245 | client_setup.get.assert_called_with( 246 | '/v1/airport/direct-destinations', a='b' 247 | ) 248 | 249 | 250 | def test_shopping_flight_offers_prediction_post(client_setup): 251 | client_setup.shopping.flight_offers.prediction.post({'foo': 'bar'}) 252 | client_setup.post.assert_called_with( 253 | '/v2/shopping/flight-offers/prediction', {'foo': 'bar'} 254 | ) 255 | 256 | 257 | def test_shopping_flight_offers_search_post(client_setup): 258 | client_setup.shopping.flight_offers_search.post({'foo': 'bar'}) 259 | client_setup.post.assert_called_with( 260 | '/v2/shopping/flight-offers', {'foo': 'bar'} 261 | ) 262 | 263 | 264 | def test_shopping_seatmaps_post(client_setup): 265 | client_setup.shopping.seatmaps.post({'foo': 'bar'}) 266 | client_setup.post.assert_called_with( 267 | '/v1/shopping/seatmaps', {'foo': 'bar'} 268 | ) 269 | 270 | 271 | def test_shopping_flight_offers_pricing_post(client_setup): 272 | client_setup.shopping.flight_offers.pricing.post( 273 | {'foo': 'bar'}, include='other-services') 274 | client_setup.post.assert_called_with( 275 | '/v1/shopping/flight-offers/pricing?'+'include=other-services', 276 | {'data': {'type': 'flight-offers-pricing', 277 | 'flightOffers': [{'foo': 'bar'}]}} 278 | ) 279 | 280 | 281 | def test_shopping_flight_offers_pricing_post_list(client_setup): 282 | client_setup.shopping.flight_offers.pricing.post([{'foo': 'bar'}]) 283 | client_setup.post.assert_called_with( 284 | '/v1/shopping/flight-offers/pricing?', 285 | {'data': {'type': 'flight-offers-pricing', 286 | 'flightOffers': [{'foo': 'bar'}]}} 287 | ) 288 | 289 | 290 | def test_shopping_booking_flight_orders_post(client_setup): 291 | client_setup.booking.flight_orders.post({'foo': 'bar'}, {'bar': 'foo'}) 292 | client_setup.post.assert_called_with( 293 | '/v1/booking/flight-orders', 294 | {'data': {'type': 'flight-order', 295 | 'flightOffers': [{'foo': 'bar'}], 296 | 'travelers': [{'bar': 'foo'}] 297 | }} 298 | ) 299 | 300 | 301 | def test_shopping_booking_flight_orders_post_list(client_setup): 302 | client_setup.booking.flight_orders.post( 303 | [{'foo': 'bar'}], [{'bar': 'foo'}]) 304 | client_setup.post.assert_called_with( 305 | '/v1/booking/flight-orders', 306 | {'data': {'type': 'flight-order', 307 | 'flightOffers': [{'foo': 'bar'}], 308 | 'travelers': [{'bar': 'foo'}] 309 | }} 310 | ) 311 | 312 | 313 | def test_shopping_availability_flight_availabilities_post(client_setup): 314 | client_setup.shopping.availability.flight_availabilities.post( 315 | {'foo': 'bar'}) 316 | client_setup.post.assert_called_with( 317 | '/v1/shopping/availability/flight-availabilities', {'foo': 'bar'} 318 | ) 319 | 320 | 321 | def test_shopping_flight_offers_upselling_post(client_setup): 322 | client_setup.shopping.flight_offers.upselling.post( 323 | {'foo': 'bar'}) 324 | client_setup.post.assert_called_with( 325 | '/v1/shopping/flight-offers/upselling', {'foo': 'bar'} 326 | ) 327 | 328 | 329 | def test_booking_flight_order_get(client_setup): 330 | client_setup.booking.flight_order('123').get(a='b') 331 | client_setup.get.assert_called_with( 332 | '/v1/booking/flight-orders/123', a='b' 333 | ) 334 | 335 | 336 | def test_booking_flight_order_delete(client_setup): 337 | client_setup.booking.flight_order('123').delete(a='b') 338 | client_setup.delete.assert_called_with( 339 | '/v1/booking/flight-orders/123', a='b' 340 | ) 341 | 342 | 343 | def test_shopping_booking_hotel_bookings_post(client_setup): 344 | client_setup.booking.hotel_bookings.post('123', 345 | {'foo': 'bar'}, 346 | {'bar': 'foo'}) 347 | client_setup.post.assert_called_with( 348 | '/v1/booking/hotel-bookings', 349 | {'data': {'offerId': '123', 350 | 'guests': [{'foo': 'bar'}], 351 | 'payments': [{'bar': 'foo'}] 352 | }} 353 | ) 354 | 355 | 356 | def test_shopping_booking_hotel_bookings_post_list(client_setup): 357 | client_setup.booking.hotel_bookings.post('123', 358 | [{'foo': 'bar'}], 359 | [{'bar': 'foo'}]) 360 | client_setup.post.assert_called_with( 361 | '/v1/booking/hotel-bookings', 362 | {'data': {'offerId': '123', 363 | 'guests': [{'foo': 'bar'}], 364 | 'payments': [{'bar': 'foo'}] 365 | }} 366 | ) 367 | 368 | 369 | def test_booking_hotel_orders_post(client_setup): 370 | client_setup.booking.hotel_orders.post({'foo': 'bar'}, 371 | {'bar': 'foo'}) 372 | client_setup.post.assert_called_with( 373 | '/v2/booking/hotel-orders', 374 | {'data': {'type': 'hotel-order', 375 | 'guests': [{'foo': 'bar'}], 376 | 'travelAgent': {'bar': 'foo'}, 377 | 'roomAssociations': [], 378 | 'payment': {}, 379 | 'arrivalInformation': {}}} 380 | ) 381 | 382 | 383 | def test_booking_hotel_orders_post_list(client_setup): 384 | client_setup.booking.hotel_orders.post([{'foo': 'bar'}], 385 | {'bar': 'foo'}, 386 | [{'a': 'b'}],) 387 | client_setup.post.assert_called_with( 388 | '/v2/booking/hotel-orders', 389 | {'data': {'type': 'hotel-order', 390 | 'guests': [{'foo': 'bar'}], 391 | 'travelAgent': {'bar': 'foo'}, 392 | 'roomAssociations': [{'a': 'b'}], 393 | 'payment': {}, 394 | 'arrivalInformation': {}}} 395 | ) 396 | 397 | 398 | def test_schedule_flights_get(client_setup): 399 | client_setup.schedule.flights.get(a='b') 400 | client_setup.get.assert_called_with( 401 | '/v2/schedule/flights', a='b' 402 | ) 403 | 404 | 405 | def test_shopping_activities_get(client_setup): 406 | client_setup.shopping.activities.get(a='b') 407 | client_setup.get.assert_called_with( 408 | '/v1/shopping/activities', a='b' 409 | ) 410 | 411 | 412 | def test_shopping_activities_by_square_get(client_setup): 413 | client_setup.shopping.activities.by_square.get(a='b') 414 | client_setup.get.assert_called_with( 415 | '/v1/shopping/activities/by-square', a='b' 416 | ) 417 | 418 | 419 | def test_shopping_activity_get(client_setup): 420 | client_setup.shopping.activity('XXX').get(a='b') 421 | client_setup.get.assert_called_with( 422 | '/v1/shopping/activities/XXX', a='b' 423 | ) 424 | 425 | 426 | def test_analytics_itinerary_price_metrics_get(client_setup): 427 | client_setup.analytics.itinerary_price_metrics.get(a='b') 428 | client_setup.get.assert_called_with( 429 | '/v1/analytics/itinerary-price-metrics', a='b' 430 | ) 431 | 432 | 433 | def test_reference_data_locations_hotels_by_hotels_get(client_setup): 434 | client_setup.reference_data.locations.hotels.by_hotels.get(a='b', 435 | c=['d', 'e']) 436 | client_setup.get.assert_called_with( 437 | '/v1/reference-data/locations/hotels/by-hotels', a='b', c='d,e' 438 | ) 439 | 440 | 441 | def test_reference_data_locations_hotels_by_city_get(client_setup): 442 | client_setup.reference_data.locations.hotels.by_city.get(a='b') 443 | client_setup.get.assert_called_with( 444 | '/v1/reference-data/locations/hotels/by-city', a='b' 445 | ) 446 | 447 | 448 | def test_reference_data_locations_hotels_by_geocode_get(client_setup): 449 | client_setup.reference_data.locations.hotels.by_geocode.get(a='b') 450 | client_setup.get.assert_called_with( 451 | '/v1/reference-data/locations/hotels/by-geocode', a='b' 452 | ) 453 | 454 | 455 | def test_reference_data_locations_hotel_get(client_setup): 456 | client_setup.reference_data.locations.hotel.get(a='b') 457 | client_setup.get.assert_called_with( 458 | '/v1/reference-data/locations/hotel', a='b' 459 | ) 460 | 461 | 462 | def test_reference_data_locations_cities_get(client_setup): 463 | client_setup.reference_data.locations.cities.get(a='b') 464 | client_setup.get.assert_called_with( 465 | '/v1/reference-data/locations/cities', a='b' 466 | ) 467 | 468 | 469 | def test_airline_destinations_get(client_setup): 470 | client_setup.airline.destinations.get(a='b') 471 | client_setup.get.assert_called_with( 472 | '/v1/airline/destinations', a='b' 473 | ) 474 | 475 | 476 | def test_shopping_transfer_offers_post(client_setup): 477 | client_setup.shopping.transfer_offers.post({'foo': 'bar'}) 478 | client_setup.post.assert_called_with( 479 | '/v1/shopping/transfer-offers', {'foo': 'bar'} 480 | ) 481 | 482 | 483 | def test_ordering_transfer_orders_post(client_setup): 484 | client_setup.ordering.transfer_orders.post( 485 | {'foo': 'bar'}, offerId='1') 486 | client_setup.post.assert_called_with( 487 | '/v1/ordering/transfer-orders?'+'offerId=1', {'foo': 'bar'} 488 | ) 489 | 490 | 491 | def test_ordering_transfer_order_transfers_cancellation_post(client_setup): 492 | client_setup.ordering.transfer_order('XXX').transfers.cancellation.post( 493 | {'foo': 'bar'}, confirmNbr=123) 494 | client_setup.post.assert_called_with( 495 | '/v1/ordering/transfer-orders/XXX/transfers/cancellation?confirmNbr=123', 496 | {'foo': 'bar'} 497 | ) 498 | -------------------------------------------------------------------------------- /specs/test_client.py: -------------------------------------------------------------------------------- 1 | from amadeus import Client, Location, Hotel 2 | 3 | 4 | def test_client_exists(): 5 | assert Client is not None 6 | 7 | 8 | def test_client_has_helper_locations(): 9 | assert Location is not None 10 | assert Location.ANY == 'AIRPORT,CITY' 11 | 12 | 13 | def test_client_has_helper_hotels(): 14 | assert Hotel is not None 15 | assert Hotel.HOTEL_GDS == 'HOTEL_GDS' 16 | assert Hotel.HOTEL_LEISURE == 'HOTEL_LEISURE' 17 | -------------------------------------------------------------------------------- /specs/test_version.py: -------------------------------------------------------------------------------- 1 | from amadeus import version 2 | 3 | 4 | def test_version(): 5 | assert version is not None 6 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38,python3.9,python10 3 | 4 | [testenv] 5 | commands = 6 | flake8 amadeus specs setup.py 7 | pytest specs/ --cov --cov-report=html 8 | 9 | deps = 10 | flake8==3.8.0 11 | flake8-quotes==2.1.1 12 | pytest==7.2.0 13 | mock==5.0.0 14 | pytest-cov==4.0.0 15 | pytest-html==3.2.0 16 | usedevelop=True 17 | 18 | [gh-actions] 19 | python = 20 | 3.8: py38 21 | 3.9: python3.9 22 | 3.10: python10 23 | 24 | [flake8] 25 | max-complexity = 5 26 | inline-quotes = single 27 | multiline-quotes = single 28 | max-line-length = 82 29 | ignore = Q002, E126, E123, W504, E226 30 | --------------------------------------------------------------------------------