diff --git a/.gitignore b/.gitignore index f0d48ba..398ec19 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ build/ dist/ *.egg-info/ -*.rst venv/ *log.txt *~ +docs/build/ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..6247f7e --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/api.rst b/docs/source/api.rst new file mode 100644 index 0000000..c0e5819 --- /dev/null +++ b/docs/source/api.rst @@ -0,0 +1,37 @@ +.. _api: + +API Reference +============= + +.. module:: newsapi-python + +This page is a technical reference to the public classes, exceptions, and data +defined in newsapi-python. + +While newsapi-python makes every effort to keep up with the API, +please consider the official News API `docs `_ +as the canonical News API reference. + +Classes +------- + +.. autoclass:: newsapi.NewsApiClient + :members: + +Exceptions +---------- + +.. autoexception:: newsapi.newsapi_exception.NewsAPIException + +Constants +--------- + +The :mod:`newsapi.const` module holds constants and allowed parameter values specified in the official News API documentation. + +.. autodata:: newsapi.const.languages + +.. autodata:: newsapi.const.countries + +.. autodata:: newsapi.const.categories + +.. autodata:: newsapi.const.sort_method diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..671bc95 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,65 @@ +from __future__ import unicode_literals + +import os +import sys + +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + +sys.path.insert(0, os.path.abspath("../../")) + +from setup import VERSION + +# -- Project information ----------------------------------------------------- + +project = "newsapi-python" +copyright = "2019, Matt Lisivick" +author = "Matt Lisivick & Brad Solomon" +version = release = VERSION + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ["sphinx.ext.autodoc"] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "classic" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +html_show_sourcelink = True +html_show_sphinx = False +html_last_updated_fmt = "" + + +# -- Autodoc ----------------------------------------------------------------- + +autodoc_warningiserror = True diff --git a/docs/source/examples.rst b/docs/source/examples.rst new file mode 100644 index 0000000..0555003 --- /dev/null +++ b/docs/source/examples.rst @@ -0,0 +1,117 @@ +.. _examples: + +Example :class:`NewsApiClient` Usage +==================================== + +This page is a tutorial-by-example for using the :class:`NewsApiClient` class. + +Basic Usage +----------- + +The top-level :class:`NewsApiClient` class allows you to access News API endpoints. Initialize the client with your API key:: + + import os + from newsapi import NewsApiClient + + # An API key; for example: "74f9e72a4bfd4dbaa0cbac8e9a17d34a" + key = os.environ["news_api_secret"] + + api = NewsApiClient(api_key=key) + +The only required parameter is an `API key `_. You can also pass a persistent ``session`` object; see `Using a Dedicated Session`_. + +Accessing the `/top-headlines` Endpoint +--------------------------------------- + +Use :meth:`newsapi.NewsApiClient.get_top_headlines` to pull from the `/top-headlines` endpoint:: + + api.get_top_headlines() + api.get_top_headlines(q="hurricane") + api.get_top_headlines(category="sports") + api.get_top_headlines(sources="abc-news,ars-technica", page_size=50) + +Accessing the `/everything` Endpoint +------------------------------------ + +Use :meth:`newsapi.NewsApiClient.get_everything` to pull from the `/everything` endpoint:: + + api.get_everything("hurricane OR tornado", sort_by="relevancy", language="en") + api.get_everything("(hurricane OR tornado) AND FEMA", sort_by="relevancy") + + +Accessing the `/sources` Endpoint +--------------------------------- + +Use :meth:`newsapi.NewsApiClient.get_sources` to pull from the `/sources` endpoint:: + + api.get_sources() + api.get_sources(category="technology") + api.get_sources(country="ru") + api.get_sources(category="health", country="us") + api.get_sources(language="en", country="in") + +Using a Dedicated Session +------------------------- + +By default, each method call from :class:`NewsApiClient` uses a new TCP session (and ``requests.Session`` instance). +This is not ideal if you'd like to call endpoints multiple times, +whereas using a single session can provide connection-pooling and cookie persistence. + +To use a single session across multiple method calls, pass the session object to :class:`NewsApiClient`:: + + import requests + + with requests.Session() as session: + # Use a single session for multiple requests. Using a 'with' + # context manager closes the session and TCP connection after use. + api = NewsApiClient(api_key=key, session=session) + data1 = api.get_top_headlines(category="technology") + data2 = api.get_everything(q="facebook", domains="mashable.com,wired.com") + +Date Inputs +----------- + +The optional parameters ``from_param`` and ``to`` used in :meth:`newsapi.NewsApiClient.get_everything` +allow you to constrain the result set to articles published within a given span. + +You can pass a handful of different types: + +- ``datetime.date`` +- ``datetime.datetime`` (assumed to be in UTC time) +- ``str`` formated as either ``%Y-%m-%d`` (e.g. *2019-09-07*) or ``%Y-%m-%dT%H:%M:%S`` (e.g. *2019-09-07T13:04:15*) +- ``int`` or ``float`` (assumed represents a Unix timestamp) +- ``None`` (the default, in which there is no constraint) + +Here are a few valid examples:: + + import datetime as dt + + api.get_everything( + q="hurricane", + from_param=dt.date(2019, 9, 1), + to=dt.date(2019, 9, 3), + ) + + api.get_everything( + q="hurricane", + from_param=dt.datetime(2019, 9, 1, hour=5), + to=dt.datetime(2019, 9, 1, hour=15), + ) + + api.get_everything( + q="hurricane", + from_param="2019-08-01", + to="2019-09-15", + ) + + api.get_everything( + q="hurricane", + from_param="2019-08-01", + to="2019-09-15", + ) + + api.get_everything( + q="venezuela", + from_param="2019-08-01T10:30:00", + to="2019-09-15T14:00:00", + ) diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..ad97800 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,23 @@ +Welcome: Documentation for `newsapi-python` +=========================================== + +.. image:: https://img.shields.io/github/license/mattlisiv/newsapi-python.svg + :target: https://github.com/mattlisiv/newsapi-python/blob/master/LICENSE.txt +.. image:: https://img.shields.io/pypi/v/newsapi-python.svg + :target: https://pypi.org/project/newsapi-python/ +.. image:: https://img.shields.io/pypi/status/newsapi-python.svg + :target: https://pypi.org/project/newsapi-python/ +.. image:: https://img.shields.io/pypi/pyversions/newsapi-python.svg + :target: https://pypi.org/project/newsapi-python + +This is documentation for version \ |version| of `newsapi-python`, +a Python client for the `News API `_. + +The project source repository is `hosted on GitHub `_. + +Documentation Contents +---------------------- + +.. toctree:: + examples + api diff --git a/newsapi/const.py b/newsapi/const.py index 71907bd..1e98618 100644 --- a/newsapi/const.py +++ b/newsapi/const.py @@ -1,6 +1,11 @@ +"""Constants and allowed parameter values specified in the News API.""" + TOP_HEADLINES_URL = "https://newsapi.org/v2/top-headlines" EVERYTHING_URL = "https://newsapi.org/v2/everything" SOURCES_URL = "https://newsapi.org/v2/sources" + +#: The 2-letter ISO 3166-1 code of the country you want to get headlines for. If not specified, +#: the results span all countries. countries = { "ae", "ar", @@ -62,8 +67,13 @@ "zh", } +#: The 2-letter ISO-639-1 code of the language you want to get articles for. If not specified, +#: the results span all languages. languages = {"ar", "en", "cn", "de", "es", "fr", "he", "it", "nl", "no", "pt", "ru", "sv", "se", "ud", "zh"} +#: The category you want to get articles for. If not specified, +#: the results span all categories. categories = {"business", "entertainment", "general", "health", "science", "sports", "technology"} +#: The order to sort article results in. If not specified, the default is ``"publishedAt"``. sort_method = {"relevancy", "popularity", "publishedAt"} diff --git a/newsapi/newsapi_client.py b/newsapi/newsapi_client.py index 84d0d5e..365a8a4 100644 --- a/newsapi/newsapi_client.py +++ b/newsapi/newsapi_client.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import requests from newsapi import const @@ -7,6 +9,19 @@ class NewsApiClient(object): + """The core client object used to fetch data from News API endpoints. + + :param api_key: Your API key, a length-32 UUID string provided for your News API account. + You must `register `_ for a News API key. + :type api_key: str + + :param session: An optional :class:`requests.Session` instance from which to execute requests. + **Note**: If you provide a ``session`` instance, :class:`NewsApiClient` will *not* close the session + for you. Remember to call ``session.close()``, or use the session as a context manager, to close + the socket and free up resources. + :type session: `requests.Session `_ or None + """ + def __init__(self, api_key, session=None): self.auth = NewsApiAuth(api_key=api_key) if session is None: @@ -17,42 +32,54 @@ def __init__(self, api_key, session=None): def get_top_headlines( # noqa: C901 self, q=None, sources=None, language="en", country=None, category=None, page_size=None, page=None ): - """ - Fetch live top and breaking headlines. - - This endpoint provides live top and breaking headlines for a - country, specific category in a country, single source, or - multiple sources. You can also search with keywords. - Articles are sorted by the earliest date published first. + """Call the `/top-headlines` endpoint. - Optional parameters: - (str) q - return headlines w/ specific keyword or phrase. For example: - 'bitcoin', 'trump', 'tesla', 'ethereum', etc. - - (str) sources - return headlines of news sources! some Valid values are: - 'bbc-news', 'the-verge', 'abc-news', 'crypto coins news', - 'ary news','associated press','wired','aftenposten','australian financial review','axios', - 'bbc news','bild','blasting news','bloomberg','business insider','engadget','google news', - 'hacker news','info money,'recode','techcrunch','techradar','the next web','the verge' etc. - - (str) language - The 2-letter ISO-639-1 code of the language you want to get headlines for. - Valid values are: - 'ar','de','en','es','fr','he','it','nl','no','pt','ru','se','ud','zh' - - (str) country - The 2-letter ISO 3166-1 code of the country you want to get headlines! Valid values are: - 'ae','ar','at','au','be','bg','br','ca','ch','cn','co','cu','cz','de','eg','fr','gb','gr', - 'hk','hu','id','ie','il','in','it','jp','kr','lt','lv','ma','mx','my','ng','nl','no','nz', - 'ph','pl','pt','ro','rs','ru','sa','se','sg','si','sk','th','tr','tw','ua','us' - - (str) category - The category you want to get headlines for! Valid values are: - 'business','entertainment','general','health','science','sports','technology' - - (int) page_size - The number of results to return per page (request). 20 is the default, 100 is the maximum. + Fetch live top and breaking headlines. - (int) page - Use this to page through the results if the total results found is greater than the page size. + This endpoint provides live top and breaking headlines for a country, specific category in a country, + single source, or multiple sources. You can also search with keywords. Articles are sorted by the earliest + date published first. + + :param q: Keywords or a phrase to search for in the article title and body. See the official News API + `documentation `_ for search syntax and examples. + :type q: str or None + + :param sources: A comma-seperated string of identifiers for the news sources or blogs you want headlines from. + Use :meth:`NewsApiClient.get_sources` to locate these programmatically, or look at the + `sources index `_. **Note**: you can't mix this param with the + ``country`` or ``category`` params. + :type sources: str or None + + :param language: The 2-letter ISO-639-1 code of the language you want to get headlines for. + See :data:`newsapi.const.languages` for the set of allowed values. + The default for this method is ``"en"`` (English). **Note**: this parameter is not mentioned in the + `/top-headlines documentation `_ as of Sep. 2019, + but *is* supported by the API. + :type language: str or None + + :param country: The 2-letter ISO 3166-1 code of the country you want to get headlines for. + See :data:`newsapi.const.countries` for the set of allowed values. + **Note**: you can't mix this parameter with the ``sources`` param. + :type country: str or None + + :param category: The category you want to get headlines for. + See :data:`newsapi.const.categories` for the set of allowed values. + **Note**: you can't mix this parameter with the ``sources`` param. + :type category: str or None + + :param page_size: Use this to page through the results if the total results found is + greater than the page size. + :type page_size: int or None + + :param page: The number of results to return per page (request). + 20 is the default, 100 is the maximum. + :type page: int or None + + :return: JSON response as nested Python dictionary. + :rtype: dict + :raises NewsAPIException: If the ``"status"`` value of the response is ``"error"`` rather than ``"ok"``. """ - # Define Payload payload = {} # Keyword/Phrase @@ -145,48 +172,52 @@ def get_everything( # noqa: C901 page=None, page_size=None, ): - """ - Search through millions of articles from news sources and blogs. - - Search through millions of articles from over 30,000 large - and small news sources and blogs. This includes breaking - news as well as lesser articles. - - Optional parameters: - (str) q - return headlines w/ specified coin! Valid values are: - 'bitcoin', 'trump', 'tesla', 'ethereum', etc - - (str) sources - return headlines of news sources! some Valid values are: - 'bbc-news', 'the-verge', 'abc-news', 'crypto coins news', - 'ary news','associated press','wired','aftenposten','australian financial review','axios', - 'bbc news','bild','blasting news','bloomberg','business insider','engadget','google news', - 'hacker news','info money,'recode','techcrunch','techradar','the next web','the verge' etc. - - (str) domains - A comma-seperated string of domains - (eg bbc.co.uk, techcrunch.com, engadget.com) - to restrict the search to. - - (str) exclude_domains - A comma_seperated string of domains to be excluded from the search - - (str, date, datetime, float, int, or None) - from_param - A date and optional time for the oldest article allowed. - (e.g. 2018-03-05 or 2018-03-05T03:46:15) - - (str, date, datetime, float, int, or None) - to - A date and optional time for the newest article allowed. - - (str) language - The 2-letter ISO-639-1 code of the language you want to get headlines for. - Valid values are: - 'ar','de','en','es','fr','he','it','nl','no','pt','ru','se','ud','zh' - - (str) sort_by - The order to sort the articles in. Valid values are: 'relevancy','popularity','publishedAt' - - (int) page_size - The number of results to return per page (request). 20 is the default, 100 is the maximum. - - (int) page - Use this to page through the results if the total results found is greater than the page size. + """Call the `/everything` endpoint. + + Search through millions of articles from over 30,000 large and small news sources and blogs. + + :param q: Keywords or a phrase to search for in the article title and body. See the official News API + `documentation `_ for search syntax and examples. + :type q: str or None + + :param sources: A comma-seperated string of identifiers for the news sources or blogs you want headlines from. + Use :meth:`NewsApiClient.get_sources` to locate these programmatically, or look at the + `sources index `_. + :type sources: str or None + + :param domains: A comma-seperated string of domains (eg bbc.co.uk, techcrunch.com, engadget.com) + to restrict the search to. + :type domains: str or None + + :param exclude_domains: A comma-seperated string of domains (eg bbc.co.uk, techcrunch.com, engadget.com) + to remove from the results. + :type exclude_domains: str or None + + :param from_param: A date and optional time for the oldest article allowed. + If a str, the format must conform to ISO-8601 specifically as one of either + ``%Y-%m-%d`` (e.g. *2019-09-07*) or ``%Y-%m-%dT%H:%M:%S`` (e.g. *2019-09-07T13:04:15*). + An int or float is assumed to represent a Unix timestamp. All datetime inputs are assumed to be UTC. + :type from_param: str or datetime.datetime or datetime.date or int or float or None + + :param to: A date and optional time for the newest article allowed. + If a str, the format must conform to ISO-8601 specifically as one of either + ``%Y-%m-%d`` (e.g. *2019-09-07*) or ``%Y-%m-%dT%H:%M:%S`` (e.g. *2019-09-07T13:04:15*). + An int or float is assumed to represent a Unix timestamp. All datetime inputs are assumed to be UTC. + :type to: str or datetime.datetime or datetime.date or int or float or None + + :param language: The 2-letter ISO-639-1 code of the language you want to get headlines for. + See :data:`newsapi.const.languages` for the set of allowed values. + :type language: str or None + + :param sort_by: The order to sort articles in. + See :data:`newsapi.const.sort_method` for the set of allowed values. + :type sort_by: str or None + + :return: JSON response as nested Python dictionary. + :rtype: dict + :raises NewsAPIException: If the ``"status"`` value of the response is ``"error"`` rather than ``"ok"``. """ - # Define Payload payload = {} # Keyword/Phrase @@ -274,27 +305,27 @@ def get_everything( # noqa: C901 return r.json() def get_sources(self, category=None, language=None, country=None): # noqa: C901 - """ - Returns the subset of news publishers that top headlines... + """Call the `/sources` endpoint. + + Fetch the subset of news publishers that /top-headlines are available from. - Optional parameters: - (str) category - The category you want to get headlines for! Valid values are: - 'business','entertainment','general','health','science','sports','technology' + :param category: Find sources that display news of this category. + See :data:`newsapi.const.categories` for the set of allowed values. + :type category: str or None - (str) language - The 2-letter ISO-639-1 code of the language you want to get headlines for. - Valid values are: - 'ar','de','en','es','fr','he','it','nl','no','pt','ru','se','ud','zh' + :param language: Find sources that display news in a specific language. + See :data:`newsapi.const.languages` for the set of allowed values. + :type language: str or None - (str) country - The 2-letter ISO 3166-1 code of the country you want to get headlines! Valid values are: - 'ae','ar','at','au','be','bg','br','ca','ch','cn','co','cu','cz','de','eg','fr','gb','gr', - 'hk','hu','id','ie','il','in','it','jp','kr','lt','lv','ma','mx','my','ng','nl','no','nz', - 'ph','pl','pt','ro','rs','ru','sa','se','sg','si','sk','th','tr','tw','ua','us' + :param country: Find sources that display news in a specific country. + See :data:`newsapi.const.countries` for the set of allowed values. + :type country: str or None - (str) category - The category you want to get headlines for! Valid values are: - 'business','entertainment','general','health','science','sports','technology' + :return: JSON response as nested Python dictionary. + :rtype: dict + :raises NewsAPIException: If the ``"status"`` value of the response is ``"error"`` rather than ``"ok"``. """ - # Define Payload payload = {} # Language @@ -317,7 +348,7 @@ def get_sources(self, category=None, language=None, country=None): # noqa: C901 else: raise TypeError("country param should be of type str") - # Category + # Category if category is not None: if is_valid_string(category): if category in const.categories: diff --git a/newsapi/newsapi_exception.py b/newsapi/newsapi_exception.py index 95c55d3..230b87b 100644 --- a/newsapi/newsapi_exception.py +++ b/newsapi/newsapi_exception.py @@ -1,4 +1,6 @@ class NewsAPIException(Exception): + """Represents an ``error`` response status value from News API.""" + def __init__(self, exception): self.exception = exception diff --git a/requirements-dev.txt b/requirements-dev.txt index a146450..20abf60 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,5 +2,6 @@ black flake8 isort pytest -recommonmark Sphinx +twine +wheel diff --git a/setup.py b/setup.py index d9380a3..f8f0f53 100644 --- a/setup.py +++ b/setup.py @@ -1,39 +1,50 @@ #!/usr/bin/env python -from setuptools import find_packages, setup +# PyPI upload: +# +# $ python -m pip install --upgrade twine wheel +# $ python setup.py sdist bdist_wheel --universal +# $ twine upload dist/* +# +# Install in development: +# +# $ python3 -m pip install -e . -install_requires = ["requests==2.21.0"] +from setuptools import setup -tests_require = ["pytest"] +VERSION = "0.2.6" +INSTALL_REQUIRES = ["requests<3.0.0"] +TESTS_REQUIRE = ["pytest"] -setup( - name="newsapi-python", - version="0.2.4", - author="Matt Lisivick", - author_email="lisivickmatt@gmail.com", - license="MIT", - url="https://github.com/mattlisiv/newsapi-python", - packages=find_packages(), - install_requires=install_requires, - tests_require=tests_require, - description="An unofficial Python client for the News API", - download_url="https://github.com/mattlisiv/newsapi-python/archive/master.zip", - keywords=["newsapi", "news"], - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Intended Audience :: Financial and Insurance Industry", - "Intended Audience :: Information Technology", - "Topic :: Software Development :: Libraries :: Python Modules", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - ], -) +if __name__ == "__main__": + setup( + name="newsapi-python", + version=VERSION, + author="Matt Lisivick", + author_email="lisivickmatt@gmail.com", + license="MIT", + url="https://github.com/mattlisiv/newsapi-python", + packages=["newsapi"], + install_requires=INSTALL_REQUIRES, + tests_require=TESTS_REQUIRE, + description="An unofficial Python client for the News API", + download_url="https://github.com/mattlisiv/newsapi-python/archive/master.zip", + keywords=["newsapi", "news"], + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Financial and Insurance Industry", + "Intended Audience :: Information Technology", + "Topic :: Software Development :: Libraries :: Python Modules", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + ], + )