Commit fceb99f2 authored by Nik Khlestov's avatar Nik Khlestov

Finished draft implementation.

parent ea99811f
## Install
Command-line interface for update-tracker.
## Installation
`pip install git+`
It can be installed in virtualenv, or globally.
## Usage
See `uptr --help`.
\ No newline at end of file
Commands are organised in groups to manage user, project and package.
All options in the commands listed here have shorter variants. You can see them with `uptr <group> <command> -h` command.
### Registration, activation, login.
To register, run:
uptr user register --email <your email> --password <your password>
If you prefer to not provide pcredentials in command line, you can use environment variables `EMAIL` and `PASSWORD`.
After you register, we'll send you an email to verify it's really yours. There'll be a command for you to run to activate account.
After your account is activated, you can login with next command:
uptr user login --email <your email> --password <your password>
You can use environment variables here also.
Your access token will be stored at `~/.uptr.yaml`.
If you'd like to logout, you can run
uptr user logout
It will erase your token from `~/.uptr.yaml`.
### Projects management
After you logged in, you can start a new project with command:
uptr project start --name <your project name>
If you want to have spaces in your project name, please enclose it in quotes:
uptr project start --name "Some project with spaces in names"
To verify it've successfully created, or just to see a list of your projects run:
uptr project list
Output is tab-separated lines of name, id, and a flag to mark a project is pinned.
#### Pinning a project
Given in most cases you'll have a tracking project per programming project, to minimize arguments required to operate packages, you can set a default project for some directory:
uptr project pin --project-id <project id>
You can get project id from the second column of `uptr project list` output.
To check if pinning was succesful, you can look at projects list.
### Packages management
First of all, all packages exist in the scope of a project. In case you don't have a pinned project, or want to use not default one, you have to provide its id as:
uptr package --project-id <id> <command> <command options>
To add package, run:
uptr package upsert --distname <distribution name> --master-site <master site> --package-version <package version>
If your package is on pypi, dockerhub or github, distibution name is not required.
For example, to track Django:
uptr package upsert -m -p 1.11.12
Next, you'll have mail notifications about outdated packages.
To update package version, run `upsert` again.
uptr package upsert -m -p 2.0.5
To see packages list, run:
uptr package list
Output is tab-separated lines of name, current version, latest version, master site, id.
If you don't want a package to be tracked anymore, run:
uptr package delete --package-id <package id>
You can get package id from the last column of `uptr package list` output.
......@@ -14,7 +14,7 @@ class ClientConfiguration(Configuration):
token = None
address = ''
address = ''
project_id = 0
......@@ -4,10 +4,11 @@ import sys
from click import MissingParameter
from .main import settings, session
from .utils import DistUrl
@click.option('--project-id', '-p', type=int, help='Project id, required if no pinned project.')
@click.option('--project-id', '-i', type=int, help='Project id, required if no pinned project.')
def package(ctx, project_id):
"""Work with packages in project."""
......@@ -20,33 +21,79 @@ def package(ctx, project_id):
raise MissingParameter(param_hint=['--project-id', '-p'], param_type='option',
message='Please provide it or pin the project.')
ctx.obj = {
'project_id': project_id ,
'project_id': current_project_id,
@click.option('--distname', '-d', required=False,
help='the name of distribution. Optional for pypi, github, dockerhub.')
@click.option('--master-site', '-m', required=True, type=DistUrl(),
help='URL of a web-site, where there is a link to distribution files')
@click.option('--package-version', '-p', required=True,
help='your local version number, e.g. the one you have installed on your machine')
def add(ctx, distname, master_site, version):
def upsert(ctx, distname, master_site, package_version, is_generic):
"""Add or update package."""
if is_generic and not distname:
raise click.MissingParameter(message='Distribution name is required for generic sites.',
param_hint=['--distname', '-d'], param_type='option')
url = settings.address + '/api/projects/%s/packages/' % ctx.obj['project_id']
resp =, data={
'distname': distname,
'master_site': master_site,
'version': version
'version': package_version
if resp.status_code == 201:
elif resp.status_code == 200:
import ipdb; ipdb.set_trace()
def list(ctx):
List all tracked projects.\n
Format (tab-delimited): name, current version, latest version, master site, id.
url = settings.address + '/api/projects/%s/packages/' % ctx.obj['project_id']
resp = session.get(url)
data = resp.json()
echo_data = []
for item in data:
pac = item['package']
'%s\t%s\t%s\t%s\t%s' % (
@click.option('--package-id', '-p', required=True, type=int,
help='id of a package, can be obtained from packages list.')
def update(ctx, url):
def delete(ctx, package_id):
"""Stop tracking a package."""
url = settings.address + '/api/projects/%s/packages/%s' % (ctx.obj['project_id'], package_id)
resp = session.delete(url)
if resp.status_code == 404:
click.echo('NO PACKAGE')
elif resp.status_code == 204:
......@@ -26,7 +26,7 @@ def start(name):
@click.option('--project-id', '-p', type=int, required=True)
@click.option('--project-id', '-i', type=int, required=True)
def pin(project_id):
"""Set project as default (in the scope of directory)."""
with open(PROJECT_CONFIG_FILE, 'w') as ofile:
......@@ -35,7 +35,9 @@ def pin(project_id):
def list():
"""List all tracked projects as tab-delimited name, id, is_pinned. """
List all tracked projects.\n
Format (tab-delimited): name, id, is_pinned. """
url = settings.address + '/api/projects/'
resp = session.get(url).json()
notification_message = []
......@@ -12,8 +12,8 @@ def user():
@click.option('--email', '-e', envvar='EMAIL', help='8 symbols minimum.')
@click.option('--password', '-p', envvar='PASSWORD')
@click.option('--email', '-e', envvar='EMAIL', help='8 symbols minimum.', required=True)
@click.option('--password', '-p', envvar='PASSWORD', required=True)
def register(email, password):
url = settings.address + '/api/auth/register/'
resp =, data={
......@@ -30,9 +30,9 @@ def register(email, password):
@click.option('--user-uuid', '-u')
@click.option('--timestamp', '-t')
@click.option('--signature', '-s')
@click.option('--user-uuid', '-u', required=True)
@click.option('--timestamp', '-t', required=True)
@click.option('--signature', '-s', required=True)
def verify(user_uuid, timestamp, signature):
url = settings.address + '/api/auth/verify-registration/'
resp =, data={
......@@ -48,8 +48,8 @@ def verify(user_uuid, timestamp, signature):
@click.option('--email', '-e', envvar='EMAIL')
@click.option('--password', '-p', envvar='PASSWORD')
@click.option('--email', '-e', envvar='EMAIL', required=True)
@click.option('--password', '-p', envvar='PASSWORD', required=True)
def login(email, password):
url = settings.address + '/api/auth/login/'
resp =, data={
import re
from urllib.parse import urlsplit
import click
IMPLEMENTED_SITES = ('', '', '')
class URL(click.ParamType):
name = 'url'
regex = re.compile(
r'^(?:http)s?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
def convert(self, value, param, ctx):
if not'invalid URL', param, ctx)
return value
class DistUrl(URL):
def convert(self, value, param, ctx):
value = super().convert(value, param, ctx)
parsed = urlsplit(value)
ctx.params['is_generic'] = parsed.hostname not in IMPLEMENTED_SITES
return value
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment