From 268c10c95a0e47675e0b10ba2a7f02816717b84c Mon Sep 17 00:00:00 2001 From: hitnik Date: Thu, 19 Aug 2021 13:04:03 +0300 Subject: [PATCH] short url generator --- app/db.py | 1 - app/utils.py | 77 +++++++++++++++++++++++++++++++++++------- main.py | 23 ++++++++++--- tests/conftest.py | 2 +- tests/test_argparse.py | 2 +- tests/test_main.py | 29 ++++++++++++++++ tests/test_utils.py | 16 +++++++-- 7 files changed, 127 insertions(+), 23 deletions(-) create mode 100644 tests/test_main.py diff --git a/app/db.py b/app/db.py index 6a62f7d..3531f77 100644 --- a/app/db.py +++ b/app/db.py @@ -6,7 +6,6 @@ DB_PATH = os.path.join(BASEDIR, 'db.sqlite3') - def init_db(db_path): """init sqlite database, create sqlite file if needed diff --git a/app/utils.py b/app/utils.py index c462a5a..55a6e93 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,23 +1,39 @@ import argparse import textwrap -from db import ( short_url_exist, get_short_url, - get_long_url_from_db, long_url_exist, - get_short_url_by_long +from db import (insert_long_url, short_url_exist, get_short_url, + get_long_url_from_db, long_url_exist, + get_short_url_by_long, insert_short_url, + insert_long_url ) +import shortuuid +from urllib.parse import urlunsplit +from config import NETLOC, SCHEME + + +class URLExistsError(Exception): + def __init__(self, *args): + super().__init__(*args) + self.message = "This short URL already exists" + + +class URLNotFoundError(Exception): + def __init__(self, *args: object): + super().__init__(*args) + self.message = "This short URL does not exist" def parser(): parser = argparse.ArgumentParser( - description="URL shortener app.", - formatter_class=argparse.RawTextHelpFormatter - ) + description="URL shortener app.", + formatter_class=argparse.RawTextHelpFormatter + ) parser.add_argument('url', metavar='URL', type=str, - help=textwrap.dedent(""" + help=textwrap.dedent(""" Provide URL here.\r If you want to shorten URL, """ + - """provide it with [--generate] argument.\r + """provide it with [--generate] argument.\r If you want to get long URL, """ + - """provide short URL without any other arguments. + """provide short URL without any other arguments. """), ) parser.add_argument('--generate', action='store_true', default=False, @@ -27,25 +43,60 @@ def parser(): help="""Use this argument and cpecify short URL, if you want to use custom short_url.""" ) - return parser + return parser class Shortener: - @staticmethod def get_long_url(short): + """ get long url by short url + + Args: + short (str): short url + + Raises: + Exception: Raises Exception when short url does not exists + + Returns: + [str]: short url + """ if short_url_exist(short): short_inst = get_short_url(short) long_inst = get_long_url_from_db(id=short_inst[1]) return long_inst[1] else: - raise Exception("This short URL does not exist") - + raise URLNotFoundError @staticmethod def gen_short_url(long): + """ Generates short url from long url. + If long url exists in DB return existing short url, + otherwise generate new short url + + Args: + long (str): long url + + Returns: + str: short url + """ if long_url_exist(long): long_inst = get_long_url_from_db(url=long) short_inst = get_short_url_by_long(long_inst[0]) return short_inst[2] + else: + uuid = shortuuid.uuid(name=long)[:7] + short_url = urlunsplit((SCHEME, NETLOC, uuid, '', '')) + long_id = insert_long_url(long) + insert_short_url(short_url, long_id) + return short_url + + @staticmethod + def save_url(short, long): + if not short_url_exist(short): + if long_url_exist(long): + long_inst = get_long_url_from_db(url=long) + insert_short_url(short, long_inst[0]) + return short + else: + raise URLExistsError diff --git a/main.py b/main.py index f969440..f393c52 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,23 @@ +import os +import sys +from app.utils import ( + parser, Shortener, + URLExistsError, URLNotFoundError +) +from app.db import DB_PATH, init_db - -from app.utils import parser +def main(): + if not os.path.exists(DB_PATH): + init_db(DB_PATH) + args = parser().parse_args() + print(args) + if not args.generate: + try: + Shortener.get_long_url(args.url) + except URLNotFoundError: + print("URL does not exists", file=sys.stderr) if __name__ == '__main__': - parser = parser() - args = parser.parse_args() - print(args) + main() diff --git a/tests/conftest.py b/tests/conftest.py index bed232c..3ca3ec2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ @pytest.fixture def db_path(): - db_path = os.path.join(tempfile.mkdtemp(), 'db.sqlite3') + db_path = os.path.join(tempfile.mkdtemp(), 'db1.sqlite3') return db_path diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 087a6a8..edd4102 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -8,5 +8,5 @@ def test_parser(argparser): assert args.url is 'test_url' assert args.generate is True assert args.short_url is 'short' - + diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..9b60f73 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,29 @@ +from main import main +from pytest_mock import mocker +import pytest +from argparse import Namespace +from test_db import script +from unittest import mock +from app.db import get_db, close_db, init_db +from argparse import ArgumentParser +import re + + + +def test_main_noparams(db_path, mocker): + mocker.patch('main.DB_PATH', db_path) + with pytest.raises(SystemExit): + main() + +def test_main_short_url(db_path, argparser, mocker, capsys): + mocker.patch('main.DB_PATH', db_path) + init_db(db_path=db_path) + with mock.patch('app.db.DB_PATH', db_path): + db = get_db() + db.executescript(script) + args = argparser.parse_args(['on.ln']) + parser = mocker.patch('argparse.ArgumentParser.parse_args', return_value=args) + main() + captured = capsys.readouterr() + print(captured) + assert 'URL does not exists\n' == captured.err \ No newline at end of file diff --git a/tests/test_utils.py b/tests/test_utils.py index 8649456..a55fdd0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,6 @@ -from app.utils import Shortener +from os import name +from urllib.parse import urlencode +from app.utils import Shortener, URLNotFoundError, URLExistsError from tests.test_db import script, manager_mock import pytest from unittest import mock @@ -7,7 +9,7 @@ def test_get_long_url(db_mock): db_mock.executescript(script) with manager_mock(db_mock, 'db.get_db', 'db.db_manager'): - with pytest.raises(Exception, match=r".* URL does .*"): + with pytest.raises(URLNotFoundError, match=r".* URL does not .*"): Shortener.get_long_url('raise') inst = Shortener.get_long_url('goo.gl') assert type(inst) is str @@ -19,3 +21,13 @@ def test_get_long_url(db_mock): inst = Shortener.gen_short_url('https://www.google.com/') assert type(inst) is str assert inst == 'goo.gl' + inst = Shortener.gen_short_url('http://www.onliner.by') + assert type(inst) is str + +def test_save_url(db_mock): + db_mock.executescript(script) + with manager_mock(db_mock, 'db.get_db', 'db.db_manager'): + with pytest.raises(URLExistsError): + Shortener.save_url('goo.gl', 'https://www.google.com/') + short = Shortener.save_url('on.by/ptrer', 'http://www.onliner.by') + assert isinstance(short, str)