From bba9ef7365e366b67dc31f4f59f29545256082eb Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 10 Oct 2021 01:14:43 +0200 Subject: [PATCH 01/86] add testing on python 3.10, use pytest --- .github/workflows/test_and_lint.yml | 8 ++++---- .gitignore | 1 + setup.py | 3 +++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_and_lint.yml b/.github/workflows/test_and_lint.yml index 48e2cb6..4e8e586 100644 --- a/.github/workflows/test_and_lint.yml +++ b/.github/workflows/test_and_lint.yml @@ -11,7 +11,7 @@ jobs: - name: Set up Python environment uses: actions/setup-python@v1 with: - python-version: "3.7" + python-version: "3.9" - name: flake8 Lint uses: py-actions/flake8@v1 with: @@ -21,7 +21,7 @@ jobs: unittests: strategy: matrix: - python-version: [ '3.7', '3.8', '3.9' ] + python-version: [ '3.7', '3.8', '3.9', '3.10' ] runs-on: ubuntu-latest name: Unit tests, Python ${{ matrix.python-version }} @@ -34,6 +34,6 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install packages - run: python -m pip install . + run: python -m pip install .[test] - name: Run unittests - run: python -m unittest + run: python -m pytest tests diff --git a/.gitignore b/.gitignore index f54f418..d04b30e 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ docs/build ignore/ venv +**/*.swp diff --git a/setup.py b/setup.py index 6a71e8a..5ab847e 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,9 @@ def get_version(rel_path): "dev": [ "Sphinx==4.0.2", "sphinx-rtd-theme==0.5.2" + ], + "test": [ + "pytest==6.2.5" ] }, python_requires='>=3.7.0', From ce2355d1fadf51d6564beae2607ca7447ccf08ce Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 10 Oct 2021 01:23:42 +0200 Subject: [PATCH 02/86] upgrade setup-python action to v2 --- .github/workflows/test_and_lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_and_lint.yml b/.github/workflows/test_and_lint.yml index 4e8e586..deae534 100644 --- a/.github/workflows/test_and_lint.yml +++ b/.github/workflows/test_and_lint.yml @@ -9,7 +9,7 @@ jobs: - name: Check out source repository uses: actions/checkout@v2 - name: Set up Python environment - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: "3.9" - name: flake8 Lint @@ -30,7 +30,7 @@ jobs: - name: Check out source repository uses: actions/checkout@v2 - name: Set up Python environment - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install packages From 85e42982dbe241ecd3033c7772c10ce626af3dfa Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 10 Oct 2021 01:30:59 +0200 Subject: [PATCH 03/86] upgrade requirement versions --- setup.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 5ab847e..4d82054 100644 --- a/setup.py +++ b/setup.py @@ -37,13 +37,13 @@ def get_version(rel_path): packages=find_packages(), include_package_data=True, install_requires=[ - "requests==2.25.1", - "python-dateutil==2.8.1" + "requests==2.26.0", + "python-dateutil==2.8.2" ], extras_require={ "dev": [ - "Sphinx==4.0.2", - "sphinx-rtd-theme==0.5.2" + "Sphinx==4.2.0", + "sphinx-rtd-theme==1.0.0" ], "test": [ "pytest==6.2.5" @@ -57,6 +57,7 @@ def get_version(rel_path): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Operating System :: OS Independent", "Topic :: Internet", "Topic :: Software Development :: Libraries", From 547b2d36953e5adb83cd7151ae85d97ae0a34047 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 10 Oct 2021 02:02:27 +0200 Subject: [PATCH 04/86] fix broken theme from sphinx update --- docs/source/_static/css/colourscheme.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/_static/css/colourscheme.css b/docs/source/_static/css/colourscheme.css index 2f4ce00..a866666 100644 --- a/docs/source/_static/css/colourscheme.css +++ b/docs/source/_static/css/colourscheme.css @@ -3103,6 +3103,9 @@ border-color: rgb(27, 84, 122); background-color: rgb(32, 35, 36); background-image: none; } + html.writer-html4 .rst-content dl:not(.docutils) .descclassname, html.writer-html4 .rst-content dl:not(.docutils) .descname, html.writer-html4 .rst-content dl:not(.docutils) .sig-name, html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descclassname, html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descname, html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .sig-name { + color: rgb(255, 255, 255); + } span[id*="MathJax-Span"] { color: rgb(192, 186, 178); } From 02e2559cb222ca420e3735491b8886e3aed028ae Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 10 Oct 2021 11:41:25 +0200 Subject: [PATCH 05/86] bump package patch version --- flightplandb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flightplandb/__init__.py b/flightplandb/__init__.py index a88b87d..abb5865 100644 --- a/flightplandb/__init__.py +++ b/flightplandb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # Version of the flightplandb package -__version__ = "0.4.1" +__version__ = "0.4.2" from flightplandb.flightplandb import * # noqa: F403, F401 from flightplandb.datatypes import * # noqa: F403, F401 From 07ee293bbad036bc31880d8a6529bf5eb635082b Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 10 Oct 2021 12:10:27 +0200 Subject: [PATCH 06/86] remove rickroll --- docs/source/user/introduction.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/source/user/introduction.rst b/docs/source/user/introduction.rst index bf329b5..0afc558 100644 --- a/docs/source/user/introduction.rst +++ b/docs/source/user/introduction.rst @@ -102,8 +102,3 @@ provide valid authentication credentials on these endpoints will result in an for maintaining the security of your private API key, which gives near full access to your Flight Plan Database account. If your key is exposed, please use :meth:`~flightplandb.FlightPlanDB.revoke()` to revoke your key manually. - - -Further information -^^^^^^^^^^^^^^^^^^^^ -A good video on the usage of the library can be found `here `_. From d54d62eea25ab40115e7d9e98ba36b40cd533e5a Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 10 Oct 2021 22:32:01 +0200 Subject: [PATCH 07/86] move package to 'src' dir --- setup.py | 5 +++-- {flightplandb => src/flightplandb}/__init__.py | 0 {flightplandb => src/flightplandb}/datatypes.py | 0 {flightplandb => src/flightplandb}/exceptions.py | 0 {flightplandb => src/flightplandb}/flightplandb.py | 0 {flightplandb => src/flightplandb}/submodules/__init__.py | 0 {flightplandb => src/flightplandb}/submodules/nav.py | 0 {flightplandb => src/flightplandb}/submodules/plan.py | 0 {flightplandb => src/flightplandb}/submodules/tags.py | 0 {flightplandb => src/flightplandb}/submodules/user.py | 0 {flightplandb => src/flightplandb}/submodules/weather.py | 0 11 files changed, 3 insertions(+), 2 deletions(-) rename {flightplandb => src/flightplandb}/__init__.py (100%) rename {flightplandb => src/flightplandb}/datatypes.py (100%) rename {flightplandb => src/flightplandb}/exceptions.py (100%) rename {flightplandb => src/flightplandb}/flightplandb.py (100%) rename {flightplandb => src/flightplandb}/submodules/__init__.py (100%) rename {flightplandb => src/flightplandb}/submodules/nav.py (100%) rename {flightplandb => src/flightplandb}/submodules/plan.py (100%) rename {flightplandb => src/flightplandb}/submodules/tags.py (100%) rename {flightplandb => src/flightplandb}/submodules/user.py (100%) rename {flightplandb => src/flightplandb}/submodules/weather.py (100%) diff --git a/setup.py b/setup.py index 4d82054..a028b20 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ def get_version(rel_path): setup( name="flightplandb", - version=get_version("flightplandb/__init__.py"), + version=get_version("src/flightplandb/__init__.py"), author="PH-KDX", url="https://github.com/PH-KDX/flightplandb-py/", project_urls={ @@ -34,7 +34,8 @@ def get_version(rel_path): description="Python wrapper for the Flight Plan Database API", long_description=long_description, long_description_content_type="text/markdown", - packages=find_packages(), + package_dir={"":"src"}, + packages=find_packages(where="src"), include_package_data=True, install_requires=[ "requests==2.26.0", diff --git a/flightplandb/__init__.py b/src/flightplandb/__init__.py similarity index 100% rename from flightplandb/__init__.py rename to src/flightplandb/__init__.py diff --git a/flightplandb/datatypes.py b/src/flightplandb/datatypes.py similarity index 100% rename from flightplandb/datatypes.py rename to src/flightplandb/datatypes.py diff --git a/flightplandb/exceptions.py b/src/flightplandb/exceptions.py similarity index 100% rename from flightplandb/exceptions.py rename to src/flightplandb/exceptions.py diff --git a/flightplandb/flightplandb.py b/src/flightplandb/flightplandb.py similarity index 100% rename from flightplandb/flightplandb.py rename to src/flightplandb/flightplandb.py diff --git a/flightplandb/submodules/__init__.py b/src/flightplandb/submodules/__init__.py similarity index 100% rename from flightplandb/submodules/__init__.py rename to src/flightplandb/submodules/__init__.py diff --git a/flightplandb/submodules/nav.py b/src/flightplandb/submodules/nav.py similarity index 100% rename from flightplandb/submodules/nav.py rename to src/flightplandb/submodules/nav.py diff --git a/flightplandb/submodules/plan.py b/src/flightplandb/submodules/plan.py similarity index 100% rename from flightplandb/submodules/plan.py rename to src/flightplandb/submodules/plan.py diff --git a/flightplandb/submodules/tags.py b/src/flightplandb/submodules/tags.py similarity index 100% rename from flightplandb/submodules/tags.py rename to src/flightplandb/submodules/tags.py diff --git a/flightplandb/submodules/user.py b/src/flightplandb/submodules/user.py similarity index 100% rename from flightplandb/submodules/user.py rename to src/flightplandb/submodules/user.py diff --git a/flightplandb/submodules/weather.py b/src/flightplandb/submodules/weather.py similarity index 100% rename from flightplandb/submodules/weather.py rename to src/flightplandb/submodules/weather.py From 3fabf47b5b1c6c920d6b78be2b3f000092dfd2b0 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 10 Oct 2021 23:41:09 +0200 Subject: [PATCH 08/86] bump up sphinx conf package version --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index cf78d40..5fa5faa 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -21,7 +21,7 @@ author = 'PH-KDX' # The full version, including alpha/beta/rc tags -release = '0.4.0' +release = '0.4.2' # -- General configuration --------------------------------------------------- From e73e55735b361508a206f87b985ea12c255eb786 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Tue, 12 Oct 2021 18:11:01 +0200 Subject: [PATCH 09/86] stop flake8 from complaining --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a028b20..f65b14e 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ def get_version(rel_path): description="Python wrapper for the Flight Plan Database API", long_description=long_description, long_description_content_type="text/markdown", - package_dir={"":"src"}, + package_dir={"": "src"}, packages=find_packages(where="src"), include_package_data=True, install_requires=[ From a817aa1913dd562be76c7be30f1b81927bdbb802 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sat, 23 Oct 2021 23:25:26 +0200 Subject: [PATCH 10/86] Change way class methods are called Originally, there was one main FlightPlanDB class, and the other classes were called as attributes of it. Every time the attribute was called, it initialised a class which inherited the key and base_url from the main class, so that methods of that class could do authenticated requests. In this commit, that changes. Each subclass now contains its own methods. It inherits the url from the main class, and the key is passed in for every method. This is slightly more annoying for the user (they can no longer pass in the key at a single class instance, but must do so for every library call) but it allows vastly simpler module architecture, which is necessary to improve test coverage. Unfortunately, all tests now fail. This will be addressed in future commits. --- src/flightplandb/__init__.py | 3 +- src/flightplandb/flightplandb.py | 167 ++++-------------------- src/flightplandb/submodules/__init__.py | 1 + src/flightplandb/submodules/api.py | 107 +++++++++++++++ src/flightplandb/submodules/nav.py | 25 ++-- src/flightplandb/submodules/plan.py | 85 +++++++----- src/flightplandb/submodules/tags.py | 12 +- src/flightplandb/submodules/user.py | 47 +++---- src/flightplandb/submodules/weather.py | 11 +- 9 files changed, 236 insertions(+), 222 deletions(-) create mode 100644 src/flightplandb/submodules/api.py diff --git a/src/flightplandb/__init__.py b/src/flightplandb/__init__.py index abb5865..31c30ab 100644 --- a/src/flightplandb/__init__.py +++ b/src/flightplandb/__init__.py @@ -3,5 +3,6 @@ # Version of the flightplandb package __version__ = "0.4.2" -from flightplandb.flightplandb import * # noqa: F403, F401 +# from flightplandb.flightplandb import * # noqa: F403, F401 from flightplandb.datatypes import * # noqa: F403, F401 +from flightplandb.submodules import * diff --git a/src/flightplandb/flightplandb.py b/src/flightplandb/flightplandb.py index 8d7e659..0ebd973 100644 --- a/src/flightplandb/flightplandb.py +++ b/src/flightplandb/flightplandb.py @@ -28,14 +28,10 @@ from flightplandb.datatypes import StatusResponse -from flightplandb.submodules.plan import PlanAPI -from flightplandb.submodules.user import UserAPI -from flightplandb.submodules.tags import TagsAPI -from flightplandb.submodules.nav import NavAPI -from flightplandb.submodules.weather import WeatherAPI # https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autoclass +# https://github.com/python/cpython/blob/main/Lib/random.py#L792 class FlightPlanDB: """This class mostly contains internal functions called by the API. @@ -54,18 +50,15 @@ class FlightPlanDB: defaults to https://api.flightplandatabase.com """ - def __init__( - self, key: Optional[str] = None, - url_base: str = "https://api.flightplandatabase.com" - ): - self.key: str = key + def __init__(self): self._header: CaseInsensitiveDict[str] = CaseInsensitiveDict() - self.url_base = url_base + self.url_base: str = "https://api.flightplandatabase.com" def _request(self, method: str, path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, + key: Optional[str] = None, *args, **kwargs) -> Union[Dict, bytes]: """General HTTP requests function for non-paginated results. @@ -82,6 +75,8 @@ def _request(self, method: str, raise an HTTPError, defaults to None params : Optional[Dict], optional Any other HTTP request parameters, defaults to None + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. **kwargs @@ -147,7 +142,7 @@ def _request(self, method: str, params["Accept"] = return_format_encoded resp = requests.request(method, urljoin(self.url_base, path), - auth=HTTPBasicAuth(self.key, None), + auth=HTTPBasicAuth(key, None), headers=params, *args, **kwargs) status_handler(resp.status_code, ignore_statuses) @@ -163,6 +158,7 @@ def _request(self, method: str, def _get(self, path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, + key: Optional[str] = None, *args, **kwargs) -> Union[Dict, bytes]: """Calls :meth:`_request()` for get requests. @@ -177,6 +173,8 @@ def _get(self, path: str, return_format="native", raise an HTTPError, defaults to None params : Optional[Dict], optional Any other HTTP request parameters, defaults to None + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. **kwargs @@ -199,12 +197,14 @@ def _get(self, path: str, return_format="native", return_format, ignore_statuses, params, + key, *args, **kwargs) return resp def _post(self, path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, + key: Optional[str] = None, *args, **kwargs) -> Union[Dict, bytes]: """Calls :meth:`_request()` for post requests. @@ -219,6 +219,8 @@ def _post(self, path: str, return_format="native", raise an HTTPError, defaults to None params : Optional[Dict], optional Any other HTTP request parameters, defaults to None + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. **kwargs @@ -240,12 +242,14 @@ def _post(self, path: str, return_format="native", return_format, ignore_statuses, params, + key, *args, **kwargs) return resp def _patch(self, path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, + key: Optional[str] = None, *args, **kwargs) -> Union[Dict, bytes]: """Calls :meth:`_request()` for patch requests. @@ -260,6 +264,8 @@ def _patch(self, path: str, return_format="native", raise an HTTPError, defaults to None params : Optional[Dict], optional Any other HTTP request parameters, defaults to None + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. **kwargs @@ -282,12 +288,14 @@ def _patch(self, path: str, return_format="native", return_format, ignore_statuses, params, + key, *args, **kwargs) return resp def _delete(self, path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, + key: Optional[str] = None, *args, **kwargs) -> Union[Dict, bytes]: """Calls :meth:`_request()` for delete requests. @@ -302,6 +310,8 @@ def _delete(self, path: str, return_format="native", raise an HTTPError, defaults to None params : Optional[Dict], optional Any other HTTP request parameters, defaults to None + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. **kwargs @@ -324,6 +334,7 @@ def _delete(self, path: str, return_format="native", return_format, ignore_statuses, params, + key, *args, **kwargs) return resp @@ -332,6 +343,7 @@ def _getiter(self, path: str, sort: str = "created", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, + key: Optional[str] = None, *args, **kwargs) -> Generator[Dict, None, None]: """Get :meth:`_request()` for paginated results. @@ -349,6 +361,8 @@ def _getiter(self, path: str, raise an HTTPError, defaults to None params : Optional[Dict], optional Any other HTTP request parameters, defaults to None + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. **kwargs @@ -378,7 +392,7 @@ def _getiter(self, path: str, params["sort"] = sort url = urljoin(self.url_base, path) - auth = HTTPBasicAuth(self.key, None) + auth = HTTPBasicAuth(key, None) session = requests.Session() # initially no results have been fetched yet @@ -412,131 +426,4 @@ def _getiter(self, path: str, if num_results == limit: return - def _header_value(self, header_key: str) -> str: - """Gets header value for key - Parameters - ---------- - header_key : str - One of the HTTP header keys - - Returns - ------- - str - The value corresponding to the passed key - """ - - if header_key not in self._header: - self.ping() # Make at least one request - return self._header[header_key] - - @property - def version(self) -> int: - """API version that returned the response - - Returns - ------- - int - API version - """ - - return int(self._header_value("X-API-Version")) - - @property - def units(self) -> str: - """The units system used for numeric values. - https://flightplandatabase.com/dev/api#units - - Returns - ------- - str - AVIATION, METRIC or SI - """ - - return self._header_value("X-Units") - - @property - def limit_cap(self) -> int: - """The number of requests allowed per day, operated on an hourly rolling - basis. i.e requests used between 19:00 and 20:00 will become available - again at 19:00 the following day. API key authenticated requests get a - higher daily rate limit and can be raised if a compelling - use case is presented. - - Returns - ------- - int - number of allowed requests per day - """ - - return int(self._header_value("X-Limit-Cap")) - - @property - def limit_used(self) -> int: - """The number of requests used in the current period - by the presented API key or IP address - - Returns - ------- - int - number of requests used in period - """ - - return int(self._header_value("X-Limit-Used")) - - def ping(self) -> StatusResponse: - """Checks API status to see if it is up - - Returns - ------- - StatusResponse - OK 200 means the service is up and running. - """ - - resp = self._get("") - return StatusResponse(**resp) - - def revoke(self) -> StatusResponse: - """Revoke the API key in use in the event it is compromised. - - Requires authentication. - - Returns - ------- - StatusResponse - If the HTTP response code is 200 and the status message is "OK", - then the key has been revoked and any further requests will be - rejected. - Any other status code or message indicates an error has - occurred and the errors array will give further details. - """ - - resp = self._get("/auth/revoke") - self._header = resp.headers - return StatusResponse(**resp.json()) - - # Sub APIs - @property - def nav(self): - """Alias for :class:`~flightplandb.submodules.nav.NavAPI()`""" - return NavAPI(self) - - @property - def plan(self): - """Alias for :class:`~flightplandb.submodules.plan.PlanAPI()`""" - return PlanAPI(self) - - @property - def tags(self): - """Alias for :class:`~flightplandb.submodules.tags.TagsAPI()`""" - return TagsAPI(self) - - @property - def user(self): - """Alias for :class:`~flightplandb.submodules.user.UserAPI()`""" - return UserAPI(self) - - @property - def weather(self): - """Alias for :class:`~flightplandb.submodules.weather.WeatherAPI()`""" - return WeatherAPI(self) diff --git a/src/flightplandb/submodules/__init__.py b/src/flightplandb/submodules/__init__.py index e69de29..067a5b1 100644 --- a/src/flightplandb/submodules/__init__.py +++ b/src/flightplandb/submodules/__init__.py @@ -0,0 +1 @@ +__all__ = ["api", "nav", "plan", "tags", "user", "weather"] \ No newline at end of file diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py new file mode 100644 index 0000000..604a46f --- /dev/null +++ b/src/flightplandb/submodules/api.py @@ -0,0 +1,107 @@ +from typing import List, Dict, Optional +from flightplandb.flightplandb import FlightPlanDB +from flightplandb.datatypes import StatusResponse + +class API(FlightPlanDB): + def _header_value(self, header_key: str, key: Optional[str] = None) -> str: + """Gets header value for key + + Parameters + ---------- + header_key : str + One of the HTTP header keys + + Returns + ------- + str + The value corresponding to the passed key + """ + + if header_key not in self._header: + self.ping(key=key) # Make at least one request + return self._header[header_key] + + + def version(self, key: Optional[str] = None) -> int: + """API version that returned the response + + Returns + ------- + int + API version + """ + + return int(self._header_value("X-API-Version", key=key)) + + + def units(self, key: Optional[str] = None) -> str: + """The units system used for numeric values. + https://flightplandatabase.com/dev/api#units + + Returns + ------- + str + AVIATION, METRIC or SI + """ + + return self._header_value("X-Units", key=key) + + + def limit_cap(self, key: Optional[str] = None) -> int: + """The number of requests allowed per day, operated on an hourly rolling + basis. i.e requests used between 19:00 and 20:00 will become available + again at 19:00 the following day. API key authenticated requests get a + higher daily rate limit and can be raised if a compelling + use case is presented. + + Returns + ------- + int + number of allowed requests per day + """ + + return int(self._header_value("X-Limit-Cap", key=key)) + + + def limit_used(self, key: Optional[str] = None) -> int: + """The number of requests used in the current period + by the presented API key or IP address + + Returns + ------- + int + number of requests used in period + """ + + return int(self._header_value("X-Limit-Used", key=key)) + + def ping(self, key: Optional[str] = None) -> StatusResponse: + """Checks API status to see if it is up + + Returns + ------- + StatusResponse + OK 200 means the service is up and running. + """ + + resp = self._get("", key=key) + return StatusResponse(**resp) + + def revoke(self, key: Optional[str] = None) -> StatusResponse: + """Revoke the API key in use in the event it is compromised. + + Requires authentication. + + Returns + ------- + StatusResponse + If the HTTP response code is 200 and the status message is "OK", + then the key has been revoked and any further requests will be + rejected. + Any other status code or message indicates an error has + occurred and the errors array will give further details. + """ + + resp = self._get("/auth/revoke", key=key) + self._header = resp.headers + return StatusResponse(**resp.json()) diff --git a/src/flightplandb/submodules/nav.py b/src/flightplandb/submodules/nav.py index fb25993..668dcb4 100644 --- a/src/flightplandb/submodules/nav.py +++ b/src/flightplandb/submodules/nav.py @@ -1,18 +1,16 @@ -from typing import Generator, List, Union +from typing import Generator, List, Union, Optional +from flightplandb.flightplandb import FlightPlanDB from flightplandb.datatypes import Airport, Track, SearchNavaid -class NavAPI: +class NavAPI(FlightPlanDB): """ Commands related to navigation aids and airports. Accessed via :meth:`~flightplandb.flightplandb.FlightPlanDB.nav`. """ - def __init__(self, flightplandb): - self._fp = flightplandb - - def airport(self, icao: str) -> Union[Airport]: + def airport(self, icao: str, key: Optional[str] = None) -> Union[Airport]: """Fetches information about an airport. Parameters @@ -31,10 +29,10 @@ def airport(self, icao: str) -> Union[Airport]: No airport with the specified ICAO code was found. """ - resp = self._fp._get(f"/nav/airport/{icao}") + resp = self._get(f"/nav/airport/{icao}", key=key) return Airport(**resp) - def nats(self) -> List[Track]: + def nats(self, key: Optional[str] = None) -> List[Track]: """Fetches current North Atlantic Tracks. Returns @@ -44,9 +42,9 @@ def nats(self) -> List[Track]: """ return list( - map(lambda n: Track(**n), self._fp._get("/nav/NATS"))) + map(lambda n: Track(**n), self._get("/nav/NATS", key=key))) - def pacots(self) -> List[Track]: + def pacots(self, key: Optional[str] = None) -> List[Track]: """Fetches current Pacific Organized Track System tracks. Returns @@ -56,10 +54,11 @@ def pacots(self) -> List[Track]: """ return list( - map(lambda t: Track(**t), self._fp._get("/nav/PACOTS"))) + map(lambda t: Track(**t), self._get("/nav/PACOTS", key=key))) def search(self, query: str, - type_: str = None) -> Generator[SearchNavaid, None, None]: + type_: str = None, key: Optional[str] = None + ) -> Generator[SearchNavaid, None, None]: r"""Searches navaids using a query. Parameters @@ -84,5 +83,5 @@ def search(self, query: str, params["types"] = type_ else: raise ValueError(f"{type_} is not a valid Navaid type") - for i in self._fp._getiter("/search/nav", params=params): + for i in self._getiter("/search/nav", params=params, key=key): yield SearchNavaid(**i) diff --git a/src/flightplandb/submodules/plan.py b/src/flightplandb/submodules/plan.py index f97746d..ca10f12 100644 --- a/src/flightplandb/submodules/plan.py +++ b/src/flightplandb/submodules/plan.py @@ -1,22 +1,21 @@ -from typing import Generator, Union +from typing import Generator, Union, Optional from flightplandb.datatypes import ( StatusResponse, PlanQuery, Plan, GenerateQuery ) +from flightplandb.flightplandb import FlightPlanDB -class PlanAPI(): +class PlanAPI(FlightPlanDB): """ Flightplan-related commands. Accessed via :meth:`~flightplandb.flightplandb.FlightPlanDB.plan`. """ - def __init__(self, flightplandb): - self._fp = flightplandb - def fetch(self, id_: int, - return_format: str = "native") -> Union[Plan, None, bytes]: + return_format: str = "native", + key: Optional[str] = None) -> Union[Plan, None, bytes]: # Underscore for id_ must be escaped as id\_ so sphinx shows the _. # However, this would raise W605. To fix this, a raw string is used. r""" @@ -45,9 +44,10 @@ def fetch(self, id_: int, No plan with the specified id was found. """ - request = self._fp._get( - f"/plan/{id_}", - return_format=return_format + request = self._get( + path=f"/plan/{id_}", + return_format=return_format, + key=key ) if return_format == "native": @@ -56,7 +56,8 @@ def fetch(self, id_: int, return request # if the format is not a dict def create(self, plan: Plan, - return_format: str = "native") -> Union[Plan, bytes]: + return_format: str = "native", + key: Optional[str] = None) -> Union[Plan, bytes]: """Creates a new flight plan. Requires authentication. @@ -84,8 +85,11 @@ def create(self, plan: Plan, or was otherwise unusable. """ - request = self._fp._post( - "/plan/", return_format=return_format, json=plan._to_api_dict()) + request = self._post( + path="/plan/", + return_format=return_format, + json=plan._to_api_dict(), + key=key) if return_format == "native": return Plan(**request) @@ -93,7 +97,8 @@ def create(self, plan: Plan, return request def edit(self, plan: Plan, - return_format: str = "native") -> Union[Plan, bytes]: + return_format: str = "native", + key: Optional[str] = None) -> Union[Plan, bytes]: """Edits a flight plan linked to your account. Requires authentication. @@ -124,14 +129,18 @@ def edit(self, plan: Plan, """ plan_data = plan._to_api_dict() - request = self._fp._patch(f"/plan/{plan_data['id']}", json=plan_data) + request = self._patch( + path=f"/plan/{plan_data['id']}", + json=plan_data, + key=key) if return_format == "native": return Plan(**request) return request - def delete(self, id_: int) -> StatusResponse: + def delete(self, id_: int, + key: Optional[str] = None) -> StatusResponse: r"""Deletes a flight plan that is linked to your account. Requires authentication. @@ -152,11 +161,12 @@ def delete(self, id_: int) -> StatusResponse: No plan with the specified id was found. """ - resp = self._fp._delete(f"/plan/{id_}") + resp = self._delete(path=f"/plan/{id_}", key=key) return(StatusResponse(**resp)) def search(self, plan_query: PlanQuery, sort: str = "created", - limit: int = 100) -> Generator[Plan, None, None]: + limit: int = 100, + key: Optional[str] = None) -> Generator[Plan, None, None]: """Searches for flight plans. A number of search parameters are available. They will be combined to form a search request. @@ -185,13 +195,15 @@ def search(self, plan_query: PlanQuery, sort: str = "created", to request it """ - for i in self._fp._getiter("/search/plans", - sort=sort, - params=plan_query._to_api_dict(), - limit=limit): + for i in self._getiter(path="/search/plans", + sort=sort, + params=plan_query._to_api_dict(), + limit=limit, + key=key): yield Plan(**i) - def has_liked(self, id_: int) -> bool: + def has_liked(self, id_: int, + key: Optional[str] = None) -> bool: r"""Fetches your like status for a flight plan. Requires authentication. @@ -208,10 +220,14 @@ def has_liked(self, id_: int) -> bool: """ sr = StatusResponse( - **self._fp._get(f"/plan/{id_}/like", ignore_statuses=[404])) + **self._get( + path=f"/plan/{id_}/like", + ignore_statuses=[404], + key=key)) return sr.message != "Not Found" - def like(self, id_: int) -> StatusResponse: + def like(self, id_: int, + key: Optional[str] = None) -> StatusResponse: r"""Likes a flight plan. Requires authentication. @@ -233,9 +249,10 @@ def like(self, id_: int) -> StatusResponse: No plan with the specified id was found. """ - return StatusResponse(**self._fp._post(f"/plan/{id_}/like")) + return StatusResponse(**self._post(path=f"/plan/{id_}/like", key=key)) - def unlike(self, id_: int) -> bool: + def unlike(self, id_: int, + key: Optional[str] = None) -> bool: r"""Removes a flight plan like. Requires authentication. @@ -257,11 +274,12 @@ def unlike(self, id_: int) -> bool: or the plan was found but wasn't liked. """ - self._fp._delete(f"/plan/{id_}/like") + self._delete(path=f"/plan/{id_}/like", key=key) return True def generate(self, gen_query: GenerateQuery, - return_format: str = "native") -> Union[Plan, bytes]: + return_format: str = "native", + key: Optional[str] = None) -> Union[Plan, bytes]: """Creates a new flight plan using the route generator. Requires authentication. @@ -284,10 +302,11 @@ def generate(self, gen_query: GenerateQuery, """ return Plan( - **self._fp._post( - "/auto/generate", json=gen_query._to_api_dict())) + **self._post( + path="/auto/generate", json=gen_query._to_api_dict(), key=key)) - def decode(self, route: str) -> Plan: + def decode(self, route: str, + key: Optional[str] = None) -> Plan: """Creates a new flight plan using the route decoder. Requires authentication. @@ -316,5 +335,5 @@ def decode(self, route: str) -> Plan: arguments or was otherwise unusable. """ - return Plan(**self._fp._post( - "/auto/decode", json={"route": route})) + return Plan(**self._post( + path="/auto/decode", json={"route": route}, key=key)) diff --git a/src/flightplandb/submodules/tags.py b/src/flightplandb/submodules/tags.py index a24d8bd..c678bb5 100644 --- a/src/flightplandb/submodules/tags.py +++ b/src/flightplandb/submodules/tags.py @@ -1,18 +1,16 @@ -from typing import List +from typing import List, Optional from flightplandb.datatypes import Tag +from flightplandb.flightplandb import FlightPlanDB -class TagsAPI: +class TagsAPI(FlightPlanDB): """ Related to flight plans. Accessed via :meth:`~flightplandb.flightplandb.FlightPlanDB.tags`. """ - def __init__(self, flightplandb): - self._fp = flightplandb - - def fetch(self) -> List[Tag]: + def fetch(self, key: Optional[str] = None) -> List[Tag]: """Fetches current popular tags from all flight plans. Only tags with sufficient popularity are included. @@ -22,4 +20,4 @@ def fetch(self) -> List[Tag]: A list of the current popular tags. """ - return list(map(lambda t: Tag(**t), self._fp._get("/tags"))) + return list(map(lambda t: Tag(**t), self._get("/tags"), key=key)) diff --git a/src/flightplandb/submodules/user.py b/src/flightplandb/submodules/user.py index 2e4cabf..d671178 100644 --- a/src/flightplandb/submodules/user.py +++ b/src/flightplandb/submodules/user.py @@ -1,18 +1,15 @@ -from typing import Generator +from typing import Generator, Optional from flightplandb.datatypes import Plan, User, UserSmall +from flightplandb.flightplandb import FlightPlanDB -class UserAPI: +class UserAPI(FlightPlanDB): """Commands related to registered users. Accessed via :meth:`~flightplandb.flightplandb.FlightPlanDB.user`. """ - def __init__(self, flightplandb): - self._fp = flightplandb - - @property - def me(self) -> User: + def me(self, key: Optional[str] = None) -> User: """Fetches profile information for the currently authenticated user. Requires authentication. @@ -28,9 +25,9 @@ def me(self) -> User: Authentication failed. """ - return User(**self._fp._get("/me")) + return User(**self._get(path="/me", key=key)) - def fetch(self, username: str) -> User: + def fetch(self, username: str, key: Optional[str] = None) -> User: """Fetches profile information for any registered user Parameters @@ -49,10 +46,11 @@ def fetch(self, username: str) -> User: No user was found with this username. """ - return User(**self._fp._get(f"/user/{username}")) + return User(**self._get(path=f"/user/{username}", key=key)) def plans(self, username: str, sort: str = "created", - limit: int = 100) -> Generator[Plan, None, None]: + limit: int = 100, + key: Optional[str] = None) -> Generator[Plan, None, None]: """Fetches flight plans created by a user. Parameters @@ -71,13 +69,15 @@ def plans(self, username: str, sort: str = "created", A generator with all the flight plans a user created, limited by ``limit`` """ - for i in self._fp._getiter(f"/user/{username}/plans", - sort=sort, - limit=limit): + for i in self._getiter(path=f"/user/{username}/plans", + sort=sort, + limit=limit, + key=key): yield Plan(**i) def likes(self, username: str, sort: str = "created", - limit: int = 100) -> Generator[Plan, None, None]: + limit: int = 100, + key: Optional[str] = None) -> Generator[Plan, None, None]: """Fetches flight plans liked by a user. Parameters @@ -97,13 +97,15 @@ def likes(self, username: str, sort: str = "created", limited by ``limit`` """ - for i in self._fp._getiter(f"/user/{username}/likes", - sort=sort, - limit=limit): + for i in self._getiter(path=f"/user/{username}/likes", + sort=sort, + limit=limit, + key=key): yield Plan(**i) def search(self, username: str, - limit=100) -> Generator[UserSmall, None, None]: + limit=100, + key: Optional[str] = None) -> Generator[UserSmall, None, None]: """Searches for users by username. For more detailed info on a specific user, use :meth:`fetch` @@ -122,7 +124,8 @@ def search(self, username: str, User, because less info is returned. """ - for i in self._fp._getiter("/search/users", - limit=limit, - params={"q": username}): + for i in self._getiter(path="/search/users", + limit=limit, + params={"q": username}, + key=key): yield UserSmall(**i) diff --git a/src/flightplandb/submodules/weather.py b/src/flightplandb/submodules/weather.py index e53fba7..322d70a 100644 --- a/src/flightplandb/submodules/weather.py +++ b/src/flightplandb/submodules/weather.py @@ -1,17 +1,16 @@ from flightplandb.datatypes import Weather +from typing import Optional +from flightplandb.flightplandb import FlightPlanDB -class WeatherAPI: +class WeatherAPI(FlightPlanDB): """Weather. I mean, how much is there to say? Accessed via :meth:`~flightplandb.flightplandb.FlightPlanDB.weather`. """ - def __init__(self, flightplandb): - self._fp = flightplandb - - def fetch(self, icao: str) -> Weather: + def fetch(self, icao: str, key: Optional[str] = None) -> Weather: """ Fetches current weather conditions at an airport @@ -31,4 +30,4 @@ def fetch(self, icao: str) -> Weather: No airport with the specified ICAO code was found. """ - return Weather(**self._fp._get(f"/weather/{icao}")) + return Weather(**self._get(path=f"/weather/{icao}", key=key)) From 9b9642c6e36eb74b986a9a724e41a38e58968bf7 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 24 Oct 2021 00:14:45 +0200 Subject: [PATCH 11/86] add path named keyword in function calls --- src/flightplandb/submodules/api.py | 4 ++-- src/flightplandb/submodules/nav.py | 6 +++--- src/flightplandb/submodules/tags.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index 604a46f..74562aa 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -84,7 +84,7 @@ def ping(self, key: Optional[str] = None) -> StatusResponse: OK 200 means the service is up and running. """ - resp = self._get("", key=key) + resp = self._get(path="", key=key) return StatusResponse(**resp) def revoke(self, key: Optional[str] = None) -> StatusResponse: @@ -102,6 +102,6 @@ def revoke(self, key: Optional[str] = None) -> StatusResponse: occurred and the errors array will give further details. """ - resp = self._get("/auth/revoke", key=key) + resp = self._get(path="/auth/revoke", key=key) self._header = resp.headers return StatusResponse(**resp.json()) diff --git a/src/flightplandb/submodules/nav.py b/src/flightplandb/submodules/nav.py index 668dcb4..f9f9064 100644 --- a/src/flightplandb/submodules/nav.py +++ b/src/flightplandb/submodules/nav.py @@ -29,7 +29,7 @@ def airport(self, icao: str, key: Optional[str] = None) -> Union[Airport]: No airport with the specified ICAO code was found. """ - resp = self._get(f"/nav/airport/{icao}", key=key) + resp = self._get(path=f"/nav/airport/{icao}", key=key) return Airport(**resp) def nats(self, key: Optional[str] = None) -> List[Track]: @@ -54,7 +54,7 @@ def pacots(self, key: Optional[str] = None) -> List[Track]: """ return list( - map(lambda t: Track(**t), self._get("/nav/PACOTS", key=key))) + map(lambda t: Track(**t), self._get(path="/nav/PACOTS", key=key))) def search(self, query: str, type_: str = None, key: Optional[str] = None @@ -83,5 +83,5 @@ def search(self, query: str, params["types"] = type_ else: raise ValueError(f"{type_} is not a valid Navaid type") - for i in self._getiter("/search/nav", params=params, key=key): + for i in self._getiter(path="/search/nav", params=params, key=key): yield SearchNavaid(**i) diff --git a/src/flightplandb/submodules/tags.py b/src/flightplandb/submodules/tags.py index c678bb5..38d598c 100644 --- a/src/flightplandb/submodules/tags.py +++ b/src/flightplandb/submodules/tags.py @@ -20,4 +20,4 @@ def fetch(self, key: Optional[str] = None) -> List[Tag]: A list of the current popular tags. """ - return list(map(lambda t: Tag(**t), self._get("/tags"), key=key)) + return list(map(lambda t: Tag(**t), self._get(path="/tags", key=key))) From 811d2d37669c86a7e67d464210a798bbfc03d54d Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 24 Oct 2021 14:42:38 +0200 Subject: [PATCH 12/86] convert tags test to pytest --- tests/test_tags.py | 72 ++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index 0291494..678dc61 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -1,41 +1,43 @@ -from unittest import TestCase, main -from unittest.mock import patch, call -from flightplandb.submodules.tags import TagsAPI +import pytest +import flightplandb from flightplandb.datatypes import Tag -class TagsTest(TestCase): - def test_tags_api(self): +def test_tags_api(mocker): + json_response = [ + { + "name": "Decoded", + "description": "Flight plans decoded", + "planCount": 7430, + "popularity": 0.010143822356129395 + }, + { + "name": "Generated", + "description": "Computer generated plans", + "planCount": 35343, + "popularity": 0.009036140132228622 + } + ] - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._get.return_value = [ - {"name": "Decoded", - "description": "Flight plans decoded", - "planCount": 7430, - "popularity": 0.010143822356129395}, - {"name": "Generated", - "description": "Computer generated plans", - "planCount": 35343, - "popularity": 0.009036140132228622} - ] - sub_instance = TagsAPI(instance) - response = sub_instance.fetch() - # check that TagsAPI method made the correct request of FlightPlanDB - instance.assert_has_calls([call._get('/tags')]) - correct_response = [ - Tag(name='Decoded', - description='Flight plans decoded', - planCount=7430, - popularity=0.010143822356129395), - Tag(name='Generated', - description='Computer generated plans', - planCount=35343, - popularity=0.009036140132228622)] - # check TagsAPI method decoded the data correctly for given response - assert response == correct_response + correct_response = [ + Tag(name='Decoded', + description='Flight plans decoded', + planCount=7430, + popularity=0.010143822356129395), + Tag(name='Generated', + description='Computer generated plans', + planCount=35343, + popularity=0.009036140132228622)] + + def patched_get(self, path, key): + return json_response + mocker.patch.object(flightplandb.submodules.tags.TagsAPI, "_get", patched_get) + instance = flightplandb.submodules.tags.TagsAPI() + spy = mocker.spy(instance, "_get") -if __name__ == "__main__": - main() + response = instance.fetch() + + spy.assert_called_once_with(path='/tags', key=None) + + assert response == correct_response From 945bda4e72ff4f033b1a28a7c04598107ac26f30 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 24 Oct 2021 14:47:21 +0200 Subject: [PATCH 13/86] fix flake8 complaints --- tests/test_tags.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index 678dc61..e4e4aa3 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -1,4 +1,3 @@ -import pytest import flightplandb from flightplandb.datatypes import Tag @@ -28,16 +27,19 @@ def test_tags_api(mocker): description='Computer generated plans', planCount=35343, popularity=0.009036140132228622)] - + def patched_get(self, path, key): return json_response - mocker.patch.object(flightplandb.submodules.tags.TagsAPI, "_get", patched_get) + mocker.patch.object( + target=flightplandb.submodules.tags.TagsAPI, + attribute="_get", + new=patched_get) instance = flightplandb.submodules.tags.TagsAPI() spy = mocker.spy(instance, "_get") response = instance.fetch() - + # check that TagsAPI method made correct request of FlightPlanDB spy.assert_called_once_with(path='/tags', key=None) - + # check that TagsAPI method decoded data correctly for given response assert response == correct_response From 4952bc0c6263ebf8f9a99fb384d253ae3077c3fa Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 24 Oct 2021 15:31:08 +0200 Subject: [PATCH 14/86] convert tags and weather tests to pytest --- tests/test_tags.py | 27 +++++++++++---------- tests/test_weather.py | 56 +++++++++++++++++++++---------------------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index e4e4aa3..f111bb2 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -4,18 +4,18 @@ def test_tags_api(mocker): json_response = [ - { - "name": "Decoded", - "description": "Flight plans decoded", - "planCount": 7430, - "popularity": 0.010143822356129395 - }, - { - "name": "Generated", - "description": "Computer generated plans", - "planCount": 35343, - "popularity": 0.009036140132228622 - } + { + "name": "Decoded", + "description": "Flight plans decoded", + "planCount": 7430, + "popularity": 0.010143822356129395 + }, + { + "name": "Generated", + "description": "Computer generated plans", + "planCount": 35343, + "popularity": 0.009036140132228622 + } ] correct_response = [ @@ -26,7 +26,8 @@ def test_tags_api(mocker): Tag(name='Generated', description='Computer generated plans', planCount=35343, - popularity=0.009036140132228622)] + popularity=0.009036140132228622) + ] def patched_get(self, path, key): return json_response diff --git a/tests/test_weather.py b/tests/test_weather.py index 695e623..545eb34 100644 --- a/tests/test_weather.py +++ b/tests/test_weather.py @@ -1,34 +1,34 @@ -from unittest import TestCase, main -from unittest.mock import patch, call -from flightplandb.submodules.weather import WeatherAPI +import flightplandb from flightplandb.datatypes import Weather -class WeatherTest(TestCase): - def test_weather_api(self): +def test_weather_api(mocker): + json_response = { + "METAR": "EHAM 250755Z 02009KT 330V130 9999\ + BKN033 07/M00 Q1029 NOSIG", + "TAF": "TAF EHAM 250442Z 2506/2612 02012KT 9999 BKN030 BECMG\ + 2507/2510 CAVOK BECMG 2608/2611 05009KT" + } - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._get.return_value = { - "METAR": "EHAM 250755Z 02009KT 330V130 9999\ - BKN033 07/M00 Q1029 NOSIG", - "TAF": "TAF EHAM 250442Z 2506/2612 02012KT 9999 BKN030 BECMG\ - 2507/2510 CAVOK BECMG 2608/2611 05009KT" - } - sub_instance = WeatherAPI(instance) - response = sub_instance.fetch("EHAM") - correct_response = Weather( - METAR="EHAM 250755Z 02009KT 330V130 9999\ - BKN033 07/M00 Q1029 NOSIG", - TAF="TAF EHAM 250442Z 2506/2612 02012KT 9999 BKN030 BECMG\ - 2507/2510 CAVOK BECMG 2608/2611 05009KT" - ) - # check that WeatherAPI method made correct request of FlightPlanDB - instance.assert_has_calls([call._get('/weather/EHAM')]) - # check WeatherAPI method decoded data correctly for given response - assert response == correct_response + correct_response = Weather( + METAR="EHAM 250755Z 02009KT 330V130 9999\ + BKN033 07/M00 Q1029 NOSIG", + TAF="TAF EHAM 250442Z 2506/2612 02012KT 9999 BKN030 BECMG\ + 2507/2510 CAVOK BECMG 2608/2611 05009KT" + ) + def patched_get(self, path, key): + return json_response -if __name__ == "__main__": - main() + mocker.patch.object( + target=flightplandb.submodules.weather.WeatherAPI, + attribute="_get", + new=patched_get) + instance = flightplandb.submodules.weather.WeatherAPI() + spy = mocker.spy(instance, "_get") + + response = instance.fetch("EHAM") + # check that TagsAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/weather/EHAM', key=None) + # check that TagsAPI method decoded data correctly for given response + assert response == correct_response From d49dffb3dc60dc368a743464c6cb0b55c79f809d Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 24 Oct 2021 16:12:54 +0200 Subject: [PATCH 15/86] convert user tests to pytest --- tests/test_user.py | 722 +++++++++++++++++++++++---------------------- 1 file changed, 377 insertions(+), 345 deletions(-) diff --git a/tests/test_user.py b/tests/test_user.py index a97a449..7cb1cf8 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,375 +1,407 @@ from unittest import TestCase, main from unittest.mock import patch, call +import flightplandb from flightplandb.submodules.user import UserAPI from flightplandb.datatypes import User, Plan, UserSmall import datetime from dateutil.tz import tzutc -class UserTest(TestCase): - def test_self_info(self): +def test_self_info(mocker): + json_response = { + "id": 18990, + "username": "discordflightplannerbot", + "location": None, + "gravatarHash": "3bcb4f39a24700e081f49c3d2d43d277", + "joined": "2020-08-06T17:04:30.000Z", + "lastSeen": "2020-12-27T12:40:06.000Z", + "plansCount": 2, + "plansDistance": 794.0094160460012, + "plansDownloads": 0, + "plansLikes": 0 + } - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._get.return_value = { - "id": 18990, - "username": "discordflightplannerbot", - "location": None, - "gravatarHash": "3bcb4f39a24700e081f49c3d2d43d277", - "joined": "2020-08-06T17:04:30.000Z", - "lastSeen": "2020-12-27T12:40:06.000Z", - "plansCount": 2, - "plansDistance": 794.0094160460012, - "plansDownloads": 0, - "plansLikes": 0 - } - sub_instance = UserAPI(instance) - response = sub_instance.me - correct_response = User( - id=18990, - username="discordflightplannerbot", - location=None, - gravatarHash="3bcb4f39a24700e081f49c3d2d43d277", - joined=datetime.datetime( - 2020, 8, 6, 17, 4, 30, - tzinfo=tzutc() - ), - lastSeen=datetime.datetime(2020, 12, 27, 12, 40, 6, - tzinfo=tzutc()), - plansCount=2, - plansDistance=794.0094160460012, - plansDownloads=0, - plansLikes=0 - ) - # check that UserAPI method made correct request of FlightPlanDB - instance.assert_has_calls([call._get("/me")]) - # check UserAPI method decoded data correctly for given response - assert response == correct_response + correct_response = User( + id=18990, + username="discordflightplannerbot", + location=None, + gravatarHash="3bcb4f39a24700e081f49c3d2d43d277", + joined=datetime.datetime( + 2020, 8, 6, 17, 4, 30, + tzinfo=tzutc() + ), + lastSeen=datetime.datetime(2020, 12, 27, 12, 40, 6, + tzinfo=tzutc()), + plansCount=2, + plansDistance=794.0094160460012, + plansDownloads=0, + plansLikes=0 + ) + + def patched_get(self, path, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.user.UserAPI, + attribute="_get", + new=patched_get) + instance = flightplandb.submodules.user.UserAPI() + spy = mocker.spy(instance, "_get") + + response = instance.me() + # check that UserAPI method decoded data correctly for given response + assert response == correct_response + # check that UserAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/me', key=None) + + +def test_user_info(mocker): + json_response = { + "id": 1, + "username": "lemon", + "location": "\U0001F601", + "gravatarHash": "7889b0d4380a7194b6b67c8e2765289d", + "joined": "2008-12-31T15:49:18.000Z", + "lastSeen": "2021-04-24T00:22:46.000Z", + "plansCount": 479, + "plansDistance": 1212799.2736187153, + "plansDownloads": 10341, + "plansLikes": 33 + } + + correct_response = User( + id=1, + username="lemon", + location="\U0001F601", + gravatarHash="7889b0d4380a7194b6b67c8e2765289d", + joined=datetime.datetime( + 2008, 12, 31, 15, 49, 18, + tzinfo=tzutc() + ), + lastSeen=datetime.datetime(2021, 4, 24, 0, 22, 46, + tzinfo=tzutc()), + plansCount=479, + plansDistance=1212799.2736187153, + plansDownloads=10341, + plansLikes=33 + ) + + def patched_get(self, path, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.user.UserAPI, + attribute="_get", + new=patched_get) + instance = flightplandb.submodules.user.UserAPI() + spy = mocker.spy(instance, "_get") + + response = instance.fetch("lemon") + # check that UserAPI method decoded data correctly for given response + assert response == correct_response + # check that UserAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/user/lemon', key=None) - def test_user_info(self): - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._get.return_value = { +def test_user_plans(mocker): + json_response = [ + { + "id": 62373, + "fromICAO": "KLAS", + "toICAO": "KLAX", + "fromName": "Mc Carran Intl", + "toName": "Los Angeles Intl", + "flightNumber": None, + "distance": 206.39578816273502, + "maxAltitude": 18000, + "waypoints": 8, + "likes": 0, + "downloads": 1, + "popularity": 1, + "notes": "", + "encodedPolyline": r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ + Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + "createdAt": "2015-08-04T20:48:08.000Z", + "updatedAt": "2015-08-04T20:48:08.000Z", + "tags": [ + "generated" + ], + "user": { + "id": 2429, + "username": "example", + "gravatarHash": "f30b58b998a11b5d417cc2c78df3f764", + "location": None + } + }, + { + "id": 62493, + "fromICAO": "EHAM", + "toICAO": "KJFK", + "fromName": "Schiphol", + "toName": "John F Kennedy Intl", + "flightNumber": None, + "distance": 3157.88876623323, + "maxAltitude": 0, + "waypoints": 2, + "popularity": 0, + "notes": None, + "encodedPolyline": r"yvh~Hgi`\\lggfAjyi~M", + "createdAt": "2015-08-05T22:44:34.000Z", + "updatedAt": "2015-08-05T22:44:34.000Z", + "tags": ["atlantic"], + "user": { "id": 1, - "username": "lemon", - "location": "😁", - "gravatarHash": "7889b0d4380a7194b6b67c8e2765289d", - "joined": "2008-12-31T15:49:18.000Z", - "lastSeen": "2021-04-24T00:22:46.000Z", - "plansCount": 479, - "plansDistance": 1212799.2736187153, - "plansDownloads": 10341, - "plansLikes": 33 + "username": "example", + "gravatarHash": "f30b58b998a11b5d417cc2c78df3f764", + "location": None } - sub_instance = UserAPI(instance) - response = sub_instance.fetch("lemon") - correct_response = User( - id=1, - username="lemon", - location="😁", - gravatarHash="7889b0d4380a7194b6b67c8e2765289d", - joined=datetime.datetime( - 2008, 12, 31, 15, 49, 18, - tzinfo=tzutc() + } + ] + + correct_response_list = [ + Plan(id=62373, + fromICAO="KLAS", + toICAO="KLAX", + fromName="Mc Carran Intl", + toName="Los Angeles Intl", + flightNumber=None, + distance=206.39578816273502, + maxAltitude=18000, + waypoints=8, + likes=0, + downloads=1, + popularity=1, + notes="", + encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ + Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + createdAt="2015-08-04T20:48:08.000Z", + updatedAt="2015-08-04T20:48:08.000Z", + tags=[ + "generated" + ], + user=User(id=2429, + username="example", + gravatarHash="f30b58b998a11b5d417cc2c78df3f764", + location=None + ) ), - lastSeen=datetime.datetime(2021, 4, 24, 0, 22, 46, - tzinfo=tzutc()), - plansCount=479, - plansDistance=1212799.2736187153, - plansDownloads=10341, - plansLikes=33 - ) - # check that UserAPI method made correct request of FlightPlanDB - instance.assert_has_calls([call._get("/user/lemon")]) - # check UserAPI method decoded data correctly for given response - assert response == correct_response + Plan(id=62493, + fromICAO="EHAM", + toICAO="KJFK", + fromName="Schiphol", + toName="John F Kennedy Intl", + flightNumber=None, + distance=3157.88876623323, + maxAltitude=0, + waypoints=2, + popularity=0, + notes=None, + encodedPolyline=r"yvh~Hgi`\\lggfAjyi~M", + createdAt="2015-08-05T22:44:34.000Z", + updatedAt="2015-08-05T22:44:34.000Z", + tags=["atlantic"], + user=User(id=1, + username="example", + gravatarHash="f30b58b998a11b5d417cc2c78df3f764", + location=None + ) + ) + ] - def test_user_plans(self): + def patched_getiter(self, path, limit, sort, key): + return (i for i in json_response) - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - mock_response = [ - { - "id": 62373, - "fromICAO": "KLAS", - "toICAO": "KLAX", - "fromName": "Mc Carran Intl", - "toName": "Los Angeles Intl", - "flightNumber": None, - "distance": 206.39578816273502, - "maxAltitude": 18000, - "waypoints": 8, - "likes": 0, - "downloads": 1, - "popularity": 1, - "notes": "", - "encodedPolyline": r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", - "createdAt": "2015-08-04T20:48:08.000Z", - "updatedAt": "2015-08-04T20:48:08.000Z", - "tags": [ - "generated" - ], - "user": { - "id": 2429, - "username": "example", - "gravatarHash": "f30b58b998a11b5d417cc2c78df3f764", - "location": None - } - }, - { - "id": 62493, - "fromICAO": "EHAM", - "toICAO": "KJFK", - "fromName": "Schiphol", - "toName": "John F Kennedy Intl", - "flightNumber": None, - "distance": 3157.88876623323, - "maxAltitude": 0, - "waypoints": 2, - "popularity": 0, - "notes": None, - "encodedPolyline": r"yvh~Hgi`\\lggfAjyi~M", - "createdAt": "2015-08-05T22:44:34.000Z", - "updatedAt": "2015-08-05T22:44:34.000Z", - "tags": ["atlantic"], - "user": { - "id": 1, - "username": "example", - "gravatarHash": "f30b58b998a11b5d417cc2c78df3f764", - "location": None - } - } - ] - instance._getiter.return_value = (i for i in mock_response) + mocker.patch.object( + target=flightplandb.submodules.user.UserAPI, + attribute="_getiter", + new=patched_getiter) + instance = flightplandb.submodules.user.UserAPI() + spy = mocker.spy(instance, "_getiter") - sub_instance = UserAPI(instance) - response = sub_instance.plans("lemon") + response = instance.plans("lemon") + # check that UserAPI method decoded data correctly for given response + assert list(i for i in response) == correct_response_list + # check that UserAPI method made correct request of FlightPlanDB + spy.assert_has_calls([call( + path='/user/lemon/plans', + limit=100, + sort='created', + key=None)]) - correct_response_list = [ - Plan(id=62373, - fromICAO="KLAS", - toICAO="KLAX", - fromName="Mc Carran Intl", - toName="Los Angeles Intl", - flightNumber=None, - distance=206.39578816273502, - maxAltitude=18000, - waypoints=8, - likes=0, - downloads=1, - popularity=1, - notes="", - encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", - createdAt="2015-08-04T20:48:08.000Z", - updatedAt="2015-08-04T20:48:08.000Z", - tags=[ - "generated" - ], - user=User(id=2429, - username="example", - gravatarHash="f30b58b998a11b5d417cc2c78df3f764", - location=None - ) - ), - Plan(id=62493, - fromICAO="EHAM", - toICAO="KJFK", - fromName="Schiphol", - toName="John F Kennedy Intl", - flightNumber=None, - distance=3157.88876623323, - maxAltitude=0, - waypoints=2, - popularity=0, - notes=None, - encodedPolyline=r"yvh~Hgi`\\lggfAjyi~M", - createdAt="2015-08-05T22:44:34.000Z", - updatedAt="2015-08-05T22:44:34.000Z", - tags=["atlantic"], - user=User(id=1, - username="example", - gravatarHash="f30b58b998a11b5d417cc2c78df3f764", - location=None - ) - ) - ] - # check UserAPI method decoded data correctly for given response - assert list(i for i in response) == correct_response_list - # check that UserAPI method made the correct request of FlightPlanDB - instance.assert_has_calls([call._getiter('/user/lemon/plans', - limit=100, - sort='created')]) - def test_user_likes(self): +def test_user_likes(mocker): + json_response = [ + { + "id": 62373, + "fromICAO": "KLAS", + "toICAO": "KLAX", + "fromName": "Mc Carran Intl", + "toName": "Los Angeles Intl", + "flightNumber": None, + "distance": 206.39578816273502, + "maxAltitude": 18000, + "waypoints": 8, + "likes": 0, + "downloads": 1, + "popularity": 1, + "notes": "", + "encodedPolyline": r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ + Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + "createdAt": "2015-08-04T20:48:08.000Z", + "updatedAt": "2015-08-04T20:48:08.000Z", + "tags": [ + "generated" + ], + "user": { + "id": 2429, + "username": "example", + "gravatarHash": "f30b58b998a11b5d417cc2c78df3f764", + "location": None + } + }, + { + "id": 62493, + "fromICAO": "EHAM", + "toICAO": "KJFK", + "fromName": "Schiphol", + "toName": "John F Kennedy Intl", + "flightNumber": None, + "distance": 3157.88876623323, + "maxAltitude": 0, + "waypoints": 2, + "popularity": 0, + "notes": None, + "encodedPolyline": r"yvh~Hgi`\\lggfAjyi~M", + "createdAt": "2015-08-05T22:44:34.000Z", + "updatedAt": "2015-08-05T22:44:34.000Z", + "tags": ["atlantic"], + "user": { + "id": 1, + "username": "example", + "gravatarHash": "f30b58b998a11b5d417cc2c78df3f764", + "location": None + } + } + ] - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - mock_response = [ - { - "id": 62373, - "fromICAO": "KLAS", - "toICAO": "KLAX", - "fromName": "Mc Carran Intl", - "toName": "Los Angeles Intl", - "flightNumber": None, - "distance": 206.39578816273502, - "maxAltitude": 18000, - "waypoints": 8, - "likes": 0, - "downloads": 1, - "popularity": 1, - "notes": "", - "encodedPolyline": r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", - "createdAt": "2015-08-04T20:48:08.000Z", - "updatedAt": "2015-08-04T20:48:08.000Z", - "tags": [ + correct_response_list = [ + Plan(id=62373, + fromICAO="KLAS", + toICAO="KLAX", + fromName="Mc Carran Intl", + toName="Los Angeles Intl", + flightNumber=None, + distance=206.39578816273502, + maxAltitude=18000, + waypoints=8, + likes=0, + downloads=1, + popularity=1, + notes="", + encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ + Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + createdAt="2015-08-04T20:48:08.000Z", + updatedAt="2015-08-04T20:48:08.000Z", + tags=[ "generated" ], - "user": { - "id": 2429, - "username": "example", - "gravatarHash": "f30b58b998a11b5d417cc2c78df3f764", - "location": None - } - }, - { - "id": 62493, - "fromICAO": "EHAM", - "toICAO": "KJFK", - "fromName": "Schiphol", - "toName": "John F Kennedy Intl", - "flightNumber": None, - "distance": 3157.88876623323, - "maxAltitude": 0, - "waypoints": 2, - "popularity": 0, - "notes": None, - "encodedPolyline": r"yvh~Hgi`\\lggfAjyi~M", - "createdAt": "2015-08-05T22:44:34.000Z", - "updatedAt": "2015-08-05T22:44:34.000Z", - "tags": ["atlantic"], - "user": { - "id": 1, - "username": "example", - "gravatarHash": "f30b58b998a11b5d417cc2c78df3f764", - "location": None - } - } - ] - instance._getiter.return_value = (i for i in mock_response) + user=User(id=2429, + username="example", + gravatarHash="f30b58b998a11b5d417cc2c78df3f764", + location=None + ) + ), + Plan(id=62493, + fromICAO="EHAM", + toICAO="KJFK", + fromName="Schiphol", + toName="John F Kennedy Intl", + flightNumber=None, + distance=3157.88876623323, + maxAltitude=0, + waypoints=2, + popularity=0, + notes=None, + encodedPolyline=r"yvh~Hgi`\\lggfAjyi~M", + createdAt="2015-08-05T22:44:34.000Z", + updatedAt="2015-08-05T22:44:34.000Z", + tags=["atlantic"], + user=User(id=1, + username="example", + gravatarHash="f30b58b998a11b5d417cc2c78df3f764", + location=None + ) + ) + ] - sub_instance = UserAPI(instance) - response = sub_instance.likes("lemon") + def patched_getiter(self, path, limit, sort, key): + return (i for i in json_response) - correct_response_list = [ - Plan(id=62373, - fromICAO="KLAS", - toICAO="KLAX", - fromName="Mc Carran Intl", - toName="Los Angeles Intl", - flightNumber=None, - distance=206.39578816273502, - maxAltitude=18000, - waypoints=8, - likes=0, - downloads=1, - popularity=1, - notes="", - encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", - createdAt="2015-08-04T20:48:08.000Z", - updatedAt="2015-08-04T20:48:08.000Z", - tags=[ - "generated" - ], - user=User(id=2429, - username="example", - gravatarHash="f30b58b998a11b5d417cc2c78df3f764", - location=None - ) - ), - Plan(id=62493, - fromICAO="EHAM", - toICAO="KJFK", - fromName="Schiphol", - toName="John F Kennedy Intl", - flightNumber=None, - distance=3157.88876623323, - maxAltitude=0, - waypoints=2, - popularity=0, - notes=None, - encodedPolyline=r"yvh~Hgi`\\lggfAjyi~M", - createdAt="2015-08-05T22:44:34.000Z", - updatedAt="2015-08-05T22:44:34.000Z", - tags=["atlantic"], - user=User(id=1, - username="example", - gravatarHash="f30b58b998a11b5d417cc2c78df3f764", - location=None - ) - ) - ] - # check UserAPI method decoded data correctly for given response - assert list(i for i in response) == correct_response_list - # check that UserAPI method made the correct request of FlightPlanDB - instance.assert_has_calls([call._getiter('/user/lemon/likes', - limit=100, - sort='created')]) + mocker.patch.object( + target=flightplandb.submodules.user.UserAPI, + attribute="_getiter", + new=patched_getiter) + instance = flightplandb.submodules.user.UserAPI() + spy = mocker.spy(instance, "_getiter") - def test_user_search(self): + response = instance.likes("lemon") + # check that UserAPI method decoded data correctly for given response + assert list(i for i in response) == correct_response_list + # check that UserAPI method made correct request of FlightPlanDB + spy.assert_has_calls([call( + path='/user/lemon/likes', + limit=100, + sort='created', + key=None)]) - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - mock_response = [ - {"id": 1, - "username": 'lemon', - "location": '😁', - "gravatarHash": '7889b0d4380a7194b6b67c8e2765289d'}, - {"id": 1851, - "username": 'lemon2', - "location": None, - "gravatarHash": '94ff72a00d4ead8c49abd5a0cf411c6f'}, - {"id": 1950, - "username": 'lemon6', - "location": None, - "gravatarHash": 'b807060d00c10513ce04b70918dd07a1'} - ] - instance._getiter.return_value = (i for i in mock_response) - sub_instance = UserAPI(instance) - response = sub_instance.search("lemon") +def test_user_search(mocker): + json_response = [ + {"id": 1, + "username": 'lemon', + "location": '\U0001F601', + "gravatarHash": '7889b0d4380a7194b6b67c8e2765289d'}, + {"id": 1851, + "username": 'lemon2', + "location": None, + "gravatarHash": '94ff72a00d4ead8c49abd5a0cf411c6f'}, + {"id": 1950, + "username": 'lemon6', + "location": None, + "gravatarHash": 'b807060d00c10513ce04b70918dd07a1'} + ] + + correct_response_list = [ + UserSmall(id=1, + username='lemon', + location='\U0001F601', + gravatarHash='7889b0d4380a7194b6b67c8e2765289d'), + UserSmall(id=1851, + username='lemon2', + location=None, + gravatarHash='94ff72a00d4ead8c49abd5a0cf411c6f'), + UserSmall(id=1950, + username='lemon6', + location=None, + gravatarHash='b807060d00c10513ce04b70918dd07a1') + ] - correct_response_list = [ - UserSmall(id=1, - username='lemon', - location='😁', - gravatarHash='7889b0d4380a7194b6b67c8e2765289d'), - UserSmall(id=1851, - username='lemon2', - location=None, - gravatarHash='94ff72a00d4ead8c49abd5a0cf411c6f'), - UserSmall(id=1950, - username='lemon6', - location=None, - gravatarHash='b807060d00c10513ce04b70918dd07a1') - ] - # check UserAPI method decoded data correctly for given response - assert list(i for i in response) == correct_response_list - # check that UserAPI method made the correct request of FlightPlanDB - instance.assert_has_calls([call._getiter('/search/users', - limit=100, - params={'q': 'lemon'})]) + def patched_getiter(self, path, limit, params=None, key=None): + return (i for i in json_response) + mocker.patch.object( + target=flightplandb.submodules.user.UserAPI, + attribute="_getiter", + new=patched_getiter) + instance = flightplandb.submodules.user.UserAPI() + spy = mocker.spy(instance, "_getiter") -if __name__ == "__main__": - main() + response = instance.search("lemon") + # check that UserAPI method decoded data correctly for given response + assert list(i for i in response) == correct_response_list + # check that UserAPI method made correct request of FlightPlanDB + spy.assert_has_calls([call( + path='/search/users', + limit=100, + params={'q': 'lemon'}, + key=None)]) From d1cdc3cf58d65e5b7e104d6b9839d4ad43668db8 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 24 Oct 2021 16:23:46 +0200 Subject: [PATCH 16/86] fix flake8 complaints --- tests/test_user.py | 232 +++++++++++++++++++++++---------------------- 1 file changed, 120 insertions(+), 112 deletions(-) diff --git a/tests/test_user.py b/tests/test_user.py index 7cb1cf8..e10f32b 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,7 +1,4 @@ -from unittest import TestCase, main -from unittest.mock import patch, call import flightplandb -from flightplandb.submodules.user import UserAPI from flightplandb.datatypes import User, Plan, UserSmall import datetime from dateutil.tz import tzutc @@ -31,7 +28,7 @@ def test_self_info(mocker): tzinfo=tzutc() ), lastSeen=datetime.datetime(2020, 12, 27, 12, 40, 6, - tzinfo=tzutc()), + tzinfo=tzutc()), plansCount=2, plansDistance=794.0094160460012, plansDownloads=0, @@ -79,7 +76,7 @@ def test_user_info(mocker): tzinfo=tzutc() ), lastSeen=datetime.datetime(2021, 4, 24, 0, 22, 46, - tzinfo=tzutc()), + tzinfo=tzutc()), plansCount=479, plansDistance=1212799.2736187153, plansDownloads=10341, @@ -159,53 +156,57 @@ def test_user_plans(mocker): ] correct_response_list = [ - Plan(id=62373, - fromICAO="KLAS", - toICAO="KLAX", - fromName="Mc Carran Intl", - toName="Los Angeles Intl", - flightNumber=None, - distance=206.39578816273502, - maxAltitude=18000, - waypoints=8, - likes=0, - downloads=1, - popularity=1, - notes="", - encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", - createdAt="2015-08-04T20:48:08.000Z", - updatedAt="2015-08-04T20:48:08.000Z", - tags=[ - "generated" - ], - user=User(id=2429, - username="example", - gravatarHash="f30b58b998a11b5d417cc2c78df3f764", - location=None - ) - ), - Plan(id=62493, - fromICAO="EHAM", - toICAO="KJFK", - fromName="Schiphol", - toName="John F Kennedy Intl", - flightNumber=None, - distance=3157.88876623323, - maxAltitude=0, - waypoints=2, - popularity=0, - notes=None, - encodedPolyline=r"yvh~Hgi`\\lggfAjyi~M", - createdAt="2015-08-05T22:44:34.000Z", - updatedAt="2015-08-05T22:44:34.000Z", - tags=["atlantic"], - user=User(id=1, - username="example", - gravatarHash="f30b58b998a11b5d417cc2c78df3f764", - location=None - ) + Plan( + id=62373, + fromICAO="KLAS", + toICAO="KLAX", + fromName="Mc Carran Intl", + toName="Los Angeles Intl", + flightNumber=None, + distance=206.39578816273502, + maxAltitude=18000, + waypoints=8, + likes=0, + downloads=1, + popularity=1, + notes="", + encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ + Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + createdAt="2015-08-04T20:48:08.000Z", + updatedAt="2015-08-04T20:48:08.000Z", + tags=[ + "generated" + ], + user=User( + id=2429, + username="example", + gravatarHash="f30b58b998a11b5d417cc2c78df3f764", + location=None ) + ), + Plan( + id=62493, + fromICAO="EHAM", + toICAO="KJFK", + fromName="Schiphol", + toName="John F Kennedy Intl", + flightNumber=None, + distance=3157.88876623323, + maxAltitude=0, + waypoints=2, + popularity=0, + notes=None, + encodedPolyline=r"yvh~Hgi`\\lggfAjyi~M", + createdAt="2015-08-05T22:44:34.000Z", + updatedAt="2015-08-05T22:44:34.000Z", + tags=["atlantic"], + user=User( + id=1, + username="example", + gravatarHash="f30b58b998a11b5d417cc2c78df3f764", + location=None + ) + ) ] def patched_getiter(self, path, limit, sort, key): @@ -222,7 +223,7 @@ def patched_getiter(self, path, limit, sort, key): # check that UserAPI method decoded data correctly for given response assert list(i for i in response) == correct_response_list # check that UserAPI method made correct request of FlightPlanDB - spy.assert_has_calls([call( + spy.assert_has_calls([mocker.call( path='/user/lemon/plans', limit=100, sort='created', @@ -285,53 +286,57 @@ def test_user_likes(mocker): ] correct_response_list = [ - Plan(id=62373, - fromICAO="KLAS", - toICAO="KLAX", - fromName="Mc Carran Intl", - toName="Los Angeles Intl", - flightNumber=None, - distance=206.39578816273502, - maxAltitude=18000, - waypoints=8, - likes=0, - downloads=1, - popularity=1, - notes="", - encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", - createdAt="2015-08-04T20:48:08.000Z", - updatedAt="2015-08-04T20:48:08.000Z", - tags=[ - "generated" - ], - user=User(id=2429, - username="example", - gravatarHash="f30b58b998a11b5d417cc2c78df3f764", - location=None - ) - ), - Plan(id=62493, - fromICAO="EHAM", - toICAO="KJFK", - fromName="Schiphol", - toName="John F Kennedy Intl", - flightNumber=None, - distance=3157.88876623323, - maxAltitude=0, - waypoints=2, - popularity=0, - notes=None, - encodedPolyline=r"yvh~Hgi`\\lggfAjyi~M", - createdAt="2015-08-05T22:44:34.000Z", - updatedAt="2015-08-05T22:44:34.000Z", - tags=["atlantic"], - user=User(id=1, - username="example", - gravatarHash="f30b58b998a11b5d417cc2c78df3f764", - location=None - ) + Plan( + id=62373, + fromICAO="KLAS", + toICAO="KLAX", + fromName="Mc Carran Intl", + toName="Los Angeles Intl", + flightNumber=None, + distance=206.39578816273502, + maxAltitude=18000, + waypoints=8, + likes=0, + downloads=1, + popularity=1, + notes="", + encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ + Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + createdAt="2015-08-04T20:48:08.000Z", + updatedAt="2015-08-04T20:48:08.000Z", + tags=[ + "generated" + ], + user=User( + id=2429, + username="example", + gravatarHash="f30b58b998a11b5d417cc2c78df3f764", + location=None + ) + ), + Plan( + id=62493, + fromICAO="EHAM", + toICAO="KJFK", + fromName="Schiphol", + toName="John F Kennedy Intl", + flightNumber=None, + distance=3157.88876623323, + maxAltitude=0, + waypoints=2, + popularity=0, + notes=None, + encodedPolyline=r"yvh~Hgi`\\lggfAjyi~M", + createdAt="2015-08-05T22:44:34.000Z", + updatedAt="2015-08-05T22:44:34.000Z", + tags=["atlantic"], + user=User( + id=1, + username="example", + gravatarHash="f30b58b998a11b5d417cc2c78df3f764", + location=None ) + ) ] def patched_getiter(self, path, limit, sort, key): @@ -348,7 +353,7 @@ def patched_getiter(self, path, limit, sort, key): # check that UserAPI method decoded data correctly for given response assert list(i for i in response) == correct_response_list # check that UserAPI method made correct request of FlightPlanDB - spy.assert_has_calls([call( + spy.assert_has_calls([mocker.call( path='/user/lemon/likes', limit=100, sort='created', @@ -372,18 +377,21 @@ def test_user_search(mocker): ] correct_response_list = [ - UserSmall(id=1, - username='lemon', - location='\U0001F601', - gravatarHash='7889b0d4380a7194b6b67c8e2765289d'), - UserSmall(id=1851, - username='lemon2', - location=None, - gravatarHash='94ff72a00d4ead8c49abd5a0cf411c6f'), - UserSmall(id=1950, - username='lemon6', - location=None, - gravatarHash='b807060d00c10513ce04b70918dd07a1') + UserSmall( + id=1, + username='lemon', + location='\U0001F601', + gravatarHash='7889b0d4380a7194b6b67c8e2765289d'), + UserSmall( + id=1851, + username='lemon2', + location=None, + gravatarHash='94ff72a00d4ead8c49abd5a0cf411c6f'), + UserSmall( + id=1950, + username='lemon6', + location=None, + gravatarHash='b807060d00c10513ce04b70918dd07a1') ] def patched_getiter(self, path, limit, params=None, key=None): @@ -400,7 +408,7 @@ def patched_getiter(self, path, limit, params=None, key=None): # check that UserAPI method decoded data correctly for given response assert list(i for i in response) == correct_response_list # check that UserAPI method made correct request of FlightPlanDB - spy.assert_has_calls([call( + spy.assert_has_calls([mocker.call( path='/search/users', limit=100, params={'q': 'lemon'}, From e9702c0d948b59c0513fe6425f70d82fafd10dd1 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 24 Oct 2021 21:06:42 +0200 Subject: [PATCH 17/86] add return format to plan edit --- src/flightplandb/submodules/plan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/flightplandb/submodules/plan.py b/src/flightplandb/submodules/plan.py index ca10f12..ed13c76 100644 --- a/src/flightplandb/submodules/plan.py +++ b/src/flightplandb/submodules/plan.py @@ -131,6 +131,7 @@ def edit(self, plan: Plan, plan_data = plan._to_api_dict() request = self._patch( path=f"/plan/{plan_data['id']}", + return_format=return_format, json=plan_data, key=key) From 43cb7aea9ad0f8682b33c8b5379d86ec6e6ba5a7 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 24 Oct 2021 23:41:40 +0200 Subject: [PATCH 18/86] remove unused attribute from dataclass --- src/flightplandb/datatypes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/flightplandb/datatypes.py b/src/flightplandb/datatypes.py index bf2dc84..3a3cd33 100644 --- a/src/flightplandb/datatypes.py +++ b/src/flightplandb/datatypes.py @@ -422,7 +422,6 @@ class PlanQuery: distanceMax: Optional[str] = None tags: Optional[str] = None includeRoute: Optional[bool] = None - limit: Optional[int] = None def _to_api_dict(self): return self.__dict__ From 8cd701c91dfd2fb82e4a2a4c1fae40801fedc81c Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 24 Oct 2021 23:42:51 +0200 Subject: [PATCH 19/86] fix indents in strings causing diffs --- tests/test_user.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/test_user.py b/tests/test_user.py index e10f32b..ec0a21d 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -116,8 +116,7 @@ def test_user_plans(mocker): "downloads": 1, "popularity": 1, "notes": "", - "encodedPolyline": r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + "encodedPolyline": r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", "createdAt": "2015-08-04T20:48:08.000Z", "updatedAt": "2015-08-04T20:48:08.000Z", "tags": [ @@ -170,8 +169,7 @@ def test_user_plans(mocker): downloads=1, popularity=1, notes="", - encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", createdAt="2015-08-04T20:48:08.000Z", updatedAt="2015-08-04T20:48:08.000Z", tags=[ @@ -246,8 +244,7 @@ def test_user_likes(mocker): "downloads": 1, "popularity": 1, "notes": "", - "encodedPolyline": r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + "encodedPolyline": r"aaf{E`|y}T|Ftf@px\\hpe@lnCxwDbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", "createdAt": "2015-08-04T20:48:08.000Z", "updatedAt": "2015-08-04T20:48:08.000Z", "tags": [ @@ -300,8 +297,7 @@ def test_user_likes(mocker): downloads=1, popularity=1, notes="", - encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxwDbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", createdAt="2015-08-04T20:48:08.000Z", updatedAt="2015-08-04T20:48:08.000Z", tags=[ From f12f0298db7222bd28b9b486297e09a741b09073 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 25 Oct 2021 00:01:00 +0200 Subject: [PATCH 20/86] increase flake8 line length limit to 120 --- .github/workflows/test_and_lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_and_lint.yml b/.github/workflows/test_and_lint.yml index deae534..1f6c97b 100644 --- a/.github/workflows/test_and_lint.yml +++ b/.github/workflows/test_and_lint.yml @@ -15,7 +15,7 @@ jobs: - name: flake8 Lint uses: py-actions/flake8@v1 with: - max-line-length: "80" + max-line-length: "120" # but we do want to run unittests on many python versions unittests: From 5958d0255be4255ad04f553b844d28aababe197a Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 25 Oct 2021 21:29:25 +0200 Subject: [PATCH 21/86] add return format to plan decode --- src/flightplandb/submodules/plan.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/flightplandb/submodules/plan.py b/src/flightplandb/submodules/plan.py index ed13c76..6ab3b21 100644 --- a/src/flightplandb/submodules/plan.py +++ b/src/flightplandb/submodules/plan.py @@ -302,9 +302,12 @@ def generate(self, gen_query: GenerateQuery, Bytes if a different format than ``"native"`` was specified """ - return Plan( - **self._post( - path="/auto/generate", json=gen_query._to_api_dict(), key=key)) + response = self._post( + path="/auto/generate", return_format=return_format, json=gen_query._to_api_dict(), key=key) + if return_format == "native": + return Plan(**response) + else: + return response def decode(self, route: str, key: Optional[str] = None) -> Plan: From b938eb5223cbcd2af529ff073805fa9db1eb0fde Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 25 Oct 2021 22:16:16 +0200 Subject: [PATCH 22/86] finish converting plan tests to pytest --- tests/test_plan.py | 1624 +++++++++++++++++++++++--------------------- 1 file changed, 851 insertions(+), 773 deletions(-) diff --git a/tests/test_plan.py b/tests/test_plan.py index 23b56e9..15cd08d 100644 --- a/tests/test_plan.py +++ b/tests/test_plan.py @@ -1,5 +1,6 @@ from unittest import TestCase, main from unittest.mock import patch, call +import flightplandb from flightplandb.submodules.plan import PlanAPI from flightplandb.datatypes import ( Plan, PlanQuery, User, Route, GenerateQuery, @@ -9,784 +10,861 @@ from dateutil.tz import tzutc -class PlanTest(TestCase): - def test_plan_fetch(self): - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._get.return_value = { - "id": 62373, - "fromICAO": "KLAS", - "toICAO": "KLAX", - "fromName": "Mc Carran Intl", - "toName": "Los Angeles Intl", - "flightNumber": None, - "distance": 206.39578816273502, - "maxAltitude": 18000, - "waypoints": 8, - "likes": 0, - "downloads": 1, - "popularity": 1, - "notes": "", - "encodedPolyline": r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", - "createdAt": "2015-08-04T20:48:08.000Z", - "updatedAt": "2015-08-04T20:48:08.000Z", - "tags": [ - "generated" - ], - "user": { - "id": 2429, - "username": "example", - "gravatarHash": "f30b58b998a11b5d417cc2c78df3f764", - "location": None - } +def test_plan_fetch(mocker): + json_response = { + "id": 62373, + "fromICAO": "KLAS", + "toICAO": "KLAX", + "fromName": "Mc Carran Intl", + "toName": "Los Angeles Intl", + "flightNumber": None, + "distance": 206.39578816273502, + "maxAltitude": 18000, + "waypoints": 8, + "likes": 0, + "downloads": 1, + "popularity": 1, + "notes": "", + "encodedPolyline": "aaf{E`|y}T|Ftf@px\\hpe@lnCxw " + "Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + "createdAt": "2015-08-04T20:48:08.000Z", + "updatedAt": "2015-08-04T20:48:08.000Z", + "tags": [ + "generated" + ], + "user": { + "id": 2429, + "username": "example", + "gravatarHash": "f30b58b998a11b5d417cc2c78df3f764", + "location": None } - - sub_instance = PlanAPI(instance) - response = sub_instance.fetch(62373) - # check PlanAPI method made the correct request of FlightPlanDB - instance.assert_has_calls( - [call._get('/plan/62373', return_format='native')]) - - correct_response = Plan( - id=62373, - fromICAO="KLAS", - toICAO="KLAX", - fromName="Mc Carran Intl", - toName="Los Angeles Intl", - flightNumber=None, - distance=206.39578816273502, - maxAltitude=18000, - waypoints=8, - likes=0, - downloads=1, - popularity=1, - notes="", - encodedPolyline=r"aaf{E`|y}T|Ftf@px\\hpe@lnCxw \ - Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", - createdAt="2015-08-04T20:48:08.000Z", - updatedAt="2015-08-04T20:48:08.000Z", - tags=[ - "generated" - ], - user=User( - id=2429, - username="example", - gravatarHash="f30b58b998a11b5d417cc2c78df3f764", - location=None - )) - - # check PlanAPI method decoded data correctly for given response - assert response == correct_response - - def test_plan_create(self): - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._post.return_value = { - "id": None, - "fromICAO": "EHAM", - "toICAO": "KJFK", - "fromName": "Schiphol", - "toName": "John F Kennedy Intl", - "route": { - "nodes": [ - { - "ident": "EHAM", - "type": "APT", - "lat": 52.31485, - "lon": 4.75812, - "alt": 0, - "name": "Schiphol", - "via": None - }, - { - "ident": "KJFK", - "type": "APT", - "lat": 40.63990, - "lon": -73.77666, - "alt": 0, - "name": "John F Kennedy Intl", - "via": None - } - ] + } + + correct_response = Plan( + id=62373, + fromICAO="KLAS", + toICAO="KLAX", + fromName="Mc Carran Intl", + toName="Los Angeles Intl", + flightNumber=None, + distance=206.39578816273502, + maxAltitude=18000, + waypoints=8, + likes=0, + downloads=1, + popularity=1, + notes="", + encodedPolyline="aaf{E`|y}T|Ftf@px\\hpe@lnCxw " + "Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + createdAt="2015-08-04T20:48:08.000Z", + updatedAt="2015-08-04T20:48:08.000Z", + tags=[ + "generated" + ], + user=User( + id=2429, + username="example", + gravatarHash="f30b58b998a11b5d417cc2c78df3f764", + location=None + )) + + def patched_get(self, path, return_format, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.plan.PlanAPI, + attribute="_get", + new=patched_get) + instance = flightplandb.submodules.plan.PlanAPI() + spy = mocker.spy(instance, "_get") + + response = instance.fetch(62373) + # check that PlanAPI method decoded data correctly for given response + assert response == correct_response + # check that PlanAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/plan/62373', return_format='native', key=None) + + +def test_plan_create(mocker): + json_response = { + "id": None, + "fromICAO": "EHAM", + "toICAO": "KJFK", + "fromName": "Schiphol", + "toName": "John F Kennedy Intl", + "route": { + "nodes": [ + { + "ident": "EHAM", + "type": "APT", + "lat": 52.31485, + "lon": 4.75812, + "alt": 0, + "name": "Schiphol", + "via": None + }, + { + "ident": "KJFK", + "type": "APT", + "lat": 40.63990, + "lon": -73.77666, + "alt": 0, + "name": "John F Kennedy Intl", + "via": None } - } - sub_instance = PlanAPI(instance) - response = sub_instance.create(Plan( - id=None, - fromICAO="EHAM", - toICAO="KJFK", - fromName="Schiphol", - toName="John F Kennedy Intl", - user=None, - route=Route([ - RouteNode(**{ - "ident": "EHAM", - "type": "APT", - "lat": 52.31485, - "lon": 4.75812, - "alt": 0, - "name": "Schiphol", - "via": None}), - RouteNode(**{ - "ident": "KJFK", - "type": "APT", - "lat": 40.63990, - "lon": -73.77666, - "alt": 0, - "name": "John F Kennedy Intl", - "via": None})]))) - # check PlanAPI method made the correct request of FlightPlanDB - instance.assert_has_calls([ - call._post( - '/plan/', - json={ + ] + } + } + + correct_response = Plan( + id=None, + fromICAO="EHAM", + toICAO="KJFK", + fromName="Schiphol", + toName="John F Kennedy Intl", + user=None, + route=Route([ + RouteNode(**{ + "ident": "EHAM", + "type": "APT", + "lat": 52.31485, + "lon": 4.75812, + "alt": 0, + "name": "Schiphol", + "via": None}), + RouteNode(**{ + "ident": "KJFK", + "type": "APT", + "lat": 40.63990, + "lon": -73.77666, + "alt": 0, + "name": "John F Kennedy Intl", + "via": None})])) + + request_data = Plan( + id=None, + fromICAO="EHAM", + toICAO="KJFK", + fromName="Schiphol", + toName="John F Kennedy Intl", + user=None, + route=Route([ + RouteNode(**{ + "ident": "EHAM", + "type": "APT", + "lat": 52.31485, + "lon": 4.75812, + "alt": 0, + "name": "Schiphol", + "via": None}), + RouteNode(**{ + "ident": "KJFK", + "type": "APT", + "lat": 40.63990, + "lon": -73.77666, + "alt": 0, + "name": "John F Kennedy Intl", + "via": None})])) + correct_call = { + 'path':'/plan/', + 'json':{ + 'id': None, + 'fromICAO': 'EHAM', + 'toICAO': 'KJFK', + 'fromName': 'Schiphol', + 'toName': 'John F Kennedy Intl', + 'flightNumber': None, + 'distance': None, + 'maxAltitude': None, + 'waypoints': None, + 'likes': None, + 'downloads': None, + 'popularity': None, + 'notes': None, + 'encodedPolyline': None, + 'createdAt': None, + 'updatedAt': None, + 'tags': None, + 'user': None, + 'application': None, + 'route': { + 'nodes': [ + {'ident': 'EHAM', + 'type': 'APT', + 'lat': 52.31485, + 'lon': 4.75812, 'id': None, - 'fromICAO': 'EHAM', - 'toICAO': 'KJFK', - 'fromName': 'Schiphol', - 'toName': 'John F Kennedy Intl', - 'flightNumber': None, - 'distance': None, - 'maxAltitude': None, - 'waypoints': None, - 'likes': None, - 'downloads': None, - 'popularity': None, - 'notes': None, - 'encodedPolyline': None, - 'createdAt': None, - 'updatedAt': None, - 'tags': None, - 'user': None, - 'application': None, - 'route': { - 'nodes': [ - {'ident': 'EHAM', - 'type': 'APT', - 'lat': 52.31485, - 'lon': 4.75812, - 'id': None, - 'alt': 0, - 'name': 'Schiphol', - 'via': None}, - {'ident': 'KJFK', - 'type': 'APT', - 'lat': 40.6399, - 'lon': -73.77666, - 'id': None, - 'alt': 0, - 'name': 'John F Kennedy Intl', - 'via': None}], - 'eastLevels': None, - 'westLevels': None}, - 'cycle': None}, - return_format='native')]) - - correct_response = Plan( - id=None, - fromICAO="EHAM", - toICAO="KJFK", - fromName="Schiphol", - toName="John F Kennedy Intl", - user=None, - route=Route([ - RouteNode(**{ - "ident": "EHAM", - "type": "APT", - "lat": 52.31485, - "lon": 4.75812, - "alt": 0, - "name": "Schiphol", - "via": None}), - RouteNode(**{ - "ident": "KJFK", - "type": "APT", - "lat": 40.63990, - "lon": -73.77666, - "alt": 0, - "name": "John F Kennedy Intl", - "via": None})])) - - # check PlanAPI method decoded data correctly for given response - assert response == correct_response - - def test_plan_delete(self): - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._delete.return_value = {"message": "OK", - "errors": None} - - sub_instance = PlanAPI(instance) - response = sub_instance.delete(62493) - # check PlanAPI method made the correct request of FlightPlanDB - instance.assert_has_calls([call._delete('/plan/62493')]) - - correct_response = StatusResponse(message="OK", errors=None) - - # check PlanAPI method decoded data correctly for given response - assert response == correct_response - - def test_plan_edit(self): - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._patch.return_value = { - "id": None, - "fromICAO": "EHAM", - "toICAO": "KJFK", - "fromName": "Schiphol", - "toName": "John F Kennedy Intl", - "route": { - "nodes": [ - { - "ident": "EHAM", - "type": "APT", - "lat": 52.31485, - "lon": 4.75812, - "alt": 0, - "name": "Schiphol", - "via": None - }, - { - "ident": "KJFK", - "type": "APT", - "lat": 40.63990, - "lon": -73.77666, - "alt": 0, - "name": "John F Kennedy Intl", - "via": None - } - ] - } - } - sub_instance = PlanAPI(instance) - response = sub_instance.edit( - Plan( - id=None, - fromICAO="EHAM", - toICAO="KJFK", - fromName="Schiphol", - toName="John F Kennedy Intl", - user=None, - route=Route([ - RouteNode(**{ - "ident": "EHAM", - "type": "APT", - "lat": 52.31485, - "lon": 4.75812, - "alt": 0, - "name": "Schiphol", - "via": None - }), - RouteNode(**{ - "ident": "KJFK", - "type": "APT", - "lat": 40.63990, - "lon": -73.77666, - "alt": 0, - "name": "John F Kennedy Intl", - "via": None - }) - ]) - ) - ) - # check PlanAPI method made the correct request of FlightPlanDB - instance.assert_has_calls( - [call._patch( - '/plan/None', - json={ + 'alt': 0, + 'name': 'Schiphol', + 'via': None}, + {'ident': 'KJFK', + 'type': 'APT', + 'lat': 40.6399, + 'lon': -73.77666, 'id': None, - 'fromICAO': 'EHAM', - 'toICAO': 'KJFK', - 'fromName': 'Schiphol', - 'toName': 'John F Kennedy Intl', - 'flightNumber': None, - 'distance': None, - 'maxAltitude': None, - 'waypoints': None, - 'likes': None, - 'downloads': None, - 'popularity': None, - 'notes': None, - 'encodedPolyline': None, - 'createdAt': None, - 'updatedAt': None, - 'tags': None, - 'user': None, - 'application': None, - 'route': { - 'nodes': [ - { - 'ident': 'EHAM', 'type': 'APT', - 'lat': 52.31485, 'lon': 4.75812, - 'id': None, 'alt': 0, 'name': 'Schiphol', - 'via': None - }, - { - 'ident': 'KJFK', 'type': 'APT', - 'lat': 40.6399, 'lon': -73.77666, - 'id': None, 'alt': 0, - 'name': 'John F Kennedy Intl', - 'via': None - } - ], - 'eastLevels': None, - 'westLevels': None - }, - 'cycle': None - } - )] - ) - - correct_response = Plan( - id=None, - fromICAO="EHAM", - toICAO="KJFK", - fromName="Schiphol", - toName="John F Kennedy Intl", - user=None, - route=Route([ - RouteNode(**{ - "ident": "EHAM", - "type": "APT", - "lat": 52.31485, - "lon": 4.75812, - "alt": 0, - "name": "Schiphol", - "via": None - }), - RouteNode(**{ - "ident": "KJFK", - "type": "APT", - "lat": 40.63990, - "lon": -73.77666, - "alt": 0, - "name": "John F Kennedy Intl", - "via": None - }) - ]) - ) - - # check PlanAPI method decoded data correctly for given response - assert response == correct_response - - def test_plan_search(self): - - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - mock_response = [ + 'alt': 0, + 'name': 'John F Kennedy Intl', + 'via': None}], + 'eastLevels': None, + 'westLevels': None}, + 'cycle': None}, + 'return_format':'native', + 'key':None + } + + def patched_post(self, path, return_format, json, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.plan.PlanAPI, + attribute="_post", + new=patched_post) + instance = flightplandb.submodules.plan.PlanAPI() + spy = mocker.spy(instance, "_post") + + response = instance.create(request_data) + # check that PlanAPI method decoded data correctly for given response + assert response == correct_response + # check that PlanAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(**correct_call) + + +def test_plan_delete(mocker): + json_response = { + "message": "OK", + "errors": None + } + + correct_response = StatusResponse(message="OK", errors=None) + + def patched_delete(self, path, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.plan.PlanAPI, + attribute="_delete", + new=patched_delete) + instance = flightplandb.submodules.plan.PlanAPI() + spy = mocker.spy(instance, "_delete") + + response = instance.delete(62493) + # check that TagsAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/plan/62493', key=None) + # check that TagsAPI method decoded data correctly for given response + assert response == correct_response + + +def test_plan_edit(mocker): + json_response = { + "id": 23896, + "fromICAO": "EHAM", + "toICAO": "KJFK", + "fromName": "Schiphol", + "toName": "John F Kennedy Intl", + "route": { + "nodes": [ { - 'application': None, - 'createdAt': '2020-09-30T21:22:37.000Z', - 'cycle': { - 'id': 31, - 'ident': 'FPD2009', - 'release': 9, - 'year': 20}, - 'distance': 76.676565810015, - 'downloads': 2, - 'encodedPolyline': 'slg~Haoa\\_fiA{xlAkotC{xcB', - 'flightNumber': None, - 'fromICAO': 'EHAM', - 'fromName': 'Amsterdam Schiphol', - 'id': 3491827, - 'likes': 0, - 'maxAltitude': 9600, - 'notes': 'foo', - 'popularity': 1601846557, - 'tags': ['generated'], - 'toICAO': 'EHAL', - 'toName': 'Ameland', - 'updatedAt': '2020-09-30T21:22:37.000Z', - 'user': None, - 'waypoints': 3}, - + "ident": "EHAM", + "type": "APT", + "lat": 52.31485, + "lon": 4.75812, + "alt": 0, + "name": "Schiphol", + "via": None + }, { - 'application': None, - 'createdAt': '2018-09-08T12:23:04.000Z', - 'cycle': { - 'id': 5, - 'ident': 'FPD1809', - 'release': 9, - 'year': 18}, - 'distance': 76.44654421193701, - 'downloads': 0, - 'encodedPolyline': 'slg~Haoa\\{hlC}|xBolqAytw@', - 'flightNumber': None, - 'fromICAO': 'EHAM', - 'fromName': 'Amsterdam Schiphol Airport', - 'id': 1295630, - 'likes': 0, - 'maxAltitude': 7700, - 'notes': 'foo', - 'popularity': 1536409384, - 'tags': ['generated'], - 'toICAO': 'EHAL', - 'toName': 'Ameland', - 'updatedAt': '2018-09-08T12:23:04.000Z', - 'user': None, - 'waypoints': 3} - ] - instance._getiter.return_value = (i for i in mock_response) - - sub_instance = PlanAPI(instance) - response = sub_instance.search( - PlanQuery( - fromICAO="EHAM", - toICAO="EHAL"), - limit=2) - - correct_response_list = [ - Plan( - id=3491827, - fromICAO='EHAM', - toICAO='EHAL', - fromName='Amsterdam Schiphol', - toName='Ameland', - flightNumber=None, - distance=76.676565810015, - maxAltitude=9600, - waypoints=3, - likes=0, - downloads=2, - popularity=1601846557, - notes='foo', - encodedPolyline='slg~Haoa\\_fiA{xlAkotC{xcB', - createdAt=datetime.datetime(2020, 9, 30, 21, 22, 37, - tzinfo=tzutc()), - updatedAt=datetime.datetime(2020, 9, 30, 21, 22, 37, - tzinfo=tzutc()), - tags=['generated'], user=None, application=None, - route=None, - cycle=Cycle(id=31, ident='FPD2009', year=20, release=9)), - - Plan( - id=1295630, - fromICAO='EHAM', - toICAO='EHAL', - fromName='Amsterdam Schiphol Airport', - toName='Ameland', - flightNumber=None, - distance=76.44654421193701, - maxAltitude=7700, - waypoints=3, - likes=0, - downloads=0, - popularity=1536409384, - notes='foo', - encodedPolyline='slg~Haoa\\{hlC}|xBolqAytw@', - createdAt=datetime.datetime(2018, 9, 8, 12, 23, 4, - tzinfo=tzutc()), - updatedAt=datetime.datetime(2018, 9, 8, 12, 23, 4, - tzinfo=tzutc()), - tags=['generated'], - user=None, - application=None, - route=None, - cycle=Cycle(id=5, ident='FPD1809', year=18, release=9)) - + "ident": "KJFK", + "type": "APT", + "lat": 40.63990, + "lon": -73.77666, + "alt": 0, + "name": "John F Kennedy Intl", + "via": None + } ] + } + } + + correct_response = Plan( + id=23896, + fromICAO="EHAM", + toICAO="KJFK", + fromName="Schiphol", + toName="John F Kennedy Intl", + user=None, + route=Route([ + RouteNode(**{ + "ident": "EHAM", + "type": "APT", + "lat": 52.31485, + "lon": 4.75812, + "alt": 0, + "name": "Schiphol", + "via": None + }), + RouteNode(**{ + "ident": "KJFK", + "type": "APT", + "lat": 40.63990, + "lon": -73.77666, + "alt": 0, + "name": "John F Kennedy Intl", + "via": None + }) + ]) + ) + + request_data = Plan( + id=23896, + fromICAO="EHAM", + toICAO="KJFK", + fromName="Schiphol", + toName="John F Kennedy Intl", + user=None, + route=Route([ + RouteNode(**{ + "ident": "EHAM", + "type": "APT", + "lat": 52.31485, + "lon": 4.75812, + "alt": 0, + "name": "Schiphol", + "via": None + }), + RouteNode(**{ + "ident": "KJFK", + "type": "APT", + "lat": 40.63990, + "lon": -73.77666, + "alt": 0, + "name": "John F Kennedy Intl", + "via": None + }) + ]) + ) + correct_call = { + 'path':'/plan/23896', + 'json':{ + 'id': 23896, + 'fromICAO': 'EHAM', + 'toICAO': 'KJFK', + 'fromName': 'Schiphol', + 'toName': 'John F Kennedy Intl', + 'flightNumber': None, + 'distance': None, + 'maxAltitude': None, + 'waypoints': None, + 'likes': None, + 'downloads': None, + 'popularity': None, + 'notes': None, + 'encodedPolyline': None, + 'createdAt': None, + 'updatedAt': None, + 'tags': None, + 'user': None, + 'application': None, + 'route': { + 'nodes': [ + {'ident': 'EHAM', + 'type': 'APT', + 'lat': 52.31485, + 'lon': 4.75812, + 'id': None, + 'alt': 0, + 'name': 'Schiphol', + 'via': None}, + {'ident': 'KJFK', + 'type': 'APT', + 'lat': 40.6399, + 'lon': -73.77666, + 'id': None, + 'alt': 0, + 'name': 'John F Kennedy Intl', + 'via': None}], + 'eastLevels': None, + 'westLevels': None}, + 'cycle': None}, + 'return_format':'native', + 'key':None + } + + def patched_patch(self, path, return_format, json, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.plan.PlanAPI, + attribute="_patch", + new=patched_patch) + instance = flightplandb.submodules.plan.PlanAPI() + spy = mocker.spy(instance, "_patch") + + response = instance.edit(plan=request_data, return_format="native", key=None) + # check that PlanAPI method decoded data correctly for given response + assert response == correct_response + # check that PlanAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(**correct_call) + + +def test_plan_search(mocker): + json_response = [ + { + 'application': None, + 'createdAt': '2020-09-30T21:22:37.000Z', + 'cycle': { + 'id': 31, + 'ident': 'FPD2009', + 'release': 9, + 'year': 20}, + 'distance': 76.676565810015, + 'downloads': 2, + 'encodedPolyline': 'slg~Haoa\\_fiA{xlAkotC{xcB', + 'flightNumber': None, + 'fromICAO': 'EHAM', + 'fromName': 'Amsterdam Schiphol', + 'id': 3491827, + 'likes': 0, + 'maxAltitude': 9600, + 'notes': 'foo', + 'popularity': 1601846557, + 'tags': ['generated'], + 'toICAO': 'EHAL', + 'toName': 'Ameland', + 'updatedAt': '2020-09-30T21:22:37.000Z', + 'user': None, + 'waypoints': 3}, + + { + 'application': None, + 'createdAt': '2018-09-08T12:23:04.000Z', + 'cycle': { + 'id': 5, + 'ident': 'FPD1809', + 'release': 9, + 'year': 18}, + 'distance': 76.44654421193701, + 'downloads': 0, + 'encodedPolyline': 'slg~Haoa\\{hlC}|xBolqAytw@', + 'flightNumber': None, + 'fromICAO': 'EHAM', + 'fromName': 'Amsterdam Schiphol Airport', + 'id': 1295630, + 'likes': 0, + 'maxAltitude': 7700, + 'notes': 'foo', + 'popularity': 1536409384, + 'tags': ['generated'], + 'toICAO': 'EHAL', + 'toName': 'Ameland', + 'updatedAt': '2018-09-08T12:23:04.000Z', + 'user': None, + 'waypoints': 3} + ] + + correct_response_list = [ + Plan( + id=3491827, + fromICAO='EHAM', + toICAO='EHAL', + fromName='Amsterdam Schiphol', + toName='Ameland', + flightNumber=None, + distance=76.676565810015, + maxAltitude=9600, + waypoints=3, + likes=0, + downloads=2, + popularity=1601846557, + notes='foo', + encodedPolyline='slg~Haoa\\_fiA{xlAkotC{xcB', + createdAt=datetime.datetime(2020, 9, 30, 21, 22, 37, + tzinfo=tzutc()), + updatedAt=datetime.datetime(2020, 9, 30, 21, 22, 37, + tzinfo=tzutc()), + tags=['generated'], user=None, application=None, + route=None, + cycle=Cycle(id=31, ident='FPD2009', year=20, release=9)), + + Plan( + id=1295630, + fromICAO='EHAM', + toICAO='EHAL', + fromName='Amsterdam Schiphol Airport', + toName='Ameland', + flightNumber=None, + distance=76.44654421193701, + maxAltitude=7700, + waypoints=3, + likes=0, + downloads=0, + popularity=1536409384, + notes='foo', + encodedPolyline='slg~Haoa\\{hlC}|xBolqAytw@', + createdAt=datetime.datetime(2018, 9, 8, 12, 23, 4, + tzinfo=tzutc()), + updatedAt=datetime.datetime(2018, 9, 8, 12, 23, 4, + tzinfo=tzutc()), + tags=['generated'], + user=None, + application=None, + route=None, + cycle=Cycle(id=5, ident='FPD1809', year=18, release=9)) + + ] + correct_calls = [mocker.call( + path='/search/plans', + sort='created', + params={ + 'q': None, + 'From': None, + 'to': None, + 'fromICAO': 'EHAM', + 'toICAO': 'EHAL', + 'fromName': None, + 'toName': None, + 'flightNumber': None, + 'distanceMin': None, + 'distanceMax': None, + 'tags': None, + 'includeRoute': None + }, + limit=2, + key=None)] + + def patched_getiter(self, path, sort="created", params=None, limit=100, key=None): + return (i for i in json_response) + + mocker.patch.object( + target=flightplandb.submodules.plan.PlanAPI, + attribute="_getiter", + new=patched_getiter) + instance = flightplandb.submodules.plan.PlanAPI() + spy = mocker.spy(instance, "_getiter") + + response = instance.search( + PlanQuery( + fromICAO="EHAM", + toICAO="EHAL"), + limit=2) + # check that PlanAPI method decoded data correctly for given response + assert list(i for i in response) == correct_response_list + # check that PlanAPI method made correct request of FlightPlanDB + spy.assert_has_calls(correct_calls) + + +def test_plan_like(mocker): + json_response = { + "message": "Not Found", + "errors": None + } + + correct_response = StatusResponse(message='Not Found', errors=None) + + def patched_post(self, path, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.plan.PlanAPI, + attribute="_post", + new=patched_post) + instance = flightplandb.submodules.plan.PlanAPI() + spy = mocker.spy(instance, "_post") + + response = instance.like(42) + # check that TagsAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/plan/42/like', key=None) + # check that TagsAPI method decoded data correctly for given response + assert response == correct_response + + +def test_plan_unlike(mocker): + json_response = { + "message": "OK", + "errors": None + } + + correct_response = True + + def patched_delete(self, path, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.plan.PlanAPI, + attribute="_delete", + new=patched_delete) + instance = flightplandb.submodules.plan.PlanAPI() + spy = mocker.spy(instance, "_delete") + + response = instance.unlike(42) + # check that TagsAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/plan/42/like', key=None) + # check that TagsAPI method decoded data correctly for given response + assert response == correct_response + + +def test_plan_has_liked(mocker): + json_response = { + "message": "OK", + "errors": None + } + + correct_response = True + + def patched_get(self, path, ignore_statuses=None, key=None): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.plan.PlanAPI, + attribute="_get", + new=patched_get) + instance = flightplandb.submodules.plan.PlanAPI() + spy = mocker.spy(instance, "_get") + + response = instance.has_liked(42) + # check that TagsAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/plan/42/like', ignore_statuses=[404], key=None) + # check that TagsAPI method decoded data correctly for given response + assert response == correct_response + + +def test_plan_generate(mocker): + json_response = { + 'application': None, + 'createdAt': '2021-04-28T19:55:45.000Z', + 'cycle': { + 'id': 38, + 'ident': 'FPD2104', + 'release': 4, + 'year': 21}, + 'distance': 36.666306664518004, + 'downloads': 0, + 'encodedPolyline': '_dgeIybta@niaA~vdD', + 'flightNumber': None, + 'fromICAO': 'EHAL', + 'fromName': 'Ameland', + 'id': 4179148, + 'likes': 0, + 'maxAltitude': 0, + 'notes': 'Basic altitude profile:\n' + '- Ascent Rate: 2500ft/min\n' + '- Ascent Speed: 250kts\n' + '- Cruise Altitude: 35000ft\n' + '- Cruise Speed: 420kts\n' + '- Descent Rate: 1500ft/min\n' + '- Descent Speed: 250kts\n' + '\n' + 'Options:\n' + '- Use NATs: yes\n' + '- Use PACOTS: yes\n' + '- Use low airways: yes\n' + '- Use high airways: yes\n', + 'popularity': 1619639745, + 'tags': ['generated'], + 'toICAO': 'EHTX', + 'toName': 'Texel', + 'updatedAt': '2021-04-28T19:55:45.000Z', + 'user': { + 'gravatarHash': '3bcb4f39a24700e081f49c3d2d43d277', + 'id': 18990, + 'location': None, + 'username': 'discordflightplannerbot'}, + 'waypoints': 2 + } + + request_data = GenerateQuery( + fromICAO="EHAL", + toICAO="EHTX" + ) + + correct_response = Plan( + id=4179148, + fromICAO='EHAL', + toICAO='EHTX', + fromName='Ameland', + toName='Texel', + flightNumber=None, + distance=36.666306664518004, + maxAltitude=0, + waypoints=2, + likes=0, + downloads=0, + popularity=1619639745, + notes='Basic altitude profile:\n' + '- Ascent Rate: 2500ft/min\n' + '- Ascent Speed: 250kts\n' + '- Cruise Altitude: 35000ft\n' + '- Cruise Speed: 420kts\n' + '- Descent Rate: 1500ft/min\n' + '- Descent Speed: 250kts\n\nOptions:\n' + '- Use NATs: yes\n' + '- Use PACOTS: yes\n' + '- Use low airways: yes\n' + '- Use high airways: yes\n', + encodedPolyline='_dgeIybta@niaA~vdD', + createdAt=datetime.datetime( + 2021, 4, 28, 19, 55, 45, tzinfo=tzutc()), + updatedAt=datetime.datetime( + 2021, 4, 28, 19, 55, 45, tzinfo=tzutc()), + tags=['generated'], + user=User( + id=18990, + username='discordflightplannerbot', + location=None, + gravatarHash='3bcb4f39a24700e081f49c3d2d43d277', + joined=None, + lastSeen=None, + plansCount=0, + plansDistance=0.0, + plansDownloads=0, + plansLikes=0), + application=None, + route=None, + cycle=Cycle( + id=38, + ident='FPD2104', + year=21, + release=4) + ) + + correct_call = { + "path":'/auto/generate', + "json":{ + 'fromICAO': 'EHAL', + 'toICAO': 'EHTX', + 'useNAT': True, + 'usePACOT': True, + 'useAWYLO': True, + 'useAWYHI': True, + 'cruiseAlt': 35000, + 'cruiseSpeed': 420, + 'ascentRate': 2500, + 'ascentSpeed': 250, + 'descentRate': 1500, + 'descentSpeed': 250 + }, + "return_format":"native", + "key":None + } + + def patched_post(self, path, return_format, json, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.plan.PlanAPI, + attribute="_post", + new=patched_post) + instance = flightplandb.submodules.plan.PlanAPI() + spy = mocker.spy(instance, "_post") + + response = instance.generate(request_data) + # check that PlanAPI method decoded data correctly for given response + assert response == correct_response + # check that PlanAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(**correct_call) + + +def test_plan_decode(mocker): + json_response = { + "id":4708699, + "fromICAO":"KSAN", + "toICAO":"KDEN", + "fromName":"San Diego Intl", + "toName":"Denver Intl", + "flightNumber":None, + "distance":757.3434118878, + "maxAltitude":0, + "waypoints":5, + "likes":0, + "downloads":0, + "popularity":1635191202, + "notes":"Requested: KSAN BROWS TRM LRAIN KDEN", + "encodedPolyline":"_hxfEntgjUr_S_`qAgvaEocvBsksPgn_a@_~kSwgbc@", + "createdAt":"2021-10-25T19:46:42.000Z", + "updatedAt":"2021-10-25T19:46:42.000Z", + "tags":["decoded"], + 'user': { + 'gravatarHash': '3bcb4f39a24700e081f49c3d2d43d277', + 'id': 18990, + 'location': None, + 'username': 'discordflightplannerbot'}, + "application":None, + "cycle":{ + "id":40, + "ident":"FPD2106", + "year":21, + "release":6 + } + } + + request_data = {"KSAN BROWS TRM LRAIN KDEN"} + + correct_response = Plan( + id=4708699, + fromICAO='KSAN', + toICAO='KDEN', + fromName='San Diego Intl', + toName='Denver Intl', + flightNumber=None, + distance=757.3434118878, + maxAltitude=0, + waypoints=5, + likes=0, + downloads=0, + popularity=1635191202, + notes='Requested: KSAN BROWS TRM LRAIN KDEN', + encodedPolyline='_hxfEntgjUr_S_`qAgvaEocvBsksPgn_a@_~kSwgbc@', + createdAt=datetime.datetime( + 2021, 10, 25, 19, 46, 42, + tzinfo=tzutc() + ), + updatedAt=datetime.datetime( + 2021, 10, 25, 19, 46, 42, + tzinfo=tzutc() + ), + tags=['decoded'], + user=User( + id=18990, + username='discordflightplannerbot', + location=None, + gravatarHash='3bcb4f39a24700e081f49c3d2d43d277', + joined=None, + lastSeen=None, + plansCount=0, + plansDistance=0.0, + plansDownloads=0, + plansLikes=0), + application=None, + route=None, + cycle=Cycle( + id=40, + ident='FPD2106', + year=21, + release=6 + ) + ) - # check PlanAPI method decoded data correctly for given response - assert list(i for i in response) == correct_response_list - # check that PlanAPI method made the correct request of FlightPlanDB - - instance.assert_has_calls([call._getiter( - '/search/plans', - sort='created', - params={ - 'q': None, - 'From': None, - 'to': None, - 'fromICAO': 'EHAM', - 'toICAO': 'EHAL', - 'fromName': None, - 'toName': None, - 'flightNumber': None, - 'distanceMin': None, - 'distanceMax': None, - 'tags': None, - 'includeRoute': None, - 'limit': None}, - limit=2)]) - - def test_plan_like(self): - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._post.return_value = {"message": "Not Found", - "errors": None} - - sub_instance = PlanAPI(instance) - response = sub_instance.like(42) - - # check PlanAPI method made the correct request of FlightPlanDB - instance.assert_has_calls([call._post('/plan/42/like')]) - - correct_response = StatusResponse(message='Not Found', errors=None) - - # check PlanAPI method decoded data correctly for given response - assert response == correct_response - - def test_plan_unlike(self): - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._delete.return_value = {"message": "OK", - "errors": None} - - sub_instance = PlanAPI(instance) - response = sub_instance.unlike(42) - # check PlanAPI method made the correct request of FlightPlanDB - instance.assert_has_calls([ - call._delete('/plan/42/like')]) - - correct_response = True - - # check PlanAPI method decoded data correctly for given response - assert response == correct_response - - def test_plan_has_liked(self): - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._get.return_value = {"message": "OK", - "errors": None} - - sub_instance = PlanAPI(instance) - response = sub_instance.has_liked(42) - # check PlanAPI method made the correct request of FlightPlanDB - instance.assert_has_calls([ - call._get('/plan/42/like', ignore_statuses=[404])]) - - correct_response = True - - # check PlanAPI method decoded data correctly for given response - assert response == correct_response - - def test_plan_generate(self): - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._post.return_value = { - 'application': None, - 'createdAt': '2021-04-28T19:55:45.000Z', - 'cycle': { - 'id': 38, - 'ident': 'FPD2104', - 'release': 4, - 'year': 21}, - 'distance': 36.666306664518004, - 'downloads': 0, - 'encodedPolyline': '_dgeIybta@niaA~vdD', - 'flightNumber': None, - 'fromICAO': 'EHAL', - 'fromName': 'Ameland', - 'id': 4179148, - 'likes': 0, - 'maxAltitude': 0, - 'notes': 'Basic altitude profile:\n' - '- Ascent Rate: 2500ft/min\n' - '- Ascent Speed: 250kts\n' - '- Cruise Altitude: 35000ft\n' - '- Cruise Speed: 420kts\n' - '- Descent Rate: 1500ft/min\n' - '- Descent Speed: 250kts\n' - '\n' - 'Options:\n' - '- Use NATs: yes\n' - '- Use PACOTS: yes\n' - '- Use low airways: yes\n' - '- Use high airways: yes\n', - 'popularity': 1619639745, - 'tags': ['generated'], - 'toICAO': 'EHTX', - 'toName': 'Texel', - 'updatedAt': '2021-04-28T19:55:45.000Z', - 'user': { - 'gravatarHash': '3bcb4f39a24700e081f49c3d2d43d277', - 'id': 18990, - 'location': None, - 'username': 'discordflightplannerbot'}, - 'waypoints': 2} - - sub_instance = PlanAPI(instance) - request_query = GenerateQuery(fromICAO="EHAL", toICAO="EHTX") - response = sub_instance.generate(request_query) - # check PlanAPI method made the correct request of FlightPlanDB - correct_calls = [ - call._post( - '/auto/generate', - json={ - 'fromICAO': 'EHAL', - 'toICAO': 'EHTX', - 'useNAT': True, - 'usePACOT': True, - 'useAWYLO': True, - 'useAWYHI': True, - 'cruiseAlt': 35000, - 'cruiseSpeed': 420, - 'ascentRate': 2500, - 'ascentSpeed': 250, - 'descentRate': 1500, - 'descentSpeed': 250})] - - instance.assert_has_calls(correct_calls) - - correct_response = Plan( - id=4179148, - fromICAO='EHAL', - toICAO='EHTX', - fromName='Ameland', - toName='Texel', - flightNumber=None, - distance=36.666306664518004, - maxAltitude=0, - waypoints=2, - likes=0, - downloads=0, - popularity=1619639745, - notes='Basic altitude profile:\n' - '- Ascent Rate: 2500ft/min\n' - '- Ascent Speed: 250kts\n' - '- Cruise Altitude: 35000ft\n' - '- Cruise Speed: 420kts\n' - '- Descent Rate: 1500ft/min\n' - '- Descent Speed: 250kts\n\nOptions:\n' - '- Use NATs: yes\n' - '- Use PACOTS: yes\n' - '- Use low airways: yes\n' - '- Use high airways: yes\n', - encodedPolyline='_dgeIybta@niaA~vdD', - createdAt=datetime.datetime( - 2021, 4, 28, 19, 55, 45, tzinfo=tzutc()), - updatedAt=datetime.datetime( - 2021, 4, 28, 19, 55, 45, tzinfo=tzutc()), - tags=['generated'], - user=User( - id=18990, - username='discordflightplannerbot', - location=None, - gravatarHash='3bcb4f39a24700e081f49c3d2d43d277', - joined=None, - lastSeen=None, - plansCount=0, - plansDistance=0.0, - plansDownloads=0, - plansLikes=0), - application=None, - route=None, - cycle=Cycle( - id=38, - ident='FPD2104', - year=21, - release=4)) - - # check PlanAPI method decoded data correctly for given response - assert response == correct_response - - def test_plan_decode(self): - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._post.return_value = { - 'application': None, - 'createdAt': '2021-04-28T20:33:57.000Z', - 'cycle': { - 'id': 38, 'ident': 'FPD2104', 'release': 4, 'year': 21}, - 'distance': 757.3434118878, - 'downloads': 0, - 'encodedPolyline': '_hxfEntgjUr_S_`qAgvaEo' - 'cvBsksPgn_a@_~kSwgbc@', - 'flightNumber': None, - 'fromICAO': 'KSAN', - 'fromName': 'San Diego Intl', - 'id': 4179222, - 'likes': 0, - 'maxAltitude': 0, - 'notes': 'Requested: KSAN BROWS TRM LRAIN KDEN', - 'popularity': 1619642037, - 'tags': ['decoded'], - 'toICAO': 'KDEN', - 'toName': 'Denver Intl', - 'updatedAt': '2021-04-28T20:33:57.000Z', - 'user': { - 'gravatarHash': '3bcb4f39a24700e081f49c3d2d43d277', - 'id': 18990, - 'location': None, - 'username': 'discordflightplannerbot'}, - 'waypoints': 5} - - sub_instance = PlanAPI(instance) - request_query = GenerateQuery(fromICAO="EHAL", toICAO="EHTX") - response = sub_instance.generate(request_query) - # check PlanAPI method made the correct request of FlightPlanDB - correct_calls = [ - call._post( - '/auto/generate', - json={ - 'fromICAO': 'EHAL', - 'toICAO': 'EHTX', - 'useNAT': True, - 'usePACOT': True, - 'useAWYLO': True, - 'useAWYHI': True, - 'cruiseAlt': 35000, - 'cruiseSpeed': 420, - 'ascentRate': 2500, - 'ascentSpeed': 250, - 'descentRate': 1500, - 'descentSpeed': 250})] - - instance.assert_has_calls(correct_calls) - - correct_response = Plan( - id=4179222, - fromICAO='KSAN', - toICAO='KDEN', - fromName='San Diego Intl', - toName='Denver Intl', - flightNumber=None, - distance=757.3434118878, - maxAltitude=0, - waypoints=5, - likes=0, - downloads=0, - popularity=1619642037, - notes='Requested: KSAN BROWS TRM LRAIN KDEN', - encodedPolyline='_hxfEntgjUr_S_`qAgvaEocvBsksPgn_a@_~kSwgbc@', - createdAt=datetime.datetime( - 2021, 4, 28, 20, 33, 57, tzinfo=tzutc()), - updatedAt=datetime.datetime( - 2021, 4, 28, 20, 33, 57, tzinfo=tzutc()), - tags=['decoded'], - user=User( - id=18990, - username='discordflightplannerbot', - location=None, - gravatarHash='3bcb4f39a24700e081f49c3d2d43d277', - joined=None, - lastSeen=None, - plansCount=0, - plansDistance=0.0, - plansDownloads=0, - plansLikes=0), - application=None, - route=None, - cycle=Cycle( - id=38, - ident='FPD2104', - year=21, - release=4)) - - # check PlanAPI method decoded data correctly for given response - assert response == correct_response - - -if __name__ == "__main__": - main() + correct_call = { + "path":'/auto/decode', + "json":{ + 'route': { + 'KSAN BROWS TRM LRAIN KDEN' + } + }, + "key":None + } + + def patched_post(self, path, json, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.plan.PlanAPI, + attribute="_post", + new=patched_post) + instance = flightplandb.submodules.plan.PlanAPI() + spy = mocker.spy(instance, "_post") + + response = instance.decode(request_data) + # check that PlanAPI method decoded data correctly for given response + assert response == correct_response + # check that PlanAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(**correct_call) From 1177ccd9d5d2cab702d6b4be8fc6831fcbff75b6 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 25 Oct 2021 22:23:45 +0200 Subject: [PATCH 23/86] add pytest-mock dependency --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f65b14e..d66161a 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,8 @@ def get_version(rel_path): "sphinx-rtd-theme==1.0.0" ], "test": [ - "pytest==6.2.5" + "pytest>=6.2.5", + "pytest-mock>=3.6.1" ] }, python_requires='>=3.7.0', From 8c1ae4fbc4bbecb5c766c8e324e21bafb973e4f6 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 25 Oct 2021 22:32:45 +0200 Subject: [PATCH 24/86] add socket blocking for tests --- pytest.ini | 2 ++ setup.py | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..807c945 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --disable-socket \ No newline at end of file diff --git a/setup.py b/setup.py index d66161a..b60381e 100644 --- a/setup.py +++ b/setup.py @@ -47,8 +47,9 @@ def get_version(rel_path): "sphinx-rtd-theme==1.0.0" ], "test": [ - "pytest>=6.2.5", - "pytest-mock>=3.6.1" + "pytest~=6.2.5", + "pytest-mock~=3.6.1", + "pytest_socket~=0.4.1" ] }, python_requires='>=3.7.0', From 058f763017e22eb297d9f1a73f8db38f7d022aee Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 25 Oct 2021 22:50:13 +0200 Subject: [PATCH 25/86] fix flake8 --- src/flightplandb/__init__.py | 3 +- src/flightplandb/flightplandb.py | 5 -- src/flightplandb/submodules/__init__.py | 2 +- src/flightplandb/submodules/api.py | 7 +- src/flightplandb/submodules/plan.py | 2 +- tests/test_plan.py | 108 ++++++++++++------------ 6 files changed, 57 insertions(+), 70 deletions(-) diff --git a/src/flightplandb/__init__.py b/src/flightplandb/__init__.py index 31c30ab..88ea018 100644 --- a/src/flightplandb/__init__.py +++ b/src/flightplandb/__init__.py @@ -3,6 +3,5 @@ # Version of the flightplandb package __version__ = "0.4.2" -# from flightplandb.flightplandb import * # noqa: F403, F401 from flightplandb.datatypes import * # noqa: F403, F401 -from flightplandb.submodules import * +from flightplandb.submodules import * # noqa: F403, F401 diff --git a/src/flightplandb/flightplandb.py b/src/flightplandb/flightplandb.py index 0ebd973..a289f10 100644 --- a/src/flightplandb/flightplandb.py +++ b/src/flightplandb/flightplandb.py @@ -26,9 +26,6 @@ import json from flightplandb.exceptions import status_handler -from flightplandb.datatypes import StatusResponse - - # https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autoclass # https://github.com/python/cpython/blob/main/Lib/random.py#L792 @@ -425,5 +422,3 @@ def _getiter(self, path: str, # ...unless the result limit has been reached if num_results == limit: return - - diff --git a/src/flightplandb/submodules/__init__.py b/src/flightplandb/submodules/__init__.py index 067a5b1..88ddd43 100644 --- a/src/flightplandb/submodules/__init__.py +++ b/src/flightplandb/submodules/__init__.py @@ -1 +1 @@ -__all__ = ["api", "nav", "plan", "tags", "user", "weather"] \ No newline at end of file +__all__ = ["api", "nav", "plan", "tags", "user", "weather"] diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index 74562aa..39a113f 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -1,7 +1,8 @@ -from typing import List, Dict, Optional +from typing import Optional from flightplandb.flightplandb import FlightPlanDB from flightplandb.datatypes import StatusResponse + class API(FlightPlanDB): def _header_value(self, header_key: str, key: Optional[str] = None) -> str: """Gets header value for key @@ -21,7 +22,6 @@ def _header_value(self, header_key: str, key: Optional[str] = None) -> str: self.ping(key=key) # Make at least one request return self._header[header_key] - def version(self, key: Optional[str] = None) -> int: """API version that returned the response @@ -33,7 +33,6 @@ def version(self, key: Optional[str] = None) -> int: return int(self._header_value("X-API-Version", key=key)) - def units(self, key: Optional[str] = None) -> str: """The units system used for numeric values. https://flightplandatabase.com/dev/api#units @@ -46,7 +45,6 @@ def units(self, key: Optional[str] = None) -> str: return self._header_value("X-Units", key=key) - def limit_cap(self, key: Optional[str] = None) -> int: """The number of requests allowed per day, operated on an hourly rolling basis. i.e requests used between 19:00 and 20:00 will become available @@ -62,7 +60,6 @@ def limit_cap(self, key: Optional[str] = None) -> int: return int(self._header_value("X-Limit-Cap", key=key)) - def limit_used(self, key: Optional[str] = None) -> int: """The number of requests used in the current period by the presented API key or IP address diff --git a/src/flightplandb/submodules/plan.py b/src/flightplandb/submodules/plan.py index 6ab3b21..ed9a3be 100644 --- a/src/flightplandb/submodules/plan.py +++ b/src/flightplandb/submodules/plan.py @@ -228,7 +228,7 @@ def has_liked(self, id_: int, return sr.message != "Not Found" def like(self, id_: int, - key: Optional[str] = None) -> StatusResponse: + key: Optional[str] = None) -> StatusResponse: r"""Likes a flight plan. Requires authentication. diff --git a/tests/test_plan.py b/tests/test_plan.py index 15cd08d..e072684 100644 --- a/tests/test_plan.py +++ b/tests/test_plan.py @@ -1,7 +1,4 @@ -from unittest import TestCase, main -from unittest.mock import patch, call import flightplandb -from flightplandb.submodules.plan import PlanAPI from flightplandb.datatypes import ( Plan, PlanQuery, User, Route, GenerateQuery, RouteNode, Cycle, StatusResponse @@ -25,8 +22,7 @@ def test_plan_fetch(mocker): "downloads": 1, "popularity": 1, "notes": "", - "encodedPolyline": "aaf{E`|y}T|Ftf@px\\hpe@lnCxw " - "Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + "encodedPolyline": "aaf{E`|y}T|Ftf@px\\hpe@lnCxw Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", "createdAt": "2015-08-04T20:48:08.000Z", "updatedAt": "2015-08-04T20:48:08.000Z", "tags": [ @@ -54,8 +50,7 @@ def test_plan_fetch(mocker): downloads=1, popularity=1, notes="", - encodedPolyline="aaf{E`|y}T|Ftf@px\\hpe@lnCxw " - "Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", + encodedPolyline="aaf{E`|y}T|Ftf@px\\hpe@lnCxw Dbsk@rfx@vhjC`nnDd~f@zkv@nb~ChdmH", createdAt="2015-08-04T20:48:08.000Z", updatedAt="2015-08-04T20:48:08.000Z", tags=[ @@ -140,7 +135,7 @@ def test_plan_create(mocker): "alt": 0, "name": "John F Kennedy Intl", "via": None})])) - + request_data = Plan( id=None, fromICAO="EHAM", @@ -166,8 +161,8 @@ def test_plan_create(mocker): "name": "John F Kennedy Intl", "via": None})])) correct_call = { - 'path':'/plan/', - 'json':{ + 'path': '/plan/', + 'json': { 'id': None, 'fromICAO': 'EHAM', 'toICAO': 'KJFK', @@ -208,8 +203,8 @@ def test_plan_create(mocker): 'eastLevels': None, 'westLevels': None}, 'cycle': None}, - 'return_format':'native', - 'key':None + 'return_format': 'native', + 'key': None } def patched_post(self, path, return_format, json, key): @@ -313,7 +308,7 @@ def test_plan_edit(mocker): }) ]) ) - + request_data = Plan( id=23896, fromICAO="EHAM", @@ -343,8 +338,8 @@ def test_plan_edit(mocker): ]) ) correct_call = { - 'path':'/plan/23896', - 'json':{ + 'path': '/plan/23896', + 'json': { 'id': 23896, 'fromICAO': 'EHAM', 'toICAO': 'KJFK', @@ -385,8 +380,8 @@ def test_plan_edit(mocker): 'eastLevels': None, 'westLevels': None}, 'cycle': None}, - 'return_format':'native', - 'key':None + 'return_format': 'native', + 'key': None } def patched_patch(self, path, return_format, json, key): @@ -727,8 +722,8 @@ def test_plan_generate(mocker): ) correct_call = { - "path":'/auto/generate', - "json":{ + "path": '/auto/generate', + "json": { 'fromICAO': 'EHAL', 'toICAO': 'EHTX', 'useNAT': True, @@ -742,8 +737,8 @@ def test_plan_generate(mocker): 'descentRate': 1500, 'descentSpeed': 250 }, - "return_format":"native", - "key":None + "return_format": "native", + "key": None } def patched_post(self, path, return_format, json, key): @@ -765,34 +760,34 @@ def patched_post(self, path, return_format, json, key): def test_plan_decode(mocker): json_response = { - "id":4708699, - "fromICAO":"KSAN", - "toICAO":"KDEN", - "fromName":"San Diego Intl", - "toName":"Denver Intl", - "flightNumber":None, - "distance":757.3434118878, - "maxAltitude":0, - "waypoints":5, - "likes":0, - "downloads":0, - "popularity":1635191202, - "notes":"Requested: KSAN BROWS TRM LRAIN KDEN", - "encodedPolyline":"_hxfEntgjUr_S_`qAgvaEocvBsksPgn_a@_~kSwgbc@", - "createdAt":"2021-10-25T19:46:42.000Z", - "updatedAt":"2021-10-25T19:46:42.000Z", - "tags":["decoded"], + "id": 4708699, + "fromICAO": "KSAN", + "toICAO": "KDEN", + "fromName": "San Diego Intl", + "toName": "Denver Intl", + "flightNumber": None, + "distance": 757.3434118878, + "maxAltitude": 0, + "waypoints": 5, + "likes": 0, + "downloads": 0, + "popularity": 1635191202, + "notes": "Requested: KSAN BROWS TRM LRAIN KDEN", + "encodedPolyline": "_hxfEntgjUr_S_`qAgvaEocvBsksPgn_a@_~kSwgbc@", + "createdAt": "2021-10-25T19:46:42.000Z", + "updatedAt": "2021-10-25T19:46:42.000Z", + "tags": ["decoded"], 'user': { 'gravatarHash': '3bcb4f39a24700e081f49c3d2d43d277', 'id': 18990, 'location': None, 'username': 'discordflightplannerbot'}, - "application":None, - "cycle":{ - "id":40, - "ident":"FPD2106", - "year":21, - "release":6 + "application": None, + "cycle": { + "id": 40, + "ident": "FPD2106", + "year": 21, + "release": 6 } } @@ -832,25 +827,26 @@ def test_plan_decode(mocker): plansCount=0, plansDistance=0.0, plansDownloads=0, - plansLikes=0), - application=None, - route=None, - cycle=Cycle( - id=40, - ident='FPD2106', - year=21, - release=6 - ) + plansLikes=0 + ), + application=None, + route=None, + cycle=Cycle( + id=40, + ident='FPD2106', + year=21, + release=6 ) + ) correct_call = { - "path":'/auto/decode', - "json":{ + "path": '/auto/decode', + "json": { 'route': { 'KSAN BROWS TRM LRAIN KDEN' } }, - "key":None + "key": None } def patched_post(self, path, json, key): From 2d372b897f4441ad13b719e8b1685711b9213d94 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 28 Oct 2021 01:24:42 +0200 Subject: [PATCH 26/86] finish converting nav tests to pytest --- tests/test_nav.py | 962 +++++++++++++++++++++++++--------------------- 1 file changed, 531 insertions(+), 431 deletions(-) diff --git a/tests/test_nav.py b/tests/test_nav.py index 722434f..c91eb0d 100644 --- a/tests/test_nav.py +++ b/tests/test_nav.py @@ -1,5 +1,6 @@ from unittest import TestCase, main from unittest.mock import patch, call +import flightplandb from flightplandb.submodules.nav import NavAPI from flightplandb.datatypes import ( Airport, Timezone, Runway, RunwayEnds, @@ -10,442 +11,541 @@ from dateutil.tz import tzutc -class NavTest(TestCase): - - def test_airport_info(self): - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._get.return_value = { - 'ICAO': 'EHAL', - 'IATA': None, - 'name': 'Ameland', - 'regionName': 'Netherlands', - 'elevation': 11.00000001672, - 'lat': 53.4536, - 'lon': 5.67869, - 'magneticVariation': 1.8677087306751243, - 'timezone': { - 'name': 'Europe/Amsterdam', - 'offset': 7200}, - 'times': { - 'sunrise': '2021-04-26T04:14:10.584Z', - 'sunset': '2021-04-26T18:58:16.572Z', - 'dawn': '2021-04-26T03:34:40.249Z', - 'dusk': '2021-04-26T19:37:46.907Z'}, - 'runwayCount': 1, - 'runways': [ +def test_airport_info(mocker): + json_response = { + 'ICAO': 'EHAL', + 'IATA': None, + 'name': 'Ameland', + 'regionName': 'Netherlands', + 'elevation': 11.00000001672, + 'lat': 53.4536, + 'lon': 5.67869, + 'magneticVariation': 1.8677087306751243, + 'timezone': { + 'name': 'Europe/Amsterdam', + 'offset': 7200}, + 'times': { + 'sunrise': '2021-04-26T04:14:10.584Z', + 'sunset': '2021-04-26T18:58:16.572Z', + 'dawn': '2021-04-26T03:34:40.249Z', + 'dusk': '2021-04-26T19:37:46.907Z'}, + 'runwayCount': 1, + 'runways': [ + { + 'ident': '08', + 'width': 97.998687813, + 'length': 2627.1784816836002, + 'bearing': 87.4099, + 'surface': 'GRASS', + 'markings': ['VISUAL'], + 'lighting': [''], + 'thresholdOffset': 0, + 'overrunLength': 0, + 'ends': [ { 'ident': '08', - 'width': 97.998687813, - 'length': 2627.1784816836002, - 'bearing': 87.4099, - 'surface': 'GRASS', - 'markings': ['VISUAL'], - 'lighting': [''], - 'thresholdOffset': 0, - 'overrunLength': 0, - 'ends': [ - { - 'ident': '08', - 'lat': 53.4534, - 'lon': 5.67265}, - { - 'ident': '26', - 'lat': 53.4534, - 'lon': 53.4538}], - 'navaids': []}, + 'lat': 53.4534, + 'lon': 5.67265}, { 'ident': '26', - 'width': 97.998687813, - 'length': 2627.1784816836002, - 'bearing': 267.42, - 'surface': 'GRASS', - 'markings': ['NONE'], - 'lighting': [''], - 'thresholdOffset': 0, - 'overrunLength': 0, - 'ends': [ - { - 'ident': '26', - 'lat': 53.4538, - 'lon': 5.68473}, - { - 'ident': '08', - 'lat': 53.4538, - 'lon': 53.4534}], - 'navaids': []}], - 'frequencies': [ + 'lat': 53.4534, + 'lon': 53.4538}], + 'navaids': []}, + { + 'ident': '26', + 'width': 97.998687813, + 'length': 2627.1784816836002, + 'bearing': 267.42, + 'surface': 'GRASS', + 'markings': ['NONE'], + 'lighting': [''], + 'thresholdOffset': 0, + 'overrunLength': 0, + 'ends': [ { - 'type': 'TWR', - 'frequency': 118350000, - 'name': 'Ameland Radio'}], - 'weather': { - 'METAR': None, - 'TAF': None}} - sub_instance = NavAPI(instance) - response = sub_instance.airport("EHAL") - correct_response = Airport( - ICAO='EHAL', - IATA=None, - name='Ameland', - regionName='Netherlands', - elevation=11.00000001672, - lat=53.4536, - lon=5.67869, - magneticVariation=1.8677087306751243, - timezone=Timezone( - name='Europe/Amsterdam', - offset=7200), - times=Times( - sunrise=datetime.datetime( - 2021, 4, 26, 4, 14, 10, 584000, tzinfo=tzutc()), - sunset=datetime.datetime( - 2021, 4, 26, 18, 58, 16, 572000, tzinfo=tzutc()), - dawn=datetime.datetime( - 2021, 4, 26, 3, 34, 40, 249000, tzinfo=tzutc()), - dusk=datetime.datetime( - 2021, 4, 26, 19, 37, 46, 907000, tzinfo=tzutc())), - runwayCount=1, - runways=[ - Runway( + 'ident': '26', + 'lat': 53.4538, + 'lon': 5.68473}, + { + 'ident': '08', + 'lat': 53.4538, + 'lon': 53.4534}], + 'navaids': []}], + 'frequencies': [ + { + 'type': 'TWR', + 'frequency': 118350000, + 'name': 'Ameland Radio'}], + 'weather': { + 'METAR': None, + 'TAF': None}} + + correct_response = Airport( + ICAO='EHAL', + IATA=None, + name='Ameland', + regionName='Netherlands', + elevation=11.00000001672, + lat=53.4536, + lon=5.67869, + magneticVariation=1.8677087306751243, + timezone=Timezone( + name='Europe/Amsterdam', + offset=7200), + times=Times( + sunrise=datetime.datetime( + 2021, 4, 26, 4, 14, 10, 584000, tzinfo=tzutc()), + sunset=datetime.datetime( + 2021, 4, 26, 18, 58, 16, 572000, tzinfo=tzutc()), + dawn=datetime.datetime( + 2021, 4, 26, 3, 34, 40, 249000, tzinfo=tzutc()), + dusk=datetime.datetime( + 2021, 4, 26, 19, 37, 46, 907000, tzinfo=tzutc())), + runwayCount=1, + runways=[ + Runway( + ident='08', + width=97.998687813, + length=2627.1784816836002, + bearing=87.4099, + surface='GRASS', + markings=['VISUAL'], + lighting=[''], + thresholdOffset=0, + overrunLength=0, + ends=[ + RunwayEnds( ident='08', - width=97.998687813, - length=2627.1784816836002, - bearing=87.4099, - surface='GRASS', - markings=['VISUAL'], - lighting=[''], - thresholdOffset=0, - overrunLength=0, - ends=[ - RunwayEnds( - ident='08', - lat=53.4534, - lon=5.67265), - RunwayEnds( - ident='26', - lat=53.4534, - lon=53.4538)], - navaids=[]), - Runway( + lat=53.4534, + lon=5.67265), + RunwayEnds( ident='26', - width=97.998687813, - length=2627.1784816836002, - bearing=267.42, - surface='GRASS', - markings=['NONE'], - lighting=[''], - thresholdOffset=0, - overrunLength=0, - ends=[ - RunwayEnds( - ident='26', - lat=53.4538, - lon=5.68473), - RunwayEnds( - ident='08', - lat=53.4538, - lon=53.4534)], - navaids=[])], - frequencies=[ - Frequency( - type='TWR', - frequency=118350000, - name='Ameland Radio' - ) - ], - weather=Weather( - METAR=None, - TAF=None - ) + lat=53.4534, + lon=53.4538)], + navaids=[]), + Runway( + ident='26', + width=97.998687813, + length=2627.1784816836002, + bearing=267.42, + surface='GRASS', + markings=['NONE'], + lighting=[''], + thresholdOffset=0, + overrunLength=0, + ends=[ + RunwayEnds( + ident='26', + lat=53.4538, + lon=5.68473), + RunwayEnds( + ident='08', + lat=53.4538, + lon=53.4534)], + navaids=[])], + frequencies=[ + Frequency( + type='TWR', + frequency=118350000, + name='Ameland Radio' ) - # check that NavAPI method made correct request of FlightPlanDB - instance.assert_has_calls( - [call._get('/nav/airport/EHAL')]) - # check NavAPI method decoded data correctly for given response - assert response == correct_response - - def test_nats(self): - - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._get.return_value = [ - { - 'ident': 'A', - 'route': { - 'eastLevels': [], - 'nodes': [{'id': 8465100, - 'ident': 'RESNO', - 'lat': 55, - 'lon': -15, - 'type': 'FIX'}, - {'id': 243738, - 'ident': '55/20', - 'lat': 55, - 'lon': -20, - 'type': 'LATLON'}, - {'id': 243581, - 'ident': '54/30', - 'lat': 54, - 'lon': -30, - 'type': 'LATLON'}, - {'id': 243584, - 'ident': '53/40', - 'lat': 53, - 'lon': -40, - 'type': 'LATLON'}, - {'id': 243583, - 'ident': '52/50', - 'lat': 52, - 'lon': -50, - 'type': 'LATLON'}, - {'id': 8423845, - 'ident': 'TUDEP', - 'lat': 51.1667, - 'lon': -53.2333, - 'type': 'FIX'}], - 'westLevels': ['350', '370', '390']}, - 'validFrom': '2021-04-28T11:30:00.000Z', - 'validTo': '2021-04-28T19:00:00.000Z'}] - sub_instance = NavAPI(instance) - response = sub_instance.nats() - correct_response = [ - Track( - ident='A', - route=Route( - nodes=[ - RouteNode( - ident='RESNO', - type='FIX', - lat=55, - lon=-15, - id=8465100, - alt=None, - name=None, - via=None), - RouteNode( - ident='55/20', - type='LATLON', - lat=55, - lon=-20, - id=243738, - alt=None, - name=None, - via=None), - RouteNode( - ident='54/30', - type='LATLON', - lat=54, - lon=-30, - id=243581, - alt=None, - name=None, - via=None), - RouteNode( - ident='53/40', - type='LATLON', - lat=53, - lon=-40, - id=243584, - alt=None, - name=None, - via=None), - RouteNode( - ident='52/50', - type='LATLON', - lat=52, - lon=-50, - id=243583, - alt=None, - name=None, - via=None), - RouteNode( - ident='TUDEP', - type='FIX', - lat=51.1667, - lon=-53.2333, - id=8423845, - alt=None, - name=None, - via=None)], - eastLevels=[], - westLevels=['350', '370', '390']), - validFrom=datetime.datetime( - 2021, 4, 28, 11, 30, tzinfo=tzutc()), - validTo=datetime.datetime( - 2021, 4, 28, 19, 0, tzinfo=tzutc()))] - # check that NavAPI method made correct request of FlightPlanDB - instance.assert_has_calls([call._get('/nav/NATS')]) - # check NavAPI method decoded data correctly for given response - assert response == correct_response - - def test_pacots(self): - - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - instance._get.return_value = [ - { - 'ident': 1, - 'route': { - 'nodes': [{'id': 8465100, - 'ident': 'RESNO', - 'lat': 55, - 'lon': -15, - 'type': 'FIX'}, - {'id': 243738, - 'ident': '55/20', - 'lat': 55, - 'lon': -20, - 'type': 'LATLON'}, - {'id': 243581, - 'ident': '54/30', - 'lat': 54, - 'lon': -30, - 'type': 'LATLON'}, - {'id': 243584, - 'ident': '53/40', - 'lat': 53, - 'lon': -40, - 'type': 'LATLON'}, - {'id': 243583, - 'ident': '52/50', - 'lat': 52, - 'lon': -50, - 'type': 'LATLON'}, - {'id': 8423845, - 'ident': 'TUDEP', - 'lat': 51.1667, - 'lon': -53.2333, - 'type': 'FIX'}]}, - 'validFrom': '2021-04-28T11:30:00.000Z', - 'validTo': '2021-04-28T19:00:00.000Z'}] - sub_instance = NavAPI(instance) - response = sub_instance.pacots() - correct_response = [ - Track( - ident=1, - route=Route( - nodes=[ - RouteNode( - ident='RESNO', - type='FIX', - lat=55, - lon=-15, - id=8465100, - alt=None, - name=None, - via=None), - RouteNode( - ident='55/20', - type='LATLON', - lat=55, - lon=-20, - id=243738, - alt=None, - name=None, - via=None), - RouteNode( - ident='54/30', - type='LATLON', - lat=54, - lon=-30, - id=243581, - alt=None, - name=None, - via=None), - RouteNode( - ident='53/40', - type='LATLON', - lat=53, - lon=-40, - id=243584, - alt=None, - name=None, - via=None), - RouteNode( - ident='52/50', - type='LATLON', - lat=52, - lon=-50, - id=243583, - alt=None, - name=None, - via=None), - RouteNode( - ident='TUDEP', - type='FIX', - lat=51.1667, - lon=-53.2333, - id=8423845, - alt=None, - name=None, - via=None)]), - validFrom=datetime.datetime( - 2021, 4, 28, 11, 30, tzinfo=tzutc()), - validTo=datetime.datetime( - 2021, 4, 28, 19, 0, tzinfo=tzutc()))] - # check that NavAPI method made correct request of FlightPlanDB - instance.assert_has_calls([call._get('/nav/PACOTS')]) - # check NavAPI method decoded data correctly for given response - assert response == correct_response - - def test_navaid_search(self): - - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - mock_response = [ - {'airportICAO': None, - 'elevation': 1.0000000015200001, - 'ident': 'SPY', - 'lat': 52.5403, - 'lon': 4.85378, - 'name': 'SPIJKERBOOR', - 'runwayIdent': None, - 'type': 'VOR'}, - {'airportICAO': None, - 'elevation': 26.000000039520003, - 'ident': 'SPY', - 'lat': 52.5403, - 'lon': 4.85378, - 'name': 'SPIJKERBOOR VOR-DME', - 'runwayIdent': None, - 'type': 'DME'} - ] - instance._getiter.return_value = (i for i in mock_response) - - sub_instance = NavAPI(instance) - response = sub_instance.search("SPY") - - correct_response_list = [ - SearchNavaid( - ident='SPY', - type='VOR', - lat=52.5403, - lon=4.85378, - elevation=1.0000000015200001, - runwayIdent=None, - airportICAO=None, - name='SPIJKERBOOR'), - SearchNavaid( - ident='SPY', - type='DME', - lat=52.5403, - lon=4.85378, - elevation=26.000000039520003, - runwayIdent=None, - airportICAO=None, - name='SPIJKERBOOR VOR-DME') - ] - # check UserAPI method decoded data correctly for given response - assert list(i for i in response) == correct_response_list - # check that UserAPI method made the correct request of FlightPlanDB - instance.assert_has_calls( - [call._getiter('/search/nav', params={'q': 'SPY'})]) - - -if __name__ == "__main__": - main() + ], + weather=Weather( + METAR=None, + TAF=None + ) + ) + + def patched_get(self, path, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.nav.NavAPI, + attribute="_get", + new=patched_get) + instance = flightplandb.submodules.nav.NavAPI() + spy = mocker.spy(instance, "_get") + + response = instance.airport("EHAL") + # check that NavAPI method decoded data correctly for given response + assert response == correct_response + # check that NavAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/nav/airport/EHAL', key=None) + + +def test_nats(mocker): + json_response = [ + { + 'ident': 'A', + 'route': { + 'eastLevels': [], + 'nodes': [{'id': 8465100, + 'ident': 'RESNO', + 'lat': 55, + 'lon': -15, + 'type': 'FIX'}, + {'id': 243738, + 'ident': '55/20', + 'lat': 55, + 'lon': -20, + 'type': 'LATLON'}, + {'id': 243581, + 'ident': '54/30', + 'lat': 54, + 'lon': -30, + 'type': 'LATLON'}, + {'id': 243584, + 'ident': '53/40', + 'lat': 53, + 'lon': -40, + 'type': 'LATLON'}, + {'id': 243583, + 'ident': '52/50', + 'lat': 52, + 'lon': -50, + 'type': 'LATLON'}, + {'id': 8423845, + 'ident': 'TUDEP', + 'lat': 51.1667, + 'lon': -53.2333, + 'type': 'FIX'}], + 'westLevels': ['350', '370', '390']}, + 'validFrom': '2021-04-28T11:30:00.000Z', + 'validTo': '2021-04-28T19:00:00.000Z'}] + + + correct_response = [ + Track( + ident='A', + route=Route( + nodes=[ + RouteNode( + ident='RESNO', + type='FIX', + lat=55, + lon=-15, + id=8465100, + alt=None, + name=None, + via=None), + RouteNode( + ident='55/20', + type='LATLON', + lat=55, + lon=-20, + id=243738, + alt=None, + name=None, + via=None), + RouteNode( + ident='54/30', + type='LATLON', + lat=54, + lon=-30, + id=243581, + alt=None, + name=None, + via=None), + RouteNode( + ident='53/40', + type='LATLON', + lat=53, + lon=-40, + id=243584, + alt=None, + name=None, + via=None), + RouteNode( + ident='52/50', + type='LATLON', + lat=52, + lon=-50, + id=243583, + alt=None, + name=None, + via=None), + RouteNode( + ident='TUDEP', + type='FIX', + lat=51.1667, + lon=-53.2333, + id=8423845, + alt=None, + name=None, + via=None)], + eastLevels=[], + westLevels=['350', '370', '390']), + validFrom=datetime.datetime( + 2021, 4, 28, 11, 30, tzinfo=tzutc()), + validTo=datetime.datetime( + 2021, 4, 28, 19, 0, tzinfo=tzutc()))] + + def patched_get(self, path, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.nav.NavAPI, + attribute="_get", + new=patched_get) + instance = flightplandb.submodules.nav.NavAPI() + spy = mocker.spy(instance, "_get") + + response = instance.nats() + # check that NavAPI method decoded data correctly for given response + assert response == correct_response + # check that NavAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/nav/NATS', key=None) + + +def test_pacots(mocker): + json_response = [ + { + 'ident': 1, + 'route': { + 'nodes': [{'id': 8465100, + 'ident': 'RESNO', + 'lat': 55, + 'lon': -15, + 'type': 'FIX'}, + {'id': 243738, + 'ident': '55/20', + 'lat': 55, + 'lon': -20, + 'type': 'LATLON'}, + {'id': 243581, + 'ident': '54/30', + 'lat': 54, + 'lon': -30, + 'type': 'LATLON'}, + {'id': 243584, + 'ident': '53/40', + 'lat': 53, + 'lon': -40, + 'type': 'LATLON'}, + {'id': 243583, + 'ident': '52/50', + 'lat': 52, + 'lon': -50, + 'type': 'LATLON'}, + {'id': 8423845, + 'ident': 'TUDEP', + 'lat': 51.1667, + 'lon': -53.2333, + 'type': 'FIX'}]}, + 'validFrom': '2021-04-28T11:30:00.000Z', + 'validTo': '2021-04-28T19:00:00.000Z'}] + + + correct_response = [ + Track( + ident=1, + route=Route( + nodes=[ + RouteNode( + ident='RESNO', + type='FIX', + lat=55, + lon=-15, + id=8465100, + alt=None, + name=None, + via=None), + RouteNode( + ident='55/20', + type='LATLON', + lat=55, + lon=-20, + id=243738, + alt=None, + name=None, + via=None), + RouteNode( + ident='54/30', + type='LATLON', + lat=54, + lon=-30, + id=243581, + alt=None, + name=None, + via=None), + RouteNode( + ident='53/40', + type='LATLON', + lat=53, + lon=-40, + id=243584, + alt=None, + name=None, + via=None), + RouteNode( + ident='52/50', + type='LATLON', + lat=52, + lon=-50, + id=243583, + alt=None, + name=None, + via=None), + RouteNode( + ident='TUDEP', + type='FIX', + lat=51.1667, + lon=-53.2333, + id=8423845, + alt=None, + name=None, + via=None)]), + validFrom=datetime.datetime( + 2021, 4, 28, 11, 30, tzinfo=tzutc()), + validTo=datetime.datetime( + 2021, 4, 28, 19, 0, tzinfo=tzutc()))] + + def patched_get(self, path, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.nav.NavAPI, + attribute="_get", + new=patched_get) + instance = flightplandb.submodules.nav.NavAPI() + spy = mocker.spy(instance, "_get") + + response = instance.pacots() + # check that NavAPI method decoded data correctly for given response + assert response == correct_response + # check that NavAPI method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/nav/PACOTS', key=None) + + + + + + + + + + +def test_navaid_search(mocker): + json_response = [ + {'airportICAO': None, + 'elevation': 1.0000000015200001, + 'ident': 'SPY', + 'lat': 52.5403, + 'lon': 4.85378, + 'name': 'SPIJKERBOOR', + 'runwayIdent': None, + 'type': 'VOR'}, + {'airportICAO': None, + 'elevation': 26.000000039520003, + 'ident': 'SPY', + 'lat': 52.5403, + 'lon': 4.85378, + 'name': 'SPIJKERBOOR VOR-DME', + 'runwayIdent': None, + 'type': 'DME'} + ] + + correct_response_list = [ + SearchNavaid( + ident='SPY', + type='VOR', + lat=52.5403, + lon=4.85378, + elevation=1.0000000015200001, + runwayIdent=None, + airportICAO=None, + name='SPIJKERBOOR'), + SearchNavaid( + ident='SPY', + type='DME', + lat=52.5403, + lon=4.85378, + elevation=26.000000039520003, + runwayIdent=None, + airportICAO=None, + name='SPIJKERBOOR VOR-DME') + ] + + correct_calls = [mocker.call( + path='/search/nav', + params={'q': 'SPY'}, + key=None)] + + def patched_getiter(self, path, params=None, key=None): + return (i for i in json_response) + + mocker.patch.object( + target=flightplandb.submodules.nav.NavAPI, + attribute="_getiter", + new=patched_getiter) + instance = flightplandb.submodules.nav.NavAPI() + spy = mocker.spy(instance, "_getiter") + + response = instance.search("SPY") + # check that PlanAPI method decoded data correctly for given response + assert list(i for i in response) == correct_response_list + # check that PlanAPI method made correct request of FlightPlanDB + spy.assert_has_calls(correct_calls) + + + + + + + + + + +def old_test_navaid_search(self): + + with patch("flightplandb.flightplandb.FlightPlanDB", + autospec=True) as MockClass: + instance = MockClass.return_value + mock_response = [ + {'airportICAO': None, + 'elevation': 1.0000000015200001, + 'ident': 'SPY', + 'lat': 52.5403, + 'lon': 4.85378, + 'name': 'SPIJKERBOOR', + 'runwayIdent': None, + 'type': 'VOR'}, + {'airportICAO': None, + 'elevation': 26.000000039520003, + 'ident': 'SPY', + 'lat': 52.5403, + 'lon': 4.85378, + 'name': 'SPIJKERBOOR VOR-DME', + 'runwayIdent': None, + 'type': 'DME'} + ] + instance._getiter.return_value = (i for i in mock_response) + + sub_instance = NavAPI(instance) + response = sub_instance.search("SPY") + + correct_response_list = [ + SearchNavaid( + ident='SPY', + type='VOR', + lat=52.5403, + lon=4.85378, + elevation=1.0000000015200001, + runwayIdent=None, + airportICAO=None, + name='SPIJKERBOOR'), + SearchNavaid( + ident='SPY', + type='DME', + lat=52.5403, + lon=4.85378, + elevation=26.000000039520003, + runwayIdent=None, + airportICAO=None, + name='SPIJKERBOOR VOR-DME') + ] + # check UserAPI method decoded data correctly for given response + assert list(i for i in response) == correct_response_list + # check that UserAPI method made the correct request of FlightPlanDB + instance.assert_has_calls( + [call._getiter('/search/nav', params={'q': 'SPY'})]) From a52cd9ac60afde70236441b8b86f02df6c59f00a Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 28 Oct 2021 01:32:54 +0200 Subject: [PATCH 27/86] remove old test, fix flake8 --- tests/test_nav.py | 351 ++++++++++++++++++++-------------------------- 1 file changed, 151 insertions(+), 200 deletions(-) diff --git a/tests/test_nav.py b/tests/test_nav.py index c91eb0d..be440c7 100644 --- a/tests/test_nav.py +++ b/tests/test_nav.py @@ -1,7 +1,4 @@ -from unittest import TestCase, main -from unittest.mock import patch, call import flightplandb -from flightplandb.submodules.nav import NavAPI from flightplandb.datatypes import ( Airport, Timezone, Runway, RunwayEnds, Frequency, Weather, Times, Track, @@ -179,106 +176,119 @@ def test_nats(mocker): 'ident': 'A', 'route': { 'eastLevels': [], - 'nodes': [{'id': 8465100, - 'ident': 'RESNO', - 'lat': 55, - 'lon': -15, - 'type': 'FIX'}, - {'id': 243738, - 'ident': '55/20', - 'lat': 55, - 'lon': -20, - 'type': 'LATLON'}, - {'id': 243581, - 'ident': '54/30', - 'lat': 54, - 'lon': -30, - 'type': 'LATLON'}, - {'id': 243584, - 'ident': '53/40', - 'lat': 53, - 'lon': -40, - 'type': 'LATLON'}, - {'id': 243583, - 'ident': '52/50', - 'lat': 52, - 'lon': -50, - 'type': 'LATLON'}, - {'id': 8423845, - 'ident': 'TUDEP', - 'lat': 51.1667, - 'lon': -53.2333, - 'type': 'FIX'}], + 'nodes': [ + { + 'id': 8465100, + 'ident': 'RESNO', + 'lat': 55, + 'lon': -15, + 'type': 'FIX' + }, + { + 'id': 243738, + 'ident': '55/20', + 'lat': 55, + 'lon': -20, + 'type': 'LATLON' + }, + { + 'id': 243581, + 'ident': '54/30', + 'lat': 54, + 'lon': -30, + 'type': 'LATLON' + }, + { + 'id': 243584, + 'ident': '53/40', + 'lat': 53, + 'lon': -40, + 'type': 'LATLON' + }, + { + 'id': 243583, + 'ident': '52/50', + 'lat': 52, + 'lon': -50, + 'type': 'LATLON' + }, + { + 'id': 8423845, + 'ident': 'TUDEP', + 'lat': 51.1667, + 'lon': -53.2333, + 'type': 'FIX' + } + ], 'westLevels': ['350', '370', '390']}, 'validFrom': '2021-04-28T11:30:00.000Z', 'validTo': '2021-04-28T19:00:00.000Z'}] - correct_response = [ - Track( - ident='A', - route=Route( - nodes=[ - RouteNode( - ident='RESNO', - type='FIX', - lat=55, - lon=-15, - id=8465100, - alt=None, - name=None, - via=None), - RouteNode( - ident='55/20', - type='LATLON', - lat=55, - lon=-20, - id=243738, - alt=None, - name=None, - via=None), - RouteNode( - ident='54/30', - type='LATLON', - lat=54, - lon=-30, - id=243581, - alt=None, - name=None, - via=None), - RouteNode( - ident='53/40', - type='LATLON', - lat=53, - lon=-40, - id=243584, - alt=None, - name=None, - via=None), - RouteNode( - ident='52/50', - type='LATLON', - lat=52, - lon=-50, - id=243583, - alt=None, - name=None, - via=None), - RouteNode( - ident='TUDEP', - type='FIX', - lat=51.1667, - lon=-53.2333, - id=8423845, - alt=None, - name=None, - via=None)], - eastLevels=[], - westLevels=['350', '370', '390']), - validFrom=datetime.datetime( - 2021, 4, 28, 11, 30, tzinfo=tzutc()), - validTo=datetime.datetime( - 2021, 4, 28, 19, 0, tzinfo=tzutc()))] + Track( + ident='A', + route=Route( + nodes=[ + RouteNode( + ident='RESNO', + type='FIX', + lat=55, + lon=-15, + id=8465100, + alt=None, + name=None, + via=None), + RouteNode( + ident='55/20', + type='LATLON', + lat=55, + lon=-20, + id=243738, + alt=None, + name=None, + via=None), + RouteNode( + ident='54/30', + type='LATLON', + lat=54, + lon=-30, + id=243581, + alt=None, + name=None, + via=None), + RouteNode( + ident='53/40', + type='LATLON', + lat=53, + lon=-40, + id=243584, + alt=None, + name=None, + via=None), + RouteNode( + ident='52/50', + type='LATLON', + lat=52, + lon=-50, + id=243583, + alt=None, + name=None, + via=None), + RouteNode( + ident='TUDEP', + type='FIX', + lat=51.1667, + lon=-53.2333, + id=8423845, + alt=None, + name=None, + via=None)], + eastLevels=[], + westLevels=['350', '370', '390']), + validFrom=datetime.datetime( + 2021, 4, 28, 11, 30, tzinfo=tzutc()), + validTo=datetime.datetime( + 2021, 4, 28, 19, 0, tzinfo=tzutc()))] def patched_get(self, path, key): return json_response @@ -302,40 +312,52 @@ def test_pacots(mocker): { 'ident': 1, 'route': { - 'nodes': [{'id': 8465100, - 'ident': 'RESNO', - 'lat': 55, - 'lon': -15, - 'type': 'FIX'}, - {'id': 243738, - 'ident': '55/20', - 'lat': 55, - 'lon': -20, - 'type': 'LATLON'}, - {'id': 243581, - 'ident': '54/30', - 'lat': 54, - 'lon': -30, - 'type': 'LATLON'}, - {'id': 243584, - 'ident': '53/40', - 'lat': 53, - 'lon': -40, - 'type': 'LATLON'}, - {'id': 243583, - 'ident': '52/50', - 'lat': 52, - 'lon': -50, - 'type': 'LATLON'}, - {'id': 8423845, - 'ident': 'TUDEP', - 'lat': 51.1667, - 'lon': -53.2333, - 'type': 'FIX'}]}, + 'nodes': [ + { + 'id': 8465100, + 'ident': 'RESNO', + 'lat': 55, + 'lon': -15, + 'type': 'FIX' + }, + { + 'id': 243738, + 'ident': '55/20', + 'lat': 55, + 'lon': -20, + 'type': 'LATLON' + }, + { + 'id': 243581, + 'ident': '54/30', + 'lat': 54, + 'lon': -30, + 'type': 'LATLON' + }, + { + 'id': 243584, + 'ident': '53/40', + 'lat': 53, + 'lon': -40, + 'type': 'LATLON' + }, + { + 'id': 243583, + 'ident': '52/50', + 'lat': 52, + 'lon': -50, + 'type': 'LATLON' + }, + { + 'id': 8423845, + 'ident': 'TUDEP', + 'lat': 51.1667, + 'lon': -53.2333, + 'type': 'FIX' + }]}, 'validFrom': '2021-04-28T11:30:00.000Z', 'validTo': '2021-04-28T19:00:00.000Z'}] - correct_response = [ Track( ident=1, @@ -417,14 +439,6 @@ def patched_get(self, path, key): spy.assert_called_once_with(path='/nav/PACOTS', key=None) - - - - - - - - def test_navaid_search(mocker): json_response = [ {'airportICAO': None, @@ -486,66 +500,3 @@ def patched_getiter(self, path, params=None, key=None): assert list(i for i in response) == correct_response_list # check that PlanAPI method made correct request of FlightPlanDB spy.assert_has_calls(correct_calls) - - - - - - - - - - -def old_test_navaid_search(self): - - with patch("flightplandb.flightplandb.FlightPlanDB", - autospec=True) as MockClass: - instance = MockClass.return_value - mock_response = [ - {'airportICAO': None, - 'elevation': 1.0000000015200001, - 'ident': 'SPY', - 'lat': 52.5403, - 'lon': 4.85378, - 'name': 'SPIJKERBOOR', - 'runwayIdent': None, - 'type': 'VOR'}, - {'airportICAO': None, - 'elevation': 26.000000039520003, - 'ident': 'SPY', - 'lat': 52.5403, - 'lon': 4.85378, - 'name': 'SPIJKERBOOR VOR-DME', - 'runwayIdent': None, - 'type': 'DME'} - ] - instance._getiter.return_value = (i for i in mock_response) - - sub_instance = NavAPI(instance) - response = sub_instance.search("SPY") - - correct_response_list = [ - SearchNavaid( - ident='SPY', - type='VOR', - lat=52.5403, - lon=4.85378, - elevation=1.0000000015200001, - runwayIdent=None, - airportICAO=None, - name='SPIJKERBOOR'), - SearchNavaid( - ident='SPY', - type='DME', - lat=52.5403, - lon=4.85378, - elevation=26.000000039520003, - runwayIdent=None, - airportICAO=None, - name='SPIJKERBOOR VOR-DME') - ] - # check UserAPI method decoded data correctly for given response - assert list(i for i in response) == correct_response_list - # check that UserAPI method made the correct request of FlightPlanDB - instance.assert_has_calls( - [call._getiter('/search/nav', params={'q': 'SPY'})]) From 03bedd8cb554ba3cda42d4ad6d4a42d81122a461 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Fri, 29 Oct 2021 00:59:56 +0200 Subject: [PATCH 28/86] remove whitespace, fix incorrect type hint --- src/flightplandb/submodules/api.py | 2 +- src/flightplandb/submodules/nav.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index 39a113f..2ef8c4b 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -3,7 +3,7 @@ from flightplandb.datatypes import StatusResponse -class API(FlightPlanDB): +class API(FlightPlanDB): def _header_value(self, header_key: str, key: Optional[str] = None) -> str: """Gets header value for key diff --git a/src/flightplandb/submodules/nav.py b/src/flightplandb/submodules/nav.py index f9f9064..4ae5dd5 100644 --- a/src/flightplandb/submodules/nav.py +++ b/src/flightplandb/submodules/nav.py @@ -1,4 +1,4 @@ -from typing import Generator, List, Union, Optional +from typing import Generator, List, Optional from flightplandb.flightplandb import FlightPlanDB from flightplandb.datatypes import Airport, Track, SearchNavaid @@ -10,7 +10,7 @@ class NavAPI(FlightPlanDB): Accessed via :meth:`~flightplandb.flightplandb.FlightPlanDB.nav`. """ - def airport(self, icao: str, key: Optional[str] = None) -> Union[Airport]: + def airport(self, icao: str, key: Optional[str] = None) -> Airport: """Fetches information about an airport. Parameters From 817bb9543a3216640da98049d822db8235b7f8db Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Fri, 29 Oct 2021 01:00:40 +0200 Subject: [PATCH 29/86] add test for api _header_value --- tests/test_api.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/test_api.py diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..1cadaa5 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,38 @@ +import flightplandb +from flightplandb.datatypes import Weather +import pytest + + +@pytest.mark.parametrize( + "header_key,ping_headers,existing_headers,correct_response,correct_mock_calls", + [ + ("X-API-Version", {"X-API-Version": 1}, {"X-API-Version": 1}, 1, None), + ("X-API-Version", {"X-API-Version": 1}, {}, 1, {"key": None}), + ("X-Units", {"X-Units": "AVIATION"}, {"X-Units": "AVIATION"}, "AVIATION", None), + ("X-Units", {"X-Units": "AVIATION"}, {}, "AVIATION", {"key": None}), + ("X-Limit-Cap", {"X-Limit-Cap": 100}, {"X-Limit-Cap": 100}, 100, None), + ("X-Limit-Cap", {"X-Limit-Cap": 100}, {}, 100, {"key": None}), + ("X-Limit-Used", {"X-Limit-Used": 1}, {"X-Limit-Used": 1}, 1, None), + ("X-Limit-Used", {"X-Limit-Used": 1}, {}, 1, {"key": None}), + ], +) +def test_api_headers(header_key, mocker, ping_headers, existing_headers, correct_response, correct_mock_calls): + + def patched_ping(self, key): + self._header = ping_headers + + mocker.patch.object( + target=flightplandb.submodules.api.API, + attribute="ping", + new=patched_ping) + instance = flightplandb.submodules.api.API() + spy = mocker.spy(instance, "ping") + + instance._header = existing_headers + + response = instance._header_value(header_key=header_key, key=None) + if correct_mock_calls == None: + spy.assert_not_called() + else: + spy.assert_called_once_with(**correct_mock_calls) + assert(response==correct_response) From 554d44fcd805a2ca58763014aa97632e9740ab8b Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Fri, 29 Oct 2021 17:52:15 +0200 Subject: [PATCH 30/86] add ping test --- tests/test_api.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 1cadaa5..f211876 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,5 +1,5 @@ import flightplandb -from flightplandb.datatypes import Weather +from flightplandb.datatypes import StatusResponse, Weather import pytest @@ -36,3 +36,29 @@ def patched_ping(self, key): else: spy.assert_called_once_with(**correct_mock_calls) assert(response==correct_response) + + +def test_api_ping(mocker): + json_response = { + "message": "OK", + "errors": None + } + + correct_response = StatusResponse(message="OK", errors=None) + + def patched_get(self, path, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.api.API, + attribute="_get", + new=patched_get + ) + instance = flightplandb.submodules.api.API() + spy = mocker.spy(instance, "_get") + + response = instance.ping() + # check that API method made correct request of FlightPlanDB + spy.assert_called_once_with(path='', key=None) + # check that API method decoded data correctly for given response + assert response == correct_response \ No newline at end of file From 312610dc72b094f2388839b0f58239ca961df903 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Fri, 29 Oct 2021 17:54:40 +0200 Subject: [PATCH 31/86] fix flake8 --- src/flightplandb/submodules/api.py | 2 +- tests/test_api.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index 2ef8c4b..39a113f 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -3,7 +3,7 @@ from flightplandb.datatypes import StatusResponse -class API(FlightPlanDB): +class API(FlightPlanDB): def _header_value(self, header_key: str, key: Optional[str] = None) -> str: """Gets header value for key diff --git a/tests/test_api.py b/tests/test_api.py index f211876..d1f5ad0 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,5 +1,5 @@ import flightplandb -from flightplandb.datatypes import StatusResponse, Weather +from flightplandb.datatypes import StatusResponse import pytest @@ -31,11 +31,11 @@ def patched_ping(self, key): instance._header = existing_headers response = instance._header_value(header_key=header_key, key=None) - if correct_mock_calls == None: + if correct_mock_calls is None: spy.assert_not_called() else: spy.assert_called_once_with(**correct_mock_calls) - assert(response==correct_response) + assert response == correct_response def test_api_ping(mocker): @@ -61,4 +61,4 @@ def patched_get(self, path, key): # check that API method made correct request of FlightPlanDB spy.assert_called_once_with(path='', key=None) # check that API method decoded data correctly for given response - assert response == correct_response \ No newline at end of file + assert response == correct_response From 8b7c0776ae82b99b99e0ba1b1bcddf5da0027bc3 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Tue, 2 Nov 2021 21:08:41 +0100 Subject: [PATCH 32/86] fix incorrect method call, remove useless assignment --- src/flightplandb/submodules/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index 39a113f..3cb48b0 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -100,5 +100,4 @@ def revoke(self, key: Optional[str] = None) -> StatusResponse: """ resp = self._get(path="/auth/revoke", key=key) - self._header = resp.headers - return StatusResponse(**resp.json()) + return StatusResponse(**resp) From 2cc7ccc1e7d653aca881813386d57bb671f24716 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Tue, 2 Nov 2021 21:10:18 +0100 Subject: [PATCH 33/86] add key revoke test --- tests/test_api.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_api.py b/tests/test_api.py index d1f5ad0..b46e337 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -62,3 +62,29 @@ def patched_get(self, path, key): spy.assert_called_once_with(path='', key=None) # check that API method decoded data correctly for given response assert response == correct_response + + +def test_key_revoke(mocker): + json_response = { + "message": "OK", + "errors": None + } + + correct_response = StatusResponse(message="OK", errors=None) + + def patched_get(self, path, key): + return json_response + + mocker.patch.object( + target=flightplandb.submodules.api.API, + attribute="_get", + new=patched_get + ) + instance = flightplandb.submodules.api.API() + spy = mocker.spy(instance, "_get") + + response = instance.revoke(key="qwertyuiop") + # check that API method made correct request of FlightPlanDB + spy.assert_called_once_with(path='/auth/revoke', key="qwertyuiop") + # check that API method decoded data correctly for given response + assert response == correct_response From 5a54ed62d92451f629f1b6c4514091c6024d14be Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Tue, 2 Nov 2021 21:20:01 +0100 Subject: [PATCH 34/86] remove unused pass statements --- src/flightplandb/exceptions.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/flightplandb/exceptions.py b/src/flightplandb/exceptions.py index 42c073c..5ed9181 100644 --- a/src/flightplandb/exceptions.py +++ b/src/flightplandb/exceptions.py @@ -28,7 +28,6 @@ class BadRequestException(BaseErrorHandler): message A verbose description of this error. """ - pass class UnauthorizedException(BaseErrorHandler): @@ -42,7 +41,6 @@ class UnauthorizedException(BaseErrorHandler): message A verbose description of this error. """ - pass class ForbiddenException(BaseErrorHandler): @@ -57,7 +55,6 @@ class ForbiddenException(BaseErrorHandler): message A verbose description of this error. """ - pass class NotFoundException(BaseErrorHandler): @@ -71,7 +68,6 @@ class NotFoundException(BaseErrorHandler): message A verbose description of this error. """ - pass class TooManyRequestsException(BaseErrorHandler): @@ -85,7 +81,6 @@ class TooManyRequestsException(BaseErrorHandler): message A verbose description of this error. """ - pass class InternalServerException(BaseErrorHandler): @@ -99,7 +94,6 @@ class InternalServerException(BaseErrorHandler): message A verbose description of this error. """ - pass def status_handler(status_code, ignore_statuses=None): From deea3bb105b4edc809ed6dc4de3d4b508340d5b7 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 25 Apr 2022 02:40:51 +0200 Subject: [PATCH 35/86] fix small merge errors --- src/flightplandb/flightplandb.py | 55 ++++++++++++++++------------- src/flightplandb/submodules/plan.py | 27 ++++++++------ 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/flightplandb/flightplandb.py b/src/flightplandb/flightplandb.py index 12e4fad..3d5d2ad 100644 --- a/src/flightplandb/flightplandb.py +++ b/src/flightplandb/flightplandb.py @@ -139,9 +139,12 @@ def _request(self, method: str, # then add it to the request headers params["Accept"] = return_format_encoded - resp = requests.request(method, urljoin(self.url_base, path), + resp = requests.request(method=method, + url=urljoin(self.url_base, path), auth=HTTPBasicAuth(key, None), - headers=params, *args, **kwargs) + headers=params, + json=json_data, + *args, **kwargs) status_handler(resp.status_code, ignore_statuses) @@ -191,11 +194,12 @@ def _get(self, path: str, return_format="native", if not params: params = {} - resp = self._request("get", path, - return_format, - ignore_statuses, - params, - key, + resp = self._request(method="get", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, *args, **kwargs) return resp @@ -236,12 +240,12 @@ def _post(self, path: str, return_format="native", if not params: params = {} - resp = self._request("post", - path, - return_format, - ignore_statuses, - params, - key, + resp = self._request(method="post", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + json_data=json_data, *args, **kwargs) return resp @@ -283,12 +287,13 @@ def _patch(self, path: str, return_format="native", if not params: params = {} - resp = self._request("patch", - path, - return_format, - ignore_statuses, - params, - key, + resp = self._request(method="patch", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, + json_data=json_data, *args, **kwargs) return resp @@ -329,12 +334,12 @@ def _delete(self, path: str, return_format="native", if not params: params = {} - resp = self._request("delete", - path, - return_format, - ignore_statuses, - params, - key, + resp = self._request(method="delete", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, *args, **kwargs) return resp diff --git a/src/flightplandb/submodules/plan.py b/src/flightplandb/submodules/plan.py index 4155578..11f9ebf 100644 --- a/src/flightplandb/submodules/plan.py +++ b/src/flightplandb/submodules/plan.py @@ -155,7 +155,7 @@ def delete(self, id_: int, return(StatusResponse(**resp)) def search(self, plan_query: PlanQuery, sort: str = "created", - limit: int = 100, + include_route: bool = False, limit: int = 100, key: Optional[str] = None) -> Generator[Plan, None, None]: """Searches for flight plans. A number of search parameters are available. @@ -182,9 +182,12 @@ def search(self, plan_query: PlanQuery, sort: str = "created", objects. """ + request_json = plan_query._to_api_dict() + request_json["includeRoute"] = include_route + for i in self._getiter(path="/search/plans", sort=sort, - params=plan_query._to_api_dict(), + params=request_json, limit=limit, key=key): yield Plan(**i) @@ -265,7 +268,7 @@ def unlike(self, id_: int, return True def generate(self, gen_query: GenerateQuery, - return_format: str = "native", + include_route: bool = False, key: Optional[str] = None) -> Union[Plan, bytes]: """Creates a new flight plan using the route generator. @@ -285,12 +288,16 @@ def generate(self, gen_query: GenerateQuery, Include route in response, defaults to false """ - response = self._post( - path="/auto/generate", return_format=return_format, json=gen_query._to_api_dict(), key=key) - if return_format == "native": - return Plan(**response) - else: - return response + request_json = gen_query._to_api_dict() + + # due to an API bug this must be a string instead of a boolean + request_json["includeRoute"] = "true" if include_route else "false" + + return Plan( + **self._post( + path="/auto/generate", + json_data=request_json, + key=key)) def decode(self, route: str, key: Optional[str] = None) -> Plan: @@ -323,4 +330,4 @@ def decode(self, route: str, """ return Plan(**self._post( - path="/auto/decode", json={"route": route}, key=key)) + path="/auto/decode", json_data={"route": route}, key=key)) From 006308e9d423ab0789b83bd995764d13a90ecad4 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 25 Apr 2022 21:08:31 +0200 Subject: [PATCH 36/86] add missing argument --- src/flightplandb/flightplandb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/flightplandb/flightplandb.py b/src/flightplandb/flightplandb.py index 3d5d2ad..13512c8 100644 --- a/src/flightplandb/flightplandb.py +++ b/src/flightplandb/flightplandb.py @@ -246,6 +246,7 @@ def _post(self, path: str, return_format="native", ignore_statuses=ignore_statuses, params=params, json_data=json_data, + key=key, *args, **kwargs) return resp From be815334f913a24fc791aa5ee7c97ae32b7652a8 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 25 Apr 2022 21:12:57 +0200 Subject: [PATCH 37/86] fix small style issues --- src/flightplandb/flightplandb.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/flightplandb/flightplandb.py b/src/flightplandb/flightplandb.py index 13512c8..7048301 100644 --- a/src/flightplandb/flightplandb.py +++ b/src/flightplandb/flightplandb.py @@ -19,11 +19,11 @@ from typing import Generator, List, Dict, Union, Optional from urllib.parse import urljoin +import json import requests from requests.auth import HTTPBasicAuth from requests.structures import CaseInsensitiveDict -import json from flightplandb.exceptions import status_handler @@ -124,17 +124,18 @@ def _request(self, method: str, params = {} # the API only takes "true" or "false", not True or False - for k, v in params.items(): - if v in (True, False): - params[k] = json.dumps(v) + for key, value in params.items(): + if value in (True, False): + params[key] = json.dumps(value) # convert the API content return_format to an HTTP Accept type try: return_format_encoded = format_return_types[return_format] # unless it's not a valid return_format - except KeyError: + except KeyError as exc: raise ValueError( - f"'{return_format}' is not a valid data return type option") + f"'{return_format}' is not a valid data return type option" + ) from exc # then add it to the request headers params["Accept"] = return_format_encoded @@ -386,9 +387,9 @@ def _getiter(self, path: str, params = {} # the API only takes "true" or "false", not True or False - for k, v in params.items(): - if v in (True, False): - params[k] = json.dumps(v) + for key, value in params.items(): + if value in (True, False): + params[key] = json.dumps(value) valid_sort_orders = ["created", "updated", "popularity", "distance"] if sort not in valid_sort_orders: From 763c78550918e588edcaafbe75ad815ba41c9487 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 17:43:23 +0200 Subject: [PATCH 38/86] move url_base out of FlightPlanDB class --- src/flightplandb/flightplandb.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/flightplandb/flightplandb.py b/src/flightplandb/flightplandb.py index 7048301..b7ce435 100644 --- a/src/flightplandb/flightplandb.py +++ b/src/flightplandb/flightplandb.py @@ -29,6 +29,10 @@ # https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autoclass # https://github.com/python/cpython/blob/main/Lib/random.py#L792 + +url_base: str = "https://api.flightplandatabase.com" + + class FlightPlanDB: """This class mostly contains internal functions called by the API. @@ -42,14 +46,10 @@ class FlightPlanDB: ---------- key : Optional[str] API token, defaults to None (which makes it unauthenticated) - url_base : str - The host of the API endpoint URL, - defaults to https://api.flightplandatabase.com """ def __init__(self): self._header: CaseInsensitiveDict[str] = CaseInsensitiveDict() - self.url_base: str = "https://api.flightplandatabase.com" def _request(self, method: str, path: str, return_format="native", @@ -141,7 +141,7 @@ def _request(self, method: str, params["Accept"] = return_format_encoded resp = requests.request(method=method, - url=urljoin(self.url_base, path), + url=urljoin(url_base, path), auth=HTTPBasicAuth(key, None), headers=params, json=json_data, @@ -398,7 +398,7 @@ def _getiter(self, path: str, else: params["sort"] = sort - url = urljoin(self.url_base, path) + url = urljoin(url_base, path) auth = HTTPBasicAuth(key, None) session = requests.Session() From 04ef849481b39f30e3c3f04469b8fb63ac1c5540 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 18:25:21 +0200 Subject: [PATCH 39/86] move internal and api functions out of class --- src/flightplandb/flightplandb.py | 765 ++++++++++++++--------------- src/flightplandb/submodules/api.py | 196 ++++---- 2 files changed, 473 insertions(+), 488 deletions(-) diff --git a/src/flightplandb/flightplandb.py b/src/flightplandb/flightplandb.py index b7ce435..d5ee91e 100644 --- a/src/flightplandb/flightplandb.py +++ b/src/flightplandb/flightplandb.py @@ -33,406 +33,393 @@ url_base: str = "https://api.flightplandatabase.com" -class FlightPlanDB: +"""This file mostly contains internal functions called by the API. +However, the internal functions are hidden, so unless you look at +the source code, you're unlikely to see them. +""" - """This class mostly contains internal functions called by the API. - However, the internal functions are hidden, so unless you look at - the source code, you're unlikely to see them. +def _request(method: str, + path: str, return_format="native", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + json_data: Optional[Dict] = None, + *args, **kwargs) -> Union[Dict, bytes]: + """General HTTP requests function for non-paginated results. + + Parameters + ---------- + method : str + An HTTP request type. One of GET, POST, PATCH, or DELETE + path : str + The endpoint's path to which the request is being made + return_format : str, optional + The API response format, defaults to ``"native"`` + ignore_statuses : Optional[List], optional + Statuses (together with 200 OK) which don't + raise an HTTPError, defaults to None + params : Optional[Dict], optional + Any other HTTP request parameters, defaults to None + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) + *args + Variable length argument list. + **kwargs + Arbitrary keyword arguments. + + Returns + ------- + Union[Dict, bytes] + A ``dataclass`` if ``return_format`` is ``"native"``, + otherwise ``bytes`` + + Raises + ------ + ValueError + Invalid return_format option + HTTPError + Invalid HTTP status in response + """ - Submodules are accessed via the alias properties at the end; for instance, - ``flightplandb.plan.fetch()`` + format_return_types = { + # if a dict is requested, the JSON will later be converted to that + "native": "application/vnd.fpd.v1+json", + # otherwise, pure JSON will be returned + "json": "application/vnd.fpd.v1+json", + "xml": "application/vnd.fpd.v1+xml", + "csv": "text/vnd.fpd.export.v1.csv+csv", + "pdf": "application/vnd.fpd.export.v1.pdf", + "kml": "application/vnd.fpd.export.v1.kml+xml", + "xplane": "application/vnd.fpd.export.v1.xplane", + "xplane11": "application/vnd.fpd.export.v1.xplane11", + "fs9": "application/vnd.fpd.export.v1.fs9", + "fsx": "application/vnd.fpd.export.v1.fsx", + "squawkbox": "application/vnd.fpd.export.v1.squawkbox", + "xfmc": "application/vnd.fpd.export.v1.xfmc", + "pmdg": "application/vnd.fpd.export.v1.pmdg", + "airbusx": "application/vnd.fpd.export.v1.airbusx", + "qualitywings": "application/vnd.fpd.export.v1.qualitywings", + "ifly747": "application/vnd.fpd.export.v1.ifly747", + "flightgear": "application/vnd.fpd.export.v1.flightgear", + "tfdi717": "application/vnd.fpd.export.v1.tfdi717", + "infiniteflight": "application/vnd.fpd.export.v1.infiniteflight" + } + + if not ignore_statuses: + ignore_statuses = [] + if not params: + params = {} + + # the API only takes "true" or "false", not True or False + for key, value in params.items(): + if value in (True, False): + params[key] = json.dumps(value) + + # convert the API content return_format to an HTTP Accept type + try: + return_format_encoded = format_return_types[return_format] + # unless it's not a valid return_format + except KeyError as exc: + raise ValueError( + f"'{return_format}' is not a valid data return type option" + ) from exc + + # then add it to the request headers + params["Accept"] = return_format_encoded + + resp = requests.request(method=method, + url=urljoin(url_base, path), + auth=HTTPBasicAuth(key, None), + headers=params, + json=json_data, + *args, **kwargs) + + status_handler(resp.status_code, ignore_statuses) + + header = resp.headers + + if return_format == "native": + return header, resp.json() + else: + return header, resp.text # if the format is not a dict + +# and here go the specific non-paginated HTTP calls +def _get(path: str, return_format="native", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + *args, **kwargs) -> Union[Dict, bytes]: + """Calls :meth:`_request()` for get requests. Parameters ---------- + path : str + The endpoint's path to which the request is being made + return_format : str, optional + The API response format, defaults to ``"native"`` + ignore_statuses : Optional[List], optional + Statuses (together with 200 OK) which don't + raise an HTTPError, defaults to None + params : Optional[Dict], optional + Any other HTTP request parameters, defaults to None key : Optional[str] API token, defaults to None (which makes it unauthenticated) + *args + Variable length argument list. + **kwargs + Arbitrary keyword arguments. + + Returns + ------- + Union[Dict, bytes] + A ``dataclass`` if ``return_format`` is ``"native"``, + otherwise ``bytes`` """ - def __init__(self): - self._header: CaseInsensitiveDict[str] = CaseInsensitiveDict() - - def _request(self, method: str, - path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: - """General HTTP requests function for non-paginated results. - - Parameters - ---------- - method : str - An HTTP request type. One of GET, POST, PATCH, or DELETE - path : str - The endpoint's path to which the request is being made - return_format : str, optional - The API response format, defaults to ``"native"`` - ignore_statuses : Optional[List], optional - Statuses (together with 200 OK) which don't - raise an HTTPError, defaults to None - params : Optional[Dict], optional - Any other HTTP request parameters, defaults to None - key : Optional[str] - API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. - **kwargs - Arbitrary keyword arguments. - - Returns - ------- - Union[Dict, bytes] - A ``dataclass`` if ``return_format`` is ``"native"``, - otherwise ``bytes`` - - Raises - ------ - ValueError - Invalid return_format option - HTTPError - Invalid HTTP status in response - """ - - format_return_types = { - # if a dict is requested, the JSON will later be converted to that - "native": "application/vnd.fpd.v1+json", - # otherwise, pure JSON will be returned - "json": "application/vnd.fpd.v1+json", - "xml": "application/vnd.fpd.v1+xml", - "csv": "text/vnd.fpd.export.v1.csv+csv", - "pdf": "application/vnd.fpd.export.v1.pdf", - "kml": "application/vnd.fpd.export.v1.kml+xml", - "xplane": "application/vnd.fpd.export.v1.xplane", - "xplane11": "application/vnd.fpd.export.v1.xplane11", - "fs9": "application/vnd.fpd.export.v1.fs9", - "fsx": "application/vnd.fpd.export.v1.fsx", - "squawkbox": "application/vnd.fpd.export.v1.squawkbox", - "xfmc": "application/vnd.fpd.export.v1.xfmc", - "pmdg": "application/vnd.fpd.export.v1.pmdg", - "airbusx": "application/vnd.fpd.export.v1.airbusx", - "qualitywings": "application/vnd.fpd.export.v1.qualitywings", - "ifly747": "application/vnd.fpd.export.v1.ifly747", - "flightgear": "application/vnd.fpd.export.v1.flightgear", - "tfdi717": "application/vnd.fpd.export.v1.tfdi717", - "infiniteflight": "application/vnd.fpd.export.v1.infiniteflight" - } - - if not ignore_statuses: - ignore_statuses = [] - if not params: - params = {} - - # the API only takes "true" or "false", not True or False - for key, value in params.items(): - if value in (True, False): - params[key] = json.dumps(value) - - # convert the API content return_format to an HTTP Accept type - try: - return_format_encoded = format_return_types[return_format] - # unless it's not a valid return_format - except KeyError as exc: - raise ValueError( - f"'{return_format}' is not a valid data return type option" - ) from exc - - # then add it to the request headers - params["Accept"] = return_format_encoded - - resp = requests.request(method=method, - url=urljoin(url_base, path), - auth=HTTPBasicAuth(key, None), - headers=params, - json=json_data, - *args, **kwargs) + # I HATE not being able to set empty lists as default arguments + if not ignore_statuses: + ignore_statuses = [] + if not params: + params = {} + + header, resp = _request(method="get", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, + *args, **kwargs) + return header, resp + +def _post(path: str, return_format="native", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + json_data: Optional[Dict] = None, + *args, **kwargs) -> Union[Dict, bytes]: + """Calls :meth:`_request()` for post requests. + + Parameters + ---------- + path : str + The endpoint's path to which the request is being made + return_format : str, optional + The API response format, defaults to ``"native"`` + ignore_statuses : Optional[List], optional + Statuses (together with 200 OK) which don't + raise an HTTPError, defaults to None + params : Optional[Dict], optional + Any other HTTP request parameters, defaults to None + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) + *args + Variable length argument list. + **kwargs + Arbitrary keyword arguments. + + Returns + ------- + Union[Dict, bytes] + A ``dataclass`` if ``return_format`` is ``"native"``, + otherwise ``bytes`` + """ + if not ignore_statuses: + ignore_statuses = [] + if not params: + params = {} + + header, resp = _request(method="post", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + json_data=json_data, + key=key, + *args, **kwargs) + return header, resp + +def _patch(path: str, return_format="native", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + json_data: Optional[Dict] = None, + *args, **kwargs) -> Union[Dict, bytes]: + """Calls :meth:`_request()` for patch requests. + + Parameters + ---------- + path : str + The endpoint's path to which the request is being made + return_format : str, optional + The API response format, defaults to ``"native"`` + ignore_statuses : Optional[List], optional + Statuses (together with 200 OK) which don't + raise an HTTPError, defaults to None + params : Optional[Dict], optional + Any other HTTP request parameters, defaults to None + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) + *args + Variable length argument list. + **kwargs + Arbitrary keyword arguments. + + Returns + ------- + Union[Dict, bytes] + A ``dataclass`` if ``return_format`` is ``"native"``, + otherwise ``bytes`` + """ + + if not ignore_statuses: + ignore_statuses = [] + if not params: + params = {} + + header, resp = _request(method="patch", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, + json_data=json_data, + *args, **kwargs) + return header, resp + +def _delete(path: str, return_format="native", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + *args, **kwargs) -> Union[Dict, bytes]: + """Calls :meth:`_request()` for delete requests. + + Parameters + ---------- + path : str + The endpoint's path to which the request is being made + return_format : str, optional + The API response format, defaults to ``"native"`` + ignore_statuses : Optional[List], optional + Statuses (together with 200 OK) which don't + raise an HTTPError, defaults to None + params : Optional[Dict], optional + Any other HTTP request parameters, defaults to None + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) + *args + Variable length argument list. + **kwargs + Arbitrary keyword arguments. + + Returns + ------- + Union[Dict, bytes] + A ``dataclass`` if ``return_format`` is ``"native"``, + otherwise ``bytes`` + """ - status_handler(resp.status_code, ignore_statuses) - - self._header = resp.headers - - if return_format == "native": - return resp.json() - - return resp.text # if the format is not a dict - - # and here go the specific non-paginated HTTP calls - def _get(self, path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - *args, **kwargs) -> Union[Dict, bytes]: - """Calls :meth:`_request()` for get requests. - - Parameters - ---------- - path : str - The endpoint's path to which the request is being made - return_format : str, optional - The API response format, defaults to ``"native"`` - ignore_statuses : Optional[List], optional - Statuses (together with 200 OK) which don't - raise an HTTPError, defaults to None - params : Optional[Dict], optional - Any other HTTP request parameters, defaults to None - key : Optional[str] - API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. - **kwargs - Arbitrary keyword arguments. - - Returns - ------- - Union[Dict, bytes] - A ``dataclass`` if ``return_format`` is ``"native"``, - otherwise ``bytes`` - """ - - # I HATE not being able to set empty lists as default arguments - if not ignore_statuses: - ignore_statuses = [] - if not params: - params = {} - - resp = self._request(method="get", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - key=key, - *args, **kwargs) - return resp - - def _post(self, path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: - """Calls :meth:`_request()` for post requests. - - Parameters - ---------- - path : str - The endpoint's path to which the request is being made - return_format : str, optional - The API response format, defaults to ``"native"`` - ignore_statuses : Optional[List], optional - Statuses (together with 200 OK) which don't - raise an HTTPError, defaults to None - params : Optional[Dict], optional - Any other HTTP request parameters, defaults to None - key : Optional[str] - API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. - **kwargs - Arbitrary keyword arguments. - - Returns - ------- - Union[Dict, bytes] - A ``dataclass`` if ``return_format`` is ``"native"``, - otherwise ``bytes`` - """ - if not ignore_statuses: - ignore_statuses = [] - if not params: - params = {} - - resp = self._request(method="post", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - json_data=json_data, - key=key, - *args, **kwargs) - return resp - - def _patch(self, path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: - """Calls :meth:`_request()` for patch requests. - - Parameters - ---------- - path : str - The endpoint's path to which the request is being made - return_format : str, optional - The API response format, defaults to ``"native"`` - ignore_statuses : Optional[List], optional - Statuses (together with 200 OK) which don't - raise an HTTPError, defaults to None - params : Optional[Dict], optional - Any other HTTP request parameters, defaults to None - key : Optional[str] - API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. - **kwargs - Arbitrary keyword arguments. - - Returns - ------- - Union[Dict, bytes] - A ``dataclass`` if ``return_format`` is ``"native"``, - otherwise ``bytes`` - """ - - if not ignore_statuses: - ignore_statuses = [] - if not params: - params = {} - - resp = self._request(method="patch", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - key=key, - json_data=json_data, - *args, **kwargs) - return resp - - def _delete(self, path: str, return_format="native", + if not ignore_statuses: + ignore_statuses = [] + if not params: + params = {} + + header, resp = _request(method="delete", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, + *args, **kwargs) + return header, resp + +def _getiter(path: str, + limit: int = 100, + sort: str = "created", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, key: Optional[str] = None, - *args, **kwargs) -> Union[Dict, bytes]: - """Calls :meth:`_request()` for delete requests. - - Parameters - ---------- - path : str - The endpoint's path to which the request is being made - return_format : str, optional - The API response format, defaults to ``"native"`` - ignore_statuses : Optional[List], optional - Statuses (together with 200 OK) which don't - raise an HTTPError, defaults to None - params : Optional[Dict], optional - Any other HTTP request parameters, defaults to None - key : Optional[str] - API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. - **kwargs - Arbitrary keyword arguments. - - Returns - ------- - Union[Dict, bytes] - A ``dataclass`` if ``return_format`` is ``"native"``, - otherwise ``bytes`` - """ - - if not ignore_statuses: - ignore_statuses = [] - if not params: - params = {} - - resp = self._request(method="delete", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - key=key, - *args, **kwargs) - return resp - - def _getiter(self, path: str, - limit: int = 100, - sort: str = "created", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - *args, **kwargs) -> Generator[Dict, None, None]: - """Get :meth:`_request()` for paginated results. - - Parameters - ---------- - path : str - The endpoint's path to which the request is being made - limit : int, optional - Maximum number of results to return, defaults to 100 - sort : str, optional - Sort order to return results in. Valid sort orders are - created, updated, popularity, and distance - ignore_statuses : Optional[List], optional - Statuses (together with 200 OK) which don't - raise an HTTPError, defaults to None - params : Optional[Dict], optional - Any other HTTP request parameters, defaults to None - key : Optional[str] - API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. - **kwargs - Arbitrary keyword arguments. - - Returns - ------- - Generator[Dict, None, None] - A generator of dicts. Return format cannot be specified. - """ - - if not ignore_statuses: - ignore_statuses = [] - if not params: - params = {} - - # the API only takes "true" or "false", not True or False - for key, value in params.items(): - if value in (True, False): - params[key] = json.dumps(value) - - valid_sort_orders = ["created", "updated", "popularity", "distance"] - if sort not in valid_sort_orders: - raise ValueError( - f"sort argument must be one of {', '.join(valid_sort_orders)}") - else: - params["sort"] = sort - - url = urljoin(url_base, path) - auth = HTTPBasicAuth(key, None) - - session = requests.Session() - # initially no results have been fetched yet - num_results = 0 - - r_fpdb = session.get( - url=url, - params=params, - auth=auth, - *args, **kwargs) - status_handler(r_fpdb.status_code, ignore_statuses) + *args, **kwargs) -> Generator[Dict, None, None]: + """Get :meth:`_request()` for paginated results. + + Parameters + ---------- + path : str + The endpoint's path to which the request is being made + limit : int, optional + Maximum number of results to return, defaults to 100 + sort : str, optional + Sort order to return results in. Valid sort orders are + created, updated, popularity, and distance + ignore_statuses : Optional[List], optional + Statuses (together with 200 OK) which don't + raise an HTTPError, defaults to None + params : Optional[Dict], optional + Any other HTTP request parameters, defaults to None + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) + *args + Variable length argument list. + **kwargs + Arbitrary keyword arguments. + + Returns + ------- + Generator[Dict, None, None] + A generator of dicts. Return format cannot be specified. + """ - # I detest responses which "may" be paginated - # therefore I choose to pretend that all pages are paginated - # if it is unpaginated I say it is paginated with 1 page - if 'X-Page-Count' in r_fpdb.headers: - num_pages = int(r_fpdb.headers['X-Page-Count']) - else: - num_pages = 1 - - # while page <= num_pages... - for page in range(0, num_pages): - params['page'] = page - r_fpdb = session.get(url=url, - params=params, - auth=auth, - *args, **kwargs) - status_handler(r_fpdb.status_code, ignore_statuses) - # ...keep cycling through pages... - for i in r_fpdb.json(): - # ...and return every dictionary in there... - yield i - num_results += 1 - # ...unless the result limit has been reached - if num_results == limit: - return + if not ignore_statuses: + ignore_statuses = [] + if not params: + params = {} + + # the API only takes "true" or "false", not True or False + for key, value in params.items(): + if value in (True, False): + params[key] = json.dumps(value) + + valid_sort_orders = ["created", "updated", "popularity", "distance"] + if sort not in valid_sort_orders: + raise ValueError( + f"sort argument must be one of {', '.join(valid_sort_orders)}") + else: + params["sort"] = sort + + url = urljoin(url_base, path) + auth = HTTPBasicAuth(key, None) + + session = requests.Session() + # initially no results have been fetched yet + num_results = 0 + + r_fpdb = session.get( + url=url, + params=params, + auth=auth, + *args, **kwargs) + status_handler(r_fpdb.status_code, ignore_statuses) + + # I detest responses which "may" be paginated + # therefore I choose to pretend that all pages are paginated + # if it is unpaginated I say it is paginated with 1 page + if 'X-Page-Count' in r_fpdb.headers: + num_pages = int(r_fpdb.headers['X-Page-Count']) + else: + num_pages = 1 + + # while page <= num_pages... + for page in range(0, num_pages): + params['page'] = page + r_fpdb = session.get(url=url, + params=params, + auth=auth, + *args, **kwargs) + status_handler(r_fpdb.status_code, ignore_statuses) + # ...keep cycling through pages... + for i in r_fpdb.json(): + # ...and return every dictionary in there... + yield i + num_results += 1 + # ...unless the result limit has been reached + if num_results == limit: + return diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index 3cb48b0..f389f81 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -1,103 +1,101 @@ from typing import Optional -from flightplandb.flightplandb import FlightPlanDB +from flightplandb.flightplandb import FlightPlanDB as flightplandb from flightplandb.datatypes import StatusResponse -class API(FlightPlanDB): - def _header_value(self, header_key: str, key: Optional[str] = None) -> str: - """Gets header value for key - - Parameters - ---------- - header_key : str - One of the HTTP header keys - - Returns - ------- - str - The value corresponding to the passed key - """ - - if header_key not in self._header: - self.ping(key=key) # Make at least one request - return self._header[header_key] - - def version(self, key: Optional[str] = None) -> int: - """API version that returned the response - - Returns - ------- - int - API version - """ - - return int(self._header_value("X-API-Version", key=key)) - - def units(self, key: Optional[str] = None) -> str: - """The units system used for numeric values. - https://flightplandatabase.com/dev/api#units - - Returns - ------- - str - AVIATION, METRIC or SI - """ - - return self._header_value("X-Units", key=key) - - def limit_cap(self, key: Optional[str] = None) -> int: - """The number of requests allowed per day, operated on an hourly rolling - basis. i.e requests used between 19:00 and 20:00 will become available - again at 19:00 the following day. API key authenticated requests get a - higher daily rate limit and can be raised if a compelling - use case is presented. - - Returns - ------- - int - number of allowed requests per day - """ - - return int(self._header_value("X-Limit-Cap", key=key)) - - def limit_used(self, key: Optional[str] = None) -> int: - """The number of requests used in the current period - by the presented API key or IP address - - Returns - ------- - int - number of requests used in period - """ - - return int(self._header_value("X-Limit-Used", key=key)) - - def ping(self, key: Optional[str] = None) -> StatusResponse: - """Checks API status to see if it is up - - Returns - ------- - StatusResponse - OK 200 means the service is up and running. - """ - - resp = self._get(path="", key=key) - return StatusResponse(**resp) - - def revoke(self, key: Optional[str] = None) -> StatusResponse: - """Revoke the API key in use in the event it is compromised. - - Requires authentication. - - Returns - ------- - StatusResponse - If the HTTP response code is 200 and the status message is "OK", - then the key has been revoked and any further requests will be - rejected. - Any other status code or message indicates an error has - occurred and the errors array will give further details. - """ - - resp = self._get(path="/auth/revoke", key=key) - return StatusResponse(**resp) +def _header_value(header_key: str, key: Optional[str] = None) -> str: + """Gets header value for key + + Parameters + ---------- + header_key : str + One of the HTTP header keys + + Returns + ------- + str + The value corresponding to the passed key + """ + + headers, _ = flightplandb._get(path="", key=key) # Make one request to fetch headers + return headers[header_key] + +def version(key: Optional[str] = None) -> int: + """API version that returned the response + + Returns + ------- + int + API version + """ + + return int(_header_value("X-API-Version", key=key)) + +def units(key: Optional[str] = None) -> str: + """The units system used for numeric values. + https://flightplandatabase.com/dev/api#units + + Returns + ------- + str + AVIATION, METRIC or SI + """ + + return _header_value("X-Units", key=key) + +def limit_cap(key: Optional[str] = None) -> int: + """The number of requests allowed per day, operated on an hourly rolling + basis. i.e requests used between 19:00 and 20:00 will become available + again at 19:00 the following day. API key authenticated requests get a + higher daily rate limit and can be raised if a compelling + use case is presented. + + Returns + ------- + int + number of allowed requests per day + """ + + return int(_header_value("X-Limit-Cap", key=key)) + +def limit_used(key: Optional[str] = None) -> int: + """The number of requests used in the current period + by the presented API key or IP address + + Returns + ------- + int + number of requests used in period + """ + + return int(_header_value("X-Limit-Used", key=key)) + +def ping(key: Optional[str] = None) -> StatusResponse: + """Checks API status to see if it is up + + Returns + ------- + StatusResponse + OK 200 means the service is up and running. + """ + + _, resp = flightplandb._get(path="", key=key) + return StatusResponse(**resp) + +def revoke(key: Optional[str] = None) -> StatusResponse: + """Revoke the API key in use in the event it is compromised. + + Requires authentication. + + Returns + ------- + StatusResponse + If the HTTP response code is 200 and the status message is "OK", + then the key has been revoked and any further requests will be + rejected. + Any other status code or message indicates an error has + occurred and the errors array will give further details. + """ + + _, resp = flightplandb._get(path="/auth/revoke", key=key) + return StatusResponse(**resp) From 8a23fd6ee0e9bb1d6158e89ec06ba51ded094280 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 19:22:49 +0200 Subject: [PATCH 40/86] rename flightplandb file, fix __all__ in __init__.py --- src/flightplandb/__init__.py | 7 +++++-- src/flightplandb/{flightplandb.py => internal.py} | 0 src/flightplandb/submodules/__init__.py | 1 + src/flightplandb/submodules/api.py | 8 ++++---- src/flightplandb/submodules/nav.py | 2 +- src/flightplandb/submodules/plan.py | 2 +- src/flightplandb/submodules/tags.py | 2 +- src/flightplandb/submodules/user.py | 2 +- src/flightplandb/submodules/weather.py | 2 +- 9 files changed, 15 insertions(+), 11 deletions(-) rename src/flightplandb/{flightplandb.py => internal.py} (100%) diff --git a/src/flightplandb/__init__.py b/src/flightplandb/__init__.py index f4c16da..c3087bc 100644 --- a/src/flightplandb/__init__.py +++ b/src/flightplandb/__init__.py @@ -3,5 +3,8 @@ # Version of the flightplandb package __version__ = "0.5.0" -from flightplandb.datatypes import * # noqa: F403, F401 -from flightplandb.submodules import * # noqa: F403, F401 +from . import internal, exceptions, datatypes, submodules + +# from flightplandb.datatypes import * # noqa: F403, F401 +# from flightplandb.submodules import * # noqa: F403, F401 +__all__ = ["internal", "exceptions", "datatypes", "submodules"] diff --git a/src/flightplandb/flightplandb.py b/src/flightplandb/internal.py similarity index 100% rename from src/flightplandb/flightplandb.py rename to src/flightplandb/internal.py diff --git a/src/flightplandb/submodules/__init__.py b/src/flightplandb/submodules/__init__.py index 88ddd43..ecda0f7 100644 --- a/src/flightplandb/submodules/__init__.py +++ b/src/flightplandb/submodules/__init__.py @@ -1 +1,2 @@ +from . import api, nav, plan, tags, user, weather __all__ = ["api", "nav", "plan", "tags", "user", "weather"] diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index f389f81..b2878a3 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -1,5 +1,5 @@ from typing import Optional -from flightplandb.flightplandb import FlightPlanDB as flightplandb +from flightplandb import internal from flightplandb.datatypes import StatusResponse @@ -17,7 +17,7 @@ def _header_value(header_key: str, key: Optional[str] = None) -> str: The value corresponding to the passed key """ - headers, _ = flightplandb._get(path="", key=key) # Make one request to fetch headers + headers, _ = internal._get(path="", key=key) # Make one request to fetch headers return headers[header_key] def version(key: Optional[str] = None) -> int: @@ -79,7 +79,7 @@ def ping(key: Optional[str] = None) -> StatusResponse: OK 200 means the service is up and running. """ - _, resp = flightplandb._get(path="", key=key) + _, resp = internal._get(path="", key=key) return StatusResponse(**resp) def revoke(key: Optional[str] = None) -> StatusResponse: @@ -97,5 +97,5 @@ def revoke(key: Optional[str] = None) -> StatusResponse: occurred and the errors array will give further details. """ - _, resp = flightplandb._get(path="/auth/revoke", key=key) + _, resp = internal._get(path="/auth/revoke", key=key) return StatusResponse(**resp) diff --git a/src/flightplandb/submodules/nav.py b/src/flightplandb/submodules/nav.py index 4ae5dd5..3bfddc3 100644 --- a/src/flightplandb/submodules/nav.py +++ b/src/flightplandb/submodules/nav.py @@ -1,5 +1,5 @@ from typing import Generator, List, Optional -from flightplandb.flightplandb import FlightPlanDB +from flightplandb.internal import FlightPlanDB from flightplandb.datatypes import Airport, Track, SearchNavaid diff --git a/src/flightplandb/submodules/plan.py b/src/flightplandb/submodules/plan.py index 11f9ebf..9ba2eb4 100644 --- a/src/flightplandb/submodules/plan.py +++ b/src/flightplandb/submodules/plan.py @@ -3,7 +3,7 @@ StatusResponse, PlanQuery, Plan, GenerateQuery ) -from flightplandb.flightplandb import FlightPlanDB +from flightplandb.internal import FlightPlanDB class PlanAPI(FlightPlanDB): diff --git a/src/flightplandb/submodules/tags.py b/src/flightplandb/submodules/tags.py index 38d598c..32d1b72 100644 --- a/src/flightplandb/submodules/tags.py +++ b/src/flightplandb/submodules/tags.py @@ -1,6 +1,6 @@ from typing import List, Optional from flightplandb.datatypes import Tag -from flightplandb.flightplandb import FlightPlanDB +from flightplandb.internal import FlightPlanDB class TagsAPI(FlightPlanDB): diff --git a/src/flightplandb/submodules/user.py b/src/flightplandb/submodules/user.py index d671178..2419f54 100644 --- a/src/flightplandb/submodules/user.py +++ b/src/flightplandb/submodules/user.py @@ -1,6 +1,6 @@ from typing import Generator, Optional from flightplandb.datatypes import Plan, User, UserSmall -from flightplandb.flightplandb import FlightPlanDB +from flightplandb.internal import FlightPlanDB class UserAPI(FlightPlanDB): diff --git a/src/flightplandb/submodules/weather.py b/src/flightplandb/submodules/weather.py index 322d70a..260e187 100644 --- a/src/flightplandb/submodules/weather.py +++ b/src/flightplandb/submodules/weather.py @@ -1,6 +1,6 @@ from flightplandb.datatypes import Weather from typing import Optional -from flightplandb.flightplandb import FlightPlanDB +from flightplandb.internal import FlightPlanDB class WeatherAPI(FlightPlanDB): From ecef85539c929f9c09c51988fefb1dcc99f1ac1c Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 19:23:26 +0200 Subject: [PATCH 41/86] remove unused import --- src/flightplandb/internal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/flightplandb/internal.py b/src/flightplandb/internal.py index d5ee91e..757b86f 100644 --- a/src/flightplandb/internal.py +++ b/src/flightplandb/internal.py @@ -22,7 +22,6 @@ import json import requests from requests.auth import HTTPBasicAuth -from requests.structures import CaseInsensitiveDict from flightplandb.exceptions import status_handler From 0ad143188cca23ddf403346074238ace76e01376 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 20:56:37 +0200 Subject: [PATCH 42/86] move remaining functions out of classes, separate headers fetching --- src/flightplandb/internal.py | 28 +- src/flightplandb/submodules/api.py | 12 +- src/flightplandb/submodules/nav.py | 152 +++--- src/flightplandb/submodules/plan.py | 640 +++++++++++++------------ src/flightplandb/submodules/tags.py | 26 +- src/flightplandb/submodules/user.py | 245 +++++----- src/flightplandb/submodules/weather.py | 50 +- 7 files changed, 577 insertions(+), 576 deletions(-) diff --git a/src/flightplandb/internal.py b/src/flightplandb/internal.py index 757b86f..e1f9d3e 100644 --- a/src/flightplandb/internal.py +++ b/src/flightplandb/internal.py @@ -142,7 +142,15 @@ def _request(method: str, else: return header, resp.text # if the format is not a dict + # and here go the specific non-paginated HTTP calls +def _get_headers(key: Optional[str] = None): + headers, _ = _request(method="get", + path="", + key=key) + return headers + + def _get(path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, @@ -181,14 +189,15 @@ def _get(path: str, return_format="native", if not params: params = {} - header, resp = _request(method="get", + _, resp = _request(method="get", path=path, return_format=return_format, ignore_statuses=ignore_statuses, params=params, key=key, *args, **kwargs) - return header, resp + return resp + def _post(path: str, return_format="native", ignore_statuses: Optional[List] = None, @@ -227,7 +236,7 @@ def _post(path: str, return_format="native", if not params: params = {} - header, resp = _request(method="post", + _, resp = _request(method="post", path=path, return_format=return_format, ignore_statuses=ignore_statuses, @@ -235,7 +244,8 @@ def _post(path: str, return_format="native", json_data=json_data, key=key, *args, **kwargs) - return header, resp + return resp + def _patch(path: str, return_format="native", ignore_statuses: Optional[List] = None, @@ -275,7 +285,7 @@ def _patch(path: str, return_format="native", if not params: params = {} - header, resp = _request(method="patch", + _, resp = _request(method="patch", path=path, return_format=return_format, ignore_statuses=ignore_statuses, @@ -283,7 +293,8 @@ def _patch(path: str, return_format="native", key=key, json_data=json_data, *args, **kwargs) - return header, resp + return resp + def _delete(path: str, return_format="native", ignore_statuses: Optional[List] = None, @@ -322,14 +333,15 @@ def _delete(path: str, return_format="native", if not params: params = {} - header, resp = _request(method="delete", + _, resp = _request(method="delete", path=path, return_format=return_format, ignore_statuses=ignore_statuses, params=params, key=key, *args, **kwargs) - return header, resp + return resp + def _getiter(path: str, limit: int = 100, diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index b2878a3..b773453 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -17,9 +17,10 @@ def _header_value(header_key: str, key: Optional[str] = None) -> str: The value corresponding to the passed key """ - headers, _ = internal._get(path="", key=key) # Make one request to fetch headers + headers = internal._get_headers(key=key) # Make one request to fetch headers return headers[header_key] + def version(key: Optional[str] = None) -> int: """API version that returned the response @@ -31,6 +32,7 @@ def version(key: Optional[str] = None) -> int: return int(_header_value("X-API-Version", key=key)) + def units(key: Optional[str] = None) -> str: """The units system used for numeric values. https://flightplandatabase.com/dev/api#units @@ -43,6 +45,7 @@ def units(key: Optional[str] = None) -> str: return _header_value("X-Units", key=key) + def limit_cap(key: Optional[str] = None) -> int: """The number of requests allowed per day, operated on an hourly rolling basis. i.e requests used between 19:00 and 20:00 will become available @@ -58,6 +61,7 @@ def limit_cap(key: Optional[str] = None) -> int: return int(_header_value("X-Limit-Cap", key=key)) + def limit_used(key: Optional[str] = None) -> int: """The number of requests used in the current period by the presented API key or IP address @@ -70,6 +74,7 @@ def limit_used(key: Optional[str] = None) -> int: return int(_header_value("X-Limit-Used", key=key)) + def ping(key: Optional[str] = None) -> StatusResponse: """Checks API status to see if it is up @@ -79,9 +84,10 @@ def ping(key: Optional[str] = None) -> StatusResponse: OK 200 means the service is up and running. """ - _, resp = internal._get(path="", key=key) + resp = internal._get(path="", key=key) return StatusResponse(**resp) + def revoke(key: Optional[str] = None) -> StatusResponse: """Revoke the API key in use in the event it is compromised. @@ -97,5 +103,5 @@ def revoke(key: Optional[str] = None) -> StatusResponse: occurred and the errors array will give further details. """ - _, resp = internal._get(path="/auth/revoke", key=key) + resp = internal._get(path="/auth/revoke", key=key) return StatusResponse(**resp) diff --git a/src/flightplandb/submodules/nav.py b/src/flightplandb/submodules/nav.py index 3bfddc3..17d091b 100644 --- a/src/flightplandb/submodules/nav.py +++ b/src/flightplandb/submodules/nav.py @@ -1,87 +1,81 @@ +"""Commands related to navigation aids and airports.""" from typing import Generator, List, Optional -from flightplandb.internal import FlightPlanDB from flightplandb.datatypes import Airport, Track, SearchNavaid +from flightplandb.internal import _get, _getiter -class NavAPI(FlightPlanDB): +def airport(icao: str, key: Optional[str] = None) -> Airport: + """Fetches information about an airport. + Parameters + ---------- + icao : str + The airport ICAO to fetch information for + + Returns + ------- + Union[Airport, None] + :class:`~flightplandb.datatypes.Airport` if the airport was found. + + Raises + ------ + :class:`~flightplandb.exceptions.BadRequestException` + No airport with the specified ICAO code was found. + """ + + resp = _get(path=f"/nav/airport/{icao}", key=key) + return Airport(**resp) + +def nats(key: Optional[str] = None) -> List[Track]: + """Fetches current North Atlantic Tracks. + + Returns + ------- + List[Track] + List of NATs """ - Commands related to navigation aids and airports. - Accessed via :meth:`~flightplandb.flightplandb.FlightPlanDB.nav`. + + return list( + map(lambda n: Track(**n), _get("/nav/NATS", key=key))) + +def pacots(key: Optional[str] = None) -> List[Track]: + """Fetches current Pacific Organized Track System tracks. + + Returns + ------- + List[Track] + List of PACOTs + """ + + return list( + map(lambda t: Track(**t), _get(path="/nav/PACOTS", key=key))) + +def search(query: str, + type_: str = None, key: Optional[str] = None + ) -> Generator[SearchNavaid, None, None]: + r"""Searches navaids using a query. + + Parameters + ---------- + query : str + The search query. Searches the navaid identifier and navaid name + type\_ : str + Navaid type. + Must be either ``None`` (default value, returns all types) or one + of :py:obj:`~flightplandb.datatypes.SearchNavaid.validtypes` + + Yields + ------- + Generator[SearchNavaid, None, None] + A generator of navaids with either a name or ident + matching the ``query`` """ - def airport(self, icao: str, key: Optional[str] = None) -> Airport: - """Fetches information about an airport. - - Parameters - ---------- - icao : str - The airport ICAO to fetch information for - - Returns - ------- - Union[Airport, None] - :class:`~flightplandb.datatypes.Airport` if the airport was found. - - Raises - ------ - :class:`~flightplandb.exceptions.BadRequestException` - No airport with the specified ICAO code was found. - """ - - resp = self._get(path=f"/nav/airport/{icao}", key=key) - return Airport(**resp) - - def nats(self, key: Optional[str] = None) -> List[Track]: - """Fetches current North Atlantic Tracks. - - Returns - ------- - List[Track] - List of NATs - """ - - return list( - map(lambda n: Track(**n), self._get("/nav/NATS", key=key))) - - def pacots(self, key: Optional[str] = None) -> List[Track]: - """Fetches current Pacific Organized Track System tracks. - - Returns - ------- - List[Track] - List of PACOTs - """ - - return list( - map(lambda t: Track(**t), self._get(path="/nav/PACOTS", key=key))) - - def search(self, query: str, - type_: str = None, key: Optional[str] = None - ) -> Generator[SearchNavaid, None, None]: - r"""Searches navaids using a query. - - Parameters - ---------- - query : str - The search query. Searches the navaid identifier and navaid name - type\_ : str - Navaid type. - Must be either ``None`` (default value, returns all types) or one - of :py:obj:`~flightplandb.datatypes.SearchNavaid.validtypes` - - Yields - ------- - Generator[SearchNavaid, None, None] - A generator of navaids with either a name or ident - matching the ``query`` - """ - - params = {"q": query} - if type_: - if type_ in SearchNavaid.validtypes: - params["types"] = type_ - else: - raise ValueError(f"{type_} is not a valid Navaid type") - for i in self._getiter(path="/search/nav", params=params, key=key): - yield SearchNavaid(**i) + params = {"q": query} + if type_: + if type_ in SearchNavaid.validtypes: + params["types"] = type_ + else: + raise ValueError(f"{type_} is not a valid Navaid type") + for i in _getiter(path="/search/nav", params=params, key=key): + yield SearchNavaid(**i) diff --git a/src/flightplandb/submodules/plan.py b/src/flightplandb/submodules/plan.py index 9ba2eb4..4fbf90c 100644 --- a/src/flightplandb/submodules/plan.py +++ b/src/flightplandb/submodules/plan.py @@ -1,333 +1,337 @@ +"""Flightplan-related commands.""" from typing import Generator, Union, Optional from flightplandb.datatypes import ( StatusResponse, PlanQuery, Plan, GenerateQuery ) -from flightplandb.internal import FlightPlanDB +from flightplandb.internal import _get, _patch, _post, _delete, _getiter + + +def fetch(id_: int, + return_format: str = "native", + key: Optional[str] = None) -> Union[Plan, None, bytes]: + # Underscore for id_ must be escaped as id\_ so sphinx shows the _. + # However, this would raise W605. To fix this, a raw string is used. + r""" + Fetches a flight plan and its associated attributes by ID. + Returns it in specified format. + + Parameters + ---------- + id\_ : int + The ID of the flight plan to fetch + return_format : str + The API response format, defaults to ``"native"``. + Must be one of the keys in the table at the top of the page. + + Returns + ------- + Union[Plan, None, bytes] + :class:`~flightplandb.datatypes.Plan` by default or if ``"native"`` + is specified as the ``return_format``. + + ``bytes`` if a different format than ``"native"`` was specified. + + Raises + ------ + :class:`~flightplandb.exceptions.NotFoundException` + No plan with the specified id was found. + """ + + request = _get( + path=f"/plan/{id_}", + return_format=return_format, + key=key + ) + + if return_format == "native": + return Plan(**request) + + return request # if the format is not a dict -class PlanAPI(FlightPlanDB): +def create(plan: Plan, + return_format: str = "native", + key: Optional[str] = None) -> Union[Plan, bytes]: + """Creates a new flight plan. + Requires authentication. + + Parameters + ---------- + plan : Plan + The Plan object to register on the website + + Returns + ------- + Plan + The registered flight plan created on flight plan database + + Raises + ------ + :class:`~flightplandb.exceptions.BadRequestException` + The plan submitted had incorrect arguments or was + otherwise unusable. """ - Flightplan-related commands. - Accessed via :meth:`~flightplandb.flightplandb.FlightPlanDB.plan`. + + request = _post( + path="/plan/", + return_format=return_format, + json=plan._to_api_dict(), + key=key) + + if return_format == "native": + return Plan(**request) + + return request + + +def edit(plan: Plan, + return_format: str = "native", + key: Optional[str] = None) -> Union[Plan, bytes]: + """Edits a flight plan linked to your account. + + Requires authentication. + + Parameters + ---------- + plan : Plan + The new Plan object to replace the old one associated with that ID + + Returns + ------- + Plan + The registered flight plan created on flight plan database, + corresponding to the route after being edited + + Raises + ------ + :class:`~flightplandb.exceptions.BadRequestException` + The plan submitted had incorrect arguments + or was otherwise unusable. + :class:`~flightplandb.exceptions.NotFoundException` + No plan with the specified id was found. """ - def fetch(self, id_: int, - return_format: str = "native", - key: Optional[str] = None) -> Union[Plan, None, bytes]: - # Underscore for id_ must be escaped as id\_ so sphinx shows the _. - # However, this would raise W605. To fix this, a raw string is used. - r""" - Fetches a flight plan and its associated attributes by ID. - Returns it in specified format. - - Parameters - ---------- - id\_ : int - The ID of the flight plan to fetch - return_format : str - The API response format, defaults to ``"native"``. - Must be one of the keys in the table at the top of the page. - - Returns - ------- - Union[Plan, None, bytes] - :class:`~flightplandb.datatypes.Plan` by default or if ``"native"`` - is specified as the ``return_format``. - - ``bytes`` if a different format than ``"native"`` was specified. - - Raises - ------ - :class:`~flightplandb.exceptions.NotFoundException` - No plan with the specified id was found. - """ - - request = self._get( - path=f"/plan/{id_}", - return_format=return_format, - key=key - ) - - if return_format == "native": - return Plan(**request) - - return request # if the format is not a dict - - def create(self, plan: Plan, - return_format: str = "native", - key: Optional[str] = None) -> Union[Plan, bytes]: - """Creates a new flight plan. - - Requires authentication. - - Parameters - ---------- - plan : Plan - The Plan object to register on the website - - Returns - ------- - Plan - The registered flight plan created on flight plan database - - Raises - ------ - :class:`~flightplandb.exceptions.BadRequestException` - The plan submitted had incorrect arguments or was - otherwise unusable. - """ - - request = self._post( - path="/plan/", - return_format=return_format, - json=plan._to_api_dict(), + plan_data = plan._to_api_dict() + request = _patch( + path=f"/plan/{plan_data['id']}", + return_format=return_format, + json=plan_data, + key=key) + + if return_format == "native": + return Plan(**request) + + return request + + +def delete(id_: int, + key: Optional[str] = None) -> StatusResponse: + r"""Deletes a flight plan that is linked to your account. + + Requires authentication. + + Parameters + ---------- + id\_ : int + The ID of the flight plan to delete + + Returns + ------- + StatusResponse + OK 200 means a successful delete + + Raises + ------ + :class:`~flightplandb.exceptions.NotFoundException` + No plan with the specified id was found. + """ + + resp = _delete(path=f"/plan/{id_}", key=key) + return StatusResponse(**resp) + + +def search(plan_query: PlanQuery, sort: str = "created", + include_route: bool = False, limit: int = 100, + key: Optional[str] = None) -> Generator[Plan, None, None]: + """Searches for flight plans. + A number of search parameters are available. + They will be combined to form a search request. + + Requires authentication if route is included in results + + Parameters + ---------- + plan_query : PlanQuery + A dataclass containing multiple options for plan searches + sort : str, optional + Sort order to return results in. Valid sort orders are + created, updated, popularity, and distance + limit : int + Maximum number of plans to return, defaults to 100 + include_route : bool, optional + Include route in response, defaults to False + + Yields + ------- + Generator[Plan, None, None] + A generator containing :class:`~flightplandb.datatypes.Plan` + objects. + """ + + request_json = plan_query._to_api_dict() + request_json["includeRoute"] = include_route + + for i in _getiter(path="/search/plans", + sort=sort, + params=request_json, + limit=limit, + key=key): + yield Plan(**i) + + +def has_liked(id_: int, + key: Optional[str] = None) -> bool: + r"""Fetches your like status for a flight plan. + + Requires authentication. + + Parameters + ---------- + id\_ : int + ID of the flightplan to be checked + + Returns + ------- + bool + ``True``/``False`` to indicate that the plan was liked / not liked + """ + + resp = _get( + path=f"/plan/{id_}/like", + ignore_statuses=[404], key=key) + sr = StatusResponse(**resp) + return sr.message != "Not Found" + + +def like(id_: int, + key: Optional[str] = None) -> StatusResponse: + r"""Likes a flight plan. + + Requires authentication. + + Parameters + ---------- + id\_ : int + ID of the flightplan to be liked + + Returns + ------- + StatusResponse + ``message=Created`` means the plan was successfully liked. + ``message=OK`` means the plan was already liked. + + Raises + ------ + :class:`~flightplandb.exceptions.NotFoundException` + No plan with the specified id was found. + """ + + resp = _post(path=f"/plan/{id_}/like", key=key) + return StatusResponse(**resp) + + +def unlike(id_: int, + key: Optional[str] = None) -> bool: + r"""Removes a flight plan like. - if return_format == "native": - return Plan(**request) - - return request - - def edit(self, plan: Plan, - return_format: str = "native", - key: Optional[str] = None) -> Union[Plan, bytes]: - """Edits a flight plan linked to your account. - - Requires authentication. - - Parameters - ---------- - plan : Plan - The new Plan object to replace the old one associated with that ID - - Returns - ------- - Plan - The registered flight plan created on flight plan database, - corresponding to the route after being edited - - Raises - ------ - :class:`~flightplandb.exceptions.BadRequestException` - The plan submitted had incorrect arguments - or was otherwise unusable. - :class:`~flightplandb.exceptions.NotFoundException` - No plan with the specified id was found. - """ - - plan_data = plan._to_api_dict() - request = self._patch( - path=f"/plan/{plan_data['id']}", - return_format=return_format, - json=plan_data, + Requires authentication. + + Parameters + ---------- + id\_ : int + ID of the flightplan to be unliked + + Returns + ------- + bool + ``True`` for a successful unlike + + Raises + ------ + :class:`~flightplandb.exceptions.NotFoundException` + No plan with the specified id was found, + or the plan was found but wasn't liked. + """ + + _delete(path=f"/plan/{id_}/like", key=key) + return True + + +def generate(gen_query: GenerateQuery, + include_route: bool = False, + key: Optional[str] = None) -> Union[Plan, bytes]: + """Creates a new flight plan using the route generator. + + Requires authentication. + + Parameters + ---------- + gen_query : GenerateQuery + A dataclass with options for flight plan generation + + Returns + ------- + Plan + The registered flight plan created on flight plan database, + corresponding to the generated route + include_route : bool, optional + Include route in response, defaults to false + """ + + request_json = gen_query._to_api_dict() + + # due to an API bug this must be a string instead of a boolean + request_json["includeRoute"] = "true" if include_route else "false" + + resp = _post( + path="/auto/generate", + json_data=request_json, key=key) + return Plan(**resp) + + +def decode(route: str, + key: Optional[str] = None) -> Plan: + """Creates a new flight plan using the route decoder. + + Requires authentication. + + Parameters + ---------- + route : str + The route to decode. Use a comma or space separated string of + waypoints, beginning and ending with valid airport ICAOs + (e.g. KSAN BROWS TRM LRAIN KDEN). Airways are supported if they + are preceded and followed by valid waypoints on the airway + (e.g. 06TRA UL851 BEGAR). SID and STAR procedures are not + currently supported and will be skipped, along with any + other unmatched waypoints. + + Returns + ------- + Plan + The registered flight plan created on flight plan database, + corresponding to the decoded route + + Raises + ------ + :class:`~flightplandb.exceptions.BadRequestException` + The encoded plan submitted had incorrect + arguments or was otherwise unusable. + """ - if return_format == "native": - return Plan(**request) - - return request - - def delete(self, id_: int, - key: Optional[str] = None) -> StatusResponse: - r"""Deletes a flight plan that is linked to your account. - - Requires authentication. - - Parameters - ---------- - id\_ : int - The ID of the flight plan to delete - - Returns - ------- - StatusResponse - OK 200 means a successful delete - - Raises - ------ - :class:`~flightplandb.exceptions.NotFoundException` - No plan with the specified id was found. - """ - - resp = self._delete(path=f"/plan/{id_}", key=key) - return(StatusResponse(**resp)) - - def search(self, plan_query: PlanQuery, sort: str = "created", - include_route: bool = False, limit: int = 100, - key: Optional[str] = None) -> Generator[Plan, None, None]: - """Searches for flight plans. - A number of search parameters are available. - They will be combined to form a search request. - - Requires authentication if route is included in results - - Parameters - ---------- - plan_query : PlanQuery - A dataclass containing multiple options for plan searches - sort : str, optional - Sort order to return results in. Valid sort orders are - created, updated, popularity, and distance - limit : int - Maximum number of plans to return, defaults to 100 - include_route : bool, optional - Include route in response, defaults to False - - Yields - ------- - Generator[Plan, None, None] - A generator containing :class:`~flightplandb.datatypes.Plan` - objects. - """ - - request_json = plan_query._to_api_dict() - request_json["includeRoute"] = include_route - - for i in self._getiter(path="/search/plans", - sort=sort, - params=request_json, - limit=limit, - key=key): - yield Plan(**i) - - def has_liked(self, id_: int, - key: Optional[str] = None) -> bool: - r"""Fetches your like status for a flight plan. - - Requires authentication. - - Parameters - ---------- - id\_ : int - ID of the flightplan to be checked - - Returns - ------- - bool - ``True``/``False`` to indicate that the plan was liked / not liked - """ - - sr = StatusResponse( - **self._get( - path=f"/plan/{id_}/like", - ignore_statuses=[404], - key=key)) - return sr.message != "Not Found" - - def like(self, id_: int, - key: Optional[str] = None) -> StatusResponse: - r"""Likes a flight plan. - - Requires authentication. - - Parameters - ---------- - id\_ : int - ID of the flightplan to be liked - - Returns - ------- - StatusResponse - ``message=Created`` means the plan was successfully liked. - ``message=OK`` means the plan was already liked. - - Raises - ------ - :class:`~flightplandb.exceptions.NotFoundException` - No plan with the specified id was found. - """ - - return StatusResponse(**self._post(path=f"/plan/{id_}/like", key=key)) - - def unlike(self, id_: int, - key: Optional[str] = None) -> bool: - r"""Removes a flight plan like. - - Requires authentication. - - Parameters - ---------- - id\_ : int - ID of the flightplan to be unliked - - Returns - ------- - bool - ``True`` for a successful unlike - - Raises - ------ - :class:`~flightplandb.exceptions.NotFoundException` - No plan with the specified id was found, - or the plan was found but wasn't liked. - """ - - self._delete(path=f"/plan/{id_}/like", key=key) - return True - - def generate(self, gen_query: GenerateQuery, - include_route: bool = False, - key: Optional[str] = None) -> Union[Plan, bytes]: - """Creates a new flight plan using the route generator. - - Requires authentication. - - Parameters - ---------- - gen_query : GenerateQuery - A dataclass with options for flight plan generation - - Returns - ------- - Plan - The registered flight plan created on flight plan database, - corresponding to the generated route - include_route : bool, optional - Include route in response, defaults to false - """ - - request_json = gen_query._to_api_dict() - - # due to an API bug this must be a string instead of a boolean - request_json["includeRoute"] = "true" if include_route else "false" - - return Plan( - **self._post( - path="/auto/generate", - json_data=request_json, - key=key)) - - def decode(self, route: str, - key: Optional[str] = None) -> Plan: - """Creates a new flight plan using the route decoder. - - Requires authentication. - - Parameters - ---------- - route : str - The route to decode. Use a comma or space separated string of - waypoints, beginning and ending with valid airport ICAOs - (e.g. KSAN BROWS TRM LRAIN KDEN). Airways are supported if they - are preceded and followed by valid waypoints on the airway - (e.g. 06TRA UL851 BEGAR). SID and STAR procedures are not - currently supported and will be skipped, along with any - other unmatched waypoints. - - Returns - ------- - Plan - The registered flight plan created on flight plan database, - corresponding to the decoded route - - Raises - ------ - :class:`~flightplandb.exceptions.BadRequestException` - The encoded plan submitted had incorrect - arguments or was otherwise unusable. - """ - - return Plan(**self._post( - path="/auto/decode", json_data={"route": route}, key=key)) + resp = _post(path="/auto/decode", json_data={"route": route}, key=key) + return Plan(**resp) diff --git a/src/flightplandb/submodules/tags.py b/src/flightplandb/submodules/tags.py index 32d1b72..ddf6bfb 100644 --- a/src/flightplandb/submodules/tags.py +++ b/src/flightplandb/submodules/tags.py @@ -1,23 +1,17 @@ +"""Related to flight plans.""" from typing import List, Optional from flightplandb.datatypes import Tag -from flightplandb.internal import FlightPlanDB +from flightplandb.internal import _get -class TagsAPI(FlightPlanDB): +def fetch(key: Optional[str] = None) -> List[Tag]: + """Fetches current popular tags from all flight plans. + Only tags with sufficient popularity are included. + Returns + ---------- + List[Tag] + A list of the current popular tags. """ - Related to flight plans. - Accessed via :meth:`~flightplandb.flightplandb.FlightPlanDB.tags`. - """ - - def fetch(self, key: Optional[str] = None) -> List[Tag]: - """Fetches current popular tags from all flight plans. - Only tags with sufficient popularity are included. - - Returns - ---------- - List[Tag] - A list of the current popular tags. - """ - return list(map(lambda t: Tag(**t), self._get(path="/tags", key=key))) + return list(map(lambda t: Tag(**t), _get(path="/tags", key=key))) diff --git a/src/flightplandb/submodules/user.py b/src/flightplandb/submodules/user.py index 2419f54..60caf1a 100644 --- a/src/flightplandb/submodules/user.py +++ b/src/flightplandb/submodules/user.py @@ -1,131 +1,128 @@ +"""Commands related to registered users.""" from typing import Generator, Optional from flightplandb.datatypes import Plan, User, UserSmall -from flightplandb.internal import FlightPlanDB +from flightplandb.internal import _get, _getiter -class UserAPI(FlightPlanDB): +def me(key: Optional[str] = None) -> User: + """Fetches profile information for the currently authenticated user. - """Commands related to registered users. - Accessed via :meth:`~flightplandb.flightplandb.FlightPlanDB.user`. + Requires authentication. + + Returns + ------- + User + The User object of the currently authenticated user + + Raises + ------ + :class:`~flightplandb.exceptions.UnauthorizedException` + Authentication failed. + """ + + resp = _get(path="/me", key=key) + return User(**resp) + +def fetch(username: str, key: Optional[str] = None) -> User: + """Fetches profile information for any registered user + + Parameters + ---------- + username : str + Username of the registered User + + Returns + ------- + User + The User object of the user associated with the username + + Raises + ------- + :class:`~flightplandb.exceptions.NotFoundException` + No user was found with this username. + """ + + resp = _get(path=f"/user/{username}", key=key) + return User(**resp) + +def plans(username: str, sort: str = "created", + limit: int = 100, + key: Optional[str] = None) -> Generator[Plan, None, None]: + """Fetches flight plans created by a user. + + Parameters + ---------- + username : str + Username of the user who created the flight plans + sort : str, optional + Sort order to return results in. Valid sort orders are + created, updated, popularity, and distance + limit: int + Maximum number of plans to fetch, defaults to ``100`` + + Yields + ------- + Generator[Plan, None, None] + A generator with all the flight plans a user created, + limited by ``limit`` + """ + for i in _getiter(path=f"/user/{username}/plans", + sort=sort, + limit=limit, + key=key): + yield Plan(**i) + +def likes(username: str, sort: str = "created", + limit: int = 100, + key: Optional[str] = None) -> Generator[Plan, None, None]: + """Fetches flight plans liked by a user. + + Parameters + ---------- + username : str + Username of the user who liked the flight plans + sort : str, optional + Sort order to return results in. Valid sort orders are + created, updated, popularity, and distance + limit : int + Maximum number of plans to fetch, defaults to ``100`` + + Yields + ------- + Generator[Plan, None, None] + A generator with all the flight plans a user liked, + limited by ``limit`` + """ + + for i in _getiter(path=f"/user/{username}/likes", + sort=sort, + limit=limit, + key=key): + yield Plan(**i) + +def search(username: str, + limit=100, + key: Optional[str] = None) -> Generator[UserSmall, None, None]: + """Searches for users by username. For more detailed info on a + specific user, use :meth:`fetch` + + Parameters + ---------- + username : str + Username to search user database for + limit : type + Maximum number of users to fetch, defaults to ``100`` + + Yields + ------- + Generator[UserSmall, None, None] + A generator with a list of users approximately matching + ``username``, limited by ``limit``. UserSmall is used instead of + User, because less info is returned. """ - def me(self, key: Optional[str] = None) -> User: - """Fetches profile information for the currently authenticated user. - - Requires authentication. - - Returns - ------- - User - The User object of the currently authenticated user - - Raises - ------ - :class:`~flightplandb.exceptions.UnauthorizedException` - Authentication failed. - """ - - return User(**self._get(path="/me", key=key)) - - def fetch(self, username: str, key: Optional[str] = None) -> User: - """Fetches profile information for any registered user - - Parameters - ---------- - username : str - Username of the registered User - - Returns - ------- - User - The User object of the user associated with the username - - Raises - ------- - :class:`~flightplandb.exceptions.NotFoundException` - No user was found with this username. - """ - - return User(**self._get(path=f"/user/{username}", key=key)) - - def plans(self, username: str, sort: str = "created", - limit: int = 100, - key: Optional[str] = None) -> Generator[Plan, None, None]: - """Fetches flight plans created by a user. - - Parameters - ---------- - username : str - Username of the user who created the flight plans - sort : str, optional - Sort order to return results in. Valid sort orders are - created, updated, popularity, and distance - limit: int - Maximum number of plans to fetch, defaults to ``100`` - - Yields - ------- - Generator[Plan, None, None] - A generator with all the flight plans a user created, - limited by ``limit`` - """ - for i in self._getiter(path=f"/user/{username}/plans", - sort=sort, - limit=limit, - key=key): - yield Plan(**i) - - def likes(self, username: str, sort: str = "created", - limit: int = 100, - key: Optional[str] = None) -> Generator[Plan, None, None]: - """Fetches flight plans liked by a user. - - Parameters - ---------- - username : str - Username of the user who liked the flight plans - sort : str, optional - Sort order to return results in. Valid sort orders are - created, updated, popularity, and distance - limit : int - Maximum number of plans to fetch, defaults to ``100`` - - Yields - ------- - Generator[Plan, None, None] - A generator with all the flight plans a user liked, - limited by ``limit`` - """ - - for i in self._getiter(path=f"/user/{username}/likes", - sort=sort, - limit=limit, - key=key): - yield Plan(**i) - - def search(self, username: str, - limit=100, - key: Optional[str] = None) -> Generator[UserSmall, None, None]: - """Searches for users by username. For more detailed info on a - specific user, use :meth:`fetch` - - Parameters - ---------- - username : str - Username to search user database for - limit : type - Maximum number of users to fetch, defaults to ``100`` - - Yields - ------- - Generator[UserSmall, None, None] - A generator with a list of users approximately matching - ``username``, limited by ``limit``. UserSmall is used instead of - User, because less info is returned. - """ - - for i in self._getiter(path="/search/users", - limit=limit, - params={"q": username}, - key=key): - yield UserSmall(**i) + for i in _getiter(path="/search/users", + limit=limit, + params={"q": username}, + key=key): + yield UserSmall(**i) diff --git a/src/flightplandb/submodules/weather.py b/src/flightplandb/submodules/weather.py index 260e187..e6a55f0 100644 --- a/src/flightplandb/submodules/weather.py +++ b/src/flightplandb/submodules/weather.py @@ -1,33 +1,27 @@ -from flightplandb.datatypes import Weather +"""Weather. I mean, how much is there to say?""" from typing import Optional -from flightplandb.internal import FlightPlanDB - - -class WeatherAPI(FlightPlanDB): +from flightplandb.datatypes import Weather +from flightplandb.internal import _get - """Weather. I mean, how much is there to say? - Accessed via :meth:`~flightplandb.flightplandb.FlightPlanDB.weather`. +def fetch(icao: str, key: Optional[str] = None) -> Weather: + """ + Fetches current weather conditions at an airport + + Parameters + ---------- + icao : str + ICAO code of the airport for which the weather will be fetched + + Returns + ------- + Weather + METAR and TAF for an airport + + Raises + ------ + :class:`~flightplandb.exceptions.NotFoundException` + No airport with the specified ICAO code was found. """ - def fetch(self, icao: str, key: Optional[str] = None) -> Weather: - """ - Fetches current weather conditions at an airport - - Parameters - ---------- - icao : str - ICAO code of the airport for which the weather will be fetched - - Returns - ------- - Weather - METAR and TAF for an airport - - Raises - ------ - :class:`~flightplandb.exceptions.NotFoundException` - No airport with the specified ICAO code was found. - """ - - return Weather(**self._get(path=f"/weather/{icao}", key=key)) + return Weather(**_get(path=f"/weather/{icao}", key=key)) From f6cfe2c3b9f3232af0ee7c5e66561836e2c10c62 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 20:57:10 +0200 Subject: [PATCH 43/86] clean up __init__.py --- src/flightplandb/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/flightplandb/__init__.py b/src/flightplandb/__init__.py index c3087bc..9742087 100644 --- a/src/flightplandb/__init__.py +++ b/src/flightplandb/__init__.py @@ -5,6 +5,4 @@ from . import internal, exceptions, datatypes, submodules -# from flightplandb.datatypes import * # noqa: F403, F401 -# from flightplandb.submodules import * # noqa: F403, F401 __all__ = ["internal", "exceptions", "datatypes", "submodules"] From 7c70c2adde61bdc77fef10f1d1417e87506a9ba0 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 21:27:05 +0200 Subject: [PATCH 44/86] fix indentation and add module docstrings --- src/flightplandb/internal.py | 123 ++++++++++++++-------------- src/flightplandb/submodules/api.py | 1 + src/flightplandb/submodules/nav.py | 10 ++- src/flightplandb/submodules/plan.py | 50 ++++++----- src/flightplandb/submodules/tags.py | 2 +- src/flightplandb/submodules/user.py | 26 +++--- 6 files changed, 110 insertions(+), 102 deletions(-) diff --git a/src/flightplandb/internal.py b/src/flightplandb/internal.py index e1f9d3e..0bb8abe 100644 --- a/src/flightplandb/internal.py +++ b/src/flightplandb/internal.py @@ -22,6 +22,7 @@ import json import requests from requests.auth import HTTPBasicAuth +from requests.structures import CaseInsensitiveDict from flightplandb.exceptions import status_handler @@ -38,12 +39,12 @@ """ def _request(method: str, - path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: + path: str, return_format="native", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + json_data: Optional[Dict] = None, + *args, **kwargs) -> Union[Dict, bytes]: """General HTTP requests function for non-paginated results. Parameters @@ -110,9 +111,9 @@ def _request(method: str, params = {} # the API only takes "true" or "false", not True or False - for key, value in params.items(): - if value in (True, False): - params[key] = json.dumps(value) + for _key, _value in params.items(): + if _value in (True, False): + params[_key] = json.dumps(_value) # convert the API content return_format to an HTTP Accept type try: @@ -144,18 +145,18 @@ def _request(method: str, # and here go the specific non-paginated HTTP calls -def _get_headers(key: Optional[str] = None): +def _get_headers(key: Optional[str] = None) -> CaseInsensitiveDict: headers, _ = _request(method="get", - path="", - key=key) + path="", + key=key) return headers def _get(path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - *args, **kwargs) -> Union[Dict, bytes]: + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + *args, **kwargs) -> Union[Dict, bytes]: """Calls :meth:`_request()` for get requests. Parameters @@ -190,21 +191,21 @@ def _get(path: str, return_format="native", params = {} _, resp = _request(method="get", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - key=key, - *args, **kwargs) + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, + *args, **kwargs) return resp def _post(path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + json_data: Optional[Dict] = None, + *args, **kwargs) -> Union[Dict, bytes]: """Calls :meth:`_request()` for post requests. Parameters @@ -237,22 +238,22 @@ def _post(path: str, return_format="native", params = {} _, resp = _request(method="post", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - json_data=json_data, - key=key, - *args, **kwargs) + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + json_data=json_data, + key=key, + *args, **kwargs) return resp def _patch(path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + json_data: Optional[Dict] = None, + *args, **kwargs) -> Union[Dict, bytes]: """Calls :meth:`_request()` for patch requests. Parameters @@ -286,13 +287,13 @@ def _patch(path: str, return_format="native", params = {} _, resp = _request(method="patch", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - key=key, - json_data=json_data, - *args, **kwargs) + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, + json_data=json_data, + *args, **kwargs) return resp @@ -334,22 +335,22 @@ def _delete(path: str, return_format="native", params = {} _, resp = _request(method="delete", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - key=key, - *args, **kwargs) + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, + *args, **kwargs) return resp def _getiter(path: str, - limit: int = 100, - sort: str = "created", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - *args, **kwargs) -> Generator[Dict, None, None]: + limit: int = 100, + sort: str = "created", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + *args, **kwargs) -> Generator[Dict, None, None]: """Get :meth:`_request()` for paginated results. Parameters @@ -422,9 +423,9 @@ def _getiter(path: str, for page in range(0, num_pages): params['page'] = page r_fpdb = session.get(url=url, - params=params, - auth=auth, - *args, **kwargs) + params=params, + auth=auth, + *args, **kwargs) status_handler(r_fpdb.status_code, ignore_statuses) # ...keep cycling through pages... for i in r_fpdb.json(): diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index b773453..9ed2e3b 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -1,3 +1,4 @@ +"""These functions return information about the API.""" from typing import Optional from flightplandb import internal from flightplandb.datatypes import StatusResponse diff --git a/src/flightplandb/submodules/nav.py b/src/flightplandb/submodules/nav.py index 17d091b..efb7c67 100644 --- a/src/flightplandb/submodules/nav.py +++ b/src/flightplandb/submodules/nav.py @@ -26,6 +26,7 @@ def airport(icao: str, key: Optional[str] = None) -> Airport: resp = _get(path=f"/nav/airport/{icao}", key=key) return Airport(**resp) + def nats(key: Optional[str] = None) -> List[Track]: """Fetches current North Atlantic Tracks. @@ -38,6 +39,7 @@ def nats(key: Optional[str] = None) -> List[Track]: return list( map(lambda n: Track(**n), _get("/nav/NATS", key=key))) + def pacots(key: Optional[str] = None) -> List[Track]: """Fetches current Pacific Organized Track System tracks. @@ -50,9 +52,11 @@ def pacots(key: Optional[str] = None) -> List[Track]: return list( map(lambda t: Track(**t), _get(path="/nav/PACOTS", key=key))) -def search(query: str, - type_: str = None, key: Optional[str] = None - ) -> Generator[SearchNavaid, None, None]: + +def search( + query: str, + type_: str = None, key: Optional[str] = None + ) -> Generator[SearchNavaid, None, None]: r"""Searches navaids using a query. Parameters diff --git a/src/flightplandb/submodules/plan.py b/src/flightplandb/submodules/plan.py index 4fbf90c..611b13b 100644 --- a/src/flightplandb/submodules/plan.py +++ b/src/flightplandb/submodules/plan.py @@ -8,8 +8,8 @@ def fetch(id_: int, - return_format: str = "native", - key: Optional[str] = None) -> Union[Plan, None, bytes]: + return_format: str = "native", + key: Optional[str] = None) -> Union[Plan, None, bytes]: # Underscore for id_ must be escaped as id\_ so sphinx shows the _. # However, this would raise W605. To fix this, a raw string is used. r""" @@ -51,8 +51,8 @@ def fetch(id_: int, def create(plan: Plan, - return_format: str = "native", - key: Optional[str] = None) -> Union[Plan, bytes]: + return_format: str = "native", + key: Optional[str] = None) -> Union[Plan, bytes]: """Creates a new flight plan. Requires authentication. @@ -87,8 +87,8 @@ def create(plan: Plan, def edit(plan: Plan, - return_format: str = "native", - key: Optional[str] = None) -> Union[Plan, bytes]: + return_format: str = "native", + key: Optional[str] = None) -> Union[Plan, bytes]: """Edits a flight plan linked to your account. Requires authentication. @@ -127,7 +127,7 @@ def edit(plan: Plan, def delete(id_: int, - key: Optional[str] = None) -> StatusResponse: + key: Optional[str] = None) -> StatusResponse: r"""Deletes a flight plan that is linked to your account. Requires authentication. @@ -153,8 +153,8 @@ def delete(id_: int, def search(plan_query: PlanQuery, sort: str = "created", - include_route: bool = False, limit: int = 100, - key: Optional[str] = None) -> Generator[Plan, None, None]: + include_route: bool = False, limit: int = 100, + key: Optional[str] = None) -> Generator[Plan, None, None]: """Searches for flight plans. A number of search parameters are available. They will be combined to form a search request. @@ -184,10 +184,10 @@ def search(plan_query: PlanQuery, sort: str = "created", request_json["includeRoute"] = include_route for i in _getiter(path="/search/plans", - sort=sort, - params=request_json, - limit=limit, - key=key): + sort=sort, + params=request_json, + limit=limit, + key=key): yield Plan(**i) @@ -208,16 +208,15 @@ def has_liked(id_: int, ``True``/``False`` to indicate that the plan was liked / not liked """ - resp = _get( - path=f"/plan/{id_}/like", - ignore_statuses=[404], - key=key) + resp = _get(path=f"/plan/{id_}/like", + ignore_statuses=[404], + key=key) sr = StatusResponse(**resp) return sr.message != "Not Found" def like(id_: int, - key: Optional[str] = None) -> StatusResponse: + key: Optional[str] = None) -> StatusResponse: r"""Likes a flight plan. Requires authentication. @@ -244,7 +243,7 @@ def like(id_: int, def unlike(id_: int, - key: Optional[str] = None) -> bool: + key: Optional[str] = None) -> bool: r"""Removes a flight plan like. Requires authentication. @@ -271,8 +270,8 @@ def unlike(id_: int, def generate(gen_query: GenerateQuery, - include_route: bool = False, - key: Optional[str] = None) -> Union[Plan, bytes]: + include_route: bool = False, + key: Optional[str] = None) -> Union[Plan, bytes]: """Creates a new flight plan using the route generator. Requires authentication. @@ -296,15 +295,14 @@ def generate(gen_query: GenerateQuery, # due to an API bug this must be a string instead of a boolean request_json["includeRoute"] = "true" if include_route else "false" - resp = _post( - path="/auto/generate", - json_data=request_json, - key=key) + resp = _post(path="/auto/generate", + json_data=request_json, + key=key) return Plan(**resp) def decode(route: str, - key: Optional[str] = None) -> Plan: + key: Optional[str] = None) -> Plan: """Creates a new flight plan using the route decoder. Requires authentication. diff --git a/src/flightplandb/submodules/tags.py b/src/flightplandb/submodules/tags.py index ddf6bfb..6f67d6f 100644 --- a/src/flightplandb/submodules/tags.py +++ b/src/flightplandb/submodules/tags.py @@ -1,4 +1,4 @@ -"""Related to flight plans.""" +"""Contains the command for fetching flight plan tags.""" from typing import List, Optional from flightplandb.datatypes import Tag from flightplandb.internal import _get diff --git a/src/flightplandb/submodules/user.py b/src/flightplandb/submodules/user.py index 60caf1a..7d37b9d 100644 --- a/src/flightplandb/submodules/user.py +++ b/src/flightplandb/submodules/user.py @@ -23,6 +23,7 @@ def me(key: Optional[str] = None) -> User: resp = _get(path="/me", key=key) return User(**resp) + def fetch(username: str, key: Optional[str] = None) -> User: """Fetches profile information for any registered user @@ -45,9 +46,10 @@ def fetch(username: str, key: Optional[str] = None) -> User: resp = _get(path=f"/user/{username}", key=key) return User(**resp) + def plans(username: str, sort: str = "created", - limit: int = 100, - key: Optional[str] = None) -> Generator[Plan, None, None]: + limit: int = 100, + key: Optional[str] = None) -> Generator[Plan, None, None]: """Fetches flight plans created by a user. Parameters @@ -67,11 +69,12 @@ def plans(username: str, sort: str = "created", limited by ``limit`` """ for i in _getiter(path=f"/user/{username}/plans", - sort=sort, - limit=limit, - key=key): + sort=sort, + limit=limit, + key=key): yield Plan(**i) + def likes(username: str, sort: str = "created", limit: int = 100, key: Optional[str] = None) -> Generator[Plan, None, None]: @@ -95,11 +98,12 @@ def likes(username: str, sort: str = "created", """ for i in _getiter(path=f"/user/{username}/likes", - sort=sort, - limit=limit, - key=key): + sort=sort, + limit=limit, + key=key): yield Plan(**i) + def search(username: str, limit=100, key: Optional[str] = None) -> Generator[UserSmall, None, None]: @@ -122,7 +126,7 @@ def search(username: str, """ for i in _getiter(path="/search/users", - limit=limit, - params={"q": username}, - key=key): + limit=limit, + params={"q": username}, + key=key): yield UserSmall(**i) From 8ecad1e5bf8d82adb4c505ae573b97531ddcd1bb Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 21:28:04 +0200 Subject: [PATCH 45/86] move module docstring to correct place --- src/flightplandb/internal.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/flightplandb/internal.py b/src/flightplandb/internal.py index 0bb8abe..8e5e5a2 100644 --- a/src/flightplandb/internal.py +++ b/src/flightplandb/internal.py @@ -16,6 +16,10 @@ # You should have received a copy of the GNU General Public License along # with FlightplanDB-py. If not, see . +"""This file mostly contains internal functions called by the API. +However, the internal functions are hidden, so unless you look at +the source code, you're unlikely to see them.""" + from typing import Generator, List, Dict, Union, Optional from urllib.parse import urljoin @@ -33,11 +37,6 @@ url_base: str = "https://api.flightplandatabase.com" -"""This file mostly contains internal functions called by the API. -However, the internal functions are hidden, so unless you look at -the source code, you're unlikely to see them. -""" - def _request(method: str, path: str, return_format="native", ignore_statuses: Optional[List] = None, From 5c302f4324954aabe356391c268ea7d6eb21938d Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 21:53:05 +0200 Subject: [PATCH 46/86] convert protected methods to public ones --- src/flightplandb/datatypes.py | 68 +++++------ src/flightplandb/internal.py | 155 ++++++++++++------------- src/flightplandb/submodules/api.py | 6 +- src/flightplandb/submodules/nav.py | 10 +- src/flightplandb/submodules/plan.py | 30 ++--- src/flightplandb/submodules/tags.py | 4 +- src/flightplandb/submodules/user.py | 12 +- src/flightplandb/submodules/weather.py | 4 +- 8 files changed, 144 insertions(+), 145 deletions(-) diff --git a/src/flightplandb/datatypes.py b/src/flightplandb/datatypes.py index 5024365..ae8354a 100644 --- a/src/flightplandb/datatypes.py +++ b/src/flightplandb/datatypes.py @@ -19,8 +19,8 @@ from typing import List, Union, Optional from dataclasses import dataclass -from dateutil.parser import isoparse from datetime import datetime +from dateutil.parser import isoparse def _datetime_to_iso(timestamp: datetime): @@ -42,7 +42,7 @@ class StatusResponse: message: str errors: Optional[List[str]] - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -90,7 +90,7 @@ def __post_init__(self): if self.lastSeen and isinstance(self.lastSeen, str): self.lastSeen = isoparse(self.lastSeen) - def _to_api_dict(self): + def to_api_dict(self): resp_dict = self.__dict__ if isinstance(resp_dict["joined"], datetime): resp_dict["joined"] = _datetime_to_iso(resp_dict["joined"]) @@ -119,7 +119,7 @@ class UserSmall: location: Optional[str] = None gravatarHash: Optional[str] = None - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -140,7 +140,7 @@ class Application: name: Optional[str] = None url: Optional[str] = None - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -166,7 +166,7 @@ def __post_init__(self): if self.type not in self.validtypes: raise ValueError(f"{self.type} is not a valid Via type") - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -213,10 +213,10 @@ def __post_init__(self): raise ValueError(f"{self.type} is not a valid RouteNode type") self.via = Via(**self.via) if type(self.via) == dict else self.via - def _to_api_dict(self): + def to_api_dict(self): resp_dict = self.__dict__ if resp_dict["via"] and isinstance(resp_dict["via"], Via): - resp_dict["via"] = resp_dict["via"]._to_api_dict() + resp_dict["via"] = resp_dict["via"].to_api_dict() return resp_dict @@ -242,10 +242,10 @@ def __post_init__(self): lambda node: (RouteNode(**node) if (type(node) == dict) else node), self.nodes)) - def _to_api_dict(self): + def to_api_dict(self): resp_dict = self.__dict__ resp_dict["nodes"] = list(map( - lambda node: node._to_api_dict(), resp_dict["nodes"])) + lambda node: node.to_api_dict(), resp_dict["nodes"])) return resp_dict @@ -269,7 +269,7 @@ class Cycle: year: int release: int - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -365,16 +365,16 @@ def __post_init__(self): if self.cycle and isinstance(self.cycle, dict): self.cycle = Cycle(**self.cycle) - def _to_api_dict(self): + def to_api_dict(self): plan_dict = self.__dict__ if type(plan_dict["createdAt"]) == datetime: plan_dict["createdAt"] = _datetime_to_iso(plan_dict["createdAt"]) if type(plan_dict["updatedAt"]) == datetime: plan_dict["updatedAt"] = _datetime_to_iso(plan_dict["updatedAt"]) if type(plan_dict["user"]) == User: - plan_dict["user"] = plan_dict["user"]._to_api_dict() + plan_dict["user"] = plan_dict["user"].to_api_dict() if type(plan_dict["route"]) == Route: - plan_dict["route"] = plan_dict["route"]._to_api_dict() + plan_dict["route"] = plan_dict["route"].to_api_dict() return plan_dict @@ -420,7 +420,7 @@ class PlanQuery: tags: Optional[str] = None includeRoute: Optional[bool] = None - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -468,7 +468,7 @@ class GenerateQuery: descentRate: Optional[float] = 1500 descentSpeed: Optional[float] = 250 - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -492,7 +492,7 @@ class Tag: planCount: int popularity: int - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -511,7 +511,7 @@ class Timezone: name: Optional[str] offset: Optional[float] - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -549,7 +549,7 @@ def __post_init__(self): if type(self.dusk) != datetime else self.dusk) - def _to_api_dict(self): + def to_api_dict(self): plan_dict = self.__dict__ plan_dict["sunrise"] = _datetime_to_iso(plan_dict["sunrise"]) plan_dict["sunset"] = _datetime_to_iso(plan_dict["sunset"]) @@ -575,7 +575,7 @@ class RunwayEnds: lat: float lon: float - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -631,7 +631,7 @@ def __post_init__(self): if self.type not in self.validtypes: raise ValueError(f"{self.type} is not a valid Navaid type") - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -682,11 +682,11 @@ def __post_init__(self): if self.navaids and (isinstance(self.navaids[0], dict)): self.navaids = list(map(lambda n: Navaid(**n), self.navaids)) - def _to_api_dict(self): + def to_api_dict(self): resp_dict = self.__dict__ - resp_dict["ends"] = list(map(lambda end: end._to_api_dict(), + resp_dict["ends"] = list(map(lambda end: end.to_api_dict(), resp_dict["ends"])) - resp_dict["navaids"] = list(map(lambda aid: aid._to_api_dict(), + resp_dict["navaids"] = list(map(lambda aid: aid.to_api_dict(), resp_dict["navaids"])) return resp_dict @@ -709,7 +709,7 @@ class Frequency: frequency: float name: Optional[str] - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -727,7 +727,7 @@ class Weather: METAR: Optional[str] TAF: Optional[str] - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ @@ -802,15 +802,15 @@ def __post_init__(self): if self.weather and isinstance(self.weather, dict): self.weather = Weather(**self.weather) - def _to_api_dict(self): + def to_api_dict(self): resp_dict = self.__dict__ - resp_dict["timezone"] = resp_dict["timezone"]._to_api_dict() - resp_dict["times"] = resp_dict["times"]._to_api_dict() - resp_dict["runways"] = list(map(lambda rwy: rwy._to_api_dict(), + resp_dict["timezone"] = resp_dict["timezone"].to_api_dict() + resp_dict["times"] = resp_dict["times"].to_api_dict() + resp_dict["runways"] = list(map(lambda rwy: rwy.to_api_dict(), resp_dict["runways"])) - resp_dict["frequencies"] = list(map(lambda freq: freq._to_api_dict(), + resp_dict["frequencies"] = list(map(lambda freq: freq.to_api_dict(), resp_dict["frequencies"])) - resp_dict["weather"] = resp_dict["weather"]._to_api_dict() + resp_dict["weather"] = resp_dict["weather"].to_api_dict() return resp_dict @@ -842,7 +842,7 @@ def __post_init__(self): if self.validTo and isinstance(self.validTo, str): self.validTo = isoparse(self.validTo) - def _to_api_dict(self): + def to_api_dict(self): resp_dict = self.__dict__ if type(resp_dict["validFrom"]) == datetime: resp_dict["validFrom"] = _datetime_to_iso(resp_dict["validFrom"]) @@ -892,5 +892,5 @@ def __post_init__(self): if self.type not in self.validtypes: raise ValueError(f"{self.type} is not a valid SearchNavaid type") - def _to_api_dict(self): + def to_api_dict(self): return self.__dict__ diff --git a/src/flightplandb/internal.py b/src/flightplandb/internal.py index 8e5e5a2..d7b63a7 100644 --- a/src/flightplandb/internal.py +++ b/src/flightplandb/internal.py @@ -16,9 +16,8 @@ # You should have received a copy of the GNU General Public License along # with FlightplanDB-py. If not, see . -"""This file mostly contains internal functions called by the API. -However, the internal functions are hidden, so unless you look at -the source code, you're unlikely to see them.""" +"""This file mostly contains internal functions called by the API, +so you're unlikely to ever use them.""" from typing import Generator, List, Dict, Union, Optional @@ -37,13 +36,13 @@ url_base: str = "https://api.flightplandatabase.com" -def _request(method: str, - path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: +def request(method: str, + path: str, return_format="native", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + json_data: Optional[Dict] = None, + *args, **kwargs) -> Union[Dict, bytes]: """General HTTP requests function for non-paginated results. Parameters @@ -144,19 +143,19 @@ def _request(method: str, # and here go the specific non-paginated HTTP calls -def _get_headers(key: Optional[str] = None) -> CaseInsensitiveDict: - headers, _ = _request(method="get", - path="", - key=key) +def get_headers(key: Optional[str] = None) -> CaseInsensitiveDict: + headers, _ = request(method="get", + path="", + key=key) return headers -def _get(path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - *args, **kwargs) -> Union[Dict, bytes]: - """Calls :meth:`_request()` for get requests. +def get(path: str, return_format="native", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + *args, **kwargs) -> Union[Dict, bytes]: + """Calls :meth:`request()` for get requests. Parameters ---------- @@ -189,23 +188,23 @@ def _get(path: str, return_format="native", if not params: params = {} - _, resp = _request(method="get", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - key=key, - *args, **kwargs) + _, resp = request(method="get", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, + *args, **kwargs) return resp -def _post(path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: - """Calls :meth:`_request()` for post requests. +def post(path: str, return_format="native", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + json_data: Optional[Dict] = None, + *args, **kwargs) -> Union[Dict, bytes]: + """Calls :meth:`request()` for post requests. Parameters ---------- @@ -236,24 +235,24 @@ def _post(path: str, return_format="native", if not params: params = {} - _, resp = _request(method="post", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - json_data=json_data, - key=key, - *args, **kwargs) + _, resp = request(method="post", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + json_data=json_data, + key=key, + *args, **kwargs) return resp -def _patch(path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: - """Calls :meth:`_request()` for patch requests. +def patch(path: str, return_format="native", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + json_data: Optional[Dict] = None, + *args, **kwargs) -> Union[Dict, bytes]: + """Calls :meth:`request()` for patch requests. Parameters ---------- @@ -285,23 +284,23 @@ def _patch(path: str, return_format="native", if not params: params = {} - _, resp = _request(method="patch", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - key=key, - json_data=json_data, - *args, **kwargs) + _, resp = request(method="patch", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, + json_data=json_data, + *args, **kwargs) return resp -def _delete(path: str, return_format="native", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - *args, **kwargs) -> Union[Dict, bytes]: - """Calls :meth:`_request()` for delete requests. +def delete(path: str, return_format="native", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + *args, **kwargs) -> Union[Dict, bytes]: + """Calls :meth:`request()` for delete requests. Parameters ---------- @@ -333,24 +332,24 @@ def _delete(path: str, return_format="native", if not params: params = {} - _, resp = _request(method="delete", - path=path, - return_format=return_format, - ignore_statuses=ignore_statuses, - params=params, - key=key, - *args, **kwargs) + _, resp = request(method="delete", + path=path, + return_format=return_format, + ignore_statuses=ignore_statuses, + params=params, + key=key, + *args, **kwargs) return resp -def _getiter(path: str, - limit: int = 100, - sort: str = "created", - ignore_statuses: Optional[List] = None, - params: Optional[Dict] = None, - key: Optional[str] = None, - *args, **kwargs) -> Generator[Dict, None, None]: - """Get :meth:`_request()` for paginated results. +def getiter(path: str, + limit: int = 100, + sort: str = "created", + ignore_statuses: Optional[List] = None, + params: Optional[Dict] = None, + key: Optional[str] = None, + *args, **kwargs) -> Generator[Dict, None, None]: + """Get :meth:`request()` for paginated results. Parameters ---------- diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index 9ed2e3b..ab1853f 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -18,7 +18,7 @@ def _header_value(header_key: str, key: Optional[str] = None) -> str: The value corresponding to the passed key """ - headers = internal._get_headers(key=key) # Make one request to fetch headers + headers = internal.get_headers(key=key) # Make one request to fetch headers return headers[header_key] @@ -85,7 +85,7 @@ def ping(key: Optional[str] = None) -> StatusResponse: OK 200 means the service is up and running. """ - resp = internal._get(path="", key=key) + resp = internal.get(path="", key=key) return StatusResponse(**resp) @@ -104,5 +104,5 @@ def revoke(key: Optional[str] = None) -> StatusResponse: occurred and the errors array will give further details. """ - resp = internal._get(path="/auth/revoke", key=key) + resp = internal.get(path="/auth/revoke", key=key) return StatusResponse(**resp) diff --git a/src/flightplandb/submodules/nav.py b/src/flightplandb/submodules/nav.py index efb7c67..455a58f 100644 --- a/src/flightplandb/submodules/nav.py +++ b/src/flightplandb/submodules/nav.py @@ -1,7 +1,7 @@ """Commands related to navigation aids and airports.""" from typing import Generator, List, Optional from flightplandb.datatypes import Airport, Track, SearchNavaid -from flightplandb.internal import _get, _getiter +from flightplandb.internal import get, getiter def airport(icao: str, key: Optional[str] = None) -> Airport: @@ -23,7 +23,7 @@ def airport(icao: str, key: Optional[str] = None) -> Airport: No airport with the specified ICAO code was found. """ - resp = _get(path=f"/nav/airport/{icao}", key=key) + resp = get(path=f"/nav/airport/{icao}", key=key) return Airport(**resp) @@ -37,7 +37,7 @@ def nats(key: Optional[str] = None) -> List[Track]: """ return list( - map(lambda n: Track(**n), _get("/nav/NATS", key=key))) + map(lambda n: Track(**n), get("/nav/NATS", key=key))) def pacots(key: Optional[str] = None) -> List[Track]: @@ -50,7 +50,7 @@ def pacots(key: Optional[str] = None) -> List[Track]: """ return list( - map(lambda t: Track(**t), _get(path="/nav/PACOTS", key=key))) + map(lambda t: Track(**t), get(path="/nav/PACOTS", key=key))) def search( @@ -81,5 +81,5 @@ def search( params["types"] = type_ else: raise ValueError(f"{type_} is not a valid Navaid type") - for i in _getiter(path="/search/nav", params=params, key=key): + for i in getiter(path="/search/nav", params=params, key=key): yield SearchNavaid(**i) diff --git a/src/flightplandb/submodules/plan.py b/src/flightplandb/submodules/plan.py index 611b13b..a98b6e1 100644 --- a/src/flightplandb/submodules/plan.py +++ b/src/flightplandb/submodules/plan.py @@ -4,7 +4,7 @@ StatusResponse, PlanQuery, Plan, GenerateQuery ) -from flightplandb.internal import _get, _patch, _post, _delete, _getiter +from flightplandb.internal import get, patch, post, delete, getiter def fetch(id_: int, @@ -38,7 +38,7 @@ def fetch(id_: int, No plan with the specified id was found. """ - request = _get( + request = get( path=f"/plan/{id_}", return_format=return_format, key=key @@ -74,10 +74,10 @@ def create(plan: Plan, otherwise unusable. """ - request = _post( + request = post( path="/plan/", return_format=return_format, - json=plan._to_api_dict(), + json=plan.to_api_dict(), key=key) if return_format == "native": @@ -113,8 +113,8 @@ def edit(plan: Plan, No plan with the specified id was found. """ - plan_data = plan._to_api_dict() - request = _patch( + plan_data = plan.to_api_dict() + request = patch( path=f"/plan/{plan_data['id']}", return_format=return_format, json=plan_data, @@ -148,7 +148,7 @@ def delete(id_: int, No plan with the specified id was found. """ - resp = _delete(path=f"/plan/{id_}", key=key) + resp = delete(path=f"/plan/{id_}", key=key) return StatusResponse(**resp) @@ -180,10 +180,10 @@ def search(plan_query: PlanQuery, sort: str = "created", objects. """ - request_json = plan_query._to_api_dict() + request_json = plan_query.to_api_dict() request_json["includeRoute"] = include_route - for i in _getiter(path="/search/plans", + for i in getiter(path="/search/plans", sort=sort, params=request_json, limit=limit, @@ -208,7 +208,7 @@ def has_liked(id_: int, ``True``/``False`` to indicate that the plan was liked / not liked """ - resp = _get(path=f"/plan/{id_}/like", + resp = get(path=f"/plan/{id_}/like", ignore_statuses=[404], key=key) sr = StatusResponse(**resp) @@ -238,7 +238,7 @@ def like(id_: int, No plan with the specified id was found. """ - resp = _post(path=f"/plan/{id_}/like", key=key) + resp = post(path=f"/plan/{id_}/like", key=key) return StatusResponse(**resp) @@ -265,7 +265,7 @@ def unlike(id_: int, or the plan was found but wasn't liked. """ - _delete(path=f"/plan/{id_}/like", key=key) + delete(path=f"/plan/{id_}/like", key=key) return True @@ -290,12 +290,12 @@ def generate(gen_query: GenerateQuery, Include route in response, defaults to false """ - request_json = gen_query._to_api_dict() + request_json = gen_query.to_api_dict() # due to an API bug this must be a string instead of a boolean request_json["includeRoute"] = "true" if include_route else "false" - resp = _post(path="/auto/generate", + resp = post(path="/auto/generate", json_data=request_json, key=key) return Plan(**resp) @@ -331,5 +331,5 @@ def decode(route: str, arguments or was otherwise unusable. """ - resp = _post(path="/auto/decode", json_data={"route": route}, key=key) + resp = post(path="/auto/decode", json_data={"route": route}, key=key) return Plan(**resp) diff --git a/src/flightplandb/submodules/tags.py b/src/flightplandb/submodules/tags.py index 6f67d6f..cede153 100644 --- a/src/flightplandb/submodules/tags.py +++ b/src/flightplandb/submodules/tags.py @@ -1,7 +1,7 @@ """Contains the command for fetching flight plan tags.""" from typing import List, Optional from flightplandb.datatypes import Tag -from flightplandb.internal import _get +from flightplandb.internal import get def fetch(key: Optional[str] = None) -> List[Tag]: @@ -14,4 +14,4 @@ def fetch(key: Optional[str] = None) -> List[Tag]: A list of the current popular tags. """ - return list(map(lambda t: Tag(**t), _get(path="/tags", key=key))) + return list(map(lambda t: Tag(**t), get(path="/tags", key=key))) diff --git a/src/flightplandb/submodules/user.py b/src/flightplandb/submodules/user.py index 7d37b9d..289ddb6 100644 --- a/src/flightplandb/submodules/user.py +++ b/src/flightplandb/submodules/user.py @@ -1,7 +1,7 @@ """Commands related to registered users.""" from typing import Generator, Optional from flightplandb.datatypes import Plan, User, UserSmall -from flightplandb.internal import _get, _getiter +from flightplandb.internal import get, getiter def me(key: Optional[str] = None) -> User: @@ -20,7 +20,7 @@ def me(key: Optional[str] = None) -> User: Authentication failed. """ - resp = _get(path="/me", key=key) + resp = get(path="/me", key=key) return User(**resp) @@ -43,7 +43,7 @@ def fetch(username: str, key: Optional[str] = None) -> User: No user was found with this username. """ - resp = _get(path=f"/user/{username}", key=key) + resp = get(path=f"/user/{username}", key=key) return User(**resp) @@ -68,7 +68,7 @@ def plans(username: str, sort: str = "created", A generator with all the flight plans a user created, limited by ``limit`` """ - for i in _getiter(path=f"/user/{username}/plans", + for i in getiter(path=f"/user/{username}/plans", sort=sort, limit=limit, key=key): @@ -97,7 +97,7 @@ def likes(username: str, sort: str = "created", limited by ``limit`` """ - for i in _getiter(path=f"/user/{username}/likes", + for i in getiter(path=f"/user/{username}/likes", sort=sort, limit=limit, key=key): @@ -125,7 +125,7 @@ def search(username: str, User, because less info is returned. """ - for i in _getiter(path="/search/users", + for i in getiter(path="/search/users", limit=limit, params={"q": username}, key=key): diff --git a/src/flightplandb/submodules/weather.py b/src/flightplandb/submodules/weather.py index e6a55f0..e7f4df2 100644 --- a/src/flightplandb/submodules/weather.py +++ b/src/flightplandb/submodules/weather.py @@ -1,7 +1,7 @@ """Weather. I mean, how much is there to say?""" from typing import Optional from flightplandb.datatypes import Weather -from flightplandb.internal import _get +from flightplandb.internal import get def fetch(icao: str, key: Optional[str] = None) -> Weather: @@ -24,4 +24,4 @@ def fetch(icao: str, key: Optional[str] = None) -> Weather: No airport with the specified ICAO code was found. """ - return Weather(**_get(path=f"/weather/{icao}", key=key)) + return Weather(**get(path=f"/weather/{icao}", key=key)) From 171c2f1103b12f6bd7a01998ca11c7d2e671e32e Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 21:59:17 +0200 Subject: [PATCH 47/86] change another protected method to public --- src/flightplandb/submodules/api.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index ab1853f..dc579ae 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -4,7 +4,7 @@ from flightplandb.datatypes import StatusResponse -def _header_value(header_key: str, key: Optional[str] = None) -> str: +def header_value(header_key: str, key: Optional[str] = None) -> str: """Gets header value for key Parameters @@ -31,7 +31,7 @@ def version(key: Optional[str] = None) -> int: API version """ - return int(_header_value("X-API-Version", key=key)) + return int(header_value("X-API-Version", key=key)) def units(key: Optional[str] = None) -> str: @@ -44,7 +44,7 @@ def units(key: Optional[str] = None) -> str: AVIATION, METRIC or SI """ - return _header_value("X-Units", key=key) + return header_value("X-Units", key=key) def limit_cap(key: Optional[str] = None) -> int: @@ -60,7 +60,7 @@ def limit_cap(key: Optional[str] = None) -> int: number of allowed requests per day """ - return int(_header_value("X-Limit-Cap", key=key)) + return int(header_value("X-Limit-Cap", key=key)) def limit_used(key: Optional[str] = None) -> int: @@ -73,7 +73,7 @@ def limit_used(key: Optional[str] = None) -> int: number of requests used in period """ - return int(_header_value("X-Limit-Used", key=key)) + return int(header_value("X-Limit-Used", key=key)) def ping(key: Optional[str] = None) -> StatusResponse: From f680c5b0f88689bf83031c9055d4eea7887c1e56 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 22:27:09 +0200 Subject: [PATCH 48/86] change wildcard to relative imports --- src/flightplandb/submodules/api.py | 2 +- src/flightplandb/submodules/nav.py | 10 +++---- src/flightplandb/submodules/plan.py | 41 +++++++++++++------------- src/flightplandb/submodules/tags.py | 4 +-- src/flightplandb/submodules/user.py | 38 ++++++++++++------------ src/flightplandb/submodules/weather.py | 4 +-- 6 files changed, 50 insertions(+), 49 deletions(-) diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/submodules/api.py index dc579ae..bd5ec8f 100644 --- a/src/flightplandb/submodules/api.py +++ b/src/flightplandb/submodules/api.py @@ -18,7 +18,7 @@ def header_value(header_key: str, key: Optional[str] = None) -> str: The value corresponding to the passed key """ - headers = internal.get_headers(key=key) # Make one request to fetch headers + headers = internal.get_headers(key=key) # Make 1 request to fetch headers return headers[header_key] diff --git a/src/flightplandb/submodules/nav.py b/src/flightplandb/submodules/nav.py index 455a58f..06ecf45 100644 --- a/src/flightplandb/submodules/nav.py +++ b/src/flightplandb/submodules/nav.py @@ -1,7 +1,7 @@ """Commands related to navigation aids and airports.""" from typing import Generator, List, Optional from flightplandb.datatypes import Airport, Track, SearchNavaid -from flightplandb.internal import get, getiter +from flightplandb import internal def airport(icao: str, key: Optional[str] = None) -> Airport: @@ -23,7 +23,7 @@ def airport(icao: str, key: Optional[str] = None) -> Airport: No airport with the specified ICAO code was found. """ - resp = get(path=f"/nav/airport/{icao}", key=key) + resp = internal.get(path=f"/nav/airport/{icao}", key=key) return Airport(**resp) @@ -37,7 +37,7 @@ def nats(key: Optional[str] = None) -> List[Track]: """ return list( - map(lambda n: Track(**n), get("/nav/NATS", key=key))) + map(lambda n: Track(**n), internal.get("/nav/NATS", key=key))) def pacots(key: Optional[str] = None) -> List[Track]: @@ -50,7 +50,7 @@ def pacots(key: Optional[str] = None) -> List[Track]: """ return list( - map(lambda t: Track(**t), get(path="/nav/PACOTS", key=key))) + map(lambda t: Track(**t), internal.get(path="/nav/PACOTS", key=key))) def search( @@ -81,5 +81,5 @@ def search( params["types"] = type_ else: raise ValueError(f"{type_} is not a valid Navaid type") - for i in getiter(path="/search/nav", params=params, key=key): + for i in internal.getiter(path="/search/nav", params=params, key=key): yield SearchNavaid(**i) diff --git a/src/flightplandb/submodules/plan.py b/src/flightplandb/submodules/plan.py index a98b6e1..f2e8094 100644 --- a/src/flightplandb/submodules/plan.py +++ b/src/flightplandb/submodules/plan.py @@ -4,7 +4,7 @@ StatusResponse, PlanQuery, Plan, GenerateQuery ) -from flightplandb.internal import get, patch, post, delete, getiter +from flightplandb import internal def fetch(id_: int, @@ -38,7 +38,7 @@ def fetch(id_: int, No plan with the specified id was found. """ - request = get( + request = internal.get( path=f"/plan/{id_}", return_format=return_format, key=key @@ -74,7 +74,7 @@ def create(plan: Plan, otherwise unusable. """ - request = post( + request = internal.post( path="/plan/", return_format=return_format, json=plan.to_api_dict(), @@ -114,7 +114,7 @@ def edit(plan: Plan, """ plan_data = plan.to_api_dict() - request = patch( + request = internal.patch( path=f"/plan/{plan_data['id']}", return_format=return_format, json=plan_data, @@ -148,7 +148,7 @@ def delete(id_: int, No plan with the specified id was found. """ - resp = delete(path=f"/plan/{id_}", key=key) + resp = internal.delete(path=f"/plan/{id_}", key=key) return StatusResponse(**resp) @@ -183,16 +183,16 @@ def search(plan_query: PlanQuery, sort: str = "created", request_json = plan_query.to_api_dict() request_json["includeRoute"] = include_route - for i in getiter(path="/search/plans", - sort=sort, - params=request_json, - limit=limit, - key=key): + for i in internal.getiter(path="/search/plans", + sort=sort, + params=request_json, + limit=limit, + key=key): yield Plan(**i) def has_liked(id_: int, - key: Optional[str] = None) -> bool: + key: Optional[str] = None) -> bool: r"""Fetches your like status for a flight plan. Requires authentication. @@ -208,9 +208,9 @@ def has_liked(id_: int, ``True``/``False`` to indicate that the plan was liked / not liked """ - resp = get(path=f"/plan/{id_}/like", - ignore_statuses=[404], - key=key) + resp = internal.get(path=f"/plan/{id_}/like", + ignore_statuses=[404], + key=key) sr = StatusResponse(**resp) return sr.message != "Not Found" @@ -238,7 +238,7 @@ def like(id_: int, No plan with the specified id was found. """ - resp = post(path=f"/plan/{id_}/like", key=key) + resp = internal.post(path=f"/plan/{id_}/like", key=key) return StatusResponse(**resp) @@ -265,7 +265,7 @@ def unlike(id_: int, or the plan was found but wasn't liked. """ - delete(path=f"/plan/{id_}/like", key=key) + internal.delete(path=f"/plan/{id_}/like", key=key) return True @@ -295,9 +295,9 @@ def generate(gen_query: GenerateQuery, # due to an API bug this must be a string instead of a boolean request_json["includeRoute"] = "true" if include_route else "false" - resp = post(path="/auto/generate", - json_data=request_json, - key=key) + resp = internal.post(path="/auto/generate", + json_data=request_json, + key=key) return Plan(**resp) @@ -331,5 +331,6 @@ def decode(route: str, arguments or was otherwise unusable. """ - resp = post(path="/auto/decode", json_data={"route": route}, key=key) + resp = internal.post( + path="/auto/decode", json_data={"route": route}, key=key) return Plan(**resp) diff --git a/src/flightplandb/submodules/tags.py b/src/flightplandb/submodules/tags.py index cede153..e9c029b 100644 --- a/src/flightplandb/submodules/tags.py +++ b/src/flightplandb/submodules/tags.py @@ -1,7 +1,7 @@ """Contains the command for fetching flight plan tags.""" from typing import List, Optional from flightplandb.datatypes import Tag -from flightplandb.internal import get +from flightplandb import internal def fetch(key: Optional[str] = None) -> List[Tag]: @@ -14,4 +14,4 @@ def fetch(key: Optional[str] = None) -> List[Tag]: A list of the current popular tags. """ - return list(map(lambda t: Tag(**t), get(path="/tags", key=key))) + return list(map(lambda t: Tag(**t), internal.get(path="/tags", key=key))) diff --git a/src/flightplandb/submodules/user.py b/src/flightplandb/submodules/user.py index 289ddb6..e907ac2 100644 --- a/src/flightplandb/submodules/user.py +++ b/src/flightplandb/submodules/user.py @@ -1,7 +1,7 @@ """Commands related to registered users.""" from typing import Generator, Optional from flightplandb.datatypes import Plan, User, UserSmall -from flightplandb.internal import get, getiter +from flightplandb import internal def me(key: Optional[str] = None) -> User: @@ -20,7 +20,7 @@ def me(key: Optional[str] = None) -> User: Authentication failed. """ - resp = get(path="/me", key=key) + resp = internal.get(path="/me", key=key) return User(**resp) @@ -43,7 +43,7 @@ def fetch(username: str, key: Optional[str] = None) -> User: No user was found with this username. """ - resp = get(path=f"/user/{username}", key=key) + resp = internal.get(path=f"/user/{username}", key=key) return User(**resp) @@ -68,16 +68,16 @@ def plans(username: str, sort: str = "created", A generator with all the flight plans a user created, limited by ``limit`` """ - for i in getiter(path=f"/user/{username}/plans", - sort=sort, - limit=limit, - key=key): + for i in internal.getiter(path=f"/user/{username}/plans", + sort=sort, + limit=limit, + key=key): yield Plan(**i) def likes(username: str, sort: str = "created", - limit: int = 100, - key: Optional[str] = None) -> Generator[Plan, None, None]: + limit: int = 100, + key: Optional[str] = None) -> Generator[Plan, None, None]: """Fetches flight plans liked by a user. Parameters @@ -97,16 +97,16 @@ def likes(username: str, sort: str = "created", limited by ``limit`` """ - for i in getiter(path=f"/user/{username}/likes", - sort=sort, - limit=limit, - key=key): + for i in internal.getiter(path=f"/user/{username}/likes", + sort=sort, + limit=limit, + key=key): yield Plan(**i) def search(username: str, - limit=100, - key: Optional[str] = None) -> Generator[UserSmall, None, None]: + limit=100, + key: Optional[str] = None) -> Generator[UserSmall, None, None]: """Searches for users by username. For more detailed info on a specific user, use :meth:`fetch` @@ -125,8 +125,8 @@ def search(username: str, User, because less info is returned. """ - for i in getiter(path="/search/users", - limit=limit, - params={"q": username}, - key=key): + for i in internal.getiter(path="/search/users", + limit=limit, + params={"q": username}, + key=key): yield UserSmall(**i) diff --git a/src/flightplandb/submodules/weather.py b/src/flightplandb/submodules/weather.py index e7f4df2..c001df5 100644 --- a/src/flightplandb/submodules/weather.py +++ b/src/flightplandb/submodules/weather.py @@ -1,7 +1,7 @@ """Weather. I mean, how much is there to say?""" from typing import Optional from flightplandb.datatypes import Weather -from flightplandb.internal import get +from flightplandb import internal def fetch(icao: str, key: Optional[str] = None) -> Weather: @@ -24,4 +24,4 @@ def fetch(icao: str, key: Optional[str] = None) -> Weather: No airport with the specified ICAO code was found. """ - return Weather(**get(path=f"/weather/{icao}", key=key)) + return Weather(**internal.get(path=f"/weather/{icao}", key=key)) From c99e81a622268343e55c031c14e9a9d8c30ee2fd Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 22:38:15 +0200 Subject: [PATCH 49/86] change type() checks to isinstance() --- src/flightplandb/datatypes.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/flightplandb/datatypes.py b/src/flightplandb/datatypes.py index ae8354a..23c10a6 100644 --- a/src/flightplandb/datatypes.py +++ b/src/flightplandb/datatypes.py @@ -211,7 +211,7 @@ class RouteNode: def __post_init__(self): if self.type not in self.validtypes: raise ValueError(f"{self.type} is not a valid RouteNode type") - self.via = Via(**self.via) if type(self.via) == dict else self.via + self.via = Via(**self.via) if isinstance(self.via, dict) else self.via def to_api_dict(self): resp_dict = self.__dict__ @@ -239,7 +239,7 @@ class Route: def __post_init__(self): self.nodes = list(map( - lambda node: (RouteNode(**node) if (type(node) == dict) else node), + lambda node: (RouteNode(**node) if (isinstance(node, dict)) else node), self.nodes)) def to_api_dict(self): @@ -367,13 +367,13 @@ def __post_init__(self): def to_api_dict(self): plan_dict = self.__dict__ - if type(plan_dict["createdAt"]) == datetime: + if isinstance(plan_dict["createdAt"], datetime): plan_dict["createdAt"] = _datetime_to_iso(plan_dict["createdAt"]) - if type(plan_dict["updatedAt"]) == datetime: + if isinstance(plan_dict["updatedAt"], datetime): plan_dict["updatedAt"] = _datetime_to_iso(plan_dict["updatedAt"]) - if type(plan_dict["user"]) == User: + if isinstance(plan_dict["user"], User): plan_dict["user"] = plan_dict["user"].to_api_dict() - if type(plan_dict["route"]) == Route: + if isinstance(plan_dict["route"], Route): plan_dict["route"] = plan_dict["route"].to_api_dict() return plan_dict @@ -537,16 +537,16 @@ class Times: def __post_init__(self): self.sunrise = (isoparse(self.sunrise) - if type(self.sunrise) != datetime + if not isinstance(self.sunrise, datetime) else self.sunrise) self.sunset = (isoparse(self.sunset) - if type(self.sunset) != datetime + if not isinstance(self.sunset, datetime) else self.sunset) self.dawn = (isoparse(self.dawn) - if type(self.dawn) != datetime + if not isinstance(self.dawn, datetime) else self.dawn) self.dusk = (isoparse(self.dusk) - if type(self.dusk) != datetime + if not isinstance(self.dusk, datetime) else self.dusk) def to_api_dict(self): @@ -844,9 +844,9 @@ def __post_init__(self): def to_api_dict(self): resp_dict = self.__dict__ - if type(resp_dict["validFrom"]) == datetime: + if isinstance(resp_dict["validFrom"], datetime): resp_dict["validFrom"] = _datetime_to_iso(resp_dict["validFrom"]) - if type(resp_dict["validTo"]) == datetime: + if isinstance(resp_dict["validTo"], datetime): resp_dict["validTo"] = _datetime_to_iso(resp_dict["validTo"]) return resp_dict From 2bfb0e9842c179e19624d756098aed12f11b8d40 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 22:42:06 +0200 Subject: [PATCH 50/86] add docstring for get_headers --- src/flightplandb/internal.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/flightplandb/internal.py b/src/flightplandb/internal.py index d7b63a7..cc722a8 100644 --- a/src/flightplandb/internal.py +++ b/src/flightplandb/internal.py @@ -144,6 +144,18 @@ def request(method: str, # and here go the specific non-paginated HTTP calls def get_headers(key: Optional[str] = None) -> CaseInsensitiveDict: + """Calls :meth:`request()` for request headers. + + Parameters + ---------- + key : Optional[str] + API token, defaults to None (which makes it unauthenticated) + + Returns + ------- + CaseInsensitiveDict + A dict of headers, but the keys are case-insensitive. + """ headers, _ = request(method="get", path="", key=key) From 3e2d8727ca8588aecd661c6567794f88b2ab5089 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 22:52:06 +0200 Subject: [PATCH 51/86] fix datatypes line length --- src/flightplandb/datatypes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/flightplandb/datatypes.py b/src/flightplandb/datatypes.py index 23c10a6..7a0d058 100644 --- a/src/flightplandb/datatypes.py +++ b/src/flightplandb/datatypes.py @@ -239,7 +239,8 @@ class Route: def __post_init__(self): self.nodes = list(map( - lambda node: (RouteNode(**node) if (isinstance(node, dict)) else node), + lambda node: ( + RouteNode(**node) if (isinstance(node, dict)) else node), self.nodes)) def to_api_dict(self): From aa8fd426d9307cc6063a87e6e90f8492ad3201f9 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 22:53:29 +0200 Subject: [PATCH 52/86] remove unused variable arguments --- src/flightplandb/internal.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/flightplandb/internal.py b/src/flightplandb/internal.py index cc722a8..d0edce3 100644 --- a/src/flightplandb/internal.py +++ b/src/flightplandb/internal.py @@ -42,7 +42,7 @@ def request(method: str, params: Optional[Dict] = None, key: Optional[str] = None, json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: + **kwargs) -> Union[Dict, bytes]: """General HTTP requests function for non-paginated results. Parameters @@ -130,7 +130,7 @@ def request(method: str, auth=HTTPBasicAuth(key, None), headers=params, json=json_data, - *args, **kwargs) + **kwargs) status_handler(resp.status_code, ignore_statuses) @@ -166,7 +166,7 @@ def get(path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, key: Optional[str] = None, - *args, **kwargs) -> Union[Dict, bytes]: + **kwargs) -> Union[Dict, bytes]: """Calls :meth:`request()` for get requests. Parameters @@ -206,7 +206,7 @@ def get(path: str, return_format="native", ignore_statuses=ignore_statuses, params=params, key=key, - *args, **kwargs) + **kwargs) return resp @@ -215,7 +215,7 @@ def post(path: str, return_format="native", params: Optional[Dict] = None, key: Optional[str] = None, json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: + **kwargs) -> Union[Dict, bytes]: """Calls :meth:`request()` for post requests. Parameters @@ -254,7 +254,7 @@ def post(path: str, return_format="native", params=params, json_data=json_data, key=key, - *args, **kwargs) + **kwargs) return resp @@ -263,7 +263,7 @@ def patch(path: str, return_format="native", params: Optional[Dict] = None, key: Optional[str] = None, json_data: Optional[Dict] = None, - *args, **kwargs) -> Union[Dict, bytes]: + **kwargs) -> Union[Dict, bytes]: """Calls :meth:`request()` for patch requests. Parameters @@ -303,7 +303,7 @@ def patch(path: str, return_format="native", params=params, key=key, json_data=json_data, - *args, **kwargs) + **kwargs) return resp @@ -311,7 +311,7 @@ def delete(path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, key: Optional[str] = None, - *args, **kwargs) -> Union[Dict, bytes]: + **kwargs) -> Union[Dict, bytes]: """Calls :meth:`request()` for delete requests. Parameters @@ -350,7 +350,7 @@ def delete(path: str, return_format="native", ignore_statuses=ignore_statuses, params=params, key=key, - *args, **kwargs) + **kwargs) return resp @@ -360,7 +360,7 @@ def getiter(path: str, ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, key: Optional[str] = None, - *args, **kwargs) -> Generator[Dict, None, None]: + **kwargs) -> Generator[Dict, None, None]: """Get :meth:`request()` for paginated results. Parameters @@ -418,7 +418,7 @@ def getiter(path: str, url=url, params=params, auth=auth, - *args, **kwargs) + **kwargs) status_handler(r_fpdb.status_code, ignore_statuses) # I detest responses which "may" be paginated @@ -435,7 +435,7 @@ def getiter(path: str, r_fpdb = session.get(url=url, params=params, auth=auth, - *args, **kwargs) + **kwargs) status_handler(r_fpdb.status_code, ignore_statuses) # ...keep cycling through pages... for i in r_fpdb.json(): From 0caa77ef9cf7def2e30b6990ba1d83400221a408 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 23:06:52 +0200 Subject: [PATCH 53/86] add missing docstrings --- src/flightplandb/__init__.py | 9 +++++++++ src/flightplandb/exceptions.py | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/src/flightplandb/__init__.py b/src/flightplandb/__init__.py index 9742087..8c83f12 100644 --- a/src/flightplandb/__init__.py +++ b/src/flightplandb/__init__.py @@ -1,4 +1,13 @@ #!/usr/bin/env python3 +""" +This is a Python 3 wrapper for the Flight Plan Database API. Flight Plan +Database is a website for creating and sharing flight plans for use in +flight simulation. +For more information on Flight Plan Database, see their excellent About page +at https://flightplandatabase.com/about. For more information about this +library, check out the documentation at https://flightplandb-py.readthedocs.io/ +""" + # Version of the flightplandb package __version__ = "0.5.0" diff --git a/src/flightplandb/exceptions.py b/src/flightplandb/exceptions.py index 5ed9181..3b83026 100644 --- a/src/flightplandb/exceptions.py +++ b/src/flightplandb/exceptions.py @@ -1,3 +1,6 @@ +"Contains all the internally defined exceptions used by the library." + + class BaseErrorHandler(Exception): """Base exception. The other exceptions all inherit from this one, but this exception will be raised directly if @@ -97,6 +100,7 @@ class InternalServerException(BaseErrorHandler): def status_handler(status_code, ignore_statuses=None): + "Raises correct custom exception for appropriate HTTP status code." if status_code not in ignore_statuses and status_code >= 400: if status_code == 400: raise BadRequestException( From a52622951e0dea3a70cb20e05aca630b5e9c6107 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 23:18:12 +0200 Subject: [PATCH 54/86] move api calls out of submodules directory --- src/flightplandb/__init__.py | 10 ++++++++-- src/flightplandb/{submodules => }/api.py | 0 src/flightplandb/{submodules => }/nav.py | 0 src/flightplandb/{submodules => }/plan.py | 0 src/flightplandb/submodules/__init__.py | 2 -- src/flightplandb/{submodules => }/tags.py | 0 src/flightplandb/{submodules => }/user.py | 0 src/flightplandb/{submodules => }/weather.py | 0 8 files changed, 8 insertions(+), 4 deletions(-) rename src/flightplandb/{submodules => }/api.py (100%) rename src/flightplandb/{submodules => }/nav.py (100%) rename src/flightplandb/{submodules => }/plan.py (100%) delete mode 100644 src/flightplandb/submodules/__init__.py rename src/flightplandb/{submodules => }/tags.py (100%) rename src/flightplandb/{submodules => }/user.py (100%) rename src/flightplandb/{submodules => }/weather.py (100%) diff --git a/src/flightplandb/__init__.py b/src/flightplandb/__init__.py index 8c83f12..afb2dd6 100644 --- a/src/flightplandb/__init__.py +++ b/src/flightplandb/__init__.py @@ -12,6 +12,12 @@ # Version of the flightplandb package __version__ = "0.5.0" -from . import internal, exceptions, datatypes, submodules +from . import ( + internal, exceptions, datatypes, + api, nav, plan, tags, user, weather + ) -__all__ = ["internal", "exceptions", "datatypes", "submodules"] +__all__ = [ + "internal", "exceptions", "datatypes", "api", + "nav", "plan", "tags", "user", "weather" + ] diff --git a/src/flightplandb/submodules/api.py b/src/flightplandb/api.py similarity index 100% rename from src/flightplandb/submodules/api.py rename to src/flightplandb/api.py diff --git a/src/flightplandb/submodules/nav.py b/src/flightplandb/nav.py similarity index 100% rename from src/flightplandb/submodules/nav.py rename to src/flightplandb/nav.py diff --git a/src/flightplandb/submodules/plan.py b/src/flightplandb/plan.py similarity index 100% rename from src/flightplandb/submodules/plan.py rename to src/flightplandb/plan.py diff --git a/src/flightplandb/submodules/__init__.py b/src/flightplandb/submodules/__init__.py deleted file mode 100644 index ecda0f7..0000000 --- a/src/flightplandb/submodules/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import api, nav, plan, tags, user, weather -__all__ = ["api", "nav", "plan", "tags", "user", "weather"] diff --git a/src/flightplandb/submodules/tags.py b/src/flightplandb/tags.py similarity index 100% rename from src/flightplandb/submodules/tags.py rename to src/flightplandb/tags.py diff --git a/src/flightplandb/submodules/user.py b/src/flightplandb/user.py similarity index 100% rename from src/flightplandb/submodules/user.py rename to src/flightplandb/user.py diff --git a/src/flightplandb/submodules/weather.py b/src/flightplandb/weather.py similarity index 100% rename from src/flightplandb/submodules/weather.py rename to src/flightplandb/weather.py From dfa9a24c62ef49437a470dfaf7a400dde23fccce Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 23:49:46 +0200 Subject: [PATCH 55/86] fix tags test --- tests/test_tags.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/test_tags.py b/tests/test_tags.py index f111bb2..9784c19 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -29,17 +29,16 @@ def test_tags_api(mocker): popularity=0.009036140132228622) ] - def patched_get(self, path, key): + def patched_get(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.tags.TagsAPI, - attribute="_get", + mocker.patch( + target='flightplandb.internal.get', new=patched_get) - instance = flightplandb.submodules.tags.TagsAPI() - spy = mocker.spy(instance, "_get") - response = instance.fetch() + spy = mocker.spy(flightplandb.internal, "get") + + response = flightplandb.tags.fetch() # check that TagsAPI method made correct request of FlightPlanDB spy.assert_called_once_with(path='/tags', key=None) # check that TagsAPI method decoded data correctly for given response From 6ca8a5acd72a4d7ee425a9250b98aa1158a15920 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Mon, 2 May 2022 23:52:49 +0200 Subject: [PATCH 56/86] fix weather test --- tests/test_weather.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/test_weather.py b/tests/test_weather.py index 545eb34..2a4b7c4 100644 --- a/tests/test_weather.py +++ b/tests/test_weather.py @@ -17,17 +17,16 @@ def test_weather_api(mocker): 2507/2510 CAVOK BECMG 2608/2611 05009KT" ) - def patched_get(self, path, key): + def patched_get(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.weather.WeatherAPI, - attribute="_get", + mocker.patch( + target='flightplandb.internal.get', new=patched_get) - instance = flightplandb.submodules.weather.WeatherAPI() - spy = mocker.spy(instance, "_get") - response = instance.fetch("EHAM") + spy = mocker.spy(flightplandb.internal, "get") + + response = flightplandb.weather.fetch("EHAM") # check that TagsAPI method made correct request of FlightPlanDB spy.assert_called_once_with(path='/weather/EHAM', key=None) # check that TagsAPI method decoded data correctly for given response From 40118bbcd4b253724f501d7bf696069ef051bbfe Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Tue, 3 May 2022 22:15:32 +0200 Subject: [PATCH 57/86] fix user tests --- tests/test_user.py | 65 +++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/tests/test_user.py b/tests/test_user.py index ec0a21d..7c07f19 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -35,17 +35,16 @@ def test_self_info(mocker): plansLikes=0 ) - def patched_get(self, path, key): + def patched_get(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.user.UserAPI, - attribute="_get", + mocker.patch( + target="flightplandb.internal.get", new=patched_get) - instance = flightplandb.submodules.user.UserAPI() - spy = mocker.spy(instance, "_get") + + spy = mocker.spy(flightplandb.internal, "get") - response = instance.me() + response = flightplandb.user.me() # check that UserAPI method decoded data correctly for given response assert response == correct_response # check that UserAPI method made correct request of FlightPlanDB @@ -83,17 +82,16 @@ def test_user_info(mocker): plansLikes=33 ) - def patched_get(self, path, key): + def patched_get(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.user.UserAPI, - attribute="_get", + mocker.patch( + target="flightplandb.internal.get", new=patched_get) - instance = flightplandb.submodules.user.UserAPI() - spy = mocker.spy(instance, "_get") + + spy = mocker.spy(flightplandb.internal, "get") - response = instance.fetch("lemon") + response = flightplandb.user.fetch("lemon") # check that UserAPI method decoded data correctly for given response assert response == correct_response # check that UserAPI method made correct request of FlightPlanDB @@ -207,17 +205,16 @@ def test_user_plans(mocker): ) ] - def patched_getiter(self, path, limit, sort, key): + def patched_getiter(path, limit, sort, key): return (i for i in json_response) - mocker.patch.object( - target=flightplandb.submodules.user.UserAPI, - attribute="_getiter", + mocker.patch( + target="flightplandb.internal.getiter", new=patched_getiter) - instance = flightplandb.submodules.user.UserAPI() - spy = mocker.spy(instance, "_getiter") - response = instance.plans("lemon") + spy = mocker.spy(flightplandb.internal, "getiter") + + response = flightplandb.user.plans("lemon") # check that UserAPI method decoded data correctly for given response assert list(i for i in response) == correct_response_list # check that UserAPI method made correct request of FlightPlanDB @@ -335,17 +332,16 @@ def test_user_likes(mocker): ) ] - def patched_getiter(self, path, limit, sort, key): + def patched_getiter(path, limit, sort, key): return (i for i in json_response) - mocker.patch.object( - target=flightplandb.submodules.user.UserAPI, - attribute="_getiter", + mocker.patch( + target="flightplandb.internal.getiter", new=patched_getiter) - instance = flightplandb.submodules.user.UserAPI() - spy = mocker.spy(instance, "_getiter") - response = instance.likes("lemon") + spy = mocker.spy(flightplandb.internal, "getiter") + + response = flightplandb.user.likes("lemon") # check that UserAPI method decoded data correctly for given response assert list(i for i in response) == correct_response_list # check that UserAPI method made correct request of FlightPlanDB @@ -390,17 +386,16 @@ def test_user_search(mocker): gravatarHash='b807060d00c10513ce04b70918dd07a1') ] - def patched_getiter(self, path, limit, params=None, key=None): + def patched_getiter(path, limit, params, key): return (i for i in json_response) - mocker.patch.object( - target=flightplandb.submodules.user.UserAPI, - attribute="_getiter", + mocker.patch( + target="flightplandb.internal.getiter", new=patched_getiter) - instance = flightplandb.submodules.user.UserAPI() - spy = mocker.spy(instance, "_getiter") - response = instance.search("lemon") + spy = mocker.spy(flightplandb.internal, "getiter") + + response = flightplandb.user.search("lemon") # check that UserAPI method decoded data correctly for given response assert list(i for i in response) == correct_response_list # check that UserAPI method made correct request of FlightPlanDB From 75b8f238a41c1c8cd94c96007c0f57a3c36b0329 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Tue, 3 May 2022 23:47:27 +0200 Subject: [PATCH 58/86] fix plan tests --- tests/test_plan.py | 140 +++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 75 deletions(-) diff --git a/tests/test_plan.py b/tests/test_plan.py index e072684..80a0025 100644 --- a/tests/test_plan.py +++ b/tests/test_plan.py @@ -63,17 +63,16 @@ def test_plan_fetch(mocker): location=None )) - def patched_get(self, path, return_format, key): + def patched_get(path, return_format, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.plan.PlanAPI, - attribute="_get", + mocker.patch( + target="flightplandb.internal.get", new=patched_get) - instance = flightplandb.submodules.plan.PlanAPI() - spy = mocker.spy(instance, "_get") + + spy = mocker.spy(flightplandb.internal, "get") - response = instance.fetch(62373) + response = flightplandb.plan.fetch(62373) # check that PlanAPI method decoded data correctly for given response assert response == correct_response # check that PlanAPI method made correct request of FlightPlanDB @@ -207,17 +206,16 @@ def test_plan_create(mocker): 'key': None } - def patched_post(self, path, return_format, json, key): + def patched_post(path, return_format, json, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.plan.PlanAPI, - attribute="_post", + mocker.patch( + target="flightplandb.internal.post", new=patched_post) - instance = flightplandb.submodules.plan.PlanAPI() - spy = mocker.spy(instance, "_post") + + spy = mocker.spy(flightplandb.internal, "post") - response = instance.create(request_data) + response = flightplandb.plan.create(request_data) # check that PlanAPI method decoded data correctly for given response assert response == correct_response # check that PlanAPI method made correct request of FlightPlanDB @@ -232,17 +230,16 @@ def test_plan_delete(mocker): correct_response = StatusResponse(message="OK", errors=None) - def patched_delete(self, path, key): + def patched_delete(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.plan.PlanAPI, - attribute="_delete", + mocker.patch( + target="flightplandb.internal.delete", new=patched_delete) - instance = flightplandb.submodules.plan.PlanAPI() - spy = mocker.spy(instance, "_delete") + + spy = mocker.spy(flightplandb.internal, "delete") - response = instance.delete(62493) + response = flightplandb.plan.delete(62493) # check that TagsAPI method made correct request of FlightPlanDB spy.assert_called_once_with(path='/plan/62493', key=None) # check that TagsAPI method decoded data correctly for given response @@ -384,17 +381,16 @@ def test_plan_edit(mocker): 'key': None } - def patched_patch(self, path, return_format, json, key): + def patched_patch(path, return_format, json, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.plan.PlanAPI, - attribute="_patch", + mocker.patch( + target="flightplandb.internal.patch", new=patched_patch) - instance = flightplandb.submodules.plan.PlanAPI() - spy = mocker.spy(instance, "_patch") + + spy = mocker.spy(flightplandb.internal, "patch") - response = instance.edit(plan=request_data, return_format="native", key=None) + response = flightplandb.plan.edit(plan=request_data, return_format="native", key=None) # check that PlanAPI method decoded data correctly for given response assert response == correct_response # check that PlanAPI method made correct request of FlightPlanDB @@ -521,22 +517,21 @@ def test_plan_search(mocker): 'distanceMin': None, 'distanceMax': None, 'tags': None, - 'includeRoute': None + 'includeRoute': False }, limit=2, key=None)] - def patched_getiter(self, path, sort="created", params=None, limit=100, key=None): + def patched_getiter(path, sort="created", params=None, limit=100, key=None): return (i for i in json_response) - mocker.patch.object( - target=flightplandb.submodules.plan.PlanAPI, - attribute="_getiter", + mocker.patch( + target="flightplandb.internal.getiter", new=patched_getiter) - instance = flightplandb.submodules.plan.PlanAPI() - spy = mocker.spy(instance, "_getiter") + + spy = mocker.spy(flightplandb.internal, "getiter") - response = instance.search( + response = flightplandb.plan.search( PlanQuery( fromICAO="EHAM", toICAO="EHAL"), @@ -555,17 +550,16 @@ def test_plan_like(mocker): correct_response = StatusResponse(message='Not Found', errors=None) - def patched_post(self, path, key): + def patched_post(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.plan.PlanAPI, - attribute="_post", + mocker.patch( + target="flightplandb.internal.post", new=patched_post) - instance = flightplandb.submodules.plan.PlanAPI() - spy = mocker.spy(instance, "_post") + + spy = mocker.spy(flightplandb.internal, "post") - response = instance.like(42) + response = flightplandb.plan.like(42) # check that TagsAPI method made correct request of FlightPlanDB spy.assert_called_once_with(path='/plan/42/like', key=None) # check that TagsAPI method decoded data correctly for given response @@ -580,17 +574,16 @@ def test_plan_unlike(mocker): correct_response = True - def patched_delete(self, path, key): + def patched_delete(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.plan.PlanAPI, - attribute="_delete", + mocker.patch( + target="flightplandb.internal.delete", new=patched_delete) - instance = flightplandb.submodules.plan.PlanAPI() - spy = mocker.spy(instance, "_delete") + + spy = mocker.spy(flightplandb.internal, "delete") - response = instance.unlike(42) + response = flightplandb.plan.unlike(42) # check that TagsAPI method made correct request of FlightPlanDB spy.assert_called_once_with(path='/plan/42/like', key=None) # check that TagsAPI method decoded data correctly for given response @@ -605,17 +598,16 @@ def test_plan_has_liked(mocker): correct_response = True - def patched_get(self, path, ignore_statuses=None, key=None): + def patched_get(path, ignore_statuses=None, key=None): return json_response - mocker.patch.object( - target=flightplandb.submodules.plan.PlanAPI, - attribute="_get", + mocker.patch( + target="flightplandb.internal.get", new=patched_get) - instance = flightplandb.submodules.plan.PlanAPI() - spy = mocker.spy(instance, "_get") + + spy = mocker.spy(flightplandb.internal, "get") - response = instance.has_liked(42) + response = flightplandb.plan.has_liked(42) # check that TagsAPI method made correct request of FlightPlanDB spy.assert_called_once_with(path='/plan/42/like', ignore_statuses=[404], key=None) # check that TagsAPI method decoded data correctly for given response @@ -723,7 +715,7 @@ def test_plan_generate(mocker): correct_call = { "path": '/auto/generate', - "json": { + "json_data": { 'fromICAO': 'EHAL', 'toICAO': 'EHTX', 'useNAT': True, @@ -735,23 +727,22 @@ def test_plan_generate(mocker): 'ascentRate': 2500, 'ascentSpeed': 250, 'descentRate': 1500, - 'descentSpeed': 250 + 'descentSpeed': 250, + 'includeRoute': 'false' }, - "return_format": "native", "key": None } - def patched_post(self, path, return_format, json, key): + def patched_post(path, json_data, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.plan.PlanAPI, - attribute="_post", + mocker.patch( + target="flightplandb.internal.post", new=patched_post) - instance = flightplandb.submodules.plan.PlanAPI() - spy = mocker.spy(instance, "_post") - response = instance.generate(request_data) + spy = mocker.spy(flightplandb.internal, "post") + + response = flightplandb.plan.generate(request_data) # check that PlanAPI method decoded data correctly for given response assert response == correct_response # check that PlanAPI method made correct request of FlightPlanDB @@ -841,7 +832,7 @@ def test_plan_decode(mocker): correct_call = { "path": '/auto/decode', - "json": { + "json_data": { 'route': { 'KSAN BROWS TRM LRAIN KDEN' } @@ -849,17 +840,16 @@ def test_plan_decode(mocker): "key": None } - def patched_post(self, path, json, key): + def patched_post(path, json_data, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.plan.PlanAPI, - attribute="_post", + mocker.patch( + target="flightplandb.internal.post", new=patched_post) - instance = flightplandb.submodules.plan.PlanAPI() - spy = mocker.spy(instance, "_post") + + spy = mocker.spy(flightplandb.internal, "post") - response = instance.decode(request_data) + response = flightplandb.plan.decode(request_data) # check that PlanAPI method decoded data correctly for given response assert response == correct_response # check that PlanAPI method made correct request of FlightPlanDB From 17ee6fa756c7c38470918dd528be02f9535fb776 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Tue, 3 May 2022 23:53:19 +0200 Subject: [PATCH 59/86] fix nav tests --- tests/test_nav.py | 52 ++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/tests/test_nav.py b/tests/test_nav.py index be440c7..9d57740 100644 --- a/tests/test_nav.py +++ b/tests/test_nav.py @@ -153,17 +153,16 @@ def test_airport_info(mocker): ) ) - def patched_get(self, path, key): + def patched_get(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.nav.NavAPI, - attribute="_get", + mocker.patch( + target="flightplandb.internal.get", new=patched_get) - instance = flightplandb.submodules.nav.NavAPI() - spy = mocker.spy(instance, "_get") + + spy = mocker.spy(flightplandb.internal, "get") - response = instance.airport("EHAL") + response = flightplandb.nav.airport("EHAL") # check that NavAPI method decoded data correctly for given response assert response == correct_response # check that NavAPI method made correct request of FlightPlanDB @@ -290,17 +289,16 @@ def test_nats(mocker): validTo=datetime.datetime( 2021, 4, 28, 19, 0, tzinfo=tzutc()))] - def patched_get(self, path, key): + def patched_get(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.nav.NavAPI, - attribute="_get", + mocker.patch( + target="flightplandb.internal.get", new=patched_get) - instance = flightplandb.submodules.nav.NavAPI() - spy = mocker.spy(instance, "_get") + + spy = mocker.spy(flightplandb.internal, "get") - response = instance.nats() + response = flightplandb.nav.nats() # check that NavAPI method decoded data correctly for given response assert response == correct_response # check that NavAPI method made correct request of FlightPlanDB @@ -422,17 +420,16 @@ def test_pacots(mocker): validTo=datetime.datetime( 2021, 4, 28, 19, 0, tzinfo=tzutc()))] - def patched_get(self, path, key): + def patched_get(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.nav.NavAPI, - attribute="_get", + mocker.patch( + target="flightplandb.internal.get", new=patched_get) - instance = flightplandb.submodules.nav.NavAPI() - spy = mocker.spy(instance, "_get") + + spy = mocker.spy(flightplandb.internal, "get") - response = instance.pacots() + response = flightplandb.nav.pacots() # check that NavAPI method decoded data correctly for given response assert response == correct_response # check that NavAPI method made correct request of FlightPlanDB @@ -485,17 +482,16 @@ def test_navaid_search(mocker): params={'q': 'SPY'}, key=None)] - def patched_getiter(self, path, params=None, key=None): + def patched_getiter(path, params=None, key=None): return (i for i in json_response) - mocker.patch.object( - target=flightplandb.submodules.nav.NavAPI, - attribute="_getiter", + mocker.patch( + target="flightplandb.internal.getiter", new=patched_getiter) - instance = flightplandb.submodules.nav.NavAPI() - spy = mocker.spy(instance, "_getiter") + + spy = mocker.spy(flightplandb.internal, "getiter") - response = instance.search("SPY") + response = flightplandb.nav.search("SPY") # check that PlanAPI method decoded data correctly for given response assert list(i for i in response) == correct_response_list # check that PlanAPI method made correct request of FlightPlanDB From 196b0bb0853fb611ccae5658979d341d445b0e53 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Wed, 4 May 2022 18:31:17 +0200 Subject: [PATCH 60/86] change variable arguments to keyword arguments --- src/flightplandb/api.py | 12 ++-- tests/test_api.py | 118 ++++++++++++++++++++++------------------ 2 files changed, 71 insertions(+), 59 deletions(-) diff --git a/src/flightplandb/api.py b/src/flightplandb/api.py index bd5ec8f..50b10da 100644 --- a/src/flightplandb/api.py +++ b/src/flightplandb/api.py @@ -5,7 +5,7 @@ def header_value(header_key: str, key: Optional[str] = None) -> str: - """Gets header value for key + """Gets header value for key. Do not call directly. Parameters ---------- @@ -23,7 +23,7 @@ def header_value(header_key: str, key: Optional[str] = None) -> str: def version(key: Optional[str] = None) -> int: - """API version that returned the response + """API version that returned the response. Returns ------- @@ -31,7 +31,7 @@ def version(key: Optional[str] = None) -> int: API version """ - return int(header_value("X-API-Version", key=key)) + return int(header_value(header_key="X-API-Version", key=key)) def units(key: Optional[str] = None) -> str: @@ -44,7 +44,7 @@ def units(key: Optional[str] = None) -> str: AVIATION, METRIC or SI """ - return header_value("X-Units", key=key) + return header_value(header_key="X-Units", key=key) def limit_cap(key: Optional[str] = None) -> int: @@ -60,7 +60,7 @@ def limit_cap(key: Optional[str] = None) -> int: number of allowed requests per day """ - return int(header_value("X-Limit-Cap", key=key)) + return int(header_value(header_key="X-Limit-Cap", key=key)) def limit_used(key: Optional[str] = None) -> int: @@ -73,7 +73,7 @@ def limit_used(key: Optional[str] = None) -> int: number of requests used in period """ - return int(header_value("X-Limit-Used", key=key)) + return int(header_value(header_key="X-Limit-Used", key=key)) def ping(key: Optional[str] = None) -> StatusResponse: diff --git a/tests/test_api.py b/tests/test_api.py index b46e337..68faa52 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,40 +1,56 @@ import flightplandb from flightplandb.datatypes import StatusResponse -import pytest - - -@pytest.mark.parametrize( - "header_key,ping_headers,existing_headers,correct_response,correct_mock_calls", - [ - ("X-API-Version", {"X-API-Version": 1}, {"X-API-Version": 1}, 1, None), - ("X-API-Version", {"X-API-Version": 1}, {}, 1, {"key": None}), - ("X-Units", {"X-Units": "AVIATION"}, {"X-Units": "AVIATION"}, "AVIATION", None), - ("X-Units", {"X-Units": "AVIATION"}, {}, "AVIATION", {"key": None}), - ("X-Limit-Cap", {"X-Limit-Cap": 100}, {"X-Limit-Cap": 100}, 100, None), - ("X-Limit-Cap", {"X-Limit-Cap": 100}, {}, 100, {"key": None}), - ("X-Limit-Used", {"X-Limit-Used": 1}, {"X-Limit-Used": 1}, 1, None), - ("X-Limit-Used", {"X-Limit-Used": 1}, {}, 1, {"key": None}), - ], -) -def test_api_headers(header_key, mocker, ping_headers, existing_headers, correct_response, correct_mock_calls): - - def patched_ping(self, key): - self._header = ping_headers - - mocker.patch.object( - target=flightplandb.submodules.api.API, - attribute="ping", - new=patched_ping) - instance = flightplandb.submodules.api.API() - spy = mocker.spy(instance, "ping") - - instance._header = existing_headers - - response = instance._header_value(header_key=header_key, key=None) - if correct_mock_calls is None: - spy.assert_not_called() - else: - spy.assert_called_once_with(**correct_mock_calls) + + +# parametrise this for key and no key, perhaps +def test_api_header_value(mocker): + json_response = { + "X-Limit-Cap": "2000", + "X-Limit-Used": "150" + } + + correct_response = "150" + + def patched_get_headers(key): + return json_response + + mocker.patch( + target="flightplandb.internal.get_headers", + new=patched_get_headers) + + spy = mocker.spy(flightplandb.internal, "get_headers") + + response = flightplandb.api.header_value( + header_key="X-Limit-Used", + key="qwertyuiop" + ) + # check that API method made correct request of FlightPlanDB + spy.assert_called_once_with( + key="qwertyuiop" + ) + # check that API method decoded data correctly for given response + assert response == correct_response + + +def test_api_version(mocker): + header_response = "1" + + correct_response = 1 + + def patched_get(header_key, key): + return header_response + + mocker.patch( + target="flightplandb.api.header_value", + new=patched_get) + + spy = mocker.spy(flightplandb.api, "header_value") + + response = flightplandb.api.version() + # check that API method made correct request of FlightPlanDB + print(spy.mock_calls) + spy.assert_called_once_with(header_key="X-API-Version", key=None) + # check that API method decoded data correctly for given response assert response == correct_response @@ -46,18 +62,16 @@ def test_api_ping(mocker): correct_response = StatusResponse(message="OK", errors=None) - def patched_get(self, path, key): + def patched_get(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.api.API, - attribute="_get", - new=patched_get - ) - instance = flightplandb.submodules.api.API() - spy = mocker.spy(instance, "_get") + mocker.patch( + target="flightplandb.internal.get", + new=patched_get) + + spy = mocker.spy(flightplandb.internal, "get") - response = instance.ping() + response = flightplandb.api.ping() # check that API method made correct request of FlightPlanDB spy.assert_called_once_with(path='', key=None) # check that API method decoded data correctly for given response @@ -72,18 +86,16 @@ def test_key_revoke(mocker): correct_response = StatusResponse(message="OK", errors=None) - def patched_get(self, path, key): + def patched_get(path, key): return json_response - mocker.patch.object( - target=flightplandb.submodules.api.API, - attribute="_get", - new=patched_get - ) - instance = flightplandb.submodules.api.API() - spy = mocker.spy(instance, "_get") + mocker.patch( + target="flightplandb.internal.get", + new=patched_get) + + spy = mocker.spy(flightplandb.internal, "get") - response = instance.revoke(key="qwertyuiop") + response = flightplandb.api.revoke(key="qwertyuiop") # check that API method made correct request of FlightPlanDB spy.assert_called_once_with(path='/auth/revoke', key="qwertyuiop") # check that API method decoded data correctly for given response From e8b3e52b105f7c4f37b9d846a9fac147bafe23b0 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 5 May 2022 00:55:27 +0200 Subject: [PATCH 61/86] fix api tests --- tests/test_api.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 68faa52..e9662fb 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -48,12 +48,74 @@ def patched_get(header_key, key): response = flightplandb.api.version() # check that API method made correct request of FlightPlanDB - print(spy.mock_calls) spy.assert_called_once_with(header_key="X-API-Version", key=None) # check that API method decoded data correctly for given response assert response == correct_response +def test_api_units(mocker): + header_response = "AVIATION" + + correct_response = "AVIATION" + + def patched_get(header_key, key): + return header_response + + mocker.patch( + target="flightplandb.api.header_value", + new=patched_get) + + spy = mocker.spy(flightplandb.api, "header_value") + + response = flightplandb.api.units() + # check that API method made correct request of FlightPlanDB + spy.assert_called_once_with(header_key="X-Units", key=None) + # check that API method decoded data correctly for given response + assert response == correct_response + + +def test_api_limit_cap(mocker): + header_response = "100" + + correct_response = 100 + + def patched_get(header_key, key): + return header_response + + mocker.patch( + target="flightplandb.api.header_value", + new=patched_get) + + spy = mocker.spy(flightplandb.api, "header_value") + + response = flightplandb.api.limit_cap() + # check that API method made correct request of FlightPlanDB + spy.assert_called_once_with(header_key="X-Limit-Cap", key=None) + # check that API method decoded data correctly for given response + assert response == correct_response + + +def test_api_limit_used(mocker): + header_response = "50" + + correct_response = 50 + + def patched_get(header_key, key): + return header_response + + mocker.patch( + target="flightplandb.api.header_value", + new=patched_get) + + spy = mocker.spy(flightplandb.api, "header_value") + + response = flightplandb.api.limit_used() + # check that API method made correct request of FlightPlanDB + spy.assert_called_once_with(header_key="X-Limit-Used", key=None) + # check that API method decoded data correctly for given response + assert response == correct_response + + def test_api_ping(mocker): json_response = { "message": "OK", From 53b4f7c950febdb1ea03e86893c7d60b99c3bc15 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 5 May 2022 01:00:08 +0200 Subject: [PATCH 62/86] remove unnecessary whitespace --- tests/test_api.py | 12 ++++++------ tests/test_nav.py | 8 ++++---- tests/test_plan.py | 18 +++++++++--------- tests/test_user.py | 4 ++-- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index e9662fb..1ed3866 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -43,7 +43,7 @@ def patched_get(header_key, key): mocker.patch( target="flightplandb.api.header_value", new=patched_get) - + spy = mocker.spy(flightplandb.api, "header_value") response = flightplandb.api.version() @@ -64,7 +64,7 @@ def patched_get(header_key, key): mocker.patch( target="flightplandb.api.header_value", new=patched_get) - + spy = mocker.spy(flightplandb.api, "header_value") response = flightplandb.api.units() @@ -85,7 +85,7 @@ def patched_get(header_key, key): mocker.patch( target="flightplandb.api.header_value", new=patched_get) - + spy = mocker.spy(flightplandb.api, "header_value") response = flightplandb.api.limit_cap() @@ -106,7 +106,7 @@ def patched_get(header_key, key): mocker.patch( target="flightplandb.api.header_value", new=patched_get) - + spy = mocker.spy(flightplandb.api, "header_value") response = flightplandb.api.limit_used() @@ -130,7 +130,7 @@ def patched_get(path, key): mocker.patch( target="flightplandb.internal.get", new=patched_get) - + spy = mocker.spy(flightplandb.internal, "get") response = flightplandb.api.ping() @@ -154,7 +154,7 @@ def patched_get(path, key): mocker.patch( target="flightplandb.internal.get", new=patched_get) - + spy = mocker.spy(flightplandb.internal, "get") response = flightplandb.api.revoke(key="qwertyuiop") diff --git a/tests/test_nav.py b/tests/test_nav.py index 9d57740..f32b44f 100644 --- a/tests/test_nav.py +++ b/tests/test_nav.py @@ -159,7 +159,7 @@ def patched_get(path, key): mocker.patch( target="flightplandb.internal.get", new=patched_get) - + spy = mocker.spy(flightplandb.internal, "get") response = flightplandb.nav.airport("EHAL") @@ -295,7 +295,7 @@ def patched_get(path, key): mocker.patch( target="flightplandb.internal.get", new=patched_get) - + spy = mocker.spy(flightplandb.internal, "get") response = flightplandb.nav.nats() @@ -426,7 +426,7 @@ def patched_get(path, key): mocker.patch( target="flightplandb.internal.get", new=patched_get) - + spy = mocker.spy(flightplandb.internal, "get") response = flightplandb.nav.pacots() @@ -488,7 +488,7 @@ def patched_getiter(path, params=None, key=None): mocker.patch( target="flightplandb.internal.getiter", new=patched_getiter) - + spy = mocker.spy(flightplandb.internal, "getiter") response = flightplandb.nav.search("SPY") diff --git a/tests/test_plan.py b/tests/test_plan.py index 80a0025..9553dcd 100644 --- a/tests/test_plan.py +++ b/tests/test_plan.py @@ -69,7 +69,7 @@ def patched_get(path, return_format, key): mocker.patch( target="flightplandb.internal.get", new=patched_get) - + spy = mocker.spy(flightplandb.internal, "get") response = flightplandb.plan.fetch(62373) @@ -212,7 +212,7 @@ def patched_post(path, return_format, json, key): mocker.patch( target="flightplandb.internal.post", new=patched_post) - + spy = mocker.spy(flightplandb.internal, "post") response = flightplandb.plan.create(request_data) @@ -236,7 +236,7 @@ def patched_delete(path, key): mocker.patch( target="flightplandb.internal.delete", new=patched_delete) - + spy = mocker.spy(flightplandb.internal, "delete") response = flightplandb.plan.delete(62493) @@ -387,7 +387,7 @@ def patched_patch(path, return_format, json, key): mocker.patch( target="flightplandb.internal.patch", new=patched_patch) - + spy = mocker.spy(flightplandb.internal, "patch") response = flightplandb.plan.edit(plan=request_data, return_format="native", key=None) @@ -528,7 +528,7 @@ def patched_getiter(path, sort="created", params=None, limit=100, key=None): mocker.patch( target="flightplandb.internal.getiter", new=patched_getiter) - + spy = mocker.spy(flightplandb.internal, "getiter") response = flightplandb.plan.search( @@ -556,7 +556,7 @@ def patched_post(path, key): mocker.patch( target="flightplandb.internal.post", new=patched_post) - + spy = mocker.spy(flightplandb.internal, "post") response = flightplandb.plan.like(42) @@ -580,7 +580,7 @@ def patched_delete(path, key): mocker.patch( target="flightplandb.internal.delete", new=patched_delete) - + spy = mocker.spy(flightplandb.internal, "delete") response = flightplandb.plan.unlike(42) @@ -604,7 +604,7 @@ def patched_get(path, ignore_statuses=None, key=None): mocker.patch( target="flightplandb.internal.get", new=patched_get) - + spy = mocker.spy(flightplandb.internal, "get") response = flightplandb.plan.has_liked(42) @@ -846,7 +846,7 @@ def patched_post(path, json_data, key): mocker.patch( target="flightplandb.internal.post", new=patched_post) - + spy = mocker.spy(flightplandb.internal, "post") response = flightplandb.plan.decode(request_data) diff --git a/tests/test_user.py b/tests/test_user.py index 7c07f19..ff3bc96 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -41,7 +41,7 @@ def patched_get(path, key): mocker.patch( target="flightplandb.internal.get", new=patched_get) - + spy = mocker.spy(flightplandb.internal, "get") response = flightplandb.user.me() @@ -88,7 +88,7 @@ def patched_get(path, key): mocker.patch( target="flightplandb.internal.get", new=patched_get) - + spy = mocker.spy(flightplandb.internal, "get") response = flightplandb.user.fetch("lemon") From d5b7be7a2fb8974e690a7d9048786f4b0b51f22a Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 5 May 2022 11:34:02 +0200 Subject: [PATCH 63/86] bump version number --- docs/source/conf.py | 2 +- src/flightplandb/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index aa9b5a8..9b6876c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -21,7 +21,7 @@ author = 'PH-KDX' # The full version, including alpha/beta/rc tags -release = '0.5.0' +release = '0.6.0' # -- General configuration --------------------------------------------------- diff --git a/src/flightplandb/__init__.py b/src/flightplandb/__init__.py index afb2dd6..8bb99a8 100644 --- a/src/flightplandb/__init__.py +++ b/src/flightplandb/__init__.py @@ -10,7 +10,7 @@ # Version of the flightplandb package -__version__ = "0.5.0" +__version__ = "0.6.0" from . import ( internal, exceptions, datatypes, From 832549db1fdea49b80688e5fe2a02331a91899db Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 5 May 2022 11:47:40 +0200 Subject: [PATCH 64/86] update docs structure to reflect module changes --- docs/source/api/flightplandb.rst | 15 --------------- docs/source/api/internal.rst | 6 ++++++ docs/source/api/{api.rst => main.rst} | 7 ++++++- docs/source/api/{submodules => }/nav.rst | 2 +- docs/source/api/{submodules => }/plan.rst | 2 +- docs/source/api/{submodules => }/tags.rst | 2 +- docs/source/api/{submodules => }/user.rst | 2 +- docs/source/api/{submodules => }/weather.rst | 2 +- docs/source/index.rst | 2 +- docs/source/user/quickstart.rst | 2 +- 10 files changed, 19 insertions(+), 23 deletions(-) delete mode 100644 docs/source/api/flightplandb.rst create mode 100644 docs/source/api/internal.rst rename docs/source/api/{api.rst => main.rst} (76%) rename docs/source/api/{submodules => }/nav.rst (58%) rename docs/source/api/{submodules => }/plan.rst (95%) rename docs/source/api/{submodules => }/tags.rst (58%) rename docs/source/api/{submodules => }/user.rst (58%) rename docs/source/api/{submodules => }/weather.rst (58%) diff --git a/docs/source/api/flightplandb.rst b/docs/source/api/flightplandb.rst deleted file mode 100644 index 627066a..0000000 --- a/docs/source/api/flightplandb.rst +++ /dev/null @@ -1,15 +0,0 @@ -Main module -==================== - -.. automodule:: flightplandb.flightplandb - :members: - :undoc-members: - -Submodules ---------------------- -.. toctree:: - :titlesonly: - :glob: - :maxdepth: 4 - - submodules/* diff --git a/docs/source/api/internal.rst b/docs/source/api/internal.rst new file mode 100644 index 0000000..03721ce --- /dev/null +++ b/docs/source/api/internal.rst @@ -0,0 +1,6 @@ +Internal functions +==================== + +.. automodule:: flightplandb.internal + :members: + :undoc-members: \ No newline at end of file diff --git a/docs/source/api/api.rst b/docs/source/api/main.rst similarity index 76% rename from docs/source/api/api.rst rename to docs/source/api/main.rst index 4521e28..21289fd 100644 --- a/docs/source/api/api.rst +++ b/docs/source/api/main.rst @@ -9,6 +9,11 @@ API docs .. toctree:: :maxdepth: 5 - flightplandb + nav + plan + tags + user + weather datatypes exceptions + internal diff --git a/docs/source/api/submodules/nav.rst b/docs/source/api/nav.rst similarity index 58% rename from docs/source/api/submodules/nav.rst rename to docs/source/api/nav.rst index 548c4b8..c78b6ae 100644 --- a/docs/source/api/submodules/nav.rst +++ b/docs/source/api/nav.rst @@ -1,6 +1,6 @@ Nav docs ==================== -.. automodule:: flightplandb.submodules.nav +.. automodule:: flightplandb.nav :members: :undoc-members: diff --git a/docs/source/api/submodules/plan.rst b/docs/source/api/plan.rst similarity index 95% rename from docs/source/api/submodules/plan.rst rename to docs/source/api/plan.rst index 97a1b0c..1bdff86 100644 --- a/docs/source/api/submodules/plan.rst +++ b/docs/source/api/plan.rst @@ -26,6 +26,6 @@ Plan docs "Infinite Flight", "infiniteflight" -.. automodule:: flightplandb.submodules.plan +.. automodule:: flightplandb.plan :members: :undoc-members: diff --git a/docs/source/api/submodules/tags.rst b/docs/source/api/tags.rst similarity index 58% rename from docs/source/api/submodules/tags.rst rename to docs/source/api/tags.rst index 3520f57..9dfe3c5 100644 --- a/docs/source/api/submodules/tags.rst +++ b/docs/source/api/tags.rst @@ -1,6 +1,6 @@ Tags docs ==================== -.. automodule:: flightplandb.submodules.tags +.. automodule:: flightplandb.tags :members: :undoc-members: diff --git a/docs/source/api/submodules/user.rst b/docs/source/api/user.rst similarity index 58% rename from docs/source/api/submodules/user.rst rename to docs/source/api/user.rst index e1ba115..0658f24 100644 --- a/docs/source/api/submodules/user.rst +++ b/docs/source/api/user.rst @@ -1,6 +1,6 @@ User docs ==================== -.. automodule:: flightplandb.submodules.user +.. automodule:: flightplandb.user :members: :undoc-members: diff --git a/docs/source/api/submodules/weather.rst b/docs/source/api/weather.rst similarity index 58% rename from docs/source/api/submodules/weather.rst rename to docs/source/api/weather.rst index e50afe4..cc71a63 100644 --- a/docs/source/api/submodules/weather.rst +++ b/docs/source/api/weather.rst @@ -1,6 +1,6 @@ Weather docs ===================== -.. automodule:: flightplandb.submodules.weather +.. automodule:: flightplandb.weather :members: :undoc-members: diff --git a/docs/source/index.rst b/docs/source/index.rst index 2e71f55..9efe728 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,7 +16,7 @@ If you're new, check out the :doc:`user/introduction` or :doc:`user/quickstart` user/userdocs - api/api + api/main diff --git a/docs/source/user/quickstart.rst b/docs/source/user/quickstart.rst index 2429df1..4a25557 100644 --- a/docs/source/user/quickstart.rst +++ b/docs/source/user/quickstart.rst @@ -46,4 +46,4 @@ This is a small example program which demonstrates basic usage of the library. Try saving it in a file called ``test.py`` in your project directory and running it. Experiment around with different commands to get a feel for the library. -For specific commands, check out the :doc:`../api/api`. +For specific commands, check out the :doc:`../api/main`. From 64bad0e40fd2f01c0d32276e24309a3eca3071cf Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 5 May 2022 12:19:34 +0200 Subject: [PATCH 65/86] make key required in key revoke method --- src/flightplandb/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flightplandb/api.py b/src/flightplandb/api.py index 50b10da..29393b4 100644 --- a/src/flightplandb/api.py +++ b/src/flightplandb/api.py @@ -89,7 +89,7 @@ def ping(key: Optional[str] = None) -> StatusResponse: return StatusResponse(**resp) -def revoke(key: Optional[str] = None) -> StatusResponse: +def revoke(key: str) -> StatusResponse: """Revoke the API key in use in the event it is compromised. Requires authentication. From 246ca2a67e49f90e8720554f1a9ebbfd249de97d Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 5 May 2022 13:21:33 +0200 Subject: [PATCH 66/86] add docs for api connection functions --- docs/source/api/api.rst | 5 +++++ docs/source/api/main.rst | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 docs/source/api/api.rst diff --git a/docs/source/api/api.rst b/docs/source/api/api.rst new file mode 100644 index 0000000..cfbc042 --- /dev/null +++ b/docs/source/api/api.rst @@ -0,0 +1,5 @@ +API docs +==================== + +.. automodule:: flightplandb.api + :members: diff --git a/docs/source/api/main.rst b/docs/source/api/main.rst index 21289fd..b911d22 100644 --- a/docs/source/api/main.rst +++ b/docs/source/api/main.rst @@ -1,4 +1,4 @@ -API docs +API reference ==================== .. automodule:: flightplandb @@ -9,6 +9,7 @@ API docs .. toctree:: :maxdepth: 5 + api nav plan tags From 65c93c0f5c35b4638abbf5f09e1cf0d18d76f47f Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 5 May 2022 13:23:27 +0200 Subject: [PATCH 67/86] update introduction and quickstart --- docs/source/user/introduction.rst | 31 +++++++++++++++++-------------- docs/source/user/quickstart.rst | 28 +++++++++++++++++++--------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/docs/source/user/introduction.rst b/docs/source/user/introduction.rst index 0afc558..9612886 100644 --- a/docs/source/user/introduction.rst +++ b/docs/source/user/introduction.rst @@ -14,8 +14,6 @@ Prerequisites FlightplanDB-py works with Python 3.7 or higher. Python 3.6 or lower is not supported due to dataclasses, which were introduced with `PEP 557 `_, being used in the library. -These instructions were written with Debian in mind, so you might have tweak them -a little to get everything working on your machine. Installation ^^^^^^^^^^^^^^^^^^^^ @@ -25,11 +23,11 @@ The easiest way to install the library is from PyPi, by running $ pip install flightplandb -Or, if you like living dangerously, install the devel branch directly from the GitHub repo: +Or, if you prefer, install the directly from the GitHub repo: .. code-block:: console - $ pip install https://github.com/PH-KDX/flightplandb-py/archive/devel.zip + $ pip install https://github.com/PH-KDX/flightplandb-py/archive/main.zip after which the package and its dependencies are installed. @@ -42,16 +40,22 @@ Start by going to your project's working directory. Create a virtual environment called, for example, ``foo`` as follows: -.. code-block:: console +.. code-block:: bash $ python3 -m venv foo -then when you want to use it, activate it with +then when you want to use it, activate it on Linux or macOS with -.. code-block:: console +.. code-block:: bash $ source foo/activate/bin +or on Windows with + +.. code-block:: dosbatch + + $ foo\Scripts\activate.bat + after which you can install the library as described in `Installation <#installation>`_. @@ -63,7 +67,7 @@ To test if the package has correctly installed, open a Python shell .. code-block:: python3 import flightplandb - flightplandb.FlightPlanDB().ping() + flightplandb.api.ping() which should return ``StatusResponse(message='OK', errors=None)`` @@ -76,9 +80,9 @@ API requests are rate limited on a 24 hour rolling basis to ensure fair access t If you reach your daily limit, a :class:`~flightplandb.exceptions.TooManyRequestsException()` will be raised on your requests. To check your limit and used requests, look at the output of -:meth:`~flightplandb.FlightPlanDB.limit_cap` and -:meth:`~flightplandb.FlightPlanDB.limit_used` respectively. -These calls, together with :meth:`~flightplandb.FlightPlanDB.ping()`, will not increment your request counter. +:meth:`flightplandb.api.limit_cap()` and +:meth:`flightplandb.api.limit_used()` respectively. +These calls, together with :meth:`flightplandb.api.ping()`, will not increment your request counter. The limit for unauthenticated users is IP-based, and is currently set to 100. The limit for authenticated users is key-based, and is currently set to 2500. @@ -89,8 +93,7 @@ Authentication Whilst many parts of the API are publicly accessible, some endpoints require authentication with an API access key, which is an alphanumeric string such as ``VtF93tXp5IUZE307kPjijoGCUtBq4INmNTS4wlRG``. If provided, this key must be -specified when initiating a :meth:`~flightplandb.FlightPlanDB` class instance, -using the ``key`` argument. +passed into every authenticated request, using the ``key`` argument. To get an API key, visit your Flight Plan Database `account settings `_ page. @@ -101,4 +104,4 @@ provide valid authentication credentials on these endpoints will result in an :class:`~flightplandb.exceptions.UnauthorizedException()` being raised. You are responsible for maintaining the security of your private API key, which gives near full access to your Flight Plan Database account. If your key is exposed, please use -:meth:`~flightplandb.FlightPlanDB.revoke()` to revoke your key manually. +:meth:`flightplandb.api.revoke()` to revoke your key manually. diff --git a/docs/source/user/quickstart.rst b/docs/source/user/quickstart.rst index 4a25557..753a22e 100644 --- a/docs/source/user/quickstart.rst +++ b/docs/source/user/quickstart.rst @@ -1,30 +1,33 @@ Quickstart -------------------- -This assumes you have the library installed; +This document assumes you have the library installed; if not, check out the `Installation `_ section of the introduction. Example ^^^^^^^^^^^^^^^^^^^^ This is a small example program which demonstrates basic usage of the library. +In this example, the only authenticated requests are those for which it is required. +In most cases, all requests would be authenticated, since authentication raises the +request limit from 100 to 2500. .. code-block:: python import flightplandb as fpdb # obviously, substitute your own token - api = fpdb.FlightPlanDB("VtF93tXp5IUZE307kPjijoGCUtBq4INmNTS4wlRG") + API_KEY = "VtF93tXp5IUZE307kPjijoGCUtBq4INmNTS4wlRG" # list all users named lemon - for user in api.user.search("lemon"): + for user in fpdb.user.search(username="lemon"): print(user) # fetch most relevant user named lemon - print(api.user.fetch("lemon")) + print(fpdb.user.fetch(username="lemon")) # fetch first 20 of lemon's plans - lemon_plans = api.user.plans(username="lemon", limit=20) + lemon_plans = fpdb.user.plans(username="lemon", limit=20) for plan in lemon_plans: print(plan) @@ -32,18 +35,25 @@ This is a small example program which demonstrates basic usage of the library. query = fpdb.datatypes.PlanQuery(fromICAO="EHAM", toICAO="EGLL") # then search for the first three results of that query, sorted by distance - resp = api.plan.search(query, limit=3, sort="distance") + # the route is included, which requires authentication + resp = fpdb.plan.search( + plan_query=query, + include_route=True, + sort="distance" + limit=3, + key=API_KEY + ) # and print each result in the response for i in resp: print(i) # fetch the weather for Schiphol Airport - print(api.weather.fetch("EHAM")) + print(fpdb.weather.fetch("EHAM")) # then check remaining requests by subtracting the requests made from the total limit - print(api.limit_cap-api.limit_used) + print(fpdb.api.limit_cap-fpdb.api.limit_used) -Try saving it in a file called ``test.py`` in your project directory and running it. +Try saving this program in a file in your project directory and running it. Experiment around with different commands to get a feel for the library. For specific commands, check out the :doc:`../api/main`. From b9ae5dbe2fcbc0d51008ab1d95e1e2a0b7aaad34 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 5 May 2022 13:26:50 +0200 Subject: [PATCH 68/86] remove word docs where not necessary --- docs/source/api/api.rst | 2 +- docs/source/api/datatypes.rst | 2 +- docs/source/api/exceptions.rst | 2 +- docs/source/api/nav.rst | 2 +- docs/source/api/plan.rst | 2 +- docs/source/api/tags.rst | 2 +- docs/source/api/user.rst | 2 +- docs/source/api/weather.rst | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/api/api.rst b/docs/source/api/api.rst index cfbc042..78539f1 100644 --- a/docs/source/api/api.rst +++ b/docs/source/api/api.rst @@ -1,4 +1,4 @@ -API docs +API ==================== .. automodule:: flightplandb.api diff --git a/docs/source/api/datatypes.rst b/docs/source/api/datatypes.rst index bf7b6be..698701f 100644 --- a/docs/source/api/datatypes.rst +++ b/docs/source/api/datatypes.rst @@ -1,4 +1,4 @@ -Datatypes docs +Datatypes ==================== .. automodule:: flightplandb.datatypes diff --git a/docs/source/api/exceptions.rst b/docs/source/api/exceptions.rst index 011b610..f5475c6 100644 --- a/docs/source/api/exceptions.rst +++ b/docs/source/api/exceptions.rst @@ -1,4 +1,4 @@ -Exceptions docs +Exceptions ==================== .. automodule:: flightplandb.exceptions diff --git a/docs/source/api/nav.rst b/docs/source/api/nav.rst index c78b6ae..96cdace 100644 --- a/docs/source/api/nav.rst +++ b/docs/source/api/nav.rst @@ -1,4 +1,4 @@ -Nav docs +Nav ==================== .. automodule:: flightplandb.nav diff --git a/docs/source/api/plan.rst b/docs/source/api/plan.rst index 1bdff86..e7d5722 100644 --- a/docs/source/api/plan.rst +++ b/docs/source/api/plan.rst @@ -1,4 +1,4 @@ -Plan docs +Plan ==================== .. csv-table:: Permitted plan return types diff --git a/docs/source/api/tags.rst b/docs/source/api/tags.rst index 9dfe3c5..464e0ec 100644 --- a/docs/source/api/tags.rst +++ b/docs/source/api/tags.rst @@ -1,4 +1,4 @@ -Tags docs +Tags ==================== .. automodule:: flightplandb.tags diff --git a/docs/source/api/user.rst b/docs/source/api/user.rst index 0658f24..df727d5 100644 --- a/docs/source/api/user.rst +++ b/docs/source/api/user.rst @@ -1,4 +1,4 @@ -User docs +User ==================== .. automodule:: flightplandb.user diff --git a/docs/source/api/weather.rst b/docs/source/api/weather.rst index cc71a63..1856593 100644 --- a/docs/source/api/weather.rst +++ b/docs/source/api/weather.rst @@ -1,4 +1,4 @@ -Weather docs +Weather ===================== .. automodule:: flightplandb.weather From 0ef75e674a320cd860f6747ab72a091812c0fe16 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 5 May 2022 14:28:03 +0200 Subject: [PATCH 69/86] update api function docs --- docs/source/user/introduction.rst | 4 ++++ src/flightplandb/api.py | 38 +++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/docs/source/user/introduction.rst b/docs/source/user/introduction.rst index 9612886..9d6c921 100644 --- a/docs/source/user/introduction.rst +++ b/docs/source/user/introduction.rst @@ -74,6 +74,8 @@ which should return if all has gone well. +.. _request-limits: + Request Limits ^^^^^^^^^^^^^^^^^^^^ API requests are rate limited on a 24 hour rolling basis to ensure fair access to all users. @@ -88,6 +90,8 @@ The limit for unauthenticated users is IP-based, and is currently set to 100. The limit for authenticated users is key-based, and is currently set to 2500. +.. _authentication: + Authentication ^^^^^^^^^^^^^^^^^^^^ Whilst many parts of the API are publicly accessible, some endpoints require diff --git a/src/flightplandb/api.py b/src/flightplandb/api.py index 29393b4..58f21a9 100644 --- a/src/flightplandb/api.py +++ b/src/flightplandb/api.py @@ -11,6 +11,8 @@ def header_value(header_key: str, key: Optional[str] = None) -> str: ---------- header_key : str One of the HTTP header keys + key : `str`, optional + API authentication key. Returns ------- @@ -25,6 +27,11 @@ def header_value(header_key: str, key: Optional[str] = None) -> str: def version(key: Optional[str] = None) -> int: """API version that returned the response. + Parameters + ---------- + key : `str`, optional + API authentication key. + Returns ------- int @@ -38,6 +45,11 @@ def units(key: Optional[str] = None) -> str: """The units system used for numeric values. https://flightplandatabase.com/dev/api#units + Parameters + ---------- + key : `str`, optional + API authentication key. + Returns ------- str @@ -52,7 +64,12 @@ def limit_cap(key: Optional[str] = None) -> int: basis. i.e requests used between 19:00 and 20:00 will become available again at 19:00 the following day. API key authenticated requests get a higher daily rate limit and can be raised if a compelling - use case is presented. + use case is presented. See :ref:`request-limits` for more details. + + Parameters + ---------- + key : `str`, optional + API authentication key. Returns ------- @@ -65,7 +82,13 @@ def limit_cap(key: Optional[str] = None) -> int: def limit_used(key: Optional[str] = None) -> int: """The number of requests used in the current period - by the presented API key or IP address + by the presented API key or IP address. + See :ref:`request-limits` for more details. + + Parameters + ---------- + key : `str`, optional + API authentication key. Returns ------- @@ -79,6 +102,11 @@ def limit_used(key: Optional[str] = None) -> int: def ping(key: Optional[str] = None) -> StatusResponse: """Checks API status to see if it is up + Parameters + ---------- + key : `str`, optional + API authentication key. + Returns ------- StatusResponse @@ -91,9 +119,15 @@ def ping(key: Optional[str] = None) -> StatusResponse: def revoke(key: str) -> StatusResponse: """Revoke the API key in use in the event it is compromised. + See :ref:`authentication` for more details. Requires authentication. + Parameters + ---------- + key : str + API authentication key. + Returns ------- StatusResponse From 85c92ddeb67d74f08bfa0395c8fc92a55459eafd Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 5 May 2022 16:17:14 +0200 Subject: [PATCH 70/86] bump version numbers --- docs/source/conf.py | 1 - setup.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 9b6876c..a55926a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -41,7 +41,6 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -add_module_names = False # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. diff --git a/setup.py b/setup.py index b60381e..fc71ee3 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ def get_version(rel_path): ], extras_require={ "dev": [ - "Sphinx==4.2.0", + "Sphinx==4.5.0", "sphinx-rtd-theme==1.0.0" ], "test": [ From 7b3ff9a5a6d22730a17936fd55edb4c2218b91d8 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 00:41:28 +0200 Subject: [PATCH 71/86] update nav docs --- src/flightplandb/nav.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/flightplandb/nav.py b/src/flightplandb/nav.py index 06ecf45..86589e5 100644 --- a/src/flightplandb/nav.py +++ b/src/flightplandb/nav.py @@ -11,6 +11,8 @@ def airport(icao: str, key: Optional[str] = None) -> Airport: ---------- icao : str The airport ICAO to fetch information for + key : `str`, optional + API authentication key. Returns ------- @@ -30,6 +32,11 @@ def airport(icao: str, key: Optional[str] = None) -> Airport: def nats(key: Optional[str] = None) -> List[Track]: """Fetches current North Atlantic Tracks. + Parameters + ---------- + key : `str`, optional + API authentication key. + Returns ------- List[Track] @@ -43,6 +50,11 @@ def nats(key: Optional[str] = None) -> List[Track]: def pacots(key: Optional[str] = None) -> List[Track]: """Fetches current Pacific Organized Track System tracks. + Parameters + ---------- + key : `str`, optional + API authentication key. + Returns ------- List[Track] @@ -67,6 +79,8 @@ def search( Navaid type. Must be either ``None`` (default value, returns all types) or one of :py:obj:`~flightplandb.datatypes.SearchNavaid.validtypes` + key : `str`, optional + API authentication key. Yields ------- From b2646634877a1355a37a3d5b6c548bef7cd0adc6 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 00:42:01 +0200 Subject: [PATCH 72/86] update plan docs --- src/flightplandb/plan.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/flightplandb/plan.py b/src/flightplandb/plan.py index f2e8094..d3ea11e 100644 --- a/src/flightplandb/plan.py +++ b/src/flightplandb/plan.py @@ -23,6 +23,8 @@ def fetch(id_: int, return_format : str The API response format, defaults to ``"native"``. Must be one of the keys in the table at the top of the page. + key : `str`, optional + API authentication key. Returns ------- @@ -61,6 +63,11 @@ def create(plan: Plan, ---------- plan : Plan The Plan object to register on the website + return_format : str + The API response format, defaults to ``"native"``. + Must be one of the keys in the table at the top of the page. + key : `str`, optional + API authentication key. Returns ------- @@ -97,6 +104,11 @@ def edit(plan: Plan, ---------- plan : Plan The new Plan object to replace the old one associated with that ID + return_format : str + The API response format, defaults to ``"native"``. + Must be one of the keys in the table at the top of the page. + key : `str`, optional + API authentication key. Returns ------- @@ -136,6 +148,8 @@ def delete(id_: int, ---------- id\_ : int The ID of the flight plan to delete + key : `str`, optional + API authentication key. Returns ------- @@ -165,13 +179,15 @@ def search(plan_query: PlanQuery, sort: str = "created", ---------- plan_query : PlanQuery A dataclass containing multiple options for plan searches - sort : str, optional + sort : str Sort order to return results in. Valid sort orders are created, updated, popularity, and distance limit : int Maximum number of plans to return, defaults to 100 - include_route : bool, optional + include_route : bool Include route in response, defaults to False + key : `str`, optional + API authentication key. Yields ------- @@ -201,6 +217,8 @@ def has_liked(id_: int, ---------- id\_ : int ID of the flightplan to be checked + key : `str`, optional + API authentication key. Returns ------- @@ -225,6 +243,8 @@ def like(id_: int, ---------- id\_ : int ID of the flightplan to be liked + key : `str`, optional + API authentication key. Returns ------- @@ -252,6 +272,8 @@ def unlike(id_: int, ---------- id\_ : int ID of the flightplan to be unliked + key : `str`, optional + API authentication key. Returns ------- @@ -280,6 +302,10 @@ def generate(gen_query: GenerateQuery, ---------- gen_query : GenerateQuery A dataclass with options for flight plan generation + include_route : bool + Include route in response, defaults to False + key : `str`, optional + API authentication key. Returns ------- @@ -317,6 +343,8 @@ def decode(route: str, (e.g. 06TRA UL851 BEGAR). SID and STAR procedures are not currently supported and will be skipped, along with any other unmatched waypoints. + key : `str`, optional + API authentication key. Returns ------- From 928490075972a0fc645e1868ec8ccbc4a71d9071 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 00:43:11 +0200 Subject: [PATCH 73/86] update tag docs --- src/flightplandb/tags.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/flightplandb/tags.py b/src/flightplandb/tags.py index e9c029b..e40cabf 100644 --- a/src/flightplandb/tags.py +++ b/src/flightplandb/tags.py @@ -8,6 +8,11 @@ def fetch(key: Optional[str] = None) -> List[Tag]: """Fetches current popular tags from all flight plans. Only tags with sufficient popularity are included. + Parameters + ---------- + key : `str`, optional + API authentication key. + Returns ---------- List[Tag] From 81a9d5ed9d6786ffb9e18976c6657027b862fd5c Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 00:45:05 +0200 Subject: [PATCH 74/86] update user api docs --- src/flightplandb/user.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/flightplandb/user.py b/src/flightplandb/user.py index e907ac2..cae3daf 100644 --- a/src/flightplandb/user.py +++ b/src/flightplandb/user.py @@ -9,6 +9,11 @@ def me(key: Optional[str] = None) -> User: Requires authentication. + Parameters + ---------- + key : `str`, optional + API authentication key. + Returns ------- User @@ -31,6 +36,8 @@ def fetch(username: str, key: Optional[str] = None) -> User: ---------- username : str Username of the registered User + key : `str`, optional + API authentication key. Returns ------- @@ -56,11 +63,13 @@ def plans(username: str, sort: str = "created", ---------- username : str Username of the user who created the flight plans - sort : str, optional + sort : str Sort order to return results in. Valid sort orders are created, updated, popularity, and distance limit: int Maximum number of plans to fetch, defaults to ``100`` + key : `str`, optional + API authentication key. Yields ------- @@ -84,11 +93,13 @@ def likes(username: str, sort: str = "created", ---------- username : str Username of the user who liked the flight plans - sort : str, optional + sort : str Sort order to return results in. Valid sort orders are created, updated, popularity, and distance limit : int Maximum number of plans to fetch, defaults to ``100`` + key : `str`, optional + API authentication key. Yields ------- @@ -116,6 +127,8 @@ def search(username: str, Username to search user database for limit : type Maximum number of users to fetch, defaults to ``100`` + key : `str`, optional + API authentication key. Yields ------- From 59ef0417d059374fbb16e53391e4b3705803cbfd Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 00:45:36 +0200 Subject: [PATCH 75/86] update weather docs --- src/flightplandb/weather.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/flightplandb/weather.py b/src/flightplandb/weather.py index c001df5..b3feb1d 100644 --- a/src/flightplandb/weather.py +++ b/src/flightplandb/weather.py @@ -12,6 +12,8 @@ def fetch(icao: str, key: Optional[str] = None) -> Weather: ---------- icao : str ICAO code of the airport for which the weather will be fetched + key : `str`, optional + API authentication key. Returns ------- From 6712cd1e9ec90bd387f54699beafe17304f73306 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 00:58:14 +0200 Subject: [PATCH 76/86] update internal function docs --- src/flightplandb/internal.py | 52 ++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/flightplandb/internal.py b/src/flightplandb/internal.py index d0edce3..139a2e0 100644 --- a/src/flightplandb/internal.py +++ b/src/flightplandb/internal.py @@ -40,8 +40,8 @@ def request(method: str, path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, - key: Optional[str] = None, json_data: Optional[Dict] = None, + key: Optional[str] = None, **kwargs) -> Union[Dict, bytes]: """General HTTP requests function for non-paginated results. @@ -51,14 +51,16 @@ def request(method: str, An HTTP request type. One of GET, POST, PATCH, or DELETE path : str The endpoint's path to which the request is being made - return_format : str, optional + return_format : `str`, optional The API response format, defaults to ``"native"`` - ignore_statuses : Optional[List], optional + ignore_statuses : `List`, optional Statuses (together with 200 OK) which don't raise an HTTPError, defaults to None - params : Optional[Dict], optional + params : `Dict`, optional Any other HTTP request parameters, defaults to None - key : Optional[str] + json_data : `Dict`, optional + Custom JSON data to be formatted into the request body + key : `str` API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. @@ -148,7 +150,7 @@ def get_headers(key: Optional[str] = None) -> CaseInsensitiveDict: Parameters ---------- - key : Optional[str] + key : `str`, optional API token, defaults to None (which makes it unauthenticated) Returns @@ -175,12 +177,12 @@ def get(path: str, return_format="native", The endpoint's path to which the request is being made return_format : str, optional The API response format, defaults to ``"native"`` - ignore_statuses : Optional[List], optional + ignore_statuses : `List`, optional, optional Statuses (together with 200 OK) which don't raise an HTTPError, defaults to None - params : Optional[Dict], optional + params : `Dict`, optional, optional Any other HTTP request parameters, defaults to None - key : Optional[str] + key : `str`, optional API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. @@ -213,8 +215,8 @@ def get(path: str, return_format="native", def post(path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, - key: Optional[str] = None, json_data: Optional[Dict] = None, + key: Optional[str] = None, **kwargs) -> Union[Dict, bytes]: """Calls :meth:`request()` for post requests. @@ -224,12 +226,14 @@ def post(path: str, return_format="native", The endpoint's path to which the request is being made return_format : str, optional The API response format, defaults to ``"native"`` - ignore_statuses : Optional[List], optional + ignore_statuses : `List`, optional, optional Statuses (together with 200 OK) which don't raise an HTTPError, defaults to None - params : Optional[Dict], optional + params : `Dict`, optional, optional Any other HTTP request parameters, defaults to None - key : Optional[str] + json_data : `Dict`, optional + Custom JSON data to be formatted into the request body + key : `str`, optional API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. @@ -261,8 +265,8 @@ def post(path: str, return_format="native", def patch(path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, - key: Optional[str] = None, json_data: Optional[Dict] = None, + key: Optional[str] = None, **kwargs) -> Union[Dict, bytes]: """Calls :meth:`request()` for patch requests. @@ -272,12 +276,14 @@ def patch(path: str, return_format="native", The endpoint's path to which the request is being made return_format : str, optional The API response format, defaults to ``"native"`` - ignore_statuses : Optional[List], optional + ignore_statuses : `List`, optional, optional Statuses (together with 200 OK) which don't raise an HTTPError, defaults to None - params : Optional[Dict], optional + params : `Dict`, optional, optional Any other HTTP request parameters, defaults to None - key : Optional[str] + json_data : `Dict`, optional + Custom JSON data to be formatted into the request body + key : `str`, optional API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. @@ -320,12 +326,12 @@ def delete(path: str, return_format="native", The endpoint's path to which the request is being made return_format : str, optional The API response format, defaults to ``"native"`` - ignore_statuses : Optional[List], optional + ignore_statuses : `List`, optional, optional Statuses (together with 200 OK) which don't raise an HTTPError, defaults to None - params : Optional[Dict], optional + params : `Dict`, optional, optional Any other HTTP request parameters, defaults to None - key : Optional[str] + key : `str`, optional API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. @@ -372,12 +378,12 @@ def getiter(path: str, sort : str, optional Sort order to return results in. Valid sort orders are created, updated, popularity, and distance - ignore_statuses : Optional[List], optional + ignore_statuses : `List`, optional, optional Statuses (together with 200 OK) which don't raise an HTTPError, defaults to None - params : Optional[Dict], optional + params : `Dict`, optional, optional Any other HTTP request parameters, defaults to None - key : Optional[str] + key : `str`, optional API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. From cb91d15d506224adc0b80bc1b157f452dabddab4 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 01:02:12 +0200 Subject: [PATCH 77/86] fix internal function docs formatting --- src/flightplandb/internal.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/flightplandb/internal.py b/src/flightplandb/internal.py index 139a2e0..53cad11 100644 --- a/src/flightplandb/internal.py +++ b/src/flightplandb/internal.py @@ -60,7 +60,7 @@ def request(method: str, Any other HTTP request parameters, defaults to None json_data : `Dict`, optional Custom JSON data to be formatted into the request body - key : `str` + key : str API token, defaults to None (which makes it unauthenticated) *args Variable length argument list. @@ -175,12 +175,12 @@ def get(path: str, return_format="native", ---------- path : str The endpoint's path to which the request is being made - return_format : str, optional + return_format : `str`, optional The API response format, defaults to ``"native"`` - ignore_statuses : `List`, optional, optional + ignore_statuses : `List`, optional Statuses (together with 200 OK) which don't raise an HTTPError, defaults to None - params : `Dict`, optional, optional + params : `Dict`, optional Any other HTTP request parameters, defaults to None key : `str`, optional API token, defaults to None (which makes it unauthenticated) @@ -224,12 +224,12 @@ def post(path: str, return_format="native", ---------- path : str The endpoint's path to which the request is being made - return_format : str, optional + return_format : `str`, optional The API response format, defaults to ``"native"`` - ignore_statuses : `List`, optional, optional + ignore_statuses : `List`, optional Statuses (together with 200 OK) which don't raise an HTTPError, defaults to None - params : `Dict`, optional, optional + params : `Dict`, optional Any other HTTP request parameters, defaults to None json_data : `Dict`, optional Custom JSON data to be formatted into the request body @@ -274,12 +274,12 @@ def patch(path: str, return_format="native", ---------- path : str The endpoint's path to which the request is being made - return_format : str, optional + return_format : `str`, optional The API response format, defaults to ``"native"`` - ignore_statuses : `List`, optional, optional + ignore_statuses : `List`, optional Statuses (together with 200 OK) which don't raise an HTTPError, defaults to None - params : `Dict`, optional, optional + params : `Dict`, optional Any other HTTP request parameters, defaults to None json_data : `Dict`, optional Custom JSON data to be formatted into the request body @@ -324,12 +324,12 @@ def delete(path: str, return_format="native", ---------- path : str The endpoint's path to which the request is being made - return_format : str, optional + return_format : `str`, optional The API response format, defaults to ``"native"`` - ignore_statuses : `List`, optional, optional + ignore_statuses : `List`, optional Statuses (together with 200 OK) which don't raise an HTTPError, defaults to None - params : `Dict`, optional, optional + params : `Dict`, optional Any other HTTP request parameters, defaults to None key : `str`, optional API token, defaults to None (which makes it unauthenticated) @@ -375,13 +375,13 @@ def getiter(path: str, The endpoint's path to which the request is being made limit : int, optional Maximum number of results to return, defaults to 100 - sort : str, optional + sort : `str`, optional Sort order to return results in. Valid sort orders are created, updated, popularity, and distance - ignore_statuses : `List`, optional, optional + ignore_statuses : `List`, optional Statuses (together with 200 OK) which don't raise an HTTPError, defaults to None - params : `Dict`, optional, optional + params : `Dict`, optional Any other HTTP request parameters, defaults to None key : `str`, optional API token, defaults to None (which makes it unauthenticated) From 94911b47d3fbf41ebe74a7396b752cc55e0fe33b Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 01:07:46 +0200 Subject: [PATCH 78/86] add version field for readthedocs --- docs/source/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index a55926a..1fdb685 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,6 +22,8 @@ # The full version, including alpha/beta/rc tags release = '0.6.0' +# readthedocs.io insists on the version field being filled for epub builds +version = release # -- General configuration --------------------------------------------------- From 534ba997475f62d5eb3c2b4c236977e638edb0fc Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 01:13:24 +0200 Subject: [PATCH 79/86] fix incorrect type for nav search --- src/flightplandb/nav.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/flightplandb/nav.py b/src/flightplandb/nav.py index 86589e5..cb3f77c 100644 --- a/src/flightplandb/nav.py +++ b/src/flightplandb/nav.py @@ -67,7 +67,7 @@ def pacots(key: Optional[str] = None) -> List[Track]: def search( query: str, - type_: str = None, key: Optional[str] = None + type_: Optional[str] = None, key: Optional[str] = None ) -> Generator[SearchNavaid, None, None]: r"""Searches navaids using a query. @@ -75,7 +75,7 @@ def search( ---------- query : str The search query. Searches the navaid identifier and navaid name - type\_ : str + type\_ : `str`, optional Navaid type. Must be either ``None`` (default value, returns all types) or one of :py:obj:`~flightplandb.datatypes.SearchNavaid.validtypes` From 4ce84a345150acdb0475728bc97d877d65c97dac Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 01:24:52 +0200 Subject: [PATCH 80/86] add full stop to package description --- src/flightplandb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flightplandb/__init__.py b/src/flightplandb/__init__.py index 8bb99a8..475ea41 100644 --- a/src/flightplandb/__init__.py +++ b/src/flightplandb/__init__.py @@ -5,7 +5,7 @@ flight simulation. For more information on Flight Plan Database, see their excellent About page at https://flightplandatabase.com/about. For more information about this -library, check out the documentation at https://flightplandb-py.readthedocs.io/ +library, check out the documentation at https://flightplandb-py.readthedocs.io/. """ From 6ee5b44aa1812d626567f5d4030b31cd4cedf8a9 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 01:25:51 +0200 Subject: [PATCH 81/86] change prosy table references to reference link --- docs/source/api/plan.rst | 3 ++- src/flightplandb/plan.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/source/api/plan.rst b/docs/source/api/plan.rst index e7d5722..3528df6 100644 --- a/docs/source/api/plan.rst +++ b/docs/source/api/plan.rst @@ -1,7 +1,8 @@ Plan ==================== -.. csv-table:: Permitted plan return types +.. _permitted-return-types: +.. csv-table:: Table of permitted plan return types :header: "Plan format", "Key" :widths: 25, 10 diff --git a/src/flightplandb/plan.py b/src/flightplandb/plan.py index d3ea11e..b021912 100644 --- a/src/flightplandb/plan.py +++ b/src/flightplandb/plan.py @@ -22,7 +22,7 @@ def fetch(id_: int, The ID of the flight plan to fetch return_format : str The API response format, defaults to ``"native"``. - Must be one of the keys in the table at the top of the page. + Must be one of the keys in the :ref:`permitted-return-types`. key : `str`, optional API authentication key. @@ -65,7 +65,7 @@ def create(plan: Plan, The Plan object to register on the website return_format : str The API response format, defaults to ``"native"``. - Must be one of the keys in the table at the top of the page. + Must be one of the keys in the :ref:`permitted-return-types`. key : `str`, optional API authentication key. @@ -106,7 +106,7 @@ def edit(plan: Plan, The new Plan object to replace the old one associated with that ID return_format : str The API response format, defaults to ``"native"``. - Must be one of the keys in the table at the top of the page. + Must be one of the keys in the :ref:`permitted-return-types`. key : `str`, optional API authentication key. From baac8710363f8ac83c82566ac1d5812dbe086e1c Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 01:39:44 +0200 Subject: [PATCH 82/86] remove outdated args from docstrings --- src/flightplandb/internal.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/flightplandb/internal.py b/src/flightplandb/internal.py index 53cad11..5495df9 100644 --- a/src/flightplandb/internal.py +++ b/src/flightplandb/internal.py @@ -62,8 +62,6 @@ def request(method: str, Custom JSON data to be formatted into the request body key : str API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. **kwargs Arbitrary keyword arguments. @@ -184,8 +182,6 @@ def get(path: str, return_format="native", Any other HTTP request parameters, defaults to None key : `str`, optional API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. **kwargs Arbitrary keyword arguments. @@ -235,8 +231,6 @@ def post(path: str, return_format="native", Custom JSON data to be formatted into the request body key : `str`, optional API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. **kwargs Arbitrary keyword arguments. @@ -285,8 +279,6 @@ def patch(path: str, return_format="native", Custom JSON data to be formatted into the request body key : `str`, optional API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. **kwargs Arbitrary keyword arguments. @@ -333,8 +325,6 @@ def delete(path: str, return_format="native", Any other HTTP request parameters, defaults to None key : `str`, optional API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. **kwargs Arbitrary keyword arguments. @@ -385,8 +375,6 @@ def getiter(path: str, Any other HTTP request parameters, defaults to None key : `str`, optional API token, defaults to None (which makes it unauthenticated) - *args - Variable length argument list. **kwargs Arbitrary keyword arguments. From c819be643f83d88d2b5c83f0cc0b7e9e95e58333 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 17:12:53 +0200 Subject: [PATCH 83/86] replace implicit by explicit kwargs --- src/flightplandb/internal.py | 51 +++++++++--------------------------- src/flightplandb/nav.py | 2 +- src/flightplandb/plan.py | 4 +-- 3 files changed, 16 insertions(+), 41 deletions(-) diff --git a/src/flightplandb/internal.py b/src/flightplandb/internal.py index 5495df9..30b1cec 100644 --- a/src/flightplandb/internal.py +++ b/src/flightplandb/internal.py @@ -41,8 +41,7 @@ def request(method: str, ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, json_data: Optional[Dict] = None, - key: Optional[str] = None, - **kwargs) -> Union[Dict, bytes]: + key: Optional[str] = None) -> Union[Dict, bytes]: """General HTTP requests function for non-paginated results. Parameters @@ -62,8 +61,6 @@ def request(method: str, Custom JSON data to be formatted into the request body key : str API token, defaults to None (which makes it unauthenticated) - **kwargs - Arbitrary keyword arguments. Returns ------- @@ -129,8 +126,7 @@ def request(method: str, url=urljoin(url_base, path), auth=HTTPBasicAuth(key, None), headers=params, - json=json_data, - **kwargs) + json=json_data) status_handler(resp.status_code, ignore_statuses) @@ -165,8 +161,7 @@ def get_headers(key: Optional[str] = None) -> CaseInsensitiveDict: def get(path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, - key: Optional[str] = None, - **kwargs) -> Union[Dict, bytes]: + key: Optional[str] = None) -> Union[Dict, bytes]: """Calls :meth:`request()` for get requests. Parameters @@ -182,8 +177,6 @@ def get(path: str, return_format="native", Any other HTTP request parameters, defaults to None key : `str`, optional API token, defaults to None (which makes it unauthenticated) - **kwargs - Arbitrary keyword arguments. Returns ------- @@ -203,8 +196,7 @@ def get(path: str, return_format="native", return_format=return_format, ignore_statuses=ignore_statuses, params=params, - key=key, - **kwargs) + key=key) return resp @@ -212,8 +204,7 @@ def post(path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, json_data: Optional[Dict] = None, - key: Optional[str] = None, - **kwargs) -> Union[Dict, bytes]: + key: Optional[str] = None) -> Union[Dict, bytes]: """Calls :meth:`request()` for post requests. Parameters @@ -231,8 +222,6 @@ def post(path: str, return_format="native", Custom JSON data to be formatted into the request body key : `str`, optional API token, defaults to None (which makes it unauthenticated) - **kwargs - Arbitrary keyword arguments. Returns ------- @@ -251,8 +240,7 @@ def post(path: str, return_format="native", ignore_statuses=ignore_statuses, params=params, json_data=json_data, - key=key, - **kwargs) + key=key) return resp @@ -260,8 +248,7 @@ def patch(path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, json_data: Optional[Dict] = None, - key: Optional[str] = None, - **kwargs) -> Union[Dict, bytes]: + key: Optional[str] = None) -> Union[Dict, bytes]: """Calls :meth:`request()` for patch requests. Parameters @@ -279,8 +266,6 @@ def patch(path: str, return_format="native", Custom JSON data to be formatted into the request body key : `str`, optional API token, defaults to None (which makes it unauthenticated) - **kwargs - Arbitrary keyword arguments. Returns ------- @@ -300,16 +285,14 @@ def patch(path: str, return_format="native", ignore_statuses=ignore_statuses, params=params, key=key, - json_data=json_data, - **kwargs) + json_data=json_data) return resp def delete(path: str, return_format="native", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, - key: Optional[str] = None, - **kwargs) -> Union[Dict, bytes]: + key: Optional[str] = None) -> Union[Dict, bytes]: """Calls :meth:`request()` for delete requests. Parameters @@ -325,8 +308,6 @@ def delete(path: str, return_format="native", Any other HTTP request parameters, defaults to None key : `str`, optional API token, defaults to None (which makes it unauthenticated) - **kwargs - Arbitrary keyword arguments. Returns ------- @@ -345,8 +326,7 @@ def delete(path: str, return_format="native", return_format=return_format, ignore_statuses=ignore_statuses, params=params, - key=key, - **kwargs) + key=key) return resp @@ -355,8 +335,7 @@ def getiter(path: str, sort: str = "created", ignore_statuses: Optional[List] = None, params: Optional[Dict] = None, - key: Optional[str] = None, - **kwargs) -> Generator[Dict, None, None]: + key: Optional[str] = None) -> Generator[Dict, None, None]: """Get :meth:`request()` for paginated results. Parameters @@ -375,8 +354,6 @@ def getiter(path: str, Any other HTTP request parameters, defaults to None key : `str`, optional API token, defaults to None (which makes it unauthenticated) - **kwargs - Arbitrary keyword arguments. Returns ------- @@ -411,8 +388,7 @@ def getiter(path: str, r_fpdb = session.get( url=url, params=params, - auth=auth, - **kwargs) + auth=auth) status_handler(r_fpdb.status_code, ignore_statuses) # I detest responses which "may" be paginated @@ -428,8 +404,7 @@ def getiter(path: str, params['page'] = page r_fpdb = session.get(url=url, params=params, - auth=auth, - **kwargs) + auth=auth) status_handler(r_fpdb.status_code, ignore_statuses) # ...keep cycling through pages... for i in r_fpdb.json(): diff --git a/src/flightplandb/nav.py b/src/flightplandb/nav.py index cb3f77c..f7eaa1e 100644 --- a/src/flightplandb/nav.py +++ b/src/flightplandb/nav.py @@ -44,7 +44,7 @@ def nats(key: Optional[str] = None) -> List[Track]: """ return list( - map(lambda n: Track(**n), internal.get("/nav/NATS", key=key))) + map(lambda n: Track(**n), internal.get(path="/nav/NATS", key=key))) def pacots(key: Optional[str] = None) -> List[Track]: diff --git a/src/flightplandb/plan.py b/src/flightplandb/plan.py index b021912..a63ff2c 100644 --- a/src/flightplandb/plan.py +++ b/src/flightplandb/plan.py @@ -84,7 +84,7 @@ def create(plan: Plan, request = internal.post( path="/plan/", return_format=return_format, - json=plan.to_api_dict(), + json_data=plan.to_api_dict(), key=key) if return_format == "native": @@ -129,7 +129,7 @@ def edit(plan: Plan, request = internal.patch( path=f"/plan/{plan_data['id']}", return_format=return_format, - json=plan_data, + json_data=plan_data, key=key) if return_format == "native": From c16107d0caea0c01bbbf67c7b18cdbbfb6b8a341 Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 17:20:04 +0200 Subject: [PATCH 84/86] fix plan tests --- tests/test_plan.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_plan.py b/tests/test_plan.py index 9553dcd..3f2d60e 100644 --- a/tests/test_plan.py +++ b/tests/test_plan.py @@ -161,7 +161,7 @@ def test_plan_create(mocker): "via": None})])) correct_call = { 'path': '/plan/', - 'json': { + 'json_data': { 'id': None, 'fromICAO': 'EHAM', 'toICAO': 'KJFK', @@ -206,7 +206,7 @@ def test_plan_create(mocker): 'key': None } - def patched_post(path, return_format, json, key): + def patched_post(path, return_format, json_data, key): return json_response mocker.patch( @@ -336,7 +336,7 @@ def test_plan_edit(mocker): ) correct_call = { 'path': '/plan/23896', - 'json': { + 'json_data': { 'id': 23896, 'fromICAO': 'EHAM', 'toICAO': 'KJFK', @@ -381,7 +381,7 @@ def test_plan_edit(mocker): 'key': None } - def patched_patch(path, return_format, json, key): + def patched_patch(path, return_format, json_data, key): return json_response mocker.patch( From b6b418f30ece7c94697c61a195d30bcd75d115bd Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Sun, 8 May 2022 18:20:42 +0200 Subject: [PATCH 85/86] update readme --- README.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/README.md b/README.md index 8c4ef2a..5af86f1 100644 --- a/README.md +++ b/README.md @@ -14,20 +14,9 @@ This is a Python 3 wrapper for the [Flight Plan Database API](https://flightplan For more information on Flight Plan Database, see their excellent [About page](https://flightplandatabase.com/about). -## API -All the API commands can be called via the `flightplandb.FlightPlanDB` class. -It is organized into subsections as specified in the docs; i.e. `FlightPlanDB.plan` has all the plan API commands. - -## Data Types -All datatypes can be found in `flightplandb.datatypes`. - -## Exceptions -All exceptions can be found in `flightplandb.exceptions`. - ## Documentation The documentation for this library can be found on readthedocs.io [here](https://flightplandb-py.readthedocs.io/). ## Installation The library can be installed from PyPi using `pip install flightplandb`; -the installation page link is [here](https://pypi.org/project/flightplandb/). For more details on -installing bleeding-edge versions, see the Installation section of the documentation. +the PyPi installation page link is [here](https://pypi.org/project/flightplandb/). For more details, see the Installation section of the documentation. From 871d1da0ac51890b438580bfdd1f6fc67df1c1cc Mon Sep 17 00:00:00 2001 From: PH-KDX Date: Thu, 19 May 2022 21:59:02 +0200 Subject: [PATCH 86/86] add changelog, shorten lines in init --- README.md | 3 +++ docs/source/user/changelog.rst | 9 +++++++++ docs/source/user/userdocs.rst | 1 + src/flightplandb/__init__.py | 4 ++-- 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 docs/source/user/changelog.rst diff --git a/README.md b/README.md index 5af86f1..f13b4c6 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,6 @@ The documentation for this library can be found on readthedocs.io [here](https:/ ## Installation The library can be installed from PyPi using `pip install flightplandb`; the PyPi installation page link is [here](https://pypi.org/project/flightplandb/). For more details, see the Installation section of the documentation. + +## Changelog +Before upgrading to a newer version, please read the appropriate changelog for your upgrade [here](https://flightplandb-py.readthedocs.io/en/stable/user/changelog.html). \ No newline at end of file diff --git a/docs/source/user/changelog.rst b/docs/source/user/changelog.rst new file mode 100644 index 0000000..df73745 --- /dev/null +++ b/docs/source/user/changelog.rst @@ -0,0 +1,9 @@ +Changelog +-------------------- + +0.6.0 +^^^^^^^^^^^^^^^^^^^^ +This is a complete rewrite of the library, which moves functions out of classes. +This does have the side effect of requiring a key to be passed into every authenticated request, +instead of being passed into a class once on initialisation. The rewrite also incorporates +several small bugfixes, and changes the test environment from unittest to pytest. \ No newline at end of file diff --git a/docs/source/user/userdocs.rst b/docs/source/user/userdocs.rst index 646b510..3e7c62c 100644 --- a/docs/source/user/userdocs.rst +++ b/docs/source/user/userdocs.rst @@ -7,3 +7,4 @@ User docs introduction quickstart + changelog diff --git a/src/flightplandb/__init__.py b/src/flightplandb/__init__.py index 475ea41..a8272fd 100644 --- a/src/flightplandb/__init__.py +++ b/src/flightplandb/__init__.py @@ -3,9 +3,9 @@ This is a Python 3 wrapper for the Flight Plan Database API. Flight Plan Database is a website for creating and sharing flight plans for use in flight simulation. -For more information on Flight Plan Database, see their excellent About page +For more information on Flight Plan Database, read their excellent About page at https://flightplandatabase.com/about. For more information about this -library, check out the documentation at https://flightplandb-py.readthedocs.io/. +library, read the documentation at https://flightplandb-py.readthedocs.io/. """