Skip to content

Commit

Permalink
Merge pull request #5 from viseshrp/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
viseshrp committed Jan 23, 2019
2 parents 922c7b7 + df55e3f commit c55105e
Show file tree
Hide file tree
Showing 13 changed files with 407 additions and 29 deletions.
7 changes: 7 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
History
=======

0.2.0 (2019-01-22)
------------------

* Add -a/--add to enable adding packages to requirement files.
* Added -d/--docs to launch docs URL in browser
* Allow version specific querying

0.1.2 (2019-01-20)
------------------

Expand Down
84 changes: 83 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,89 @@ Features
...
* More to come ..
* Version specific information..

Examples:

.. code-block:: bash
$ whatsonpypi django==2.1.4 --more
...
* Launch documentation URL of project in a browser tab

Examples:

.. code-block:: bash
$ whatsonpypi django --docs
* Add packages to your requirements files.

Examples:

.. code-block:: bash
$ whatsonpypi django --add
By default, it searches for files with names matching ``requirements*.txt``
in the current working directory and adds the dependency to the end of the
file.

If there's more than one file, you will see a prompt allowing you to select the files
that should be modified.

If you want the dependency to be added to a specific line,
mention a comment ``#wopp`` on its own line, which will be replaced with the dependency:

Example:

Do this in your requirements.txt:

.. code-block:: yaml
# Django
django==2.1.5
# testing
pytest==4.1.1
#wopp
Then running this:

.. code-block:: bash
$ whatsonpypi pytest-runner --add
will produce this:

.. code-block:: yaml
# Django
django==2.1.5
# testing
pytest==4.1.1
pytest-runner==4.2
Existing dependencies will be replaced with newer versions. Dependency version
by default is the latest unless specified explicitly like:

.. code-block:: bash
$ whatsonpypi pytest-runner==4.1 --add
Optionally, directory to search for requirement files can be specified with ``--req-dir``.
Both absolute and relative paths are allowed. Must be a directory.

.. code-block:: bash
$ whatsonpypi pytest-runner==4.1 --add --req-dir /Users/Me/Documents/GitHub/project/requirements
Default value (if not provided) is the directory where the command is run (cwd).


See all options with:

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Click>=6.0
Click>=7.0
requests>=2.18.0
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.1.2
current_version = 0.2.0
commit = True

[bumpversion:file:setup.py]
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

# Package meta-data.
NAME = 'whatsonpypi'
VERSION = '0.1.2'
VERSION = '0.2.0'
DESCRIPTION = "CLI tool to find package info on PyPI"
AUTHOR = "Visesh Prasad"
EMAIL = 'viseshrprasad@gmail.com'
URL = 'https://github.com/viseshrp/whatsonpypi'
REQUIRES_PYTHON = ">=2.7"
REQUIREMENTS = ['future>=0.15.2', 'Click>=6.0', 'requests>=2.18.0', ]
REQUIREMENTS = ['future>=0.15.2', 'Click>=7.0', 'requests>=2.18.0', ]
SETUP_REQUIREMENTS = ['pytest-runner', ]
TEST_REQUIREMENTS = ['pytest', ]

Expand Down
2 changes: 1 addition & 1 deletion whatsonpypi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

__author__ = """Visesh Prasad"""
__email__ = 'viseshrprasad@gmail.com'
__version__ = '0.1.2'
__version__ = '0.2.0'
53 changes: 48 additions & 5 deletions whatsonpypi/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
from __future__ import unicode_literals # unicode support for py2

import click
from whatsonpypi import __version__

from .utils import pretty
from whatsonpypi import __version__
from .utils import pretty, extract_pkg_version
from .whatsonpypi import get_query_response


Expand All @@ -24,7 +24,41 @@
show_default=True,
help="Flag to enable expanded output"
)
def main(package, more):
@click.option(
'-d',
'--docs',
is_flag=True,
required=False,
default=False,
help="Flag to open docs or homepage of project"
)
@click.option(
'-a',
'--add',
is_flag=True,
required=False,
default=False,
help="Flag to enable adding of dependencies to requirement files."
" By default, it searches for files with names matching requirements*.txt"
" in the current working directory and adds the dependency to the end of the"
" file. If you want the dependency to be added to a specific line,"
" mention a comment '#wopp' on its own line which will be replaced with the dependency."
" Existing dependencies will be replaced with newer versions. Dependency version"
" by default is the latest unless specified explicitly with 'whatsonpypi package==version'."
" Directory to search for requirement files can be specified with --req-dir"
)
@click.option(
'-r',
'--req-dir',
type=click.Path(exists=True, file_okay=False, dir_okay=True,
readable=True, writable=True, resolve_path=True,
allow_dash=False),
required=False,
default=".",
show_default=True,
help="Directory to search for requirement files. Only used when --add is used."
)
def main(package, more, docs, add, req_dir):
"""
CLI tool to find the latest version of a package on PyPI.
Expand All @@ -33,11 +67,20 @@ def main(package, more):
$ whatsonpypi django
"""
try:
# get version if given
package_, version = extract_pkg_version(package)

