├── .github └── workflows │ └── python-publish.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── examples └── ex_newsdataapi_request.py ├── newsdata-logo.png ├── newsdataapi ├── __init__.py ├── constants.py ├── helpers.py ├── newsdataapi_client.py ├── newsdataapi_exception.py └── utils.py ├── requirements.txt ├── setup.py ├── tests ├── __init__.py └── test_newsdataapi.py └── tox.ini /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.8' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine pytest 25 | - name: Build and publish 26 | env: 27 | PYTEST_TOKEN: ${{ secrets.PYTEST_NEWSDATA_API }} 28 | TWINE_USERNAME: __token__ 29 | TWINE_PASSWORD: ${{ secrets.NEWSDATAAPI_TOKEN }} 30 | run: | 31 | pytest 32 | python setup.py sdist bdist_wheel 33 | twine upload dist/* 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | project_env 7 | my_test.py 8 | .vscode/ 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | pip-wheel-metadata/ 31 | share/python-wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | MANIFEST 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .nox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | *.py,cover 58 | .hypothesis/ 59 | .pytest_cache/ 60 | pytestdebug.log 61 | 62 | # Translations 63 | *.mo 64 | *.pot 65 | 66 | # Django stuff: 67 | *.log 68 | local_settings.py 69 | db.sqlite3 70 | db.sqlite3-journal 71 | 72 | # Flask stuff: 73 | instance/ 74 | .webassets-cache 75 | 76 | # Scrapy stuff: 77 | .scrapy 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | doc/_build/ 82 | 83 | # PyBuilder 84 | target/ 85 | 86 | # Jupyter Notebook 87 | .ipynb_checkpoints 88 | 89 | # IPython 90 | profile_default/ 91 | ipython_config.py 92 | 93 | # pyenv 94 | .python-version 95 | 96 | # pipenv 97 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 98 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 99 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 100 | # install all needed dependencies. 101 | #Pipfile.lock 102 | 103 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 104 | __pypackages__/ 105 | 106 | # Celery stuff 107 | celerybeat-schedule 108 | celerybeat.pid 109 | 110 | # SageMath parsed files 111 | *.sage.py 112 | 113 | # Environments 114 | .env 115 | .venv 116 | env/ 117 | venv/ 118 | ENV/ 119 | env.bak/ 120 | venv.bak/ 121 | pythonenv* 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | # pytype static type analyzer 142 | .pytype/ 143 | 144 | # profiling data 145 | .prof 146 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include *.py 3 | include LICENSE 4 | recursive-include tests *.py 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![NewsData.io logo](https://raw.githubusercontent.com/bytesview/python-client/main/newsdata-logo.png)](https://newsdata.io) 3 | 4 | #

