From dea297c66d1e2ec51c454b99c4c1c91c2409a0f2 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Wed, 8 May 2024 14:55:31 +0200 Subject: [PATCH 01/22] fixed typo in requests package --- shell.nix | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shell.nix b/shell.nix index 47598f8..5156546 100644 --- a/shell.nix +++ b/shell.nix @@ -8,7 +8,8 @@ in pkgs.mkShell { # Python 3.12 + packages (pkgs.python312.withPackages (python-pkgs: [ - python-pkgs.requestsn + python-pkgs.requests + ])) ]; -} +} \ No newline at end of file From 1fe139842ce0c0c6bdfa82da04e7d2293d436578 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Wed, 8 May 2024 14:56:19 +0200 Subject: [PATCH 02/22] added vscode --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5bb096d..2724747 100644 --- a/.gitignore +++ b/.gitignore @@ -159,6 +159,9 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +.vscode + +# Secrets secrets/ # Sample flows to generate Swagger API -flows/ \ No newline at end of file +flows/ From 5fbd1944a52e4a9a96532c7fd6b8d23e17d921b0 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Wed, 8 May 2024 14:57:55 +0200 Subject: [PATCH 03/22] some description --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2724747..03992c0 100644 --- a/.gitignore +++ b/.gitignore @@ -159,6 +159,7 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +# Visual Studio Code config .vscode # Secrets From f04e9048447437dec8bbd71985da92e65344fe50 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Wed, 8 May 2024 15:02:42 +0200 Subject: [PATCH 04/22] Added methods and validation for explorer endpoint --- src/pyventim/public.py | 70 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/pyventim/public.py b/src/pyventim/public.py index 09c09fa..6f3359d 100644 --- a/src/pyventim/public.py +++ b/src/pyventim/public.py @@ -1,3 +1,69 @@ +import requests +from typing import Dict +import pprint -def add_one(number): - return number + 1 \ No newline at end of file + +class EventimExploration: + def __init__(self, session: requests.Session = None): + # If a valid session is not provided by the user create a new one. + if not isinstance(session, requests.Session): + self.session = requests.Session() + else: + self.session = session + + self.endpoint = ( + "https://public-api.eventim.com/websearch/search/api/exploration" + ) + + def _validate_search_term(self, search_term: str): + if len(search_term) == 0: + raise ValueError(f"search_term must have atleast one character...") + + + def attrations(self, search_term: str, page: int = 1) -> Dict: + self._validate_search_term(search_term) + + r: requests.Response = self.session.get( + f"{self.endpoint}/v1/attractions", + params={"search_term": search_term, "page": page}, + ) + r.raise_for_status() + return r.json() + + def content(self, search_term: str, page: int = 1): + self._validate_search_term(search_term) + + r: requests.Response = self.session.get( + f"{self.endpoint}/v1/content", + params={"search_term": search_term, "page": page}, + ) + r.raise_for_status() + return r.json() + + def locations(self, search_term: str, page: int = 1): + self._validate_search_term(search_term) + r: requests.Response = self.session.get( + f"{self.endpoint}/v1/locations", + params={"search_term": search_term, "page": page}, + ) + r.raise_for_status() + return r.json() + + def product_groups(self, search_term: str, page: int = 1): + self._validate_search_term(search_term) + r: requests.Response = self.session.get( + f"{self.endpoint}/v2/productGroups", + params={"search_term": search_term, "page": page}, + ) + + r.raise_for_status() + return r.json() + +if __name__ == "__main__": + exp = EventimExploration() + search_term = "Disneys Der König der Löwen" + + pprint.pprint(exp.attrations(search_term=search_term)) + pprint.pprint(exp.content(search_term=search_term)) + pprint.pprint(exp.locations(search_term=search_term)) + pprint.pprint(exp.product_groups(search_term=search_term)) From fd7173913cbac8939f80e6f81d0c85c93ff31286 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Wed, 8 May 2024 16:14:59 +0200 Subject: [PATCH 05/22] changed validation to actually validate a search word to two characters --- src/pyventim/public.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pyventim/public.py b/src/pyventim/public.py index 6f3359d..609a5b1 100644 --- a/src/pyventim/public.py +++ b/src/pyventim/public.py @@ -15,9 +15,9 @@ def __init__(self, session: requests.Session = None): "https://public-api.eventim.com/websearch/search/api/exploration" ) - def _validate_search_term(self, search_term: str): - if len(search_term) == 0: - raise ValueError(f"search_term must have atleast one character...") + def _validate_search_term(self, search_term: str) -> None: + if len(search_term) < 2: + raise ValueError(f"search_term must have atleast two characters...") def attrations(self, search_term: str, page: int = 1) -> Dict: From d71b4950b25f2c3e4ad93c8c306d89f6779de579 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Wed, 8 May 2024 16:15:17 +0200 Subject: [PATCH 06/22] code cleanup and typos fixed --- src/pyventim/public.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/pyventim/public.py b/src/pyventim/public.py index 609a5b1..b1a54eb 100644 --- a/src/pyventim/public.py +++ b/src/pyventim/public.py @@ -4,7 +4,7 @@ class EventimExploration: - def __init__(self, session: requests.Session = None): + def __init__(self, session: requests.Session = None) -> None: # If a valid session is not provided by the user create a new one. if not isinstance(session, requests.Session): self.session = requests.Session() @@ -20,7 +20,7 @@ def _validate_search_term(self, search_term: str) -> None: raise ValueError(f"search_term must have atleast two characters...") - def attrations(self, search_term: str, page: int = 1) -> Dict: + def attractions(self, search_term: str, page: int = 1) -> Dict: self._validate_search_term(search_term) r: requests.Response = self.session.get( @@ -57,13 +57,4 @@ def product_groups(self, search_term: str, page: int = 1): ) r.raise_for_status() - return r.json() - -if __name__ == "__main__": - exp = EventimExploration() - search_term = "Disneys Der König der Löwen" - - pprint.pprint(exp.attrations(search_term=search_term)) - pprint.pprint(exp.content(search_term=search_term)) - pprint.pprint(exp.locations(search_term=search_term)) - pprint.pprint(exp.product_groups(search_term=search_term)) + return r.json() \ No newline at end of file From e903c0e65ec52cc746cf1e91624bec488ab12998 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Wed, 8 May 2024 16:15:36 +0200 Subject: [PATCH 07/22] Changed readme to reflect more the intention of this project --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a5d8ea8..5b3a2a4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # pyventim -Python module to fetch a reverse engineered Eventim API + +Python wrapper to fetch a reverse engineered Eventim API From ba4a186ce3f9d7cf6896b8a40cab105639ffefb6 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Wed, 8 May 2024 16:15:53 +0200 Subject: [PATCH 08/22] added python tests --- pyproject.toml | 16 ++++++++++++-- shell.nix | 2 +- tests/__init__.py | 0 tests/test_public_explorer.py | 39 +++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/test_public_explorer.py diff --git a/pyproject.toml b/pyproject.toml index fa0d1aa..1eb9768 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "0.0.1" authors = [ { name="Kilian Braun", email="hello@kilianbraun.de" }, ] -description = "A small example package" +description = "Python wrapper to fetch a reverse engineered Eventim API." readme = "README.md" requires-python = ">=3.12" classifiers = [ @@ -19,4 +19,16 @@ classifiers = [ [project.urls] Homepage = "https://github.com/kggx/pyventim" -Issues = "https://github.com/kggx/pyventim/issues" \ No newline at end of file +Issues = "https://github.com/kggx/pyventim/issues" + + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = [ + "--import-mode=importlib", +] +pythonpath = "src" +# addopts = "-ra -q" +testpaths = [ + "tests" +] diff --git a/shell.nix b/shell.nix index 5156546..ddce2bd 100644 --- a/shell.nix +++ b/shell.nix @@ -9,7 +9,7 @@ in pkgs.mkShell { # Python 3.12 + packages (pkgs.python312.withPackages (python-pkgs: [ python-pkgs.requests - + python-pkgs.pytest ])) ]; } \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_public_explorer.py b/tests/test_public_explorer.py new file mode 100644 index 0000000..7bfcfd9 --- /dev/null +++ b/tests/test_public_explorer.py @@ -0,0 +1,39 @@ +from pyventim.public import EventimExploration + +explorer: EventimExploration = EventimExploration() +search_term:str = "Sleep" + +def test_validate_search_term(): + # Check if validation passes for a invalid search term + try: + explorer._validate_search_term(search_term[0]) + except ValueError: + assert(True) + + # Validate if given a valid word it should return nothing + assert(explorer._validate_search_term(search_term) is None) + +def test_content(): + # We check for needed keys + needed_keys = ['content', 'results', 'totalResults', 'page', 'totalPages', '_links'] + json = explorer.content(search_term) + assert(x in needed_keys for x in json.keys()) + +def test_attractions(): + # We check for needed keys + needed_keys = ['attractions', 'results', 'totalResults', 'page', 'totalPages', '_links'] + json = explorer.attractions(search_term) + assert(x in needed_keys for x in json.keys()) + +def test_locations(): + # We check for needed keys + needed_keys = ['locations', 'results', 'totalResults', 'page', 'totalPages', '_links'] + json = explorer.locations(search_term) + assert(x in needed_keys for x in json.keys()) + + +def test_product_groups(): + # We check for needed keys + needed_keys = ['productGroups', 'results', 'totalResults', 'page', 'totalPages', '_links'] + json = explorer.product_groups(search_term) + assert(x in needed_keys for x in json.keys()) From 2517a3dcedb08c6c68bc0306ecbefe891fcc4547 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Wed, 8 May 2024 16:22:23 +0200 Subject: [PATCH 09/22] added code documentation --- src/pyventim/public.py | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/pyventim/public.py b/src/pyventim/public.py index b1a54eb..75719c9 100644 --- a/src/pyventim/public.py +++ b/src/pyventim/public.py @@ -16,11 +16,28 @@ def __init__(self, session: requests.Session = None) -> None: ) def _validate_search_term(self, search_term: str) -> None: + """Function validates an input string. Should only be used internally. + + Args: + search_term (str): Search term to check + + Raises: + ValueError: If the search_term is not valid then raise an error. + """ if len(search_term) < 2: raise ValueError(f"search_term must have atleast two characters...") def attractions(self, search_term: str, page: int = 1) -> Dict: + """This function returns a requested page for the given search_term of the attractions endpoint. + + Args: + search_term (str): Search term to be querried. + page (int, optional): Page number to fetch. Defaults to 1. + + Returns: + Dict: Returns the requested page with meta data. + """ self._validate_search_term(search_term) r: requests.Response = self.session.get( @@ -31,6 +48,15 @@ def attractions(self, search_term: str, page: int = 1) -> Dict: return r.json() def content(self, search_term: str, page: int = 1): + """This function returns a requested page for the given search_term of the content endpoint. + + Args: + search_term (str): Search term to be querried. + page (int, optional): Page number to fetch. Defaults to 1. + + Returns: + Dict: Returns the requested page with meta data. + """ self._validate_search_term(search_term) r: requests.Response = self.session.get( @@ -41,6 +67,15 @@ def content(self, search_term: str, page: int = 1): return r.json() def locations(self, search_term: str, page: int = 1): + """This function returns a requested page for the given search_term of the locations endpoint. + + Args: + search_term (str): Search term to be querried. + page (int, optional): Page number to fetch. Defaults to 1. + + Returns: + Dict: Returns the requested page with meta data. + """ self._validate_search_term(search_term) r: requests.Response = self.session.get( f"{self.endpoint}/v1/locations", @@ -50,6 +85,15 @@ def locations(self, search_term: str, page: int = 1): return r.json() def product_groups(self, search_term: str, page: int = 1): + """This function returns a requested page for the given search_term of the product_groups endpoint. + + Args: + search_term (str): Search term to be querried. + page (int, optional): Page number to fetch. Defaults to 1. + + Returns: + Dict: Returns the requested page with meta data. + """ self._validate_search_term(search_term) r: requests.Response = self.session.get( f"{self.endpoint}/v2/productGroups", From 147b710cddff746284449a130fa3aee52ab21368 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Fri, 10 May 2024 10:45:38 +0200 Subject: [PATCH 10/22] Removed reverse engineering dependencies from project --- shell.nix | 4 ---- 1 file changed, 4 deletions(-) diff --git a/shell.nix b/shell.nix index ddce2bd..afa1db9 100644 --- a/shell.nix +++ b/shell.nix @@ -2,10 +2,6 @@ let pkgs = import {}; in pkgs.mkShell { packages = [ - # MITM Proxy to reverse engineer apis - pkgs.mitmproxy - pkgs.mitmproxy2swagger - # Python 3.12 + packages (pkgs.python312.withPackages (python-pkgs: [ python-pkgs.requests From 9f3f073971b6b88674cbfd40d889435b294f6c4b Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 08:43:07 +0200 Subject: [PATCH 11/22] added some files created for debugging --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 03992c0..28057e6 100644 --- a/.gitignore +++ b/.gitignore @@ -166,3 +166,9 @@ cython_debug/ secrets/ # Sample flows to generate Swagger API flows/ +frida-hooks/ + +temp/ + +debug.py +debug.json \ No newline at end of file From a2bedf00b527901f4fda604f29760b039291f50e Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 08:43:37 +0200 Subject: [PATCH 12/22] removed orphan addops --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1eb9768..e11e6bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,6 @@ addopts = [ "--import-mode=importlib", ] pythonpath = "src" -# addopts = "-ra -q" testpaths = [ "tests" ] From 69f4e926b8ec63a1224b3692b58dc5223294d128 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 08:44:06 +0200 Subject: [PATCH 13/22] removed old validation fucntion --- src/pyventim/public.py | 26 +++++++++++++------------- tests/test_public_explorer.py | 12 +----------- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/pyventim/public.py b/src/pyventim/public.py index 75719c9..4516ceb 100644 --- a/src/pyventim/public.py +++ b/src/pyventim/public.py @@ -15,20 +15,20 @@ def __init__(self, session: requests.Session = None) -> None: "https://public-api.eventim.com/websearch/search/api/exploration" ) - def _validate_search_term(self, search_term: str) -> None: - """Function validates an input string. Should only be used internally. - - Args: - search_term (str): Search term to check - - Raises: - ValueError: If the search_term is not valid then raise an error. - """ - if len(search_term) < 2: - raise ValueError(f"search_term must have atleast two characters...") - + def _validate_query(self, query_parameters:Dict[str, Any]) -> bool: + # TODO: Docs + parameters = query_parameters.keys() + if "search_term" not in parameters and "categories" not in parameters and "city_ids" not in parameters: + raise ValueError('Must have search_term, categories or city_ids in the query parameters') + + if query_parameters.get('search_term'): + if len(search_term) < 2: + raise ValueError(f"search_term must have atleast two characters...") + + + return True - def attractions(self, search_term: str, page: int = 1) -> Dict: + def explore_attractions(self, search_term: str, page: int = 1) -> Dict: """This function returns a requested page for the given search_term of the attractions endpoint. Args: diff --git a/tests/test_public_explorer.py b/tests/test_public_explorer.py index 7bfcfd9..605d271 100644 --- a/tests/test_public_explorer.py +++ b/tests/test_public_explorer.py @@ -2,18 +2,8 @@ explorer: EventimExploration = EventimExploration() search_term:str = "Sleep" - -def test_validate_search_term(): - # Check if validation passes for a invalid search term - try: - explorer._validate_search_term(search_term[0]) - except ValueError: - assert(True) - - # Validate if given a valid word it should return nothing - assert(explorer._validate_search_term(search_term) is None) -def test_content(): +def test_explore_content(): # We check for needed keys needed_keys = ['content', 'results', 'totalResults', 'page', 'totalPages', '_links'] json = explorer.content(search_term) From a2f732147e54db3ad74fb20dc098c46abf491745 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 10:52:55 +0200 Subject: [PATCH 14/22] rewritten a lot of public code to better reflect the api --- src/pyventim/public.py | 173 ++++++++++++++++++++++++---------- tests/test_public_explorer.py | 114 ++++++++++++++++++---- 2 files changed, 218 insertions(+), 69 deletions(-) diff --git a/src/pyventim/public.py b/src/pyventim/public.py index 4516ceb..11270e9 100644 --- a/src/pyventim/public.py +++ b/src/pyventim/public.py @@ -1,6 +1,6 @@ import requests -from typing import Dict -import pprint +from typing import Dict, List, Literal, Any +from datetime import datetime class EventimExploration: @@ -15,29 +15,97 @@ def __init__(self, session: requests.Session = None) -> None: "https://public-api.eventim.com/websearch/search/api/exploration" ) - def _validate_query(self, query_parameters:Dict[str, Any]) -> bool: + def _build_query_parameters(self, **kwargs) -> Dict[str, Any]: # TODO: Docs - parameters = query_parameters.keys() + query: Dict[str, Any] = {} + parameters = kwargs.keys() + + # Check if atlease one of the required keywords is present if "search_term" not in parameters and "categories" not in parameters and "city_ids" not in parameters: - raise ValueError('Must have search_term, categories or city_ids in the query parameters') + raise ValueError('Must have search_term, categories or city_ids in the query parameters!') + + # Validate if page is valid + if kwargs.get('page'): + page: int = kwargs.get('page') + if page < 1: + raise ValueError('page must be a positive integer > 0!') + + query['page'] = page + + # Validate if sort is valid + if kwargs.get('sort'): + sort: str = kwargs.get('sort') + allowed: List[str] = ['DateAsc', 'DateDesc' , 'NameAsc', 'NameDesc' , 'Rating' , 'Recommendation'] + if sort not in allowed: + raise ValueError('sort must be one of the following values: "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation"!') + + query['sort'] = sort - if query_parameters.get('search_term'): + # Validate if search_term is valid + if kwargs.get('search_term'): + search_term: str = kwargs.get('search_term') if len(search_term) < 2: - raise ValueError(f"search_term must have atleast two characters...") + raise ValueError("search_term must have atleast two characters!") + + query['search_term'] = search_term + + # Validate if categories is valid + if kwargs.get('categories'): + categories: List[str] = kwargs.get('categories') + if not isinstance(categories, List): + raise ValueError("categories must be of type List[str]!") + + # Check values to be of type str + for x in categories: + if not isinstance(x, str): + raise ValueError("categories must be of type List[str]!") + + query['categories'] = categories + + # Validate if city_ids are valid + if kwargs.get('city_ids'): + city_ids: List[int] = kwargs.get('city_ids') + if not isinstance(city_ids, List): + raise ValueError(f"city_ids must be of type List[int]!") + + # Check values to be of type str + for x in city_ids: + if not isinstance(x, int): + raise ValueError("city_ids must be of type List[int]!") + query['city_ids'] = city_ids + + # Validate if start_date is valid + if kwargs.get('start_date'): + start_date:datetime = kwargs.get('start_date') + if not isinstance(start_date, datetime): + raise ValueError(f"start_date must be of type datetime!") + + query['date_from'] = start_date.strftime('%Y-%m-%d') + query['time_from'] = start_date.strftime('%H:%M') + + # Validate if end_date is valid + if kwargs.get('end_date'): + end_date:datetime = kwargs.get('end_date') + if not isinstance(end_date, datetime): + raise ValueError(f"end_date must be of type datetime!") + + query['date_to'] = end_date.strftime('%Y-%m-%d') + query['time_to'] = end_date.strftime('%H:%M') - return True + # Validate if in_stock is valid + if kwargs.get('in_stock'): + in_stock:bool = kwargs.get('in_stock') + if not isinstance(in_stock, bool): + raise ValueError(f"in_stock must be of type bool!") + + query['in_stock'] = in_stock + + return query def explore_attractions(self, search_term: str, page: int = 1) -> Dict: - """This function returns a requested page for the given search_term of the attractions endpoint. - - Args: - search_term (str): Search term to be querried. - page (int, optional): Page number to fetch. Defaults to 1. - - Returns: - Dict: Returns the requested page with meta data. - """ + # AKA: Events / Artists etc... will give an artist + # TODO: Update function self._validate_search_term(search_term) r: requests.Response = self.session.get( @@ -47,17 +115,9 @@ def explore_attractions(self, search_term: str, page: int = 1) -> Dict: r.raise_for_status() return r.json() - def content(self, search_term: str, page: int = 1): - """This function returns a requested page for the given search_term of the content endpoint. - - Args: - search_term (str): Search term to be querried. - page (int, optional): Page number to fetch. Defaults to 1. - - Returns: - Dict: Returns the requested page with meta data. - """ - self._validate_search_term(search_term) + def explore_content(self, search_term: str, page: int = 1): + # TODO: Update function + #self._validate_search_term(search_term) r: requests.Response = self.session.get( f"{self.endpoint}/v1/content", @@ -66,17 +126,10 @@ def content(self, search_term: str, page: int = 1): r.raise_for_status() return r.json() - def locations(self, search_term: str, page: int = 1): - """This function returns a requested page for the given search_term of the locations endpoint. - - Args: - search_term (str): Search term to be querried. - page (int, optional): Page number to fetch. Defaults to 1. - - Returns: - Dict: Returns the requested page with meta data. - """ - self._validate_search_term(search_term) + def explore_locations(self, search_term: str, page: int = 1): + # TODO: Update function + # self._validate_search_term(search_term) + r: requests.Response = self.session.get( f"{self.endpoint}/v1/locations", params={"search_term": search_term, "page": page}, @@ -84,21 +137,39 @@ def locations(self, search_term: str, page: int = 1): r.raise_for_status() return r.json() - def product_groups(self, search_term: str, page: int = 1): - """This function returns a requested page for the given search_term of the product_groups endpoint. + def explore_product_groups( + self, + search_term: str = None, + categories: List['str'] = None, + city_ids: List[int] = None, + start_date:datetime = None, + end_date:datetime = None, + page: int = 1, + sort: Literal['DateAsc','DateDesc' , 'NameAsc', 'NameDesc' , 'Rating' , 'Recommendation' ] = 'DateAsc', + in_stock:bool = True, + ): + #TODO: Documentation + params: Dict[str, Any] = self._build_query_parameters( + search_term=search_term, + categories=categories, + city_ids=city_ids, + start_date=start_date, + end_date=end_date, + page=page, + sort=sort, + in_stock=in_stock, + ) + + print(params) - Args: - search_term (str): Search term to be querried. - page (int, optional): Page number to fetch. Defaults to 1. - - Returns: - Dict: Returns the requested page with meta data. - """ - self._validate_search_term(search_term) r: requests.Response = self.session.get( f"{self.endpoint}/v2/productGroups", - params={"search_term": search_term, "page": page}, + params=params, ) r.raise_for_status() - return r.json() \ No newline at end of file + print(r.request.url) + return r.json() + + + \ No newline at end of file diff --git a/tests/test_public_explorer.py b/tests/test_public_explorer.py index 605d271..54b6b54 100644 --- a/tests/test_public_explorer.py +++ b/tests/test_public_explorer.py @@ -1,29 +1,107 @@ +import pytest from pyventim.public import EventimExploration +import datetime + explorer: EventimExploration = EventimExploration() -search_term:str = "Sleep" + +search_term = "Disneys DER KÖNIG DER LÖWEN" +categories = ['Musical & Show|Musical'] +days = 7 +sort = "DateAsc" +in_stock = False +start_date = datetime.datetime.now() +end_date = start_date + datetime.timedelta(days=days) -def test_explore_content(): - # We check for needed keys - needed_keys = ['content', 'results', 'totalResults', 'page', 'totalPages', '_links'] - json = explorer.content(search_term) - assert(x in needed_keys for x in json.keys()) +# def test_explore_content(): +# # We check for needed keys +# needed_keys = ['content', 'results', 'totalResults', 'page', 'totalPages', '_links'] +# json = explorer.explore_content(search_term) +# assert(x in needed_keys for x in json.keys()) -def test_attractions(): - # We check for needed keys - needed_keys = ['attractions', 'results', 'totalResults', 'page', 'totalPages', '_links'] - json = explorer.attractions(search_term) - assert(x in needed_keys for x in json.keys()) +# def test_explore_attractions(): +# # We check for needed keys +# needed_keys = ['attractions', 'results', 'totalResults', 'page', 'totalPages', '_links'] +# json = explorer.explore_attractions(search_term) +# assert(x in needed_keys for x in json.keys()) -def test_locations(): - # We check for needed keys - needed_keys = ['locations', 'results', 'totalResults', 'page', 'totalPages', '_links'] - json = explorer.locations(search_term) - assert(x in needed_keys for x in json.keys()) +# def test_explore_locations(): +# # We check for needed keys +# needed_keys = ['locations', 'results', 'totalResults', 'page', 'totalPages', '_links'] +# json = explorer.explore_locations(search_term) +# assert(x in needed_keys for x in json.keys()) -def test_product_groups(): +def test_explore_product_groups(): + json = explorer.explore_product_groups( + search_term=search_term, + categories=categories, + start_date=start_date, + end_date=end_date, + sort=sort, + in_stock=in_stock + ) + # We check for needed keys needed_keys = ['productGroups', 'results', 'totalResults', 'page', 'totalPages', '_links'] - json = explorer.product_groups(search_term) + json = explorer.explore_product_groups(search_term) assert(x in needed_keys for x in json.keys()) + + +# Query parameter validation tests +def test_build_query_parameters_required_parameters(): + with pytest.raises(ValueError, match = 'Must have search_term, categories or city_ids in the query parameters!'): + explorer._build_query_parameters() + +def test_build_query_parameters_page_parameter(): + with pytest.raises(ValueError, match = 'page must be a positive integer > 0!'): + explorer._build_query_parameters(search_term = search_term, page = -1) + +def test_build_query_parameters_sort_parameter(): + with pytest.raises(ValueError, match = 'sort must be one of the following values: "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation"!'): + explorer._build_query_parameters(search_term = "ABC", sort = "This should fail.") + +def test_build_query_parameters_search_term_parameter(): + with pytest.raises(ValueError, match = 'search_term must have atleast two characters!'): + explorer._build_query_parameters(search_term = "A") + +def test_build_query_parameters_categories_parameter(): + with pytest.raises(ValueError, match = 'categories must be of type List\[str\]!'): + # Variable should be a list. + explorer._build_query_parameters(categories = "abcs") + + with pytest.raises(ValueError, match = 'categories must be of type List\[str\]!'): + # All values in the list must be str + explorer._build_query_parameters(categories = [123, "abcs"]) + + with pytest.raises(ValueError, match = 'categories must be of type List\[str\]!'): + # All values in the list must be str + explorer._build_query_parameters(categories = [123, 234]) + +def test_build_query_parameters_city_ids_parameter(): + with pytest.raises(ValueError, match = 'city_ids must be of type List\[int\]!'): + # Variable should be a list. + explorer._build_query_parameters(city_ids = 123) + + with pytest.raises(ValueError, match = 'city_ids must be of type List\[int\]!'): + # All values in the list must be str + explorer._build_query_parameters(city_ids = [123, "abcs"]) + + with pytest.raises(ValueError, match = 'city_ids must be of type List\[int\]!'): + # All values in the list must be str + explorer._build_query_parameters(city_ids = ["123", "234"]) + +def test_build_query_parameters_start_date_parameter(): + with pytest.raises(ValueError, match = 'start_date must be of type datetime!'): + # Variable should be a list. + explorer._build_query_parameters(search_term = search_term, start_date="This is not a valid timestamp") + +def test_build_query_parameters_end_date_parameter(): + with pytest.raises(ValueError, match = 'end_date must be of type datetime!'): + # Variable should be a list. + explorer._build_query_parameters(search_term = search_term, end_date="This is not a valid timestamp") + +def test_build_query_parameters_in_stock_parameter(): + with pytest.raises(ValueError, match = 'in_stock must be of type bool!'): + # Variable should be a list. + explorer._build_query_parameters(search_term = search_term, in_stock="This is not a valid boolean") From 08f6403d794180d38ddf97c11d679e56d8903d83 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 12:20:50 +0200 Subject: [PATCH 15/22] Added date_from, date_to, time_from, time_to params. Fixes #5 --- pyproject.toml | 5 +- src/pyventim/public.py | 231 ++++++++++++++++++++-------------- tests/test_public_explorer.py | 203 ++++++++++++++++++++---------- 3 files changed, 278 insertions(+), 161 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e11e6bd..3ba02cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,9 +24,8 @@ Issues = "https://github.com/kggx/pyventim/issues" [tool.pytest.ini_options] minversion = "6.0" -addopts = [ - "--import-mode=importlib", -] +addopts = [ "--import-mode=importlib" ] +filterwarnings = "ignore:.*:DeprecationWarning" pythonpath = "src" testpaths = [ "tests" diff --git a/src/pyventim/public.py b/src/pyventim/public.py index 11270e9..089455b 100644 --- a/src/pyventim/public.py +++ b/src/pyventim/public.py @@ -1,6 +1,8 @@ import requests from typing import Dict, List, Literal, Any -from datetime import datetime +from datetime import datetime, date, time + +import warnings class EventimExploration: @@ -14,111 +16,152 @@ def __init__(self, session: requests.Session = None) -> None: self.endpoint = ( "https://public-api.eventim.com/websearch/search/api/exploration" ) - + def _build_query_parameters(self, **kwargs) -> Dict[str, Any]: # TODO: Docs + print(kwargs.keys()) query: Dict[str, Any] = {} parameters = kwargs.keys() - + # Check if atlease one of the required keywords is present - if "search_term" not in parameters and "categories" not in parameters and "city_ids" not in parameters: - raise ValueError('Must have search_term, categories or city_ids in the query parameters!') - - # Validate if page is valid - if kwargs.get('page'): - page: int = kwargs.get('page') + if ( + "search_term" not in parameters + and "categories" not in parameters + and "city_ids" not in parameters + ): + raise ValueError( + "Must have search_term, categories or city_ids in the query parameters!" + ) + + # Validate if page is valid + if kwargs.get("page"): + page: int = kwargs.get("page") if page < 1: - raise ValueError('page must be a positive integer > 0!') - - query['page'] = page - - # Validate if sort is valid - if kwargs.get('sort'): - sort: str = kwargs.get('sort') - allowed: List[str] = ['DateAsc', 'DateDesc' , 'NameAsc', 'NameDesc' , 'Rating' , 'Recommendation'] + raise ValueError("page must be a positive integer > 0!") + + query["page"] = page + + # Validate if sort is valid + if kwargs.get("sort"): + sort: str = kwargs.get("sort") + allowed: List[str] = [ + "DateAsc", + "DateDesc", + "NameAsc", + "NameDesc", + "Rating", + "Recommendation", + ] if sort not in allowed: - raise ValueError('sort must be one of the following values: "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation"!') - - query['sort'] = sort - + raise ValueError( + 'sort must be one of the following values: "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation"!' + ) + + query["sort"] = sort + # Validate if search_term is valid - if kwargs.get('search_term'): - search_term: str = kwargs.get('search_term') + if kwargs.get("search_term"): + search_term: str = kwargs.get("search_term") if len(search_term) < 2: raise ValueError("search_term must have atleast two characters!") - - query['search_term'] = search_term - + + query["search_term"] = search_term + # Validate if categories is valid - if kwargs.get('categories'): - categories: List[str] = kwargs.get('categories') + if kwargs.get("categories"): + categories: List[str] = kwargs.get("categories") if not isinstance(categories, List): - raise ValueError("categories must be of type List[str]!") - + raise ValueError("categories must be a list of strings!") + # Check values to be of type str for x in categories: if not isinstance(x, str): - raise ValueError("categories must be of type List[str]!") - - query['categories'] = categories - + raise ValueError("categories must be a list of strings!") + + query["categories"] = categories + # Validate if city_ids are valid - if kwargs.get('city_ids'): - city_ids: List[int] = kwargs.get('city_ids') + if kwargs.get("city_ids"): + city_ids: List[int] = kwargs.get("city_ids") if not isinstance(city_ids, List): - raise ValueError(f"city_ids must be of type List[int]!") - + raise ValueError(f"city_ids must be a list of integers!") + # Check values to be of type str for x in city_ids: if not isinstance(x, int): - raise ValueError("city_ids must be of type List[int]!") - - query['city_ids'] = city_ids - - # Validate if start_date is valid - if kwargs.get('start_date'): - start_date:datetime = kwargs.get('start_date') - if not isinstance(start_date, datetime): - raise ValueError(f"start_date must be of type datetime!") - - query['date_from'] = start_date.strftime('%Y-%m-%d') - query['time_from'] = start_date.strftime('%H:%M') - - # Validate if end_date is valid - if kwargs.get('end_date'): - end_date:datetime = kwargs.get('end_date') - if not isinstance(end_date, datetime): - raise ValueError(f"end_date must be of type datetime!") - - query['date_to'] = end_date.strftime('%Y-%m-%d') - query['time_to'] = end_date.strftime('%H:%M') - + raise ValueError("city_ids must be a list of integers!") + + query["city_ids"] = city_ids + + # Validate if valid date_from + if kwargs.get("date_from"): + date_from: date = kwargs.get("date_from") + if not isinstance(date_from, date): + raise ValueError(f"date_from must be of type date!") + + query["date_from"] = date_from.strftime("%Y-%m-%d") + + # Validate if valid date_to + if kwargs.get("date_to"): + date_to: date = kwargs.get("date_to") + if not isinstance(date_to, date): + raise ValueError(f"date_to must be of type date!") + + query["date_to"] = date_to.strftime("%Y-%m-%d") + + # Validate if valid time_from + if kwargs.get("time_from"): + time_from: time = kwargs.get("time_from") + if not isinstance(time_from, time): + raise ValueError(f"time_from must be of type time!") + + query["time_from"] = time_from.strftime("%H:%M") + + # Validate if valid time_to + if kwargs.get("time_to"): + time_to: time = kwargs.get("time_to") + if not isinstance(time_to, time): + raise ValueError(f"time_to must be of type time!") + + query["time_to"] = time_to.strftime("%Y-%m-%d") + # Validate if in_stock is valid - if kwargs.get('in_stock'): - in_stock:bool = kwargs.get('in_stock') + if kwargs.get("in_stock"): + in_stock: bool = kwargs.get("in_stock") if not isinstance(in_stock, bool): raise ValueError(f"in_stock must be of type bool!") - - query['in_stock'] = in_stock - - return query - def explore_attractions(self, search_term: str, page: int = 1) -> Dict: + query["in_stock"] = in_stock + + return query + + def explore_attractions( + self, + search_term: str = None, + categories: List["str"] = None, + city_ids: List[int] = None, + page: int = 1, + sort: Literal[ + "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" + ] = "DateAsc", + in_stock: bool = True, + ) -> Dict: # AKA: Events / Artists etc... will give an artist # TODO: Update function - self._validate_search_term(search_term) - + params = self._build_query_parameters() + r: requests.Response = self.session.get( f"{self.endpoint}/v1/attractions", - params={"search_term": search_term, "page": page}, + params=params, + # params={"search_term": search_term, "page": page}, ) r.raise_for_status() return r.json() def explore_content(self, search_term: str, page: int = 1): # TODO: Update function - #self._validate_search_term(search_term) - + # self._validate_search_term(search_term) + r: requests.Response = self.session.get( f"{self.endpoint}/v1/content", params={"search_term": search_term, "page": page}, @@ -129,7 +172,7 @@ def explore_content(self, search_term: str, page: int = 1): def explore_locations(self, search_term: str, page: int = 1): # TODO: Update function # self._validate_search_term(search_term) - + r: requests.Response = self.session.get( f"{self.endpoint}/v1/locations", params={"search_term": search_term, "page": page}, @@ -138,38 +181,38 @@ def explore_locations(self, search_term: str, page: int = 1): return r.json() def explore_product_groups( - self, - search_term: str = None, - categories: List['str'] = None, - city_ids: List[int] = None, - start_date:datetime = None, - end_date:datetime = None, - page: int = 1, - sort: Literal['DateAsc','DateDesc' , 'NameAsc', 'NameDesc' , 'Rating' , 'Recommendation' ] = 'DateAsc', - in_stock:bool = True, - ): - #TODO: Documentation + self, + search_term: str = None, + categories: List["str"] = None, + city_ids: List[int] = None, + date_from: datetime.date = None, + date_to: datetime.date = None, + time_from: datetime.time = None, + time_to: datetime.time = None, + page: int = 1, + sort: Literal[ + "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" + ] = "DateAsc", + in_stock: bool = True, + ) -> Dict: + # TODO: Documentation params: Dict[str, Any] = self._build_query_parameters( search_term=search_term, categories=categories, city_ids=city_ids, - start_date=start_date, - end_date=end_date, + date_from=date_from, + date_to=date_to, + time_from=time_from, + time_to=time_to, page=page, sort=sort, in_stock=in_stock, ) - - print(params) - + r: requests.Response = self.session.get( f"{self.endpoint}/v2/productGroups", params=params, ) - + r.raise_for_status() - print(r.request.url) return r.json() - - - \ No newline at end of file diff --git a/tests/test_public_explorer.py b/tests/test_public_explorer.py index 54b6b54..45c3da1 100644 --- a/tests/test_public_explorer.py +++ b/tests/test_public_explorer.py @@ -1,18 +1,24 @@ import pytest -from pyventim.public import EventimExploration - import datetime +from typing import Dict -explorer: EventimExploration = EventimExploration() +# Module to test +from pyventim.public import EventimExploration +# Setup +explorer: EventimExploration = EventimExploration() search_term = "Disneys DER KÖNIG DER LÖWEN" -categories = ['Musical & Show|Musical'] +categories = ["Musical & Show|Musical"] days = 7 sort = "DateAsc" in_stock = False -start_date = datetime.datetime.now() -end_date = start_date + datetime.timedelta(days=days) - + +now = datetime.datetime.now() +current_time = datetime.time(now.hour, now.minute, now.second) +today = datetime.date.today() +next_week = today + datetime.timedelta(days=7) + + # def test_explore_content(): # # We check for needed keys # needed_keys = ['content', 'results', 'totalResults', 'page', 'totalPages', '_links'] @@ -24,84 +30,153 @@ # needed_keys = ['attractions', 'results', 'totalResults', 'page', 'totalPages', '_links'] # json = explorer.explore_attractions(search_term) # assert(x in needed_keys for x in json.keys()) - + # def test_explore_locations(): # # We check for needed keys # needed_keys = ['locations', 'results', 'totalResults', 'page', 'totalPages', '_links'] # json = explorer.explore_locations(search_term) # assert(x in needed_keys for x in json.keys()) - -def test_explore_product_groups(): + +def test_explore_product_groups(): json = explorer.explore_product_groups( search_term=search_term, categories=categories, - start_date=start_date, - end_date=end_date, + date_from=today, + date_to=next_week, sort=sort, - in_stock=in_stock + in_stock=in_stock, ) - + # We check for needed keys - needed_keys = ['productGroups', 'results', 'totalResults', 'page', 'totalPages', '_links'] + needed_keys = [ + "productGroups", + "results", + "totalResults", + "page", + "totalPages", + "_links", + ] json = explorer.explore_product_groups(search_term) - assert(x in needed_keys for x in json.keys()) - + assert (x in needed_keys for x in json.keys()) + # Query parameter validation tests def test_build_query_parameters_required_parameters(): - with pytest.raises(ValueError, match = 'Must have search_term, categories or city_ids in the query parameters!'): - explorer._build_query_parameters() + with pytest.raises( + ValueError, + match="Must have search_term, categories or city_ids in the query parameters!", + ): + explorer._build_query_parameters() + def test_build_query_parameters_page_parameter(): - with pytest.raises(ValueError, match = 'page must be a positive integer > 0!'): - explorer._build_query_parameters(search_term = search_term, page = -1) + with pytest.raises(ValueError, match="page must be a positive integer > 0!"): + explorer._build_query_parameters(search_term=search_term, page=-1) + + assert isinstance( + explorer._build_query_parameters(search_term=search_term, page=1), Dict + ) + def test_build_query_parameters_sort_parameter(): - with pytest.raises(ValueError, match = 'sort must be one of the following values: "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation"!'): - explorer._build_query_parameters(search_term = "ABC", sort = "This should fail.") - + with pytest.raises( + ValueError, + match='sort must be one of the following values: "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation"!', + ): + explorer._build_query_parameters(search_term="ABC", sort="This should fail.") + + assert isinstance( + explorer._build_query_parameters(search_term=search_term, sort=sort), Dict + ) + + def test_build_query_parameters_search_term_parameter(): - with pytest.raises(ValueError, match = 'search_term must have atleast two characters!'): - explorer._build_query_parameters(search_term = "A") - -def test_build_query_parameters_categories_parameter(): - with pytest.raises(ValueError, match = 'categories must be of type List\[str\]!'): - # Variable should be a list. - explorer._build_query_parameters(categories = "abcs") - - with pytest.raises(ValueError, match = 'categories must be of type List\[str\]!'): - # All values in the list must be str - explorer._build_query_parameters(categories = [123, "abcs"]) - - with pytest.raises(ValueError, match = 'categories must be of type List\[str\]!'): - # All values in the list must be str - explorer._build_query_parameters(categories = [123, 234]) - -def test_build_query_parameters_city_ids_parameter(): - with pytest.raises(ValueError, match = 'city_ids must be of type List\[int\]!'): - # Variable should be a list. - explorer._build_query_parameters(city_ids = 123) - - with pytest.raises(ValueError, match = 'city_ids must be of type List\[int\]!'): - # All values in the list must be str - explorer._build_query_parameters(city_ids = [123, "abcs"]) - - with pytest.raises(ValueError, match = 'city_ids must be of type List\[int\]!'): - # All values in the list must be str - explorer._build_query_parameters(city_ids = ["123", "234"]) - -def test_build_query_parameters_start_date_parameter(): - with pytest.raises(ValueError, match = 'start_date must be of type datetime!'): - # Variable should be a list. - explorer._build_query_parameters(search_term = search_term, start_date="This is not a valid timestamp") - -def test_build_query_parameters_end_date_parameter(): - with pytest.raises(ValueError, match = 'end_date must be of type datetime!'): - # Variable should be a list. - explorer._build_query_parameters(search_term = search_term, end_date="This is not a valid timestamp") + with pytest.raises( + ValueError, match="search_term must have atleast two characters!" + ): + explorer._build_query_parameters(search_term="A") + + assert isinstance(explorer._build_query_parameters(search_term=search_term), Dict) + + +def test_build_query_parameters_categories_parameter(): + with pytest.raises(ValueError, match="categories must be a list of strings!"): + explorer._build_query_parameters(categories="abcs") + + with pytest.raises(ValueError, match="categories must be a list of strings!"): + explorer._build_query_parameters(categories=[123, "abcs"]) + + with pytest.raises(ValueError, match="categories must be a list of strings!"): + explorer._build_query_parameters(categories=[123, 234]) + + assert isinstance(explorer._build_query_parameters(categories=categories), Dict) + + +def test_build_query_parameters_city_ids_parameter(): + with pytest.raises(ValueError, match="city_ids must be a list of integers!"): + explorer._build_query_parameters(city_ids=123) + + with pytest.raises(ValueError, match="city_ids must be a list of integers!"): + explorer._build_query_parameters(city_ids=[123, "abcs"]) + + with pytest.raises(ValueError, match="city_ids must be a list of integers!"): + explorer._build_query_parameters(city_ids=["123", "234"]) + + assert isinstance(explorer._build_query_parameters(city_ids=[1]), Dict) + + +def test_build_query_parameters_date_from_parameter(): + with pytest.raises(ValueError, match="date_from must be of type date!"): + explorer._build_query_parameters( + search_term=search_term, date_from="Not a valid date" + ) + + assert isinstance( + explorer._build_query_parameters(search_term=search_term, date_from=today), Dict + ) + + +def test_build_query_parameters_date_to_parameter(): + with pytest.raises(ValueError, match="date_to must be of type date!"): + explorer._build_query_parameters( + search_term=search_term, date_to="Not a valid date" + ) + + assert isinstance( + explorer._build_query_parameters(search_term=search_term, date_to=today), Dict + ) + + +def test_build_query_parameters_time_from_parameter(): + with pytest.raises(ValueError, match="time_from must be of type time!"): + explorer._build_query_parameters( + search_term=search_term, time_from="Not a valid time" + ) + + assert isinstance( + explorer._build_query_parameters( + search_term=search_term, time_from=current_time + ), + Dict, + ) + + +def test_build_query_parameters_time_to_parameter(): + with pytest.raises(ValueError, match="time_to must be of type time!"): + explorer._build_query_parameters( + search_term=search_term, time_to="Not a valid time" + ) + + assert isinstance( + explorer._build_query_parameters(search_term=search_term, time_to=current_time), + Dict, + ) + def test_build_query_parameters_in_stock_parameter(): - with pytest.raises(ValueError, match = 'in_stock must be of type bool!'): + with pytest.raises(ValueError, match="in_stock must be of type bool!"): # Variable should be a list. - explorer._build_query_parameters(search_term = search_term, in_stock="This is not a valid boolean") + explorer._build_query_parameters( + search_term=search_term, in_stock="This is not a valid boolean" + ) From e83ef462e2aee17d5aad9044a33a2f9e59bd8909 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 12:20:55 +0200 Subject: [PATCH 16/22] added black --- shell.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/shell.nix b/shell.nix index afa1db9..caa18e1 100644 --- a/shell.nix +++ b/shell.nix @@ -6,6 +6,7 @@ in pkgs.mkShell { (pkgs.python312.withPackages (python-pkgs: [ python-pkgs.requests python-pkgs.pytest + python-pkgs.black ])) ]; } \ No newline at end of file From cad362e501bf3d7acbda84608b9f666e413f8263 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 13:21:29 +0200 Subject: [PATCH 17/22] added utils module and tests --- src/pyventim/utils.py | 25 +++++++++++++++++++++++++ tests/test_utils.py | 14 ++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/pyventim/utils.py create mode 100644 tests/test_utils.py diff --git a/src/pyventim/utils.py b/src/pyventim/utils.py new file mode 100644 index 0000000..0df4c07 --- /dev/null +++ b/src/pyventim/utils.py @@ -0,0 +1,25 @@ +import requests +import pathlib +from typing import Dict, Any + +def parse_city_name_from_link(city_url:str) -> str: + """Given a link like "https://www.eventim.de/city/hamburg-7/venue/stage-theater-im-hafen-hamburg-3880": This function will return the city name. + + Args: + city_url (str): Link to parse + + Returns: + str: The city name from the url. + """ + return pathlib.Path(city_url).parts[3].split('-')[0] + +def parse_city_id_from_link(city_url:str) -> int: + """Given a link like "https://www.eventim.de/city/hamburg-7/venue/stage-theater-im-hafen-hamburg-3880": This function returns the city id. + + Args: + city_url (str): Link to parse + + Returns: + int: The city id from the url. + """ + return pathlib.Path(city_url).parts[3].split('-')[1] \ No newline at end of file diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..09d6b0e --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,14 @@ +import pytest + +import pyventim.utils + +def test_parse_city_name_from_link(): + url = "https://www.eventim.de/city/hamburg-7/venue/stage-theater-im-hafen-hamburg-3880" + expected_name = "hamburg" + returned_name = pyventim.utils.parse_city_name_from_link(city_url=url) + +def test_parse_city_id_from_link(): + url = "https://www.eventim.de/city/hamburg-7/venue/stage-theater-im-hafen-hamburg-3880" + expected_name = 1 + returned_name = pyventim.utils.parse_city_id_from_link(city_url=url) + From 81c1d83caca98990bf2b769c68752e14282fa106 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 13:30:26 +0200 Subject: [PATCH 18/22] formatting --- src/pyventim/utils.py | 12 +++++++----- tests/test_utils.py | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/pyventim/utils.py b/src/pyventim/utils.py index 0df4c07..40c1505 100644 --- a/src/pyventim/utils.py +++ b/src/pyventim/utils.py @@ -2,7 +2,8 @@ import pathlib from typing import Dict, Any -def parse_city_name_from_link(city_url:str) -> str: + +def parse_city_name_from_link(city_url: str) -> str: """Given a link like "https://www.eventim.de/city/hamburg-7/venue/stage-theater-im-hafen-hamburg-3880": This function will return the city name. Args: @@ -10,10 +11,11 @@ def parse_city_name_from_link(city_url:str) -> str: Returns: str: The city name from the url. - """ - return pathlib.Path(city_url).parts[3].split('-')[0] + """ + return pathlib.Path(city_url).parts[3].split("-")[0] + -def parse_city_id_from_link(city_url:str) -> int: +def parse_city_id_from_link(city_url: str) -> int: """Given a link like "https://www.eventim.de/city/hamburg-7/venue/stage-theater-im-hafen-hamburg-3880": This function returns the city id. Args: @@ -22,4 +24,4 @@ def parse_city_id_from_link(city_url:str) -> int: Returns: int: The city id from the url. """ - return pathlib.Path(city_url).parts[3].split('-')[1] \ No newline at end of file + return pathlib.Path(city_url).parts[3].split("-")[1] diff --git a/tests/test_utils.py b/tests/test_utils.py index 09d6b0e..6f9c067 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,13 +2,14 @@ import pyventim.utils + def test_parse_city_name_from_link(): url = "https://www.eventim.de/city/hamburg-7/venue/stage-theater-im-hafen-hamburg-3880" expected_name = "hamburg" returned_name = pyventim.utils.parse_city_name_from_link(city_url=url) - + + def test_parse_city_id_from_link(): url = "https://www.eventim.de/city/hamburg-7/venue/stage-theater-im-hafen-hamburg-3880" expected_name = 1 returned_name = pyventim.utils.parse_city_id_from_link(city_url=url) - From de3a4ea606dc31ea071e54ba234b3db76aad6b30 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 15:51:37 +0200 Subject: [PATCH 19/22] deleted old spec file --- swagger/public-eventim.yaml | 992 ------------------------------------ 1 file changed, 992 deletions(-) delete mode 100644 swagger/public-eventim.yaml diff --git a/swagger/public-eventim.yaml b/swagger/public-eventim.yaml deleted file mode 100644 index 98f901d..0000000 --- a/swagger/public-eventim.yaml +++ /dev/null @@ -1,992 +0,0 @@ -openapi: 3.0.0 -info: - title: flows/flows Mitmproxy2Swagger - version: 1.0.0 -servers: - - url: https://public-api.eventim.com - description: The default server - - url: https://api.eventim.com - description: The default server -paths: - /evi/api/evi/public/rule: - get: - summary: GET rule - responses: - "200": - description: "" - content: - application/json: - schema: - type: object - properties: - revision: - type: number - features: - type: object - properties: {} - rules: - type: array - items: - type: object - properties: - status: - type: string - data: - type: object - properties: - text_consent_after: - type: string - title_success: - type: string - text_above: - type: string - text_alreadyregistered: - type: string - text_below: - type: string - text_identity_feature_blacklisted: - type: string - title: - type: string - title_inputMail: - type: string - error_consent_check: - type: string - text_helpinputfield: - type: string - error_wrongformateMail: - type: string - text_consent_before: - type: string - text_doi_sent: - type: string - error_missingdata: - type: string - title_error: - type: string - label_button: - type: string - link_consent: - type: string - text_success: - type: string - title_doi_sent: - type: string - title_alreadyregistered: - type: string - link_consent_text: - type: string - doubleOptInType: - type: string - name: - type: string - actions: - type: array - items: - type: object - properties: - id: - type: string - version: - type: number - type: - type: string - permissionLinks: - type: array - items: {} - data: - type: object - properties: - mail.text.regards: - type: string - mail.doubleOptInAlreadyGranted.text: - type: string - mail.text.footer.impressum: - type: string - mail.doubleOptIn.subject: - type: string - mail.doubleOptIn.text.link: - type: string - mail.doubleOptInAlreadyGranted.subject: - type: string - title: - type: string - mail.doubleOptIn.text.devorstagingactive: - type: string - mail.text.salutation: - type: string - mail.doubleOptIn.text.below: - type: string - mail.doubleOptIn.text.above: - type: string - mail.text.footer.noreply: - type: string - status: - type: string - doubleOptIn: - type: number - id: - type: string - version: - type: number - template: - type: string - optInType: - type: string - parameters: - - name: evi-token - in: query - required: false - schema: - type: string - - name: widget-version - in: query - required: false - schema: - type: string - /websearch/search/api/exploration/v2/productGroups: - options: - summary: OPTIONS productgroups - responses: - "200": - description: OK - content: {} - parameters: - - name: webId - in: query - required: false - schema: - type: string - - name: search_term - in: query - required: false - schema: - type: string - - name: language - in: query - required: false - schema: - type: string - - name: retail_partner - in: query - required: false - schema: - type: string - - name: sort - in: query - required: false - schema: - type: string - - name: auto_suggest - in: query - required: false - schema: - type: string - get: - summary: GET productgroups - responses: - "200": - description: "" - content: - application/json: - schema: - type: object - properties: - productGroups: - type: array - items: - type: object - properties: - productGroupId: - type: string - name: - type: string - startDate: - type: string - endDate: - type: string - productCount: - type: number - link: - type: string - url: - type: object - properties: - path: - type: string - domain: - type: string - imageUrl: - type: string - currency: - type: string - rating: - type: object - properties: - count: - type: number - average: - type: number - tags: - type: array - items: - type: string - status: - type: string - products: - type: array - items: - type: object - properties: - productId: - type: string - name: - type: string - type: - type: string - status: - type: string - link: - type: string - url: - type: object - properties: - path: - type: string - domain: - type: string - typeAttributes: - type: object - properties: - liveEntertainment: - type: object - properties: - startDate: - type: string - location: - type: object - properties: - name: - type: string - city: - type: string - geoLocation: - type: object - properties: - longitude: - type: number - latitude: - type: number - rating: - type: object - properties: - count: - type: number - average: - type: number - tags: - type: array - items: - type: string - hasRecommendation: - type: number - facets: - type: array - items: {} - searchTerm: - type: string - results: - type: number - totalResults: - type: number - page: - type: number - totalPages: - type: number - _links: - type: object - properties: - self: - type: object - properties: - href: - type: string - next: - type: object - properties: - href: - type: string - parameters: - - name: webId - in: query - required: false - schema: - type: string - - name: search_term - in: query - required: false - schema: - type: string - - name: language - in: query - required: false - schema: - type: string - - name: retail_partner - in: query - required: false - schema: - type: string - - name: sort - in: query - required: false - schema: - type: string - - name: auto_suggest - in: query - required: false - schema: - type: string - /websearch/search/api/exploration/v1/attractions: - options: - summary: OPTIONS attractions - responses: - "200": - description: OK - content: {} - parameters: - - name: webId - in: query - required: false - schema: - type: string - - name: search_term - in: query - required: false - schema: - type: string - - name: language - in: query - required: false - schema: - type: string - - name: retail_partner - in: query - required: false - schema: - type: string - - name: auto_suggest - in: query - required: false - schema: - type: string - get: - summary: GET attractions - responses: - "200": - description: "" - content: - application/json: - schema: - type: object - properties: - attractions: - type: array - items: - type: object - properties: - attractionId: - type: string - name: - type: string - link: - type: string - url: - type: object - properties: - path: - type: string - domain: - type: string - imageUrl: - type: string - rating: - type: object - properties: - count: - type: number - average: - type: number - searchTerm: - type: string - results: - type: number - totalResults: - type: number - page: - type: number - totalPages: - type: number - _links: - type: object - properties: - self: - type: object - properties: - href: - type: string - next: - type: object - properties: - href: - type: string - parameters: - - name: webId - in: query - required: false - schema: - type: string - - name: search_term - in: query - required: false - schema: - type: string - - name: language - in: query - required: false - schema: - type: string - - name: retail_partner - in: query - required: false - schema: - type: string - - name: auto_suggest - in: query - required: false - schema: - type: string - /websearch/search/api/exploration/v1/locations: - options: - summary: OPTIONS locations - responses: - "200": - description: OK - content: {} - parameters: - - name: webId - in: query - required: false - schema: - type: string - - name: search_term - in: query - required: false - schema: - type: string - - name: language - in: query - required: false - schema: - type: string - - name: retail_partner - in: query - required: false - schema: - type: string - - name: auto_suggest - in: query - required: false - schema: - type: string - get: - summary: GET locations - responses: - "200": - description: "" - content: - application/json: - schema: - type: object - properties: - locations: - type: array - items: - type: object - properties: - locationId: - type: string - name: - type: string - city: - type: string - link: - type: string - url: - type: object - properties: - path: - type: string - domain: - type: string - rating: - type: object - properties: - count: - type: number - average: - type: number - searchTerm: - type: string - results: - type: number - totalResults: - type: number - page: - type: number - totalPages: - type: number - _links: - type: object - properties: - self: - type: object - properties: - href: - type: string - next: - type: object - properties: - href: - type: string - parameters: - - name: webId - in: query - required: false - schema: - type: string - - name: search_term - in: query - required: false - schema: - type: string - - name: language - in: query - required: false - schema: - type: string - - name: retail_partner - in: query - required: false - schema: - type: string - - name: auto_suggest - in: query - required: false - schema: - type: string - /websearch/search/api/exploration/v1/content: - options: - summary: OPTIONS content - responses: - "200": - description: OK - content: {} - parameters: - - name: webId - in: query - required: false - schema: - type: string - - name: search_term - in: query - required: false - schema: - type: string - - name: language - in: query - required: false - schema: - type: string - - name: retail_partner - in: query - required: false - schema: - type: string - - name: auto_suggest - in: query - required: false - schema: - type: string - get: - summary: GET content - responses: - "200": - description: "" - content: - application/json: - schema: - type: object - properties: - content: - type: array - items: - type: object - properties: - contentId: - type: string - title: - type: string - url: - type: object - properties: - path: - type: string - domain: - type: string - type: - type: string - searchTerm: - type: string - results: - type: number - totalResults: - type: number - page: - type: number - totalPages: - type: number - _links: - type: object - properties: - self: - type: object - properties: - href: - type: string - parameters: - - name: webId - in: query - required: false - schema: - type: string - - name: search_term - in: query - required: false - schema: - type: string - - name: language - in: query - required: false - schema: - type: string - - name: retail_partner - in: query - required: false - schema: - type: string - - name: auto_suggest - in: query - required: false - schema: - type: string - /seatmap/api/SeatMapHandler: - options: - summary: OPTIONS seatmaphandler - responses: - "200": - description: OK - content: {} - parameters: - - name: smcVersion - in: query - required: false - schema: - type: string - - name: version - in: query - required: false - schema: - type: string - - name: cType - in: query - required: false - schema: - type: string - - name: cId - in: query - required: false - schema: - type: number - - name: evId - in: query - required: false - schema: - type: number - - name: a_holds - in: query - required: false - schema: - type: number - - name: a_rowRules - in: query - required: false - schema: - type: number - - name: a_systemId - in: query - required: false - schema: - type: number - - name: a_promotionId - in: query - required: false - schema: - type: number - - name: a_sessionId - in: query - required: false - schema: - type: string - - name: timestamp - in: query - required: false - schema: - type: number - - name: expiryTime - in: query - required: false - schema: - type: number - - name: chash - in: query - required: false - schema: - type: string - - name: signature - in: query - required: false - schema: - type: string - - name: fun - in: query - required: false - schema: - type: string - - name: areaId - in: query - required: false - schema: - type: number - get: - summary: GET seatmaphandler - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - version: - type: string - timestamp: - type: number - tileSize: - type: number - serverName: - type: string - areaId: - type: number - zipFileName: - type: string - tilesGraphicFormat: - type: string - emptyTilePath: - type: string - rowRules: - type: object - properties: - LEFT: - type: array - items: {} - MIDDLE: - type: array - items: {} - RIGHT: - type: array - items: {} - GA: - type: array - items: {} - type: - type: number - key: - type: string - individualSeats: - type: number - availabilityTimestamp: - type: number - seatSize: - type: number - dimension: - type: array - items: - type: number - bounds: - type: array - items: - type: number - maxZoom: - type: number - minZoom: - type: number - stage: - type: array - items: - type: number - allSeatsHave: - type: number - blocks: - type: array - items: - type: object - properties: - blockId: - type: string - name: - type: string - blockDescription: - type: string - r: - type: array - items: - type: object - properties: - i: - type: number - g: - type: array - items: - type: object - properties: - i: - type: number - s: - type: array - items: - type: array - items: - type: number - graphics: - type: array - items: - type: object - properties: - hull: - type: number - type: - type: string - points: - type: array - items: - type: array - items: - type: number - pcBlock: - type: array - items: - type: array - items: - type: number - pcs: - type: array - items: - type: array - items: - type: string - parameters: - - name: smcVersion - in: query - required: false - schema: - type: string - - name: version - in: query - required: false - schema: - type: string - - name: cType - in: query - required: false - schema: - type: string - - name: cId - in: query - required: false - schema: - type: number - - name: evId - in: query - required: false - schema: - type: number - - name: a_holds - in: query - required: false - schema: - type: number - - name: a_rowRules - in: query - required: false - schema: - type: number - - name: a_systemId - in: query - required: false - schema: - type: number - - name: a_promotionId - in: query - required: false - schema: - type: number - - name: a_sessionId - in: query - required: false - schema: - type: string - - name: timestamp - in: query - required: false - schema: - type: number - - name: expiryTime - in: query - required: false - schema: - type: number - - name: chash - in: query - required: false - schema: - type: string - - name: signature - in: query - required: false - schema: - type: string - - name: fun - in: query - required: false - schema: - type: string - - name: areaId - in: query - required: false - schema: - type: number -x-path-templates: - # Remove the ignore: prefix to generate an endpoint with its URL - # Lines that are closer to the top take precedence, the matching is greedy - [] From 4c7ae65f3d4d577b24c85de8ac66ca89764454ae Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 15:51:44 +0200 Subject: [PATCH 20/22] refactored code and test --- pyproject.toml | 4 + src/pyventim/public.py | 272 ++++++++++++++++++++-------------- tests/test_public_explorer.py | 258 ++++++++++++++++---------------- 3 files changed, 290 insertions(+), 244 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3ba02cc..b6b4055 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,3 +30,7 @@ pythonpath = "src" testpaths = [ "tests" ] + +[tool.pylint.MASTER] +ignore = ['.git', "tests"] + diff --git a/src/pyventim/public.py b/src/pyventim/public.py index 089455b..dc73639 100644 --- a/src/pyventim/public.py +++ b/src/pyventim/public.py @@ -1,11 +1,14 @@ -import requests +"""Module for the public Eventim-API wrapper.""" + from typing import Dict, List, Literal, Any from datetime import datetime, date, time -import warnings +import requests class EventimExploration: + """Class that handles access to the public Eventim API.""" + def __init__(self, session: requests.Session = None) -> None: # If a valid session is not provided by the user create a new one. if not isinstance(session, requests.Session): @@ -17,165 +20,214 @@ def __init__(self, session: requests.Session = None) -> None: "https://public-api.eventim.com/websearch/search/api/exploration" ) - def _build_query_parameters(self, **kwargs) -> Dict[str, Any]: - # TODO: Docs - print(kwargs.keys()) - query: Dict[str, Any] = {} - parameters = kwargs.keys() - - # Check if atlease one of the required keywords is present + def validate_required_parameters(self, **kwargs) -> bool: if ( - "search_term" not in parameters - and "categories" not in parameters - and "city_ids" not in parameters + "search_term" not in kwargs + and "categories" not in kwargs + and "city_ids" not in kwargs ): raise ValueError( "Must have search_term, categories or city_ids in the query parameters!" ) - # Validate if page is valid - if kwargs.get("page"): - page: int = kwargs.get("page") - if page < 1: - raise ValueError("page must be a positive integer > 0!") - - query["page"] = page - - # Validate if sort is valid - if kwargs.get("sort"): - sort: str = kwargs.get("sort") - allowed: List[str] = [ - "DateAsc", - "DateDesc", - "NameAsc", - "NameDesc", - "Rating", - "Recommendation", - ] - if sort not in allowed: - raise ValueError( - 'sort must be one of the following values: "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation"!' - ) - - query["sort"] = sort - - # Validate if search_term is valid - if kwargs.get("search_term"): - search_term: str = kwargs.get("search_term") + return True + + def validate_page_parameter(self, page: int) -> bool: + if page: + if not isinstance(page, int): + raise ValueError("page must be a positive integer!") + + if not page >= 1: + raise ValueError("page must be a positive integer!") + + return True + + def validate_sort_parameter(self, sort: str) -> bool: + _allowed_values = [ + "DateAsc", + "DateDesc", + "NameAsc", + "NameDesc", + "Rating", + "Recommendation", + ] + + if sort: + if not isinstance(sort, str): + raise ValueError(f"sort must match: {", ".join(_allowed_values)}") + if not sort in _allowed_values: + raise ValueError(f"sort must match: {", ".join(_allowed_values)}") + + return True + + def validate_search_term_parameter(self, search_term: str) -> bool: + if search_term: + if not isinstance(search_term, str): + raise ValueError("search_term must have atleast two characters!") if len(search_term) < 2: raise ValueError("search_term must have atleast two characters!") + return True - query["search_term"] = search_term - - # Validate if categories is valid - if kwargs.get("categories"): - categories: List[str] = kwargs.get("categories") + def validate_categories_parameter(self, categories: List[str]) -> bool: + if categories: if not isinstance(categories, List): raise ValueError("categories must be a list of strings!") - # Check values to be of type str - for x in categories: - if not isinstance(x, str): + for value in categories: + if not isinstance(value, str): raise ValueError("categories must be a list of strings!") - query["categories"] = categories + return True - # Validate if city_ids are valid - if kwargs.get("city_ids"): - city_ids: List[int] = kwargs.get("city_ids") + def validate_city_ids_parameter(self, city_ids: List[int]) -> bool: + if city_ids: if not isinstance(city_ids, List): - raise ValueError(f"city_ids must be a list of integers!") + raise ValueError("city_ids must be a list of integers!") - # Check values to be of type str - for x in city_ids: - if not isinstance(x, int): + for value in city_ids: + if not isinstance(value, int): raise ValueError("city_ids must be a list of integers!") - query["city_ids"] = city_ids + return True - # Validate if valid date_from - if kwargs.get("date_from"): - date_from: date = kwargs.get("date_from") + def validate_date_from_parameter(self, date_from) -> bool: + if date_from: if not isinstance(date_from, date): - raise ValueError(f"date_from must be of type date!") - - query["date_from"] = date_from.strftime("%Y-%m-%d") + raise ValueError("date_from must be of type datetime.date!") + return True - # Validate if valid date_to - if kwargs.get("date_to"): - date_to: date = kwargs.get("date_to") + def validate_date_to_parameter(self, date_to) -> bool: + if date_to: if not isinstance(date_to, date): - raise ValueError(f"date_to must be of type date!") - - query["date_to"] = date_to.strftime("%Y-%m-%d") + raise ValueError("date_to must be of type datetime.date!") + return True - # Validate if valid time_from - if kwargs.get("time_from"): - time_from: time = kwargs.get("time_from") + def validate_time_from_parameter(self, time_from) -> bool: + if time_from: if not isinstance(time_from, time): - raise ValueError(f"time_from must be of type time!") + raise ValueError("time_from must be of type datetime.time!") + return True - query["time_from"] = time_from.strftime("%H:%M") - - # Validate if valid time_to - if kwargs.get("time_to"): - time_to: time = kwargs.get("time_to") + def validate_time_to_parameter(self, time_to) -> bool: + if time_to: if not isinstance(time_to, time): - raise ValueError(f"time_to must be of type time!") - - query["time_to"] = time_to.strftime("%Y-%m-%d") + raise ValueError("time_to must be of type datetime.time!") + return True - # Validate if in_stock is valid - if kwargs.get("in_stock"): - in_stock: bool = kwargs.get("in_stock") + def validate_in_stock_parameter(self, in_stock) -> bool: + if in_stock: if not isinstance(in_stock, bool): - raise ValueError(f"in_stock must be of type bool!") + raise ValueError("in_stock must be of type bool!") + + return True + + def build_query_parameters(self, **kwargs) -> Dict[str, Any]: + """Build & validates known input parameters for the Eventim-API on obvious mistakes. + + Raises: + ValueError: Raises an error if a know invalid parameter is found. - query["in_stock"] = in_stock + + Returns: + Dict[str, Any]: The query parameters based on the **kwargs + """ + query: Dict[str, Any] = {} + + # Check if atlease one of the required keywords is present + self.validate_required_parameters(**kwargs) + + for kwarg in kwargs.items(): + if not kwarg[1]: + continue + + match kwarg[0]: + case "page": + self.validate_page_parameter(kwarg[1]) + query[kwarg[0]] = kwarg[1] + case "sort": + self.validate_sort_parameter(kwarg[1]) + query[kwarg[0]] = kwarg[1] + case "search_term": + self.validate_search_term_parameter(kwarg[1]) + query[kwarg[0]] = kwarg[1] + case "categories": + self.validate_categories_parameter(kwarg[1]) + query[kwarg[0]] = kwarg[1] + case "city_ids": + self.validate_city_ids_parameter(kwarg[1]) + query[kwarg[0]] = kwarg[1] + case "date_from": + self.validate_date_from_parameter(kwarg[1]) + query[kwarg[0]] = kwarg[1].strftime("%Y-%m-%d") + case "date_to": + self.validate_date_to_parameter(kwarg[1]) + query[kwarg[0]] = kwarg[1].strftime("%Y-%m-%d") + case "time_from": + self.validate_time_from_parameter(kwarg[1]) + query[kwarg[0]] = kwarg[1].strftime("%H:%M") + case "time_to": + self.validate_time_to_parameter(kwarg[1]) + query[kwarg[0]] = kwarg[1].strftime("%H:%M") + case "in_stock": + self.validate_in_stock_parameter(kwarg[1]) + query[kwarg[0]] = kwarg[1] + case _: + query[kwarg[0]] = kwarg[1] return query def explore_attractions( self, - search_term: str = None, - categories: List["str"] = None, + search_term: str, city_ids: List[int] = None, page: int = 1, sort: Literal[ "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" ] = "DateAsc", - in_stock: bool = True, ) -> Dict: - # AKA: Events / Artists etc... will give an artist - # TODO: Update function - params = self._build_query_parameters() - + params: Dict[str, Any] = self.build_query_parameters( + search_term=search_term, city_ids=city_ids, page=page, sort=sort + ) r: requests.Response = self.session.get( - f"{self.endpoint}/v1/attractions", - params=params, - # params={"search_term": search_term, "page": page}, + f"{self.endpoint}/v1/attractions", params=params ) r.raise_for_status() return r.json() - def explore_content(self, search_term: str, page: int = 1): - # TODO: Update function - # self._validate_search_term(search_term) + def explore_content( + self, + search_term: str, + page: int = 1, + sort: Literal[ + "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" + ] = "DateAsc", + ) -> Dict: + # Refactor if this returns any meaning full output: + # https://github.com/kggx/pyventim/issues/6 + params: Dict[str, Any] = self.build_query_parameters( + search_term=search_term, page=page, sort=sort + ) r: requests.Response = self.session.get( - f"{self.endpoint}/v1/content", - params={"search_term": search_term, "page": page}, + f"{self.endpoint}/v1/content", params=params ) r.raise_for_status() return r.json() - def explore_locations(self, search_term: str, page: int = 1): - # TODO: Update function - # self._validate_search_term(search_term) + def explore_locations( + self, + search_term: str, + page: int = 1, + sort: Literal[ + "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" + ] = "DateAsc", + ) -> Dict: + params: Dict[str, Any] = self.build_query_parameters( + search_term=search_term, page=page, sort=sort + ) r: requests.Response = self.session.get( - f"{self.endpoint}/v1/locations", - params={"search_term": search_term, "page": page}, + f"{self.endpoint}/v1/locations", params=params ) r.raise_for_status() return r.json() @@ -195,8 +247,7 @@ def explore_product_groups( ] = "DateAsc", in_stock: bool = True, ) -> Dict: - # TODO: Documentation - params: Dict[str, Any] = self._build_query_parameters( + params: Dict[str, Any] = self.build_query_parameters( search_term=search_term, categories=categories, city_ids=city_ids, @@ -210,8 +261,7 @@ def explore_product_groups( ) r: requests.Response = self.session.get( - f"{self.endpoint}/v2/productGroups", - params=params, + f"{self.endpoint}/v2/productGroups", params=params ) r.raise_for_status() diff --git a/tests/test_public_explorer.py b/tests/test_public_explorer.py index 45c3da1..aa2f819 100644 --- a/tests/test_public_explorer.py +++ b/tests/test_public_explorer.py @@ -1,182 +1,174 @@ +# pylint: skip-file + import pytest import datetime -from typing import Dict +from typing import Dict, List, Any # Module to test from pyventim.public import EventimExploration # Setup -explorer: EventimExploration = EventimExploration() -search_term = "Disneys DER KÖNIG DER LÖWEN" -categories = ["Musical & Show|Musical"] -days = 7 -sort = "DateAsc" -in_stock = False - -now = datetime.datetime.now() -current_time = datetime.time(now.hour, now.minute, now.second) -today = datetime.date.today() -next_week = today + datetime.timedelta(days=7) - - -# def test_explore_content(): -# # We check for needed keys -# needed_keys = ['content', 'results', 'totalResults', 'page', 'totalPages', '_links'] -# json = explorer.explore_content(search_term) -# assert(x in needed_keys for x in json.keys()) - -# def test_explore_attractions(): -# # We check for needed keys -# needed_keys = ['attractions', 'results', 'totalResults', 'page', 'totalPages', '_links'] -# json = explorer.explore_attractions(search_term) -# assert(x in needed_keys for x in json.keys()) - -# def test_explore_locations(): -# # We check for needed keys -# needed_keys = ['locations', 'results', 'totalResults', 'page', 'totalPages', '_links'] -# json = explorer.explore_locations(search_term) -# assert(x in needed_keys for x in json.keys()) - - -def test_explore_product_groups(): - json = explorer.explore_product_groups( - search_term=search_term, - categories=categories, - date_from=today, - date_to=next_week, - sort=sort, - in_stock=in_stock, - ) - - # We check for needed keys - needed_keys = [ - "productGroups", - "results", - "totalResults", - "page", - "totalPages", - "_links", - ] - json = explorer.explore_product_groups(search_term) - assert (x in needed_keys for x in json.keys()) - - -# Query parameter validation tests -def test_build_query_parameters_required_parameters(): +SEARCH_TERM = "The" +CATEGORIES = ["Musical & Show|Musical"] +CITY_IDS = [7] # Hamburg +SORT = "DateAsc" +INSTOCK = False + +NOW = datetime.datetime.now() +CURRENT_TIME = datetime.time(NOW.hour, NOW.minute, NOW.second) +TODAY = datetime.date.today() +NEXT_WEEK = TODAY + datetime.timedelta(days=7) + + +######################################## +### Query parameter validation tests ### +######################################## +def test_validate_required_parameters(): + explorer: EventimExploration = EventimExploration() with pytest.raises( ValueError, match="Must have search_term, categories or city_ids in the query parameters!", ): - explorer._build_query_parameters() + explorer.validate_required_parameters() - -def test_build_query_parameters_page_parameter(): - with pytest.raises(ValueError, match="page must be a positive integer > 0!"): - explorer._build_query_parameters(search_term=search_term, page=-1) - - assert isinstance( - explorer._build_query_parameters(search_term=search_term, page=1), Dict + assert ( + explorer.validate_required_parameters(search_term="Sample search term...") + is True ) -def test_build_query_parameters_sort_parameter(): +def test_validate_page_parameter(): # pylint: disable=C0116 + explorer: EventimExploration = EventimExploration() with pytest.raises( ValueError, - match='sort must be one of the following values: "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation"!', + match="page must be a positive integer!", ): - explorer._build_query_parameters(search_term="ABC", sort="This should fail.") + explorer.validate_page_parameter("2") + explorer.validate_page_parameter(-1) - assert isinstance( - explorer._build_query_parameters(search_term=search_term, sort=sort), Dict - ) + assert explorer.validate_page_parameter(1) + assert explorer.validate_page_parameter(10) -def test_build_query_parameters_search_term_parameter(): +def test_validate_sort_parameter(): + explorer: EventimExploration = EventimExploration() with pytest.raises( - ValueError, match="search_term must have atleast two characters!" + ValueError, + match="sort must match: DateAsc, DateDesc, NameAsc, NameDesc, Rating, Recommendation", ): - explorer._build_query_parameters(search_term="A") - - assert isinstance(explorer._build_query_parameters(search_term=search_term), Dict) + explorer.validate_sort_parameter("This is not a valid sort value.") + assert explorer.validate_sort_parameter("DateAsc") + assert explorer.validate_sort_parameter("DateDesc") + assert explorer.validate_sort_parameter("NameAsc") + assert explorer.validate_sort_parameter("NameDesc") + assert explorer.validate_sort_parameter("Rating") + assert explorer.validate_sort_parameter("Recommendation") -def test_build_query_parameters_categories_parameter(): - with pytest.raises(ValueError, match="categories must be a list of strings!"): - explorer._build_query_parameters(categories="abcs") - with pytest.raises(ValueError, match="categories must be a list of strings!"): - explorer._build_query_parameters(categories=[123, "abcs"]) +def test_validate_search_term_parameter(): # pylint: disable=C0116 + explorer: EventimExploration = EventimExploration() + with pytest.raises( + ValueError, + match="search_term must have atleast two characters!", + ): + explorer.validate_search_term_parameter("2") - with pytest.raises(ValueError, match="categories must be a list of strings!"): - explorer._build_query_parameters(categories=[123, 234]) + assert explorer.validate_search_term_parameter(SEARCH_TERM) - assert isinstance(explorer._build_query_parameters(categories=categories), Dict) +def test_validate_categories_parameter(): + explorer: EventimExploration = EventimExploration() + with pytest.raises(ValueError, match="categories must be a list of strings!"): + explorer.validate_categories_parameter("This is not category value.") + explorer.validate_categories_parameter(["This is valid", False]) -def test_build_query_parameters_city_ids_parameter(): - with pytest.raises(ValueError, match="city_ids must be a list of integers!"): - explorer._build_query_parameters(city_ids=123) + assert explorer.validate_categories_parameter(CATEGORIES) - with pytest.raises(ValueError, match="city_ids must be a list of integers!"): - explorer._build_query_parameters(city_ids=[123, "abcs"]) +def test_validate_city_ids_parameter(): + explorer: EventimExploration = EventimExploration() with pytest.raises(ValueError, match="city_ids must be a list of integers!"): - explorer._build_query_parameters(city_ids=["123", "234"]) + explorer.validate_city_ids_parameter("This is not category value.") + explorer.validate_city_ids_parameter(["This is not valid valid", 123]) - assert isinstance(explorer._build_query_parameters(city_ids=[1]), Dict) + assert explorer.validate_city_ids_parameter(CITY_IDS) -def test_build_query_parameters_date_from_parameter(): - with pytest.raises(ValueError, match="date_from must be of type date!"): - explorer._build_query_parameters( - search_term=search_term, date_from="Not a valid date" - ) +def test_validate_date_from_parameter(): + explorer: EventimExploration = EventimExploration() + with pytest.raises(ValueError, match="date_from must be of type datetime.date!"): + explorer.validate_date_from_parameter("This is not a valid value.") - assert isinstance( - explorer._build_query_parameters(search_term=search_term, date_from=today), Dict - ) + assert explorer.validate_date_from_parameter(TODAY) -def test_build_query_parameters_date_to_parameter(): - with pytest.raises(ValueError, match="date_to must be of type date!"): - explorer._build_query_parameters( - search_term=search_term, date_to="Not a valid date" - ) +def test_validate_date_to_parameter(): + explorer: EventimExploration = EventimExploration() + with pytest.raises(ValueError, match="date_to must be of type datetime.date!"): + explorer.validate_date_to_parameter("This is not a valid value.") - assert isinstance( - explorer._build_query_parameters(search_term=search_term, date_to=today), Dict - ) + assert explorer.validate_date_to_parameter(TODAY) -def test_build_query_parameters_time_from_parameter(): - with pytest.raises(ValueError, match="time_from must be of type time!"): - explorer._build_query_parameters( - search_term=search_term, time_from="Not a valid time" - ) +def test_validate_time_from_parameter(): + explorer: EventimExploration = EventimExploration() + with pytest.raises(ValueError, match="time_from must be of type datetime.time!"): + explorer.validate_time_from_parameter("This is not a valid value.") - assert isinstance( - explorer._build_query_parameters( - search_term=search_term, time_from=current_time - ), - Dict, - ) + assert explorer.validate_time_from_parameter(CURRENT_TIME) -def test_build_query_parameters_time_to_parameter(): - with pytest.raises(ValueError, match="time_to must be of type time!"): - explorer._build_query_parameters( - search_term=search_term, time_to="Not a valid time" - ) +def test_validate_time_to_parameter(): + explorer: EventimExploration = EventimExploration() + with pytest.raises(ValueError, match="time_to must be of type datetime.time!"): + explorer.validate_time_to_parameter("This is not a valid value.") - assert isinstance( - explorer._build_query_parameters(search_term=search_term, time_to=current_time), - Dict, - ) + assert explorer.validate_time_to_parameter(CURRENT_TIME) -def test_build_query_parameters_in_stock_parameter(): +def test_validate_in_stock_parameter(): + explorer: EventimExploration = EventimExploration() with pytest.raises(ValueError, match="in_stock must be of type bool!"): - # Variable should be a list. - explorer._build_query_parameters( - search_term=search_term, in_stock="This is not a valid boolean" - ) + explorer.validate_in_stock_parameter("This is not a valid value.") + + assert explorer.validate_in_stock_parameter(INSTOCK) + + +###################################### +### Query builder validation tests ### +###################################### +def test_build_query_parameters(): + explorer: EventimExploration = EventimExploration() + inputs = { + "search_term": SEARCH_TERM, + "date_from": TODAY, + "date_to": NEXT_WEEK, + "categories": CATEGORIES, + "in_stock": None, + "not_defined_key": "sample_value", + } + + params: Dict[str, Any] = explorer.build_query_parameters(**inputs) + assert isinstance(params, Dict) + + # Check if expected keys are present + for key in params.keys(): + assert key in [ + "search_term", + "date_from", + "date_to", + "categories", + "not_defined_key", + ] + + # Check if a not defined key is in the dictionary + assert params["not_defined_key"] == "sample_value" + + # Check if empty value is not in the param + assert "in_stock" not in params.keys() + + +############################ +### API validation tests ### +############################ From 1c98d3417389609090119b9d6f902b1ad5ff57b0 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 16:24:41 +0200 Subject: [PATCH 21/22] moved api validation to a single class --- src/pyventim/public.py | 3 +- tests/test_public_explorer.py | 5 -- tests/test_public_explorer_attraction.py | 42 ++++++++++++ tests/test_public_explorer_location.py | 49 +++++++++++++ tests/test_public_explorer_product_groups.py | 72 ++++++++++++++++++++ 5 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 tests/test_public_explorer_attraction.py create mode 100644 tests/test_public_explorer_location.py create mode 100644 tests/test_public_explorer_product_groups.py diff --git a/src/pyventim/public.py b/src/pyventim/public.py index dc73639..bf76e48 100644 --- a/src/pyventim/public.py +++ b/src/pyventim/public.py @@ -179,14 +179,13 @@ def build_query_parameters(self, **kwargs) -> Dict[str, Any]: def explore_attractions( self, search_term: str, - city_ids: List[int] = None, page: int = 1, sort: Literal[ "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" ] = "DateAsc", ) -> Dict: params: Dict[str, Any] = self.build_query_parameters( - search_term=search_term, city_ids=city_ids, page=page, sort=sort + search_term=search_term, page=page, sort=sort ) r: requests.Response = self.session.get( f"{self.endpoint}/v1/attractions", params=params diff --git a/tests/test_public_explorer.py b/tests/test_public_explorer.py index aa2f819..d108600 100644 --- a/tests/test_public_explorer.py +++ b/tests/test_public_explorer.py @@ -167,8 +167,3 @@ def test_build_query_parameters(): # Check if empty value is not in the param assert "in_stock" not in params.keys() - - -############################ -### API validation tests ### -############################ diff --git a/tests/test_public_explorer_attraction.py b/tests/test_public_explorer_attraction.py new file mode 100644 index 0000000..dc3462b --- /dev/null +++ b/tests/test_public_explorer_attraction.py @@ -0,0 +1,42 @@ +# pylint: skip-file + +import pytest +import datetime +from typing import Dict, List, Any + +# Module to test +from pyventim.public import EventimExploration + +# Setup +# We are testing against a long running german musical prone NOT to change +SEARCH_TERM = "Stage Theater im Hafen Hamburg" +SORT = "DateAsc" + +EXPLORER: EventimExploration = EventimExploration() +RESULT = EXPLORER.explore_attractions( + search_term=SEARCH_TERM, + sort=SORT, +) + + +############################ +### API validation tests ### +############################ +# Make a sample request and reuse the result to verify varous components +def test_request_successfull(): + excepted_keys = [ + "attractions", + "results", + "totalResults", + "page", + "totalPages", + "_links", + ] + assert isinstance(RESULT, Dict) + assert (key in excepted_keys for key in RESULT.keys()) + + +def test_request_location_id(): + # Get the first as this should be the only result + excepted_id = "450962" + RESULT["attractions"][0]["attractionId"] = excepted_id diff --git a/tests/test_public_explorer_location.py b/tests/test_public_explorer_location.py new file mode 100644 index 0000000..9e91527 --- /dev/null +++ b/tests/test_public_explorer_location.py @@ -0,0 +1,49 @@ +# pylint: skip-file + +import pytest +import datetime +from typing import Dict, List, Any + +# Module to test +from pyventim.public import EventimExploration + +# Setup +# We are testing against a long running german musical prone NOT to change +SEARCH_TERM = "Stage Theater im Hafen Hamburg" +SORT = "DateAsc" + +EXPLORER: EventimExploration = EventimExploration() +RESULT = EXPLORER.explore_locations( + search_term=SEARCH_TERM, + sort=SORT, +) + + +############################ +### API validation tests ### +############################ +# Make a sample request and reuse the result to verify varous components +def test_request_successfull(): + excepted_keys = [ + "locations", + "results", + "totalResults", + "page", + "totalPages", + "_links", + ] + + assert isinstance(RESULT, Dict) + assert (key in excepted_keys for key in RESULT.keys()) + + +def test_request_location_id(): + # Get the first as this should be the only result + excepted_id = "vg_3880" + RESULT["locations"][0]["locationId"] = excepted_id + + +def test_request_location_city(): + # Get the first as this should be the only result + excepted_id = "Hamburg" + RESULT["locations"][0]["city"] = excepted_id diff --git a/tests/test_public_explorer_product_groups.py b/tests/test_public_explorer_product_groups.py new file mode 100644 index 0000000..2d3d7ce --- /dev/null +++ b/tests/test_public_explorer_product_groups.py @@ -0,0 +1,72 @@ +# pylint: skip-file + +import pytest +import datetime +from typing import Dict, List, Any + +# Module to test +from pyventim.public import EventimExploration + +# Setup +# We are testing against a long running german musical prone NOT to change +SEARCH_TERM = "Disneys DER KÖNIG DER LÖWEN" +CATEGORIES = ["Musical & Show|Musical"] +SORT = "DateAsc" + +TODAY = datetime.date.today() +NEXT_YEAR = TODAY + datetime.timedelta(days=365) + +EXPLORER: EventimExploration = EventimExploration() +RESULT = EXPLORER.explore_product_groups( + search_term=SEARCH_TERM, + categories=CATEGORIES, + date_from=TODAY, + date_to=NEXT_YEAR, + sort=SORT, +) + + +############################ +### API validation tests ### +############################ +# Make a sample request and reuse the result to verify varous components +def test_request_successfull(): + excepted_keys = [ + "productGroups", + "results", + "totalResults", + "page", + "totalPages", + "_links", + ] + + assert isinstance(RESULT, Dict) + assert (key in excepted_keys for key in RESULT.keys()) + + +def test_request_product_group_id(): + # Get the first as this should be the only result + excepted_id = 473431 + RESULT["productGroups"][0]["productGroupId"] = excepted_id + + +def test_request_product_group_categories(): + # Get the first as this should be the only result and check against known categories + excepted_categories = ["Musical & Show", "Musical"] + + assert ( + category in excepted_categories + for category in RESULT["productGroups"][0]["categories"] + ) + + +def test_page_product_group_request(): + excepted_page = 2 + + page_result = EXPLORER.explore_product_groups( + search_term="The", + page=2, + sort=SORT, + ) + + page_result["page"] = excepted_page From 8e9635f4e85c37704bd3c6848197aa140e9f27b1 Mon Sep 17 00:00:00 2001 From: Kilian Braun Date: Sun, 12 May 2024 17:09:55 +0200 Subject: [PATCH 22/22] written doc strings --- src/pyventim/public.py | 188 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 175 insertions(+), 13 deletions(-) diff --git a/src/pyventim/public.py b/src/pyventim/public.py index bf76e48..66f6392 100644 --- a/src/pyventim/public.py +++ b/src/pyventim/public.py @@ -1,8 +1,7 @@ """Module for the public Eventim-API wrapper.""" from typing import Dict, List, Literal, Any -from datetime import datetime, date, time - +import datetime import requests @@ -21,6 +20,14 @@ def __init__(self, session: requests.Session = None) -> None: ) def validate_required_parameters(self, **kwargs) -> bool: + """Validates if all required parameters are present + + Raises: + ValueError: Raised if missing query parameters. + + Returns: + bool: True if valid + """ if ( "search_term" not in kwargs and "categories" not in kwargs @@ -33,6 +40,18 @@ def validate_required_parameters(self, **kwargs) -> bool: return True def validate_page_parameter(self, page: int) -> bool: + """Validates the page parameter + + Args: + page (int): Page parameter to check. + + Raises: + ValueError: Raised if page is not a positive and not zero integer. + + + Returns: + bool: True if valid + """ if page: if not isinstance(page, int): raise ValueError("page must be a positive integer!") @@ -43,6 +62,17 @@ def validate_page_parameter(self, page: int) -> bool: return True def validate_sort_parameter(self, sort: str) -> bool: + """Validates the sort parameter based on a known set of values + + Args: + sort (str): Sort parameter to check + + Raises: + ValueError: Raised if sort is not of a predefined value. + + Returns: + bool: True if valid + """ _allowed_values = [ "DateAsc", "DateDesc", @@ -61,6 +91,17 @@ def validate_sort_parameter(self, sort: str) -> bool: return True def validate_search_term_parameter(self, search_term: str) -> bool: + """Validates if the search_term is valid + + Args: + search_term (str): Search_term to check + + Raises: + ValueError: Raised if the search_term is not a valid value. + + Returns: + bool: True if valid + """ if search_term: if not isinstance(search_term, str): raise ValueError("search_term must have atleast two characters!") @@ -69,6 +110,17 @@ def validate_search_term_parameter(self, search_term: str) -> bool: return True def validate_categories_parameter(self, categories: List[str]) -> bool: + """Validates if the categories parameter is valid + + Args: + categories (List[str]): List of categories to check + + Raises: + ValueError: Raised if the categories are not of type List[str] + + Returns: + bool: True if valid + """ if categories: if not isinstance(categories, List): raise ValueError("categories must be a list of strings!") @@ -80,6 +132,17 @@ def validate_categories_parameter(self, categories: List[str]) -> bool: return True def validate_city_ids_parameter(self, city_ids: List[int]) -> bool: + """Validates if the city_ids parameter is valid + + Args: + city_ids (List[int]): List of city_ids to check + + Raises: + ValueError: Raised if the city_ids are not of type List[int] + + Returns: + bool: True if vaild + """ if city_ids: if not isinstance(city_ids, List): raise ValueError("city_ids must be a list of integers!") @@ -90,31 +153,86 @@ def validate_city_ids_parameter(self, city_ids: List[int]) -> bool: return True - def validate_date_from_parameter(self, date_from) -> bool: + def validate_date_from_parameter(self, date_from: datetime.date) -> bool: + """Validates if the date_from parameter is valid. + + Args: + date_from (datetime.date): Value to check. + + Raises: + ValueError: Raised if not a valid value. + + Returns: + bool: True if valid + """ if date_from: - if not isinstance(date_from, date): + if not isinstance(date_from, datetime.date): raise ValueError("date_from must be of type datetime.date!") return True - def validate_date_to_parameter(self, date_to) -> bool: + def validate_date_to_parameter(self, date_to: datetime.date) -> bool: + """Validates if the date_to parameter is valid. + + Args: + date_to (datetime.date): Value to check. + + Raises: + ValueError: Raised if not a valid value. + + Returns: + bool: True if valid + """ if date_to: - if not isinstance(date_to, date): + if not isinstance(date_to, datetime.date): raise ValueError("date_to must be of type datetime.date!") return True - def validate_time_from_parameter(self, time_from) -> bool: + def validate_time_from_parameter(self, time_from: datetime.time) -> bool: + """Validates if the time_from parameter is valid. + + Args: + time_from (datetime.time): Value to check. + + Raises: + ValueError: Raised if not a valid value. + + Returns: + bool: True if valid + """ if time_from: - if not isinstance(time_from, time): + if not isinstance(time_from, datetime.time): raise ValueError("time_from must be of type datetime.time!") return True - def validate_time_to_parameter(self, time_to) -> bool: + def validate_time_to_parameter(self, time_to: datetime.time) -> bool: + """Validates if the time_to parameter is valid. + + Args: + time_to (datetime.time): Value to check. + + Raises: + ValueError: Raised if not a valid value. + + Returns: + bool: True if valid + """ if time_to: - if not isinstance(time_to, time): + if not isinstance(time_to, datetime.time): raise ValueError("time_to must be of type datetime.time!") return True - def validate_in_stock_parameter(self, in_stock) -> bool: + def validate_in_stock_parameter(self, in_stock: bool) -> bool: + """Validates if the in_stock parameter is true. + + Args: + in_stock (bool): Value to check + + Raises: + ValueError: Raised if not a valid value + + Returns: + bool: True if valid + """ if in_stock: if not isinstance(in_stock, bool): raise ValueError("in_stock must be of type bool!") @@ -184,6 +302,16 @@ def explore_attractions( "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" ] = "DateAsc", ) -> Dict: + """This requests attractions (such as artists, musicals, festivals) via the public Eventim API + + Args: + search_term (str): Search_term to query the endpoint + page (int, optional): Page to fetch. Defaults to 1. + sort (Literal[ "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" ], optional): Sorting options. Defaults to "DateAsc". + + Returns: + Dict: Returns a result in form of json. + """ params: Dict[str, Any] = self.build_query_parameters( search_term=search_term, page=page, sort=sort ) @@ -193,7 +321,7 @@ def explore_attractions( r.raise_for_status() return r.json() - def explore_content( + def explore_content( # pylint: disable=C0116 self, search_term: str, page: int = 1, @@ -201,6 +329,7 @@ def explore_content( "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" ] = "DateAsc", ) -> Dict: + """Cant be validated at the moment. Returns no meaning full output.""" # Refactor if this returns any meaning full output: # https://github.com/kggx/pyventim/issues/6 params: Dict[str, Any] = self.build_query_parameters( @@ -221,6 +350,16 @@ def explore_locations( "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" ] = "DateAsc", ) -> Dict: + """This requests locations (arenas, bars, clubs, etc...) via the public Eventim API. + + Args: + search_term (str): Search_term to query the endpoint + page (int, optional): Page to fetch. Defaults to 1. + sort (Literal[ "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" ], optional): Sorting sptions. Defaults to "DateAsc". + + Returns: + Dict: Returns a result in form of json + """ params: Dict[str, Any] = self.build_query_parameters( search_term=search_term, page=page, sort=sort ) @@ -246,6 +385,30 @@ def explore_product_groups( ] = "DateAsc", in_stock: bool = True, ) -> Dict: + # pylint: disable=C0301 + """This requests product groups (Musicals, Tours, etc..) via the public Eventim API. + Notes: + - Atleast one of the following must be present: search_term, categories or city_ids. + - date_from and date_to are handled match agains the startDate of an event. + For example: date_from: 2024-01-01 / date_to: 2024-01-02 returns events starting between 2024-01-01 and 2024-01-02 + - time_from and time_to are handled match agains the startDate of an event. + For example: time_from: 11:00am / time_to: 12:00am only returns events starting between 11:00am and 12:00am + + Args: + search_term (str, optional): Search_term to query the endpoint. Defaults to None. + categories (List["str"], optional): Categories to query the endpoint. Defaults to None. + city_ids (List[int], optional): City Ids to query the endpoint. Defaults to None. + date_from (datetime.date, optional): Event start date. Defaults to None. + date_to (datetime.date, optional): Event end date. Defaults to None. + time_from (datetime.time, optional): Event begin time. Defaults to None. + time_to (datetime.time, optional): Event end time. Defaults to None. + page (int, optional): Page to fetch. Defaults to 1. + sort (Literal[ "DateAsc", "DateDesc", "NameAsc", "NameDesc", "Rating", "Recommendation" ], optional): Sorting options. Defaults to "DateAsc". + in_stock (bool, optional): Only show in stock product groups. Defaults to True. + + Returns: + Dict: Returns a result in form of json + """ params: Dict[str, Any] = self.build_query_parameters( search_term=search_term, categories=categories, @@ -258,7 +421,6 @@ def explore_product_groups( sort=sort, in_stock=in_stock, ) - r: requests.Response = self.session.get( f"{self.endpoint}/v2/productGroups", params=params )