output = get_query_response(
package=package,
package=package_ or package,
version=version,
more_out=more,
launch_docs=docs,
add_to_req=add,
req_dir=req_dir
)
pretty(output)
# output is not always expected and maybe None sometimes.
if output:
pretty(output)
except Exception as e:
# all other exceptions
raise click.ClickException(e)
Expand Down
23 changes: 17 additions & 6 deletions whatsonpypi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def summary(self):

@property
def homepage(self):
return self.json.get('home_page')
return self.json.get('homepage')

@property
def package_url(self):
Expand All @@ -47,6 +47,10 @@ def package_url(self):
def project_urls(self):
return self.json.get('project_urls')

@property
def project_docs(self):
return self.project_urls.get('Documentation') or self.homepage

@property
def requires_python(self):
return self.json.get('requires_python')
Expand Down Expand Up @@ -103,16 +107,17 @@ def __init__(self, pool_connections=True, request_hooks=None):
# default_hooks() returns {'response': []}
self.request_hooks = request_hooks or hooks.default_hooks()

def request(self, package=None, timeout=3.1, max_retries=3, ):
def request(self, package=None, version=None, timeout=3.1, max_retries=3):
"""
Make a HTTP GET request with the provided params
:param timeout: request timeout seconds
:param max_retries: number of times to retry on failure
:param package: name of the python package to search
:param version: version of the python package to search
:return: response serialized by WoppResponse object
"""
url = self._build_url(package)
url = self._build_url(package, version)
req_kwargs = {
'method': 'GET',
'url': url,
Expand Down Expand Up @@ -143,20 +148,26 @@ def request(self, package=None, timeout=3.1, max_retries=3, ):
)

if response.status_code == 404:
raise PackageNotFoundError("Sorry, but that package couldn't be found on PyPI.")
raise PackageNotFoundError("Sorry, but that package/version couldn't be found on PyPI.")

# serialize response
wopp_response = WoppResponse(int(response.status_code), response.cleaned_json)
return wopp_response

def _build_url(self, package=None):
def _build_url(self, package=None, version=None):
"""
Builds the URL with the path params provided.
:param package: name of package
:param version: version of package
:return: fully qualified URL
"""
if package is None:
raise PackageNotProvidedError('A package name is needed to proceed.')

return "{}/{}/json".format(self.base_url, package)
if version is not None:
url = "{}/{}/{}/json".format(self.base_url, package, version)
else:
url = "{}/{}/json".format(self.base_url, package)

return url
5 changes: 5 additions & 0 deletions whatsonpypi/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@
from __future__ import unicode_literals # unicode support for py2

PYPI_BASE_URL = 'https://pypi.org/pypi'

REQUIREMENTS_FILE_PATTERN = "requirements*.txt"
REQUIREMENTS_REPLACE_COMMENT = "#wopp"
ALL_OPTION = 'ALL'
REQ_LINE_REGEX = "([A-Za-z0-9_-]+)((==)|(>=)|(<=))([A-Za-z0-9_.]+)"
18 changes: 18 additions & 0 deletions whatsonpypi/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,21 @@ class PackageNotFoundError(WoppException):
"""
Raised when a package is not found on PyPI
"""


class DocsNotFoundError(WoppException):
"""
Raised when a package does not have documentation or homepage urls
"""


class URLLaunchError(WoppException):
"""
Raised when there's a problem opening a URL in the browser
"""


class BadRequirementsFormatError(WoppException):
"""
Raised when the requirements file is not in the right format.
"""
33 changes: 33 additions & 0 deletions whatsonpypi/param_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-

from __future__ import unicode_literals # unicode support for py2

import click


class MultipleChoice(click.Choice):
"""
extension of click.Choice that
accepts multiple choice inputs
and converts them into a list
"""
name = 'Multiple Choice Param Type'

def convert(self, value, param, ctx):
cleaned_value = value.strip()

if cleaned_value in self.choices:
return [cleaned_value]

choice_list = cleaned_value.split(',')
valid_choice_list = []

for choice in choice_list:
choice = choice.strip().lower() # lower is for when we have a,b,c as options.
if choice not in self.choices:
self.fail('Invalid choice: %s (choose from %s)' %
(choice, ', '.join(self.choices)), param, ctx)

valid_choice_list.append(choice)

return valid_choice_list
25 changes: 25 additions & 0 deletions whatsonpypi/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,33 @@
"""Utility methods"""
from __future__ import unicode_literals # unicode support for py2

import re

import click

from .constants import REQ_LINE_REGEX


def extract_pkg_version(in_str):
"""
Use regex to extract package and version
:param in_str: input string
:return: tuple of pkg, version
"""
package = None
version = None

try:
reg_search = re.search(REQ_LINE_REGEX, in_str)
if reg_search:
package = reg_search.group(1)
version = reg_search.group(6)
except IndexError:
pass

return package, version


def pretty(input_, indent=0):
"""
Expand Down

0 comments on commit c55105e

Please sign in to comment.