From 0ddbba556a53674cba7c2d6ec44ad155f9f92652 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 3 Apr 2023 18:00:25 +0800 Subject: [PATCH 01/39] initial commit --- LICENSE | 21 ++++++++ README.md | 25 +++++++++- grepper_python/__init__.py | 39 +++++++++++++++ grepper_python/answer.py | 12 +++++ grepper_python/exceptions.py | 45 +++++++++++++++++ grepper_python/main.py | 96 ++++++++++++++++++++++++++++++++++++ setup.py | 24 +++++++++ tests/__init__.py | 0 tests/test_search.py | 66 +++++++++++++++++++++++++ 9 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 LICENSE create mode 100644 grepper_python/__init__.py create mode 100644 grepper_python/answer.py create mode 100644 grepper_python/exceptions.py create mode 100644 grepper_python/main.py create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/test_search.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f7e9d31 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2010-2023 Grepper, Inc. (https://www.grepper.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 5bcea0b..c3123dd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,23 @@ -# grepper-python -python client library +# Grepper Python Client +The Grepper Python library provides convenient access to the Grepper API from applications written in the Python language. + +## Requirements +Python 3.7 and later. + +## PIP +Coming soon. + +## Manual Installation +```bash +git clone https://github.com/CantCode023/grepper-python +cd grepper-python +python setup.py install +``` + +## Getting Started +Simple usage: +```py +grepper = Grepper("your-grepper-api-key") +answers = grepper.search("query") +print(answers) +``` \ No newline at end of file diff --git a/grepper_python/__init__.py b/grepper_python/__init__.py new file mode 100644 index 0000000..fa244cd --- /dev/null +++ b/grepper_python/__init__.py @@ -0,0 +1,39 @@ +""" +Python Grepper API +~~~~~~~~~~~~~~~~~~~ +An API wrapper for the Grepper API. +""" + +__title__ = "grepper-python" +__author__ = "CodeGrepper" +__license__ = "MIT" +__copyright__ = "Copyright 2010-2023 Grepper, Inc." +__version__ = "0.0.1a" + +__path__ = __import__("pkgutil").extend_path(__path__, __name__) + +import logging +from typing import NamedTuple, Literal + +from .answer import GrepperAnswer + + +class VersionInfo(NamedTuple): + major: int + minor: int + micro: int + releaselevel: Literal["alpha", "beta", "candidate", "final"] + serial: int + + +version_info: VersionInfo = VersionInfo( + major=0, minor=0, micro=1, releaselevel="alpha", serial=0 +) + +logging.getLogger(__name__).addHandler(logging.NullHandler()) + +del ( + logging, + NamedTuple, + Literal, +) diff --git a/grepper_python/answer.py b/grepper_python/answer.py new file mode 100644 index 0000000..9ecf806 --- /dev/null +++ b/grepper_python/answer.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass + + +@dataclass +class GrepperAnswer: + id: int + content: str + author_name: str + author_profile_url: str + title: str + upvotes: int + downvotes: int diff --git a/grepper_python/exceptions.py b/grepper_python/exceptions.py new file mode 100644 index 0000000..dea8e12 --- /dev/null +++ b/grepper_python/exceptions.py @@ -0,0 +1,45 @@ +""" +grepper-python.exceptions +~~~~~~~~~~~~~~~~~~~ +This module contains the set of grepper-python's exceptions. +""" + + +# 400 +class BadRequest: + """Your request is invalid.""" + + +# 401 +class Unauthorized: + """Your API key is wrong.""" + + +# 403 +class Forbidden: + """You do not have access to the requested resource.""" + + +# 404 +class NotFound: + """The specified enpoint could not be found.""" + + +# 405 +class MethodNotAllowed: + """You tried to access an enpoint with an invalid method.""" + + +# 429 +class TooManyRequests: + """You're making too many requests! Slow down!""" + + +# 500 +class InternalServerError: + """We had a problem with our server. Try again later.""" + + +# 503 +class ServiceUnavailable: + """We're temporarily offline for maintenance. Please try again later.""" diff --git a/grepper_python/main.py b/grepper_python/main.py new file mode 100644 index 0000000..8c25a78 --- /dev/null +++ b/grepper_python/main.py @@ -0,0 +1,96 @@ +""" +The MIT License + +Copyright (c) 2010-2023 Grepper, Inc. (https://www.grepper.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +from __future__ import annotations + +from typing import Optional + +from .answer import GrepperAnswer +from .exceptions import * + +import requests + + +def exception_handler(status_code: str): + if status_code == "400": + return BadRequest + elif status_code == "401": + return Unauthorized + elif status_code == "403": + return Forbidden + elif status_code == "404": + return NotFound + elif status_code == "405": + return MethodNotAllowed + elif status_code == "429": + return TooManyRequests + elif status_code == "500": + return InternalServerError + elif status_code == "503": + return ServiceUnavailable + + +class Grepper: + """ + Python Grepper API Wrapper + """ + + def __init__(self, api_key: str): + self.api_key = api_key + + def search( + self, query: str = False, similarity: Optional[int] = 60 + ): + """This function searches all answers based on a query. + + Args: + query (str, optional): Query to search through answer titles. ex: "Javascript loop array backwords". Defaults to False. + similarity (Optional[int], optional): How similar the query has to be to the answer title. 1-100 where 1 is really loose matching and 100 is really strict/tight match. Defaults to 60. + + Returns: + GrepperAnswer + """ + response = requests.get( + "https://api.grepper.com/v1/answers/search", + params={"query": query, "similarity": similarity}, + auth=(self.api_key, ""), + ) + if str(response.status_code) != "200": + exception = exception_handler(str(response.status_code)) + raise exception(exception.__doc__) + json_response = response.json() + print(json_response) + data = [] + for i in json_response["data"]: + new_answer = GrepperAnswer( + id=i["id"], + content=i["content"], + author_name=i["author_name"], + author_profile_url=i["author_profile_url"], + title=i["title"], + upvotes=i["upvotes"], + downvotes=i["downvotes"], + ) + data.append(new_answer) + return data diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f7cdd11 --- /dev/null +++ b/setup.py @@ -0,0 +1,24 @@ +import setuptools + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setup_info = { + "name": "grepper-python", + "version": "0.0.1a", + "author": "CodedGrepper", + "author_email": "support@grepper.com", + "description": "An API wrapper for the Grepper API.", + "long_description": long_description, + "long_description_content_type": "text/markdown", + "url": "https://github.com/CodeGrepper/grepper-python", + "packages": setuptools.find_packages(), + "install_requires": ["requests", "urllib3"], + "classifiers": [ + "Programming Language :: Python :: 3", + ], + "python_requires": '>=3.7' +} + + +setuptools.setup(**setup_info) \ 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_search.py b/tests/test_search.py new file mode 100644 index 0000000..ce5408b --- /dev/null +++ b/tests/test_search.py @@ -0,0 +1,66 @@ +import unittest +from unittest.mock import patch, Mock +from grepper_python.main import Grepper + + +class TestGrepper(unittest.TestCase): + def setUp(self): + self.api_key = "my_api_key" + self.grepper = Grepper(self.api_key) + + @patch("grepper_python.main.requests.get") + def test_search(self, mock_get): + # Mock the response from the API + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "data": [ + { + "id": 1, + "content": "example content", + "author_name": "example author", + "author_profile_url": "https://example.com", + "title": "example title", + "upvotes": 10, + "downvotes": 2, + } + ] + } + mock_get.return_value = mock_response + + # Test the search function + results = self.grepper.search( + query="example query", similarity=80 + ) + self.assertEqual(len(results), 1) + self.assertEqual(results[0].id, 1) + self.assertEqual(results[0].content, "example content") + self.assertEqual(results[0].author_name, "example author") + self.assertEqual( + results[0].author_profile_url, "https://example.com" + ) + self.assertEqual(results[0].title, "example title") + self.assertEqual(results[0].upvotes, 10) + self.assertEqual(results[0].downvotes, 2) + + # Test that the API was called with the correct parameters + mock_get.assert_called_once_with( + "https://api.grepper.com/v1/answers/search", + params={"query": "example query", "similarity": 80}, + auth=(self.api_key, ""), + ) + + @patch("grepper_python.main.exception_handler") + @patch("grepper_python.main.requests.get") + def test_search_with_error(self, mock_get, mock_exception_handler): + # Mock the response from the API with an error status code + mock_response = Mock() + mock_response.status_code = 404 + mock_get.return_value = mock_response + + # Test the search function with an error response + with self.assertRaises(Exception): + self.grepper.search(query="example query", similarity=80) + + # Test that the exception handler was called with the correct parameter + mock_exception_handler.assert_called_once_with("404") From 50ea955e59288926637a46efd9c97db05ca6f9f0 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Sun, 3 Mar 2024 21:36:40 +0800 Subject: [PATCH 02/39] added Grepper to __init__ for importing --- grepper_python/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/grepper_python/__init__.py b/grepper_python/__init__.py index fa244cd..3470450 100644 --- a/grepper_python/__init__.py +++ b/grepper_python/__init__.py @@ -16,6 +16,7 @@ from typing import NamedTuple, Literal from .answer import GrepperAnswer +from .main import Grepper class VersionInfo(NamedTuple): From 208af5d725ef88722fc1111472626b4cc544a7e4 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Sun, 3 Mar 2024 21:37:29 +0800 Subject: [PATCH 03/39] removed line 83 which i had for debugging but forgot to remove from the code --- grepper_python/main.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/grepper_python/main.py b/grepper_python/main.py index 8c25a78..e5abb74 100644 --- a/grepper_python/main.py +++ b/grepper_python/main.py @@ -56,8 +56,8 @@ class Grepper: Python Grepper API Wrapper """ - def __init__(self, api_key: str): - self.api_key = api_key + def __init__(self, __api_key: str): + self.__api_key = __api_key def search( self, query: str = False, similarity: Optional[int] = 60 @@ -74,13 +74,12 @@ def search( response = requests.get( "https://api.grepper.com/v1/answers/search", params={"query": query, "similarity": similarity}, - auth=(self.api_key, ""), + auth=(self.__api_key, ""), ) if str(response.status_code) != "200": exception = exception_handler(str(response.status_code)) raise exception(exception.__doc__) json_response = response.json() - print(json_response) data = [] for i in json_response["data"]: new_answer = GrepperAnswer( @@ -93,4 +92,4 @@ def search( downvotes=i["downvotes"], ) data.append(new_answer) - return data + return data \ No newline at end of file From 0eb8e8c32cad068181b2a56ab04177f88ec4a4fd Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Sun, 3 Mar 2024 21:43:55 +0800 Subject: [PATCH 04/39] added fetch_answer to retrieve an answer by id --- grepper_python/main.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/grepper_python/main.py b/grepper_python/main.py index e5abb74..1370c80 100644 --- a/grepper_python/main.py +++ b/grepper_python/main.py @@ -92,4 +92,34 @@ def search( downvotes=i["downvotes"], ) data.append(new_answer) - return data \ No newline at end of file + return data + + def fetch_answer( + self, id: int + ): + """This function returns an answer specified by the id. + + Args: + id (int): The id for the specified answer. ex: "560676 ". + + Returns: + GrepperAnswer + """ + response = requests.get( + f"https://api.grepper.com/v1/answers/{id}", + auth=(self.__api_key, "") + ) + if str(response.status_code) != "200": + exception = exception_handler(str(response.status_code)) + raise exception(exception.__doc__) + json_response = response.json() + answer = GrepperAnswer( + id=json_response["id"], + content=json_response["content"], + author_name=json_response["author_name"], + author_profile_url=json_response["author_profile_url"], + title=json_response["title"], + upvotes=json_response["upvotes"], + downvotes=json_response["downvotes"], + ) + return answer \ No newline at end of file From 6dc0461dd700f10d3d25c4abaa7d030809dcf817 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Sun, 3 Mar 2024 21:50:21 +0800 Subject: [PATCH 05/39] Added update_answer to update an answer with specified id. --- grepper_python/main.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/grepper_python/main.py b/grepper_python/main.py index 1370c80..889acc8 100644 --- a/grepper_python/main.py +++ b/grepper_python/main.py @@ -100,7 +100,7 @@ def fetch_answer( """This function returns an answer specified by the id. Args: - id (int): The id for the specified answer. ex: "560676 ". + id (int, required): The id for the specified answer. ex: "560676 ". Returns: GrepperAnswer @@ -122,4 +122,31 @@ def fetch_answer( upvotes=json_response["upvotes"], downvotes=json_response["downvotes"], ) - return answer \ No newline at end of file + return answer + + def update_answer( + self, id: int, answer: str + ): + """This function returns an answer specified by the id. + + Args: + id (int, required): The id for the specified answer. ex: "560676 ". + answer (str, required): The answer you want it to update to. ex "new answer content here". + + Returns: + Dict + """ + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + } + data = f"""answer[content]={answer}""" + response = requests.post( + f"https://api.grepper.com/v1/answers/{id}", + headers=headers, + data=data + ) + if str(response.status_code) != "200": + exception = exception_handler(str(response.status_code)) + raise exception(exception.__doc__) + else: + return response.json() \ No newline at end of file From bf656b4eb94bad872da303d93b9db8b5cca5cfe6 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Sun, 3 Mar 2024 21:52:47 +0800 Subject: [PATCH 06/39] Changed class TestGrepper to Test --- tests/test_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_search.py b/tests/test_search.py index ce5408b..e94dc52 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -3,7 +3,7 @@ from grepper_python.main import Grepper -class TestGrepper(unittest.TestCase): +class Test(unittest.TestCase): def setUp(self): self.api_key = "my_api_key" self.grepper = Grepper(self.api_key) From d3c7dbed1af0f68d8c1137e1054651cd177e8979 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Sun, 3 Mar 2024 21:55:31 +0800 Subject: [PATCH 07/39] added unittest.main() to run tests --- tests/test_search.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_search.py b/tests/test_search.py index e94dc52..3270a22 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -64,3 +64,6 @@ def test_search_with_error(self, mock_get, mock_exception_handler): # Test that the exception handler was called with the correct parameter mock_exception_handler.assert_called_once_with("404") + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From cf542b0a6a404bcb196c2135a0212dfe17a73e05 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Sun, 3 Mar 2024 21:57:26 +0800 Subject: [PATCH 08/39] changed from grepper_python.main to from grepper_python --- tests/test_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_search.py b/tests/test_search.py index 3270a22..c0f0716 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -1,6 +1,6 @@ import unittest from unittest.mock import patch, Mock -from grepper_python.main import Grepper +from grepper_python import Grepper class Test(unittest.TestCase): From 8bf68c18d1536678bc2f5d9a858f2a6bf9cda188 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Sun, 3 Mar 2024 22:01:43 +0800 Subject: [PATCH 09/39] added test for fetch_answer --- tests/test_fetch_answer.py | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/test_fetch_answer.py diff --git a/tests/test_fetch_answer.py b/tests/test_fetch_answer.py new file mode 100644 index 0000000..51ae8dd --- /dev/null +++ b/tests/test_fetch_answer.py @@ -0,0 +1,71 @@ +import unittest +from unittest.mock import patch + +from grepper_python import Grepper +from grepper_python.exceptions import * + + +class Test(unittest.TestCase): + def setUp(self): + self.api_key = "my_api_key" # Replace with your actual API key + self.grepper = Grepper(self.api_key) + + @patch("requests.get") + def test_fetch_answer_success(self, mock_get): + # Mock successful API response + mock_response = mock_get.return_value + mock_response.status_code = 200 + mock_response.json.return_value = { + "id": 12345, + "title": "Sample answer title", + "content": "Sample answer content", + "author_name": "John Doe", + "author_profile_url": "https://www.example.com/johndoe", + "upvotes": 10, + "downvotes": 0, + } + + # Call the function + answer = self.grepper.fetch_answer(12345) + + # Assertions + self.assertEqual(answer.id, 12345) + self.assertEqual(answer.title, "Sample answer title") + self.assertEqual(answer.content, "Sample answer content") + self.assertEqual(answer.author_name, "John Doe") + self.assertEqual(answer.author_profile_url, "https://www.example.com/johndoe") + self.assertEqual(answer.upvotes, 10) + self.assertEqual(answer.downvotes, 0) + + @patch("requests.get") + def test_fetch_answer_not_found(self, mock_get): + # Mock API response with 404 status code + mock_response = mock_get.return_value + mock_response.status_code = 404 + mock_response.text = "Not Found" + + # Call the function and expect an exception + with self.assertRaises(NotFound) as cm: + self.grepper.fetch_answer(12345) + + self.assertEqual(str(cm.exception), "HTTPException: Not Found") + + @patch("requests.get") + def test_fetch_answer_other_error(self, mock_get): + # Mock API response with unexpected status code + mock_response = mock_get.return_value + mock_response.status_code = 500 + mock_response.text = "Internal Server Error" + + # Call the function and expect an exception + with self.assertRaises(Exception) as cm: + self.grepper.fetch_answer(12345) + + self.assertEqual( + str(cm.exception), + "HTTPException: Unexpected status code: 500 (Internal Server Error)", + ) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From fdfa092ed029abda82e275b9b1a0a9e3dffec2f1 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Sun, 3 Mar 2024 22:03:58 +0800 Subject: [PATCH 10/39] added unittest for update_answer --- tests/test_update_answer.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/test_update_answer.py diff --git a/tests/test_update_answer.py b/tests/test_update_answer.py new file mode 100644 index 0000000..b1f55d1 --- /dev/null +++ b/tests/test_update_answer.py @@ -0,0 +1,48 @@ +import unittest +from unittest.mock import patch + +from grepper_python import Grepper + + +class TestGrepper(unittest.TestCase): + def setUp(self): + self.api_key = "my_api_key" + self.grepper = Grepper(self.api_key) + + @patch("requests.post") + def test_update_answer_success(self, mock_post): + # Mock successful API response (without modifying data) + mock_response = mock_post.return_value + mock_response.status_code = 200 + mock_response.json.return_value = {"message": "Answer updated successfully"} + + # Call the function (simulating the update) + answer_id = 12345 # Replace with the ID of an answer you have permission to update + new_content = "updated content" # Replace with the desired update + + # Assertions (limited due to mocking) + self.grepper.update_answer(answer_id, new_content) + mock_post.assert_called_once_with( # Verify API call with placeholders + f"https://api.grepper.com/v1/answers/{answer_id}", + headers={"Content-Type": "application/x-www-form-urlencoded"}, + data=f"answer[content]={new_content}", + auth=(self.api_key, ""), + ) + + @patch("requests.post") + def test_update_answer_error(self, mock_post): + # Mock API error response + mock_response = mock_post.return_value + mock_response.status_code = 401 + mock_response.text = "Unauthorized" + + # Call the function and expect an exception + with self.assertRaises(Exception) as cm: + self.grepper.update_answer(12345, "new answer content") + + # Assertions (limited due to mocking) + self.assertEqual(str(cm.exception).startswith("HTTPException:"), True) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From edbc4ab4e9a8549ea06130d5421e11703cbd4a70 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Sun, 3 Mar 2024 22:10:31 +0800 Subject: [PATCH 11/39] added pip support --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c3123dd..d2659b0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ The Grepper Python library provides convenient access to the Grepper API from ap Python 3.7 and later. ## PIP -Coming soon. +``` +pip install grepper-python +``` ## Manual Installation ```bash From 8790f11ae6f25afacf01c2d0b279d6b31c0ff804 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Sun, 3 Mar 2024 22:32:20 +0800 Subject: [PATCH 12/39] removed class VersionInfo --- grepper_python/__init__.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/grepper_python/__init__.py b/grepper_python/__init__.py index 3470450..fb4b060 100644 --- a/grepper_python/__init__.py +++ b/grepper_python/__init__.py @@ -18,19 +18,6 @@ from .answer import GrepperAnswer from .main import Grepper - -class VersionInfo(NamedTuple): - major: int - minor: int - micro: int - releaselevel: Literal["alpha", "beta", "candidate", "final"] - serial: int - - -version_info: VersionInfo = VersionInfo( - major=0, minor=0, micro=1, releaselevel="alpha", serial=0 -) - logging.getLogger(__name__).addHandler(logging.NullHandler()) del ( From d79aaa4979cbad93be865e3d187db156cbfbb912 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 16:33:13 +0800 Subject: [PATCH 13/39] deleted unnecessary stuffs and added __all__ --- grepper_python/__init__.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/grepper_python/__init__.py b/grepper_python/__init__.py index fb4b060..0777dbf 100644 --- a/grepper_python/__init__.py +++ b/grepper_python/__init__.py @@ -10,18 +10,15 @@ __copyright__ = "Copyright 2010-2023 Grepper, Inc." __version__ = "0.0.1a" -__path__ = __import__("pkgutil").extend_path(__path__, __name__) +from grepper_python.answer import GrepperAnswer +from grepper_python.main import Grepper +from grepper_python.exceptions import BadRequest +from grepper_python.exceptions import Unauthorized +from grepper_python.exceptions import Forbidden +from grepper_python.exceptions import NotFound +from grepper_python.exceptions import MethodNotAllowed +from grepper_python.exceptions import TooManyRequests +from grepper_python.exceptions import InternalServerError +from grepper_python.exceptions import ServiceUnavailable -import logging -from typing import NamedTuple, Literal - -from .answer import GrepperAnswer -from .main import Grepper - -logging.getLogger(__name__).addHandler(logging.NullHandler()) - -del ( - logging, - NamedTuple, - Literal, -) +__all__ = ["GrepperAnswer", "Grepper", "BadRequest", "Unauthorized", "Forbidden", "NotFound", "MethodNotAllowed", "TooManyRequests", "InternalServerError", "ServiceUnavailable"] \ No newline at end of file From 6321d98ec09b2cdae142ee661ef0aeaf2295be8a Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 16:34:24 +0800 Subject: [PATCH 14/39] added exception base class for raising exceptions --- grepper_python/exceptions.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/grepper_python/exceptions.py b/grepper_python/exceptions.py index dea8e12..387a421 100644 --- a/grepper_python/exceptions.py +++ b/grepper_python/exceptions.py @@ -6,40 +6,40 @@ # 400 -class BadRequest: +class BadRequest(Exception): """Your request is invalid.""" # 401 -class Unauthorized: +class Unauthorized(Exception): """Your API key is wrong.""" # 403 -class Forbidden: +class Forbidden(Exception): """You do not have access to the requested resource.""" # 404 -class NotFound: +class NotFound(Exception): """The specified enpoint could not be found.""" # 405 -class MethodNotAllowed: +class MethodNotAllowed(Exception): """You tried to access an enpoint with an invalid method.""" # 429 -class TooManyRequests: +class TooManyRequests(Exception): """You're making too many requests! Slow down!""" # 500 -class InternalServerError: +class InternalServerError(Exception): """We had a problem with our server. Try again later.""" # 503 -class ServiceUnavailable: +class ServiceUnavailable(Exception): """We're temporarily offline for maintenance. Please try again later.""" From 911ab3f632971cbc2bd7174e37b34de7352ae760 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 16:35:31 +0800 Subject: [PATCH 15/39] changed import * to prevent errors --- grepper_python/main.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/grepper_python/main.py b/grepper_python/main.py index 889acc8..72cc5f3 100644 --- a/grepper_python/main.py +++ b/grepper_python/main.py @@ -27,7 +27,14 @@ from typing import Optional from .answer import GrepperAnswer -from .exceptions import * +from .exceptions import BadRequest +from .exceptions import Unauthorized +from .exceptions import Forbidden +from .exceptions import NotFound +from .exceptions import MethodNotAllowed +from .exceptions import TooManyRequests +from .exceptions import InternalServerError +from .exceptions import ServiceUnavailable import requests From 8c1927c73a7e6d689c1f4af70244076cf3709a29 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 16:38:35 +0800 Subject: [PATCH 16/39] changed __api_key to _api_key since there is no need for double underscore as thatt makes a name mangling --- grepper_python/main.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/grepper_python/main.py b/grepper_python/main.py index 72cc5f3..17ecf4c 100644 --- a/grepper_python/main.py +++ b/grepper_python/main.py @@ -62,9 +62,8 @@ class Grepper: """ Python Grepper API Wrapper """ - - def __init__(self, __api_key: str): - self.__api_key = __api_key + def __init__(self, api_key: str): + self._api_key = api_key def search( self, query: str = False, similarity: Optional[int] = 60 @@ -81,7 +80,7 @@ def search( response = requests.get( "https://api.grepper.com/v1/answers/search", params={"query": query, "similarity": similarity}, - auth=(self.__api_key, ""), + auth=(self._api_key, ""), ) if str(response.status_code) != "200": exception = exception_handler(str(response.status_code)) @@ -114,7 +113,7 @@ def fetch_answer( """ response = requests.get( f"https://api.grepper.com/v1/answers/{id}", - auth=(self.__api_key, "") + auth=(self._api_key, "") ) if str(response.status_code) != "200": exception = exception_handler(str(response.status_code)) @@ -150,7 +149,8 @@ def update_answer( response = requests.post( f"https://api.grepper.com/v1/answers/{id}", headers=headers, - data=data + data=data, + auth=(self._api_key, "") ) if str(response.status_code) != "200": exception = exception_handler(str(response.status_code)) From 540d09e61a3f041520480e93ea8cfc91da64c938 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 16:43:29 +0800 Subject: [PATCH 17/39] made the error message more customizable --- grepper_python/exceptions.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/grepper_python/exceptions.py b/grepper_python/exceptions.py index 387a421..3fe31ff 100644 --- a/grepper_python/exceptions.py +++ b/grepper_python/exceptions.py @@ -4,42 +4,45 @@ This module contains the set of grepper-python's exceptions. """ +class DocDefaultException(Exception): + def __init__(self, msg:str=None, *args, **kwargs): + super().__init__(msg or self.__doc__, *args, **kwargs) # 400 -class BadRequest(Exception): +class BadRequest(DocDefaultException): """Your request is invalid.""" # 401 -class Unauthorized(Exception): +class Unauthorized(DocDefaultException): """Your API key is wrong.""" # 403 -class Forbidden(Exception): +class Forbidden(DocDefaultException): """You do not have access to the requested resource.""" # 404 -class NotFound(Exception): +class NotFound(DocDefaultException): """The specified enpoint could not be found.""" # 405 -class MethodNotAllowed(Exception): +class MethodNotAllowed(DocDefaultException): """You tried to access an enpoint with an invalid method.""" # 429 -class TooManyRequests(Exception): +class TooManyRequests(DocDefaultException): """You're making too many requests! Slow down!""" # 500 -class InternalServerError(Exception): +class InternalServerError(DocDefaultException): """We had a problem with our server. Try again later.""" # 503 -class ServiceUnavailable(Exception): +class ServiceUnavailable(DocDefaultException): """We're temporarily offline for maintenance. Please try again later.""" From 5b376ec030b287dd7925c86b61dfeeb7ba30bfeb Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 16:44:25 +0800 Subject: [PATCH 18/39] changed str(response code) to just response code --- grepper_python/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grepper_python/main.py b/grepper_python/main.py index 17ecf4c..baae005 100644 --- a/grepper_python/main.py +++ b/grepper_python/main.py @@ -82,7 +82,7 @@ def search( params={"query": query, "similarity": similarity}, auth=(self._api_key, ""), ) - if str(response.status_code) != "200": + if response.status_code != 200: exception = exception_handler(str(response.status_code)) raise exception(exception.__doc__) json_response = response.json() @@ -115,7 +115,7 @@ def fetch_answer( f"https://api.grepper.com/v1/answers/{id}", auth=(self._api_key, "") ) - if str(response.status_code) != "200": + if response.status_code != 200: exception = exception_handler(str(response.status_code)) raise exception(exception.__doc__) json_response = response.json() @@ -152,7 +152,7 @@ def update_answer( data=data, auth=(self._api_key, "") ) - if str(response.status_code) != "200": + if response.status_code != 200: exception = exception_handler(str(response.status_code)) raise exception(exception.__doc__) else: From f0004cba1e99b642450bffaf0b8987a05a47bd64 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 16:45:26 +0800 Subject: [PATCH 19/39] changed for i to for answer for readability --- grepper_python/main.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/grepper_python/main.py b/grepper_python/main.py index baae005..3aea73a 100644 --- a/grepper_python/main.py +++ b/grepper_python/main.py @@ -87,15 +87,15 @@ def search( raise exception(exception.__doc__) json_response = response.json() data = [] - for i in json_response["data"]: + for answer in json_response["data"]: new_answer = GrepperAnswer( - id=i["id"], - content=i["content"], - author_name=i["author_name"], - author_profile_url=i["author_profile_url"], - title=i["title"], - upvotes=i["upvotes"], - downvotes=i["downvotes"], + id=answer["id"], + content=answer["content"], + author_name=answer["author_name"], + author_profile_url=answer["author_profile_url"], + title=answer["title"], + upvotes=answer["upvotes"], + downvotes=answer["downvotes"], ) data.append(new_answer) return data From b50fa0475a88d201d8c267aca9d243e8b9ffd895 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 16:48:04 +0800 Subject: [PATCH 20/39] changed msg from str to any --- grepper_python/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grepper_python/exceptions.py b/grepper_python/exceptions.py index 3fe31ff..0bc619c 100644 --- a/grepper_python/exceptions.py +++ b/grepper_python/exceptions.py @@ -5,7 +5,7 @@ """ class DocDefaultException(Exception): - def __init__(self, msg:str=None, *args, **kwargs): + def __init__(self, msg=None, *args, **kwargs): super().__init__(msg or self.__doc__, *args, **kwargs) # 400 From 3d10b53fff1c0a3cc2eb8c3d4c0ee7f2716f71eb Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 16:49:13 +0800 Subject: [PATCH 21/39] optimized raising exception --- grepper_python/main.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/grepper_python/main.py b/grepper_python/main.py index 3aea73a..e8d7f4c 100644 --- a/grepper_python/main.py +++ b/grepper_python/main.py @@ -24,7 +24,7 @@ from __future__ import annotations -from typing import Optional +from typing import Optional, List from .answer import GrepperAnswer from .exceptions import BadRequest @@ -67,7 +67,7 @@ def __init__(self, api_key: str): def search( self, query: str = False, similarity: Optional[int] = 60 - ): + ) -> List[GrepperAnswer]: """This function searches all answers based on a query. Args: @@ -83,8 +83,7 @@ def search( auth=(self._api_key, ""), ) if response.status_code != 200: - exception = exception_handler(str(response.status_code)) - raise exception(exception.__doc__) + raise exception_handler(response.status_code) json_response = response.json() data = [] for answer in json_response["data"]: @@ -102,7 +101,7 @@ def search( def fetch_answer( self, id: int - ): + ) -> GrepperAnswer: """This function returns an answer specified by the id. Args: @@ -116,8 +115,7 @@ def fetch_answer( auth=(self._api_key, "") ) if response.status_code != 200: - exception = exception_handler(str(response.status_code)) - raise exception(exception.__doc__) + raise exception_handler(response.status_code) json_response = response.json() answer = GrepperAnswer( id=json_response["id"], @@ -153,7 +151,6 @@ def update_answer( auth=(self._api_key, "") ) if response.status_code != 200: - exception = exception_handler(str(response.status_code)) - raise exception(exception.__doc__) + raise exception_handler(response.status_code) else: return response.json() \ No newline at end of file From 9f1966db17312815f279b87df2fe1aa551907f1e Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 17:05:38 +0800 Subject: [PATCH 22/39] added pyproject.toml --- pyproject.toml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f656d8d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "grepper-python" +version ="0.0.1a" +dependencies = [ + "requests", + "urllib3" +] +authors = [ + {name = "CodeGrepper", email = "support@grepper.com"}, +] +description = "An API wrapper for the Grepper API." +readme = "README.md" +license = {file = "LICENSE"} +classifiers = [ + "Programming Language :: Python :: 3", +] +requires-python = ">=3.7" + +[project.urls] +Repository="https://github.com/CodeGrepper/grepper-python" \ No newline at end of file From 854cd5898d15fb734e7bf2ce446ef99ba0727187 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 17:10:15 +0800 Subject: [PATCH 23/39] updated --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f656d8d..3c73adb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "grepper-python" -version ="0.0.1a" +dynamic = ["version"] dependencies = [ "requests", "urllib3" From 48e4af4e5ac68a711fc8a347534a2fa76c624032 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 17:10:38 +0800 Subject: [PATCH 24/39] wrote exceptions in a tuple --- grepper_python/__init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/grepper_python/__init__.py b/grepper_python/__init__.py index 0777dbf..e1edaf1 100644 --- a/grepper_python/__init__.py +++ b/grepper_python/__init__.py @@ -12,13 +12,15 @@ from grepper_python.answer import GrepperAnswer from grepper_python.main import Grepper -from grepper_python.exceptions import BadRequest -from grepper_python.exceptions import Unauthorized -from grepper_python.exceptions import Forbidden -from grepper_python.exceptions import NotFound -from grepper_python.exceptions import MethodNotAllowed -from grepper_python.exceptions import TooManyRequests -from grepper_python.exceptions import InternalServerError -from grepper_python.exceptions import ServiceUnavailable +from grepper_python.exceptions import ( + BadRequest, + Unauthorized, + Forbidden, + NotFound, + MethodNotAllowed, + TooManyRequests, + InternalServerError, + ServiceUnavailable +) __all__ = ["GrepperAnswer", "Grepper", "BadRequest", "Unauthorized", "Forbidden", "NotFound", "MethodNotAllowed", "TooManyRequests", "InternalServerError", "ServiceUnavailable"] \ No newline at end of file From 376cfb06628f8790ab4b6c3f7bc10e9fe67ed975 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 17:11:05 +0800 Subject: [PATCH 25/39] changed version to static instead of dynamic --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3c73adb..cb6045e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "grepper-python" -dynamic = ["version"] +version = "0.0.1a" dependencies = [ "requests", "urllib3" From 3d5f2a2c22229604be4904f6a1886dae8159e85c Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 17:12:02 +0800 Subject: [PATCH 26/39] changed exception_handler to support int instead of just string --- grepper_python/main.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/grepper_python/main.py b/grepper_python/main.py index e8d7f4c..069003d 100644 --- a/grepper_python/main.py +++ b/grepper_python/main.py @@ -39,22 +39,22 @@ import requests -def exception_handler(status_code: str): - if status_code == "400": +def exception_handler(status_code): + if status_code == 400: return BadRequest - elif status_code == "401": + elif status_code == 401: return Unauthorized - elif status_code == "403": + elif status_code == 403: return Forbidden - elif status_code == "404": + elif status_code == 404: return NotFound - elif status_code == "405": + elif status_code == 405: return MethodNotAllowed - elif status_code == "429": + elif status_code == 429: return TooManyRequests - elif status_code == "500": + elif status_code == 500: return InternalServerError - elif status_code == "503": + elif status_code == 503: return ServiceUnavailable From 389477b62c272ab244d8c9fc490cdd64c56d13a6 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 17:12:37 +0800 Subject: [PATCH 27/39] nvm changign back to dynamic --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cb6045e..3c73adb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "grepper-python" -version = "0.0.1a" +dynamic = ["version"] dependencies = [ "requests", "urllib3" From 8e02f5f9d9f4882cf046173c8b6b29e2a2cffcab Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 17:13:36 +0800 Subject: [PATCH 28/39] changed import * to avoid problems --- tests/test_fetch_answer.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_fetch_answer.py b/tests/test_fetch_answer.py index 51ae8dd..9602f78 100644 --- a/tests/test_fetch_answer.py +++ b/tests/test_fetch_answer.py @@ -2,7 +2,16 @@ from unittest.mock import patch from grepper_python import Grepper -from grepper_python.exceptions import * +from grepper_python.exceptions import ( + BadRequest, + Unauthorized, + Forbidden, + NotFound, + MethodNotAllowed, + TooManyRequests, + InternalServerError, + ServiceUnavailable +) class Test(unittest.TestCase): From e2d649e8417fa9f8f2a4b4535dfdc89d9ae2b779 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 17:14:54 +0800 Subject: [PATCH 29/39] removed comments --- tests/test_fetch_answer.py | 8 +------- tests/test_search.py | 6 ------ tests/test_update_answer.py | 12 +++--------- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/tests/test_fetch_answer.py b/tests/test_fetch_answer.py index 9602f78..497ed0a 100644 --- a/tests/test_fetch_answer.py +++ b/tests/test_fetch_answer.py @@ -16,12 +16,11 @@ class Test(unittest.TestCase): def setUp(self): - self.api_key = "my_api_key" # Replace with your actual API key + self.api_key = "my_api_key" self.grepper = Grepper(self.api_key) @patch("requests.get") def test_fetch_answer_success(self, mock_get): - # Mock successful API response mock_response = mock_get.return_value mock_response.status_code = 200 mock_response.json.return_value = { @@ -34,7 +33,6 @@ def test_fetch_answer_success(self, mock_get): "downvotes": 0, } - # Call the function answer = self.grepper.fetch_answer(12345) # Assertions @@ -48,12 +46,10 @@ def test_fetch_answer_success(self, mock_get): @patch("requests.get") def test_fetch_answer_not_found(self, mock_get): - # Mock API response with 404 status code mock_response = mock_get.return_value mock_response.status_code = 404 mock_response.text = "Not Found" - # Call the function and expect an exception with self.assertRaises(NotFound) as cm: self.grepper.fetch_answer(12345) @@ -61,12 +57,10 @@ def test_fetch_answer_not_found(self, mock_get): @patch("requests.get") def test_fetch_answer_other_error(self, mock_get): - # Mock API response with unexpected status code mock_response = mock_get.return_value mock_response.status_code = 500 mock_response.text = "Internal Server Error" - # Call the function and expect an exception with self.assertRaises(Exception) as cm: self.grepper.fetch_answer(12345) diff --git a/tests/test_search.py b/tests/test_search.py index c0f0716..cf94648 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -10,7 +10,6 @@ def setUp(self): @patch("grepper_python.main.requests.get") def test_search(self, mock_get): - # Mock the response from the API mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { @@ -28,7 +27,6 @@ def test_search(self, mock_get): } mock_get.return_value = mock_response - # Test the search function results = self.grepper.search( query="example query", similarity=80 ) @@ -43,7 +41,6 @@ def test_search(self, mock_get): self.assertEqual(results[0].upvotes, 10) self.assertEqual(results[0].downvotes, 2) - # Test that the API was called with the correct parameters mock_get.assert_called_once_with( "https://api.grepper.com/v1/answers/search", params={"query": "example query", "similarity": 80}, @@ -53,16 +50,13 @@ def test_search(self, mock_get): @patch("grepper_python.main.exception_handler") @patch("grepper_python.main.requests.get") def test_search_with_error(self, mock_get, mock_exception_handler): - # Mock the response from the API with an error status code mock_response = Mock() mock_response.status_code = 404 mock_get.return_value = mock_response - # Test the search function with an error response with self.assertRaises(Exception): self.grepper.search(query="example query", similarity=80) - # Test that the exception handler was called with the correct parameter mock_exception_handler.assert_called_once_with("404") if __name__ == "__main__": diff --git a/tests/test_update_answer.py b/tests/test_update_answer.py index b1f55d1..2e3b623 100644 --- a/tests/test_update_answer.py +++ b/tests/test_update_answer.py @@ -11,18 +11,15 @@ def setUp(self): @patch("requests.post") def test_update_answer_success(self, mock_post): - # Mock successful API response (without modifying data) mock_response = mock_post.return_value mock_response.status_code = 200 mock_response.json.return_value = {"message": "Answer updated successfully"} - # Call the function (simulating the update) - answer_id = 12345 # Replace with the ID of an answer you have permission to update - new_content = "updated content" # Replace with the desired update + answer_id = 12345 + new_content = "updated content" - # Assertions (limited due to mocking) self.grepper.update_answer(answer_id, new_content) - mock_post.assert_called_once_with( # Verify API call with placeholders + mock_post.assert_called_once_with( f"https://api.grepper.com/v1/answers/{answer_id}", headers={"Content-Type": "application/x-www-form-urlencoded"}, data=f"answer[content]={new_content}", @@ -31,16 +28,13 @@ def test_update_answer_success(self, mock_post): @patch("requests.post") def test_update_answer_error(self, mock_post): - # Mock API error response mock_response = mock_post.return_value mock_response.status_code = 401 mock_response.text = "Unauthorized" - # Call the function and expect an exception with self.assertRaises(Exception) as cm: self.grepper.update_answer(12345, "new answer content") - # Assertions (limited due to mocking) self.assertEqual(str(cm.exception).startswith("HTTPException:"), True) From 9be5d0f6686e00f6862409ec23b01382f4a0ddda Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 17:15:18 +0800 Subject: [PATCH 30/39] changed from importing all exceptions to only NotFound --- tests/test_fetch_answer.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/test_fetch_answer.py b/tests/test_fetch_answer.py index 497ed0a..c3fcfa7 100644 --- a/tests/test_fetch_answer.py +++ b/tests/test_fetch_answer.py @@ -2,16 +2,7 @@ from unittest.mock import patch from grepper_python import Grepper -from grepper_python.exceptions import ( - BadRequest, - Unauthorized, - Forbidden, - NotFound, - MethodNotAllowed, - TooManyRequests, - InternalServerError, - ServiceUnavailable -) +from grepper_python.exceptions import NotFound class Test(unittest.TestCase): From 94122208e82db213b9545458745c97061aa6a118 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 18:32:09 +0800 Subject: [PATCH 31/39] renamed DocDefaultException to GrepperDefaultException --- grepper_python/exceptions.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/grepper_python/exceptions.py b/grepper_python/exceptions.py index 0bc619c..8217d47 100644 --- a/grepper_python/exceptions.py +++ b/grepper_python/exceptions.py @@ -4,45 +4,45 @@ This module contains the set of grepper-python's exceptions. """ -class DocDefaultException(Exception): +class GrepperDefaultException(Exception): def __init__(self, msg=None, *args, **kwargs): super().__init__(msg or self.__doc__, *args, **kwargs) # 400 -class BadRequest(DocDefaultException): +class BadRequest(GrepperDefaultException): """Your request is invalid.""" # 401 -class Unauthorized(DocDefaultException): +class Unauthorized(GrepperDefaultException): """Your API key is wrong.""" # 403 -class Forbidden(DocDefaultException): +class Forbidden(GrepperDefaultException): """You do not have access to the requested resource.""" # 404 -class NotFound(DocDefaultException): +class NotFound(GrepperDefaultException): """The specified enpoint could not be found.""" # 405 -class MethodNotAllowed(DocDefaultException): +class MethodNotAllowed(GrepperDefaultException): """You tried to access an enpoint with an invalid method.""" # 429 -class TooManyRequests(DocDefaultException): +class TooManyRequests(GrepperDefaultException): """You're making too many requests! Slow down!""" # 500 -class InternalServerError(DocDefaultException): +class InternalServerError(GrepperDefaultException): """We had a problem with our server. Try again later.""" # 503 -class ServiceUnavailable(DocDefaultException): +class ServiceUnavailable(GrepperDefaultException): """We're temporarily offline for maintenance. Please try again later.""" From 8ce26252350c2ed75530d762977344fd510fab44 Mon Sep 17 00:00:00 2001 From: CantCode023 Date: Mon, 4 Mar 2024 18:32:47 +0800 Subject: [PATCH 32/39] changed relative imports to absolute imports --- grepper_python/main.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/grepper_python/main.py b/grepper_python/main.py index 069003d..931dd3d 100644 --- a/grepper_python/main.py +++ b/grepper_python/main.py @@ -26,15 +26,17 @@ from typing import Optional, List -from .answer import GrepperAnswer -from .exceptions import BadRequest -from .exceptions import Unauthorized -from .exceptions import Forbidden -from .exceptions import NotFound -from .exceptions import MethodNotAllowed -from .exceptions import TooManyRequests -from .exceptions import InternalServerError -from .exceptions import ServiceUnavailable +from grepper_python.answer import GrepperAnswer +from grepper_python.exceptions import ( + BadRequest, + Unauthorized, + Forbidden, + NotFound, + MethodNotAllowed, + TooManyRequests, + InternalServerError, + ServiceUnavailable +) import requests From 333203f69e1826e611daa91bc404e3bd172c5fe9 Mon Sep 17 00:00:00 2001 From: Tanvish <101194211+TanvishGG@users.noreply.github.com> Date: Fri, 21 Jun 2024 08:15:21 +0000 Subject: [PATCH 33/39] updated docs for 2 methods --- README.md | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d2659b0..7f81680 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,88 @@ cd grepper-python python setup.py install ``` -## Getting Started -Simple usage: +## Getting Grepper API key +Visit [Grepper Account Settings](https://www.grepper.com/app/settings-account.php) to get your API key. + +## Initialising a new Grepper Class +```py +grepper = Grepper("Your grepper API Key") +``` +The following code initialises a new `Grepper` Class. + +## GrepperAnswer Type Class Reference +```py +class GrepperAnswer: + id: int + content: str + author_name: str + author_profile_url: str + title: str + upvotes: int + downvotes: int +``` +Refer the above type class whenever you see `GrepperAnswer` + +--- +## Available methods + +## search +This function searches all answers based on a query. + +### Arguments + - `query (str, optional)`: Query to search through answer titles. ex: "Javascript loop array backwords". Defaults to False. + - `similarity (Optional[int], optional)`: How similar the query has to be to the answer title. 1-100 where 1 is really loose matching and 100 is really strict/tight match. Defaults to 60. +### Result +Search returns a Array of `GrepperAnswer` type classes on successful query + +### Examples +#### Example query ```py grepper = Grepper("your-grepper-api-key") -answers = grepper.search("query") +answers = grepper.search("javascript loop array backwords") print(answers) -``` \ No newline at end of file +``` +#### Example Output +```py +[ + { + "id": 560676, + "content": "let arr = [1, 2, 3];\n\narr.slice().reverse().forEach(x => console.log(x))\n Run code snippetHide results", + "author_name": "Homely Hyena", + "author_profile_url": "https://www.grepper.com/profile/homely-hyena-qrcy8ksj0gew", + "title": "javascript loop through array backwords", + "upvotes": 0, + "object": "answer", + "downvotes": 0 + } +] +``` +--- +## fetch_answer +This function returns an answer specified by the id. +### Arguments + - `id (int, required)`: The id for the specified answer. ex: "560676 ". +### Result +fetch_answer returns `GrepperAnswer` type class on successful search. + +### Examples +#### Example fetch_answer +```py +grepper = Grepper("your-grepper-api-key") +answer = grepper.fetch_answer("504956") +print(answer) +``` +### Example Output +```py +{ + "id": 504956, + "content": "var arr=[1,2,3];\narr.reverse().forEach(x=> console.log(x))", + "author_name": "Yanislav Ivanov", + "author_profile_url": "https://www.grepper.com/profile/yanislav-ivanov-r2lfrl14s6xy", + "title": "js loop array back", + "upvotes": 2, + "object": "answer", + "downvotes": 2 + } +``` +--- \ No newline at end of file From 0480e8f61a86db17bcf4bae6a6e88b5de047c38e Mon Sep 17 00:00:00 2001 From: Tanvish <101194211+TanvishGG@users.noreply.github.com> Date: Fri, 21 Jun 2024 08:40:47 +0000 Subject: [PATCH 34/39] added 3rd method docs --- README.md | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7f81680..b38a8f1 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,7 @@ class GrepperAnswer: Refer the above type class whenever you see `GrepperAnswer` --- -## Available methods - -## search +# search This function searches all answers based on a query. ### Arguments @@ -73,10 +71,10 @@ print(answers) ] ``` --- -## fetch_answer +# fetch_answer This function returns an answer specified by the id. ### Arguments - - `id (int, required)`: The id for the specified answer. ex: "560676 ". + - `id (int, required)`: The id for the specified answer. ex: "504956 ". ### Result fetch_answer returns `GrepperAnswer` type class on successful search. @@ -100,4 +98,29 @@ print(answer) "downvotes": 2 } ``` ---- \ No newline at end of file +--- + +# update_answer +This function updates/edits the answer of specified id. +- `NOTE:` This endpoint is in progress and not yet available according to grepper API docs. +### Arguments + - `id (int, required)`: The id for the specified answer. ex: "504956 ". + - `answer (str, required)`: The answer you want it to update to. ex "new answer content here". +### Result +returns a `Dict` + +### Examples +#### Example update_answer +```py +grepper = Grepper("your-grepper-api-key") +result = grepper.update_answer(id="504956",answer="The new edited answer") +print(result) +``` +#### Example Result +```py +{ + id: 2, + success: "true" +} +``` +--- From 10b288488e40092aa8f8208064f1548fe9158d3a Mon Sep 17 00:00:00 2001 From: gaghackz <68729994+gaghackz@users.noreply.github.com> Date: Fri, 21 Jun 2024 09:36:58 +0000 Subject: [PATCH 35/39] Commit the sequel. --- README.md | 59 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b38a8f1..e1550b7 100644 --- a/README.md +++ b/README.md @@ -39,36 +39,47 @@ class GrepperAnswer: Refer the above type class whenever you see `GrepperAnswer` --- -# search +## Search function + This function searches all answers based on a query. -### Arguments - - `query (str, optional)`: Query to search through answer titles. ex: "Javascript loop array backwords". Defaults to False. - - `similarity (Optional[int], optional)`: How similar the query has to be to the answer title. 1-100 where 1 is really loose matching and 100 is really strict/tight match. Defaults to 60. -### Result -Search returns a Array of `GrepperAnswer` type classes on successful query +## Arguements required + +1. ``query (str, optional)``: Query to search through answer titles. +2. ``similarity (Optional[int], optional)``: How similar the query has to be to the answer title. 1-100 where 1 is really loose matching and 100 is really strict/tight match. Defaults to 60. + +## Returned value + +GrepperAnswer + +## Example of the function by using in a code: -### Examples -#### Example query ```py -grepper = Grepper("your-grepper-api-key") -answers = grepper.search("javascript loop array backwords") -print(answers) +import grepper_python + +grepper = grepper_python.Grepper("YOUR API") + +data = grepper.search("cat videos") +for i in data: + print(i) ``` -#### Example Output + +## Output + ```py -[ - { - "id": 560676, - "content": "let arr = [1, 2, 3];\n\narr.slice().reverse().forEach(x => console.log(x))\n Run code snippetHide results", - "author_name": "Homely Hyena", - "author_profile_url": "https://www.grepper.com/profile/homely-hyena-qrcy8ksj0gew", - "title": "javascript loop through array backwords", - "upvotes": 0, - "object": "answer", - "downvotes": 0 - } -] + +GrepperAnswer(id=667265, content='{"tags":[{"tag":"p","content":"Get back to work"}]}', + +author_name='Smyth Family', + +author_profile_url='https://www.grepper.com/profile/smyth-family', + + title='cat videos', + + upvotes=4, + + downvotes=0) + ``` --- # fetch_answer From 8665dc477335d4078cd4307e42a3fbcca257ccf2 Mon Sep 17 00:00:00 2001 From: Bharath1910 Date: Fri, 21 Jun 2024 15:38:51 +0530 Subject: [PATCH 36/39] fixed consistency issues and added better example for search --- README.md | 111 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 61 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index e1550b7..893df4a 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,18 @@ cd grepper-python python setup.py install ``` -## Getting Grepper API key -Visit [Grepper Account Settings](https://www.grepper.com/app/settings-account.php) to get your API key. + +# Usage ## Initialising a new Grepper Class ```py +from grepper_python import Grepper + grepper = Grepper("Your grepper API Key") ``` +> Visit [Grepper Account Settings](https://www.grepper.com/app/settings-account.php) to get your API key. + + The following code initialises a new `Grepper` Class. ## GrepperAnswer Type Class Reference @@ -39,99 +44,105 @@ class GrepperAnswer: Refer the above type class whenever you see `GrepperAnswer` --- -## Search function +## 1. Search function This function searches all answers based on a query. -## Arguements required +### Arguments required 1. ``query (str, optional)``: Query to search through answer titles. 2. ``similarity (Optional[int], optional)``: How similar the query has to be to the answer title. 1-100 where 1 is really loose matching and 100 is really strict/tight match. Defaults to 60. -## Returned value +### Returned value GrepperAnswer -## Example of the function by using in a code: +### Example: ```py -import grepper_python - -grepper = grepper_python.Grepper("YOUR API") - -data = grepper.search("cat videos") -for i in data: - print(i) +from grepper_python import Grepper + +grepper = Grepper("YOUR_API_KEY") +data = grepper.search("git abort command") + +# Returns list of GrepperAnswer objects +for answer in data: + print(answer.id) + print(answer.content) + print(answer.author_name) + print(answer.author_profile_url) + print(answer.title) + print(answer.upvotes) + print(answer.downvotes) ``` -## Output - -```py - -GrepperAnswer(id=667265, content='{"tags":[{"tag":"p","content":"Get back to work"}]}', - -author_name='Smyth Family', - -author_profile_url='https://www.grepper.com/profile/smyth-family', - - title='cat videos', - - upvotes=4, - - downvotes=0) +### Output +``` +674394 +git merge --abort +git rebase --abort +# if you are still into some command on git and got back # +Magnificent Moose +https://www.grepper.com/profile/mou-biswas +abort a git command +0 +0 ``` --- -# fetch_answer + +## 2. fetch_answer This function returns an answer specified by the id. + ### Arguments - - `id (int, required)`: The id for the specified answer. ex: "504956 ". + - `id (int, required)`: The id for the specified answer. ex: 504956. ### Result fetch_answer returns `GrepperAnswer` type class on successful search. ### Examples -#### Example fetch_answer ```py -grepper = Grepper("your-grepper-api-key") -answer = grepper.fetch_answer("504956") +grepper = Grepper("YOUR_API_KEY") +answer = grepper.fetch_answer(504956) print(answer) ``` -### Example Output +### Output ```py { - "id": 504956, - "content": "var arr=[1,2,3];\narr.reverse().forEach(x=> console.log(x))", - "author_name": "Yanislav Ivanov", - "author_profile_url": "https://www.grepper.com/profile/yanislav-ivanov-r2lfrl14s6xy", - "title": "js loop array back", - "upvotes": 2, - "object": "answer", - "downvotes": 2 - } + "id": 504956, + "content": "var arr=[1,2,3];\narr.reverse().forEach(x=> console.log(x))", + "author_name": "Yanislav Ivanov", + "author_profile_url": "https://www.grepper.com/profile/yanislav-ivanov-r2lfrl14s6xy", + "title": "js loop array back", + "upvotes": 2, + "object": "answer", + "downvotes": 2 +} ``` --- -# update_answer +## 3. update_answer This function updates/edits the answer of specified id. - `NOTE:` This endpoint is in progress and not yet available according to grepper API docs. + ### Arguments - `id (int, required)`: The id for the specified answer. ex: "504956 ". - `answer (str, required)`: The answer you want it to update to. ex "new answer content here". + ### Result returns a `Dict` -### Examples -#### Example update_answer +### Example ```py -grepper = Grepper("your-grepper-api-key") +grepper = Grepper("YOUR_API_KEY") result = grepper.update_answer(id="504956",answer="The new edited answer") print(result) ``` -#### Example Result + +### Output ```py { - id: 2, - success: "true" + id: 2, + success: "true" } ``` --- From 5c7668963f81784ed5856ef9364cf1e856a8472d Mon Sep 17 00:00:00 2001 From: Bharath1910 Date: Fri, 21 Jun 2024 15:43:11 +0530 Subject: [PATCH 37/39] fixed consistency issues --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 893df4a..cc37bcb 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ python setup.py install # Usage ## Initialising a new Grepper Class +The following code initialises a new `Grepper` Class. + ```py from grepper_python import Grepper @@ -28,8 +30,6 @@ grepper = Grepper("Your grepper API Key") > Visit [Grepper Account Settings](https://www.grepper.com/app/settings-account.php) to get your API key. -The following code initialises a new `Grepper` Class. - ## GrepperAnswer Type Class Reference ```py class GrepperAnswer: @@ -48,7 +48,7 @@ Refer the above type class whenever you see `GrepperAnswer` This function searches all answers based on a query. -### Arguments required +### Required parameters 1. ``query (str, optional)``: Query to search through answer titles. 2. ``similarity (Optional[int], optional)``: How similar the query has to be to the answer title. 1-100 where 1 is really loose matching and 100 is really strict/tight match. Defaults to 60. @@ -94,7 +94,7 @@ abort a git command ## 2. fetch_answer This function returns an answer specified by the id. -### Arguments +### Required parameters - `id (int, required)`: The id for the specified answer. ex: 504956. ### Result fetch_answer returns `GrepperAnswer` type class on successful search. @@ -124,7 +124,7 @@ print(answer) This function updates/edits the answer of specified id. - `NOTE:` This endpoint is in progress and not yet available according to grepper API docs. -### Arguments +### Required parameters - `id (int, required)`: The id for the specified answer. ex: "504956 ". - `answer (str, required)`: The answer you want it to update to. ex "new answer content here". From ada403eb96447b522018e3e8733b519836cd4e48 Mon Sep 17 00:00:00 2001 From: Bharath Date: Fri, 21 Jun 2024 15:54:28 +0530 Subject: [PATCH 38/39] fixed spacing --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index cc37bcb..de1325e 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ This function searches all answers based on a query. ### Returned value -GrepperAnswer +list[[GrepperAnswer](#grepperanswer-type-class-reference)] ### Example: @@ -67,13 +67,13 @@ data = grepper.search("git abort command") # Returns list of GrepperAnswer objects for answer in data: - print(answer.id) - print(answer.content) - print(answer.author_name) - print(answer.author_profile_url) - print(answer.title) - print(answer.upvotes) - print(answer.downvotes) + print(answer.id) + print(answer.content) + print(answer.author_name) + print(answer.author_profile_url) + print(answer.title) + print(answer.upvotes) + print(answer.downvotes) ``` ### Output @@ -108,14 +108,14 @@ print(answer) ### Output ```py { - "id": 504956, - "content": "var arr=[1,2,3];\narr.reverse().forEach(x=> console.log(x))", - "author_name": "Yanislav Ivanov", - "author_profile_url": "https://www.grepper.com/profile/yanislav-ivanov-r2lfrl14s6xy", - "title": "js loop array back", - "upvotes": 2, - "object": "answer", - "downvotes": 2 + "id": 504956, + "content": "var arr=[1,2,3];\narr.reverse().forEach(x=> console.log(x))", + "author_name": "Yanislav Ivanov", + "author_profile_url": "https://www.grepper.com/profile/yanislav-ivanov-r2lfrl14s6xy", + "title": "js loop array back", + "upvotes": 2, + "object": "answer", + "downvotes": 2 } ``` --- @@ -141,8 +141,8 @@ print(result) ### Output ```py { - id: 2, - success: "true" + id: 2, + success: "true" } ``` --- From fd8b11864059d625ede5463b03059bd24a4ed6d9 Mon Sep 17 00:00:00 2001 From: bd Date: Fri, 21 Jun 2024 19:41:47 +0800 Subject: [PATCH 39/39] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de1325e..9de5852 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ returns a `Dict` ### Example ```py grepper = Grepper("YOUR_API_KEY") -result = grepper.update_answer(id="504956",answer="The new edited answer") +result = grepper.update_answer(id=504956,answer="The new edited answer") print(result) ```