NewsData.io Python Client 5 | 6 | newsdataapi allows you to create a library for accessing http services easily, in a centralized way. An API defined by newsdataapi will return a JSON object when called. 7 | 8 | [![Build Status](https://img.shields.io/github/actions/workflow/status/bytesview/python-client/python-publish.yml)](https://github.com/bytesview/python-client/actions/workflows/python-publish.yml) 9 | [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/newsdataapi/python-client/blob/main/LICENSE) 10 | [![PyPI](https://img.shields.io/pypi/v/newsdataapi?color=084298)](https://pypi.org/project/newsdataapi) 11 | [![PyPI - Downloads](https://img.shields.io/pypi/dm/newsdataapi)](https://pypi.org/project/newsdataapi) 12 | [![Supported Python versions](https://img.shields.io/pypi/pyversions/newsdataapi)](https://pypi.org/project/newsdataapi) 13 | 14 | ## Installation 15 | ``` 16 | pip install newsdataapi 17 | ``` 18 | 19 | ## Documentation 20 | 21 | Newsdataapi docs can be seen [here](https://newsdata.io/documentation) -------------------------------------------------------------------------------- /examples/ex_newsdataapi_request.py: -------------------------------------------------------------------------------- 1 | from newsdataapi import NewsDataApiClient 2 | 3 | 4 | # API key authorization, Initialize the client with your API key 5 | api = NewsDataApiClient(apikey='API Key') 6 | 7 | 8 | # News API 9 | response = api.news_api() 10 | print(response) 11 | 12 | 13 | # Archive API 14 | response = api.archive_api(q='test') 15 | print(response) 16 | 17 | 18 | # Sources API 19 | response = api.sources_api() 20 | print(response) 21 | 22 | # Crypto API 23 | response = api.crypto_api() 24 | print(response) 25 | -------------------------------------------------------------------------------- /newsdata-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesview/python-client/e94f9edbcf504b4ba4d620499b02dd51a75c5c5c/newsdata-logo.png -------------------------------------------------------------------------------- /newsdataapi/__init__.py: -------------------------------------------------------------------------------- 1 | from newsdataapi.newsdataapi_client import NewsDataApiClient -------------------------------------------------------------------------------- /newsdataapi/constants.py: -------------------------------------------------------------------------------- 1 | # All the Newsdata supported API URL 2 | BASE_URL = 'https://newsdata.io/api/1/' 3 | 4 | # Latest News URL 5 | LATEST_ENDPOINT = 'latest' 6 | 7 | # News Archive URL 8 | ARCHIVE_ENDPOINT = 'archive' 9 | 10 | # News Sources URL 11 | SOURCES_ENDPOINT = 'sources' 12 | 13 | # News Crypto URL 14 | CRYPTO_ENDPOINT = 'crypto' 15 | 16 | COUNT_ENDPOINT = 'count' 17 | 18 | # Default request values 19 | DEFAULT_REQUEST_TIMEOUT = 300 20 | DEFAULT_MAX_RETRIES = 5 21 | DEFAULT_RETRY_DELAY = 1800 22 | DEFAULT_RETRY_DELAY_TooManyRequests = 10 23 | DEFAULT_RETRY_DELAY_RateLimitExceeded = 900 24 | -------------------------------------------------------------------------------- /newsdataapi/helpers.py: -------------------------------------------------------------------------------- 1 | import time,os,csv 2 | 3 | def get(request_method, URL, URL_parameters_encoded, proxies, request_timeout): 4 | if proxies is None: 5 | return request_method.get(URL + "?" + URL_parameters_encoded, timeout=request_timeout) 6 | else: 7 | return request_method.get(URL + "?" + URL_parameters_encoded, timeout=request_timeout, proxies = proxies) 8 | 9 | def MaxRetries(response, max_retries, retry_delay, request_method, URL, URL_parameters_encoded, proxies, request_timeout): 10 | while (max_retries): 11 | time.sleep(retry_delay) 12 | response = get(request_method, URL, URL_parameters_encoded, proxies, request_timeout) 13 | if response.status_code!=500: 14 | break 15 | max_retries-=1 16 | return response 17 | 18 | 19 | class FileHandler: 20 | 21 | def __init__(self,folder_path:str=None) -> None: 22 | self.folder_path = folder_path 23 | 24 | def generate_csv_file(self,collected_data:list,full_filepath:str): 25 | with open(full_filepath,'w',newline='', encoding='utf-8') as csv_file: 26 | writer = csv.DictWriter(csv_file,fieldnames=list(collected_data[0].keys())) 27 | writer.writeheader() 28 | writer.writerows(collected_data) 29 | 30 | 31 | def save_to_csv(self, response: dict, folder_path: str=None, filename: str=None)->str: 32 | folder_path = folder_path or self.folder_path 33 | filename = filename or str(time.time_ns()) 34 | 35 | if not folder_path or not os.path.exists(folder_path): 36 | raise FileNotFoundError(f'Provided folder path not found: {folder_path}') 37 | 38 | filename = f'{filename}.csv' if not filename.endswith('.csv') else filename 39 | full_path = os.path.join(folder_path, filename) 40 | 41 | if os.path.exists(full_path): 42 | raise FileExistsError(f'Provided file already exists: {filename} in folder: {folder_path}') 43 | 44 | results = response.get('results', []) 45 | 46 | for result in results: 47 | for k, v in result.items(): 48 | if isinstance(v, dict): 49 | data = ','.join(f'{i}:{j}' for i, j in v.items()) 50 | result[k] = f'"{data}"' 51 | elif isinstance(v, list): 52 | data = ','.join(map(str, v)) 53 | result[k] = f'"{data}"' 54 | else: 55 | if v is not None: 56 | result[k] = f'"{v}"' 57 | 58 | self.generate_csv_file(results, full_path) 59 | 60 | return full_path 61 | -------------------------------------------------------------------------------- /newsdataapi/newsdataapi_client.py: -------------------------------------------------------------------------------- 1 | import requests,time 2 | from warnings import warn 3 | from newsdataapi import constants 4 | from typing import Optional,Union 5 | from datetime import datetime,timezone 6 | from newsdataapi.helpers import FileHandler 7 | from requests.exceptions import RequestException 8 | from newsdataapi.newsdataapi_exception import NewsdataException 9 | from urllib.parse import urlencode, quote,urlparse,parse_qs,urljoin 10 | 11 | class NewsDataApiClient(FileHandler): 12 | 13 | def __init__( 14 | self, apikey:str, session:bool= False, max_retries:int= constants.DEFAULT_MAX_RETRIES, retry_delay:int= constants.DEFAULT_RETRY_DELAY, 15 | proxies:Optional[dict]=None, request_timeout:int= constants.DEFAULT_REQUEST_TIMEOUT,max_result:int=10**10, debug:Optional[bool]=False, 16 | folder_path:str=None,include_headers:bool=False 17 | ): 18 | """Initializes newsdata client object for access Newsdata APIs.""" 19 | self.apikey = apikey 20 | self.request_method:requests = requests if session == False else requests.Session() 21 | self.max_result = max_result 22 | self.max_retries = max_retries 23 | self.retry_delay = retry_delay 24 | self.proxies = proxies 25 | self.request_timeout = request_timeout 26 | self.is_debug = debug 27 | self.include_headers = include_headers 28 | self.set_base_url() 29 | super().__init__(folder_path=folder_path) 30 | 31 | def set_base_url(self,new_base_url:str=constants.BASE_URL)->None: 32 | self.latest_url = urljoin(new_base_url,constants.LATEST_ENDPOINT) 33 | self.archive_url = urljoin(new_base_url,constants.ARCHIVE_ENDPOINT) 34 | self.crypto_url = urljoin(new_base_url,constants.CRYPTO_ENDPOINT) 35 | self.sources_url = urljoin(new_base_url,constants.SOURCES_ENDPOINT) 36 | self.count_url = urljoin(new_base_url,constants.COUNT_ENDPOINT) 37 | 38 | def set_retries( self, max_retries:int, retry_delay:int)->None: 39 | """ API maximum retry and delay""" 40 | self.max_retries = max_retries 41 | self.retry_delay = retry_delay 42 | 43 | def set_request_timeout( self, request_timeout:int)->None: 44 | """ API maximum timeout for the request """ 45 | self.request_timeout = request_timeout 46 | 47 | def get_current_dt(self)->str: 48 | return datetime.now(tz=timezone.utc).replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S") 49 | 50 | def api_proxies( self, proxies:dict)->None: 51 | """ Configure Proxie dictionary """ 52 | self.proxies = proxies 53 | 54 | def __validate_parms(self,user_param:dict)->dict: 55 | bool_params = {'full_content','image','video','removeduplicate'} 56 | int_params = {'size'} 57 | string_params = { 58 | 'q','qInTitle','country','category','language','domain','domainurl','excludedomain','timezone','page', 59 | 'from_date','to_date','apikey','qInMeta','prioritydomain','timeframe','tag','sentiment','region','coin', 60 | 'excludefield' 61 | } 62 | 63 | def validate_url(url:str)-> str: 64 | valid_fn_parms = {k.lower() for k in user_param.keys()} 65 | parsed_url = urlparse(url) 66 | if parsed_url.netloc: 67 | q_string = parse_qs(parsed_url.query) 68 | else: 69 | q_string = parse_qs(url) 70 | 71 | valid_q_string = {} 72 | for k,v in q_string.items(): 73 | k = k.strip().strip('%').strip('?').lower() 74 | if k in valid_fn_parms: 75 | valid_q_string[k] = v[0] 76 | else: 77 | raise TypeError(f'Provided parameter is invalid: {k}') 78 | 79 | if not valid_q_string.get('apikey'): 80 | valid_q_string['apikey'] = self.apikey 81 | 82 | return valid_q_string 83 | 84 | if user_param.get('raw_query'): 85 | value = user_param['raw_query'] 86 | if not isinstance(value,str): 87 | raise TypeError('raw_query should be of type string.') 88 | return validate_url(url=value) 89 | 90 | valid_parms = {} 91 | for param,value in user_param.items(): 92 | if not value:continue 93 | if param in string_params: 94 | if isinstance(value,list): 95 | value = ','.join(value) 96 | if not isinstance(value,str): 97 | raise TypeError(f'{param} should be of type string.') 98 | elif param in bool_params: 99 | if not isinstance(value,bool): 100 | raise TypeError(f'{param} should be of type bool.') 101 | value = 1 if value == True else 0 102 | elif param in int_params: 103 | if not isinstance(value,int): 104 | raise TypeError(f'{param} should be of type int.') 105 | 106 | valid_parms[param] = value 107 | 108 | return valid_parms 109 | 110 | def __get_feeds(self,url:str,retry_count:int=None)-> dict: 111 | try: 112 | if retry_count is None: 113 | retry_count = self.max_retries 114 | 115 | if retry_count <= 0: 116 | raise NewsdataException('Maximum retry limit reached. For more information use debug parameter while initializing NewsDataApiClient.') 117 | 118 | response = self.request_method.get(url=url,proxies=self.proxies,timeout=self.request_timeout) 119 | headers = dict(response.headers) 120 | 121 | if self.is_debug == True: 122 | print(f'Debug | {self.get_current_dt()} | x_rate_limit_remaining: {headers.get("x_rate_limit_remaining")} | x_api_limit_remaining: {headers.get("x_api_limit_remaining")}') 123 | 124 | feeds_data:dict = response.json() 125 | if self.include_headers == True: 126 | feeds_data.update({'response_headers':headers}) 127 | 128 | if response.status_code != 200: 129 | 130 | if response.status_code == 500: 131 | if self.is_debug == True: 132 | print(f"Debug | {self.get_current_dt()} | Encountered 'ServerError' going to sleep for: {self.retry_delay} seconds.") 133 | time.sleep(self.retry_delay) 134 | return self.__get_feeds(url=url,retry_count=retry_count-1) 135 | 136 | elif feeds_data.get('results',{}).get('code') == 'TooManyRequests': 137 | if self.is_debug == True: 138 | print(f"Debug | {self.get_current_dt()} | Encountered 'TooManyRequests' going to sleep for: {constants.DEFAULT_RETRY_DELAY_TooManyRequests} seconds.") 139 | time.sleep(constants.DEFAULT_RETRY_DELAY_TooManyRequests) 140 | return self.__get_feeds(url=url,retry_count=retry_count-1) 141 | 142 | elif feeds_data.get('results',{}).get('code') == 'RateLimitExceeded': 143 | if self.is_debug == True: 144 | print(f"Debug | {self.get_current_dt()} | Encountered 'RateLimitExceeded' going to sleep for: {constants.DEFAULT_RETRY_DELAY_RateLimitExceeded} seconds.") 145 | time.sleep(constants.DEFAULT_RETRY_DELAY_RateLimitExceeded) 146 | return self.__get_feeds(url=url,retry_count=retry_count-1) 147 | 148 | else: 149 | raise NewsdataException(response.json()) 150 | 151 | else: 152 | return feeds_data 153 | 154 | except RequestException: 155 | 156 | if self.is_debug == True: 157 | print(f"Debug | {self.get_current_dt()} | Encountered 'ConnectionError' going to sleep for: {self.retry_delay} seconds.") 158 | time.sleep(self.retry_delay) 159 | 160 | if isinstance(self.request_method,requests.Session): 161 | self.request_method = requests.Session() 162 | 163 | return self.__get_feeds(url=url,retry_count=retry_count-1) 164 | 165 | def __get_feeds_all(self,url:str,max_result:int)-> dict: 166 | 167 | if max_result is None: 168 | max_result = self.max_result 169 | 170 | if not isinstance(max_result,int): 171 | raise TypeError('max_result should be of type int.') 172 | 173 | if not isinstance(self.request_method,requests.Session): 174 | self.request_method = requests.Session() 175 | 176 | feeds_count = 0 177 | data = {'totalResults':None,'results':[],'nextPage':True} 178 | while data.get("nextPage"): 179 | try: 180 | response = self.__get_feeds(url=f'{url}&page={data.get("nextPage")}' if data.get('results') else url) 181 | except NewsdataException as e: 182 | if data['totalResults'] is None: 183 | raise e 184 | return data 185 | data['totalResults'] = response.get('totalResults') 186 | results = response.get('results') 187 | data['results'].extend(results) 188 | data['nextPage'] = response.get('nextPage') 189 | if self.include_headers: 190 | data['response_headers'] = response.get('response_headers') 191 | feeds_count+=len(results) 192 | if self.is_debug == True: 193 | print(f"Debug | {self.get_current_dt()} | total results: {data['totalResults']} | extracted: {feeds_count}") 194 | if feeds_count >= max_result: 195 | return data 196 | time.sleep(0.5) 197 | return data 198 | 199 | def news_api( 200 | self, q:Optional[str]=None, qInTitle:Optional[str]=None, country:Optional[Union[str, list]]=None, category:Optional[Union[str, list]]=None, 201 | language:Optional[Union[str, list]]=None, domain:Optional[Union[str, list]]=None, timeframe:Optional[Union[int,str]]=None, size:Optional[int]=None, 202 | domainurl:Optional[Union[str, list]]=None, excludedomain:Optional[Union[str, list]]=None, timezone:Optional[str]=None, full_content:Optional[bool]=None, 203 | image:Optional[bool]=None, video:Optional[bool]=None, prioritydomain:Optional[str]=None, page:Optional[str]=None, scroll:Optional[bool]=False, 204 | max_result:Optional[int]=None, qInMeta:Optional[str]=None, tag:Optional[Union[str,list]]=None, sentiment:Optional[str]=None, 205 | region:Optional[Union[str,list]]=None,excludefield:Optional[Union[str,list]]=None,removeduplicate:Optional[bool]=None,raw_query:Optional[str]=None 206 | )->dict: 207 | """ 208 | Sending GET request to the news api. 209 | For more information about parameters and input, Please visit our documentation page: https://newsdata.io/documentation 210 | """ 211 | warn('This method is deprecated and will be removed in upcoming updates, Instead use latest_api()', DeprecationWarning, stacklevel=2) 212 | params = { 213 | 'apikey':self.apikey,'q':q,'qInTitle':qInTitle,'country':country,'category':category,'language':language,'domain':domain,'timeframe':str(timeframe) if timeframe else timeframe, 214 | 'size':size,'domainurl':domainurl,'excludedomain':excludedomain,'timezone':timezone,'full_content':full_content,'image':image,'video':video,'prioritydomain':prioritydomain, 215 | 'page':page,'qInMeta':qInMeta,'tag':tag, 'sentiment':sentiment, 'region':region,'excludefield':excludefield,'removeduplicate':removeduplicate,'raw_query':raw_query 216 | } 217 | URL_parameters = self.__validate_parms(user_param=params) 218 | URL_parameters_encoded = urlencode(URL_parameters, quote_via=quote) 219 | if scroll == True: 220 | return self.__get_feeds_all(url=f'{self.latest_url}?{URL_parameters_encoded}',max_result=max_result) 221 | else: 222 | return self.__get_feeds(url=f'{self.latest_url}?{URL_parameters_encoded}') 223 | 224 | def latest_api( 225 | self, q:Optional[str]=None, qInTitle:Optional[str]=None, country:Optional[Union[str, list]]=None, category:Optional[Union[str, list]]=None, 226 | language:Optional[Union[str, list]]=None, domain:Optional[Union[str, list]]=None, timeframe:Optional[Union[int,str]]=None, size:Optional[int]=None, 227 | domainurl:Optional[Union[str, list]]=None, excludedomain:Optional[Union[str, list]]=None, timezone:Optional[str]=None, full_content:Optional[bool]=None, 228 | image:Optional[bool]=None, video:Optional[bool]=None, prioritydomain:Optional[str]=None, page:Optional[str]=None, scroll:Optional[bool]=False, 229 | max_result:Optional[int]=None, qInMeta:Optional[str]=None, tag:Optional[Union[str,list]]=None, sentiment:Optional[str]=None, 230 | region:Optional[Union[str,list]]=None,excludefield:Optional[Union[str,list]]=None,removeduplicate:Optional[bool]=None,raw_query:Optional[str]=None 231 | )->dict: 232 | """ 233 | Sending GET request to the latest api. 234 | For more information about parameters and input, Please visit our documentation page: https://newsdata.io/documentation 235 | """ 236 | params = { 237 | 'apikey':self.apikey,'q':q,'qInTitle':qInTitle,'country':country,'category':category,'language':language,'domain':domain,'timeframe':str(timeframe) if timeframe else timeframe, 238 | 'size':size,'domainurl':domainurl,'excludedomain':excludedomain,'timezone':timezone,'full_content':full_content,'image':image,'video':video,'prioritydomain':prioritydomain, 239 | 'page':page,'qInMeta':qInMeta,'tag':tag, 'sentiment':sentiment, 'region':region,'excludefield':excludefield,'removeduplicate':removeduplicate,'raw_query':raw_query 240 | } 241 | URL_parameters = self.__validate_parms(user_param=params) 242 | URL_parameters_encoded = urlencode(URL_parameters, quote_via=quote) 243 | if scroll == True: 244 | return self.__get_feeds_all(url=f'{self.latest_url}?{URL_parameters_encoded}',max_result=max_result) 245 | else: 246 | return self.__get_feeds(url=f'{self.latest_url}?{URL_parameters_encoded}') 247 | 248 | def archive_api( 249 | self, q:Optional[str]=None, qInTitle:Optional[str]=None, country:Optional[Union[str, list]]=None, category:Optional[Union[str, list]]=None, 250 | language:Optional[Union[str, list]]=None, domain:Optional[Union[str, list]]=None, size:Optional[int]=None,domainurl:Optional[Union[str, list]]=None, 251 | excludedomain:Optional[Union[str, list]]=None, timezone:Optional[str]=None, full_content:Optional[bool]=None,image:Optional[bool]=None, 252 | video:Optional[bool]=None,prioritydomain:Optional[str]=None, page:Optional[str]=None, scroll:Optional[bool]=False, max_result:Optional[int]=None, 253 | from_date:Optional[str]=None, to_date:Optional[str]=None, qInMeta:Optional[str]=None, excludefield:Optional[Union[str,list]]=None,raw_query:Optional[str]=None 254 | ) -> dict: 255 | """ 256 | Sending GET request to the archive api 257 | For more information about parameters and input, Please visit our documentation page: https://newsdata.io/documentation 258 | """ 259 | params = { 260 | 'q':q,'qInTitle':qInTitle,'country':country,'category':category,'language':language,'domain':domain,'size':size,'domainurl':domainurl,'excludedomain':excludedomain, 261 | 'timezone':timezone,'full_content':full_content,'image':image,'video':video,'prioritydomain':prioritydomain,'page':page,'from_date':from_date,'to_date':to_date, 262 | 'apikey':self.apikey,'qInMeta':qInMeta,'excludefield':excludefield,'raw_query':raw_query 263 | } 264 | URL_parameters = self.__validate_parms(user_param=params) 265 | URL_parameters_encoded = urlencode(URL_parameters, quote_via=quote) 266 | if scroll == True: 267 | return self.__get_feeds_all(url=f'{self.archive_url}?{URL_parameters_encoded}',max_result=max_result) 268 | else: 269 | return self.__get_feeds(url=f'{self.archive_url}?{URL_parameters_encoded}') 270 | 271 | def sources_api( self, country:Optional[str]= None, category:Optional[str]= None, language:Optional[str]= None, prioritydomain:Optional[str]= None,raw_query:Optional[str]=None): 272 | """ 273 | Sending GET request to the sources api 274 | For more information about parameters and input, Please visit our documentation page: https://newsdata.io/documentation 275 | """ 276 | params = {"apikey":self.apikey, "country":country, "category":category, "language":language, "prioritydomain":prioritydomain,'raw_query':raw_query} 277 | URL_parameters = self.__validate_parms(user_param=params) 278 | URL_parameters_encoded = urlencode(URL_parameters, quote_via=quote) 279 | return self.__get_feeds(url=f'{self.sources_url}?{URL_parameters_encoded}') 280 | 281 | def crypto_api( 282 | self, q:Optional[str]=None, qInTitle:Optional[str]=None,language:Optional[Union[str, list]]=None, domain:Optional[Union[str, list]]=None, 283 | timeframe:Optional[Union[int,str]]=None, size:Optional[int]=None,domainurl:Optional[Union[str, list]]=None, excludedomain:Optional[Union[str, list]]=None, 284 | timezone:Optional[str]=None, full_content:Optional[bool]=None,image:Optional[bool]=None, video:Optional[bool]=None, prioritydomain:Optional[str]=None, 285 | page:Optional[str]=None, scroll:Optional[bool]=False,max_result:Optional[int]=None, qInMeta:Optional[str]=None,tag:Optional[Union[str,list]]=None, 286 | sentiment:Optional[str]=None,coin:Optional[Union[str, list]]=None,excludefield:Optional[Union[str,list]]=None,from_date:Optional[str]=None, 287 | to_date:Optional[str]=None,removeduplicate:Optional[bool]=None,raw_query:Optional[str]=None, 288 | )->dict: 289 | """ 290 | Sending GET request to the crypto api 291 | For more information about parameters and input, Please visit our documentation page: https://newsdata.io/documentation 292 | """ 293 | 294 | params = { 295 | 'apikey':self.apikey,'q':q,'qInTitle':qInTitle,'language':language,'domain':domain,'size':size,'domainurl':domainurl, 296 | 'excludedomain':excludedomain,'timezone':timezone,'full_content':full_content,'image':image,'video':video,'prioritydomain':prioritydomain,'page':page, 297 | 'timeframe':str(timeframe) if timeframe else timeframe,'qInMeta':qInMeta,'tag':tag, 'sentiment':sentiment,'coin':coin,'excludefield':excludefield, 298 | 'from_date':from_date,'to_date':to_date,'removeduplicate':removeduplicate,'raw_query':raw_query 299 | } 300 | URL_parameters = self.__validate_parms(user_param=params) 301 | URL_parameters_encoded = urlencode(URL_parameters, quote_via=quote) 302 | if scroll == True: 303 | return self.__get_feeds_all(url=f'{self.crypto_url}?{URL_parameters_encoded}',max_result=max_result) 304 | else: 305 | return self.__get_feeds(url=f'{self.crypto_url}?{URL_parameters_encoded}') 306 | 307 | def count_api( 308 | self, q:Optional[str]=None, qInTitle:Optional[str]=None, qInMeta:Optional[str]=None, country:Optional[Union[str, list]]=None, 309 | category:Optional[Union[str, list]]=None,language:Optional[Union[str, list]]=None, from_date:Optional[str]=None, 310 | to_date:Optional[str]=None,raw_query:Optional[str]=None 311 | ) -> dict: 312 | """ 313 | Sending GET request to the count api 314 | For more information about parameters and input, Please visit our documentation page: https://newsdata.io/documentation 315 | """ 316 | params = { 317 | 'q':q,'qInTitle':qInTitle,'country':country,'category':category,'language':language,'from_date':from_date,'to_date':to_date, 318 | 'apikey':self.apikey,'qInMeta':qInMeta,'raw_query':raw_query 319 | } 320 | URL_parameters = self.__validate_parms(user_param=params) 321 | URL_parameters_encoded = urlencode(URL_parameters, quote_via=quote) 322 | return self.__get_feeds(url=f'{self.count_url}?{URL_parameters_encoded}') 323 | 324 | def __del__(self): 325 | if isinstance(self.request_method,requests.Session): 326 | self.request_method.close() 327 | -------------------------------------------------------------------------------- /newsdataapi/newsdataapi_exception.py: -------------------------------------------------------------------------------- 1 | 2 | class NewsdataException(Exception): 3 | """Base class for all other exceptions""" 4 | 5 | def __init__(self, Error): 6 | self.Error = Error 7 | -------------------------------------------------------------------------------- /newsdataapi/utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | PY3 = sys.version_info[0] == 3 5 | 6 | 7 | if PY3: 8 | 9 | def is_valid_string(lang): 10 | return isinstance(lang, str) 11 | 12 | def is_valid_integer(lang): 13 | return isinstance(lang, int) 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==20.3.0 2 | certifi==2020.12.5 3 | chardet==4.0.0 4 | idna==2.10 5 | iniconfig==1.1.1 6 | importlib-metadata==3.4.0 7 | packaging==20.9 8 | pluggy==0.13.1 9 | py==1.10.0 10 | pyparsing==2.4.7 11 | pytest==6.2.2 12 | requests==2.25.1 13 | toml==0.10.2 14 | typing-extensions==3.7.4.3 15 | urllib3==1.26.3 16 | zipp==3.4.0 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | 7 | setup( 8 | name='newsdataapi', 9 | version='0.1.23', 10 | packages=['newsdataapi'], 11 | description='Python library for newsdata client-API Call', 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url='https://github.com/newsdataapi/python-client', 15 | author='NewsData.io', 16 | author_email='contact@newsdata.io', 17 | license='MIT', 18 | install_requires=["requests<3.0.0"], 19 | setup_requires=['pytest-runner'], 20 | tests_require=['pytest'], 21 | test_suite='tests', 22 | python_requires='>=3.5', 23 | keywords=[ 24 | 'news', 25 | 'news data', 26 | ], 27 | classifiers=[ 28 | "Development Status :: 2 - Pre-Alpha", 29 | "Intended Audience :: Developers", 30 | "Intended Audience :: Customer Service", 31 | "License :: OSI Approved :: MIT License", 32 | "Programming Language :: Python :: 3", 33 | "Programming Language :: Python :: 3.5", 34 | "Programming Language :: Python :: 3.6", 35 | "Programming Language :: Python :: 3.7", 36 | "Programming Language :: Python :: 3.8", 37 | "Programming Language :: Python :: 3.9", 38 | ] 39 | 40 | ) 41 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_newsdataapi.py: -------------------------------------------------------------------------------- 1 | import os,unittest 2 | from newsdataapi import NewsDataApiClient 3 | 4 | class test_newsdataapi(unittest.TestCase): 5 | def setUp(self): 6 | # your private API key. 7 | key = os.environ.get("PYTEST_TOKEN") 8 | self.api = NewsDataApiClient(apikey=key) 9 | 10 | def test_news_api(self): 11 | response = self.api.news_api() 12 | 13 | self.assertEqual(response['status'], "success") 14 | 15 | def test_latest_api(self): 16 | response = self.api.latest_api() 17 | 18 | self.assertEqual(response['status'], "success") 19 | 20 | def test_archive_api(self): 21 | response = self.api.archive_api(q='test') 22 | 23 | self.assertEqual(response['status'], "success") 24 | 25 | def test_sources_api(self): 26 | response = self.api.sources_api() 27 | 28 | self.assertEqual(response['status'], "success") 29 | 30 | def test_crypto_api(self): 31 | response = self.api.crypto_api() 32 | 33 | self.assertEqual(response['status'], "success") 34 | 35 | # def test_count_api(self): 36 | # response = self.api.count_api(language='en') 37 | 38 | # self.assertEqual(response['status'], "success") 39 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py39, py38, py37, py36, py35 3 | 4 | 5 | [testenv] 6 | deps = pytest 7 | commands = pytest 8 | --------------------------------------------------------------------------------