From 13469ef9026671cd7eee2a499200e827e24fb51a Mon Sep 17 00:00:00 2001 From: Lenarduzzi Date: Fri, 8 Mar 2019 19:11:45 -0300 Subject: [PATCH 1/6] [FL] First version of short_url solution. Some test scenarios are missing. --- short_url/.gitignore | 1 + short_url/api/__init__.py | 0 short_url/api/__init__.pyc | Bin 0 -> 155 bytes short_url/api/api.py | 80 ++++++++++++++++++++++++++++++++ short_url/api/api.pyc | Bin 0 -> 3204 bytes short_url/app/__init__.py | 0 short_url/app/__init__.pyc | Bin 0 -> 155 bytes short_url/app/service.py | 36 ++++++++++++++ short_url/app/service.pyc | Bin 0 -> 1237 bytes short_url/test/__init__.py | 0 short_url/test/test_shorturl.py | 64 +++++++++++++++++++++++++ 11 files changed, 181 insertions(+) create mode 100644 short_url/.gitignore create mode 100644 short_url/api/__init__.py create mode 100644 short_url/api/__init__.pyc create mode 100644 short_url/api/api.py create mode 100644 short_url/api/api.pyc create mode 100644 short_url/app/__init__.py create mode 100644 short_url/app/__init__.pyc create mode 100644 short_url/app/service.py create mode 100644 short_url/app/service.pyc create mode 100644 short_url/test/__init__.py create mode 100644 short_url/test/test_shorturl.py diff --git a/short_url/.gitignore b/short_url/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/short_url/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/short_url/api/__init__.py b/short_url/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/short_url/api/__init__.pyc b/short_url/api/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82eaffdedc2b6fd894daa52c456c96b049333a5e GIT binary patch literal 155 zcmZSn%*$mlzb+=30SXv_v;zv@#i>QbF>XGo zd5J|Sr7(eMah}Psl_qH8Tmye@ufvMF^L74G4b)4d6^~g a@p=W7B^*G5Y;yBcN^?@}K-LulF#`ZuVI>Lx literal 0 HcmV?d00001 diff --git a/short_url/api/api.py b/short_url/api/api.py new file mode 100644 index 0000000..216b505 --- /dev/null +++ b/short_url/api/api.py @@ -0,0 +1,80 @@ +import re +import random +import string +import json +import datetime +from flask import Response, request + + +# global variables +stored_urls = {} +letters_and_digits = string.ascii_letters + string.digits + + +def is_valid(short_code): + return re.match(r'[A-Za-z0-9]', short_code) and len(short_code) == 6 + + +def exist(short_code): + return short_code in stored_urls.keys() + + +def generate_random_code(string_length=6): + """ Generates a random alphanumeric string. """ + global letters_and_digits + return ''.join(random.choice(letters_and_digits) for i in range(string_length)) + + +def short_url(): + """ Relates a short alphanumeric code which will represents a long url.""" + content = request.get_json() + print json.dumps(content) + short_code = "" + # checks if code is missing. If yes, a new code is generated. + if 'code' in content.keys(): + # validate short code + if is_valid(content['code']): + if not exist(content['code']): + short_code = content['code'] + else: + return "Short code is already in use", 409 + else: + return "Precondition failed", 412 + else: + short_code = generate_random_code() + print short_code + + # stores url into a dictionary. + new_url_item = { + 'url': content['url'], + 'created_at': datetime.datetime.utcnow().isoformat(), + 'last_usage': datetime.datetime.utcnow().isoformat(), + 'usage_count': 0} + + stored_urls[short_code] = new_url_item + + response = {'code': short_code} + return Response(json.dumps(response), status=201, mimetype='application/json') + + +def get_url(short_name): + """ Returns the long url associated to short code name.""" + if short_name in stored_urls.keys(): + # update usage count + current_count = stored_urls[short_name]['usage_count'] + stored_urls[short_name]['usage_count'] = current_count+1 + + # update last usage time + stored_urls[short_name]['last_usage'] = datetime.datetime.utcnow().isoformat() + return "Location: {0}".format(stored_urls[short_name]['url']), 302 + else: + return "Bad Request.", 400 + + +def get_stats(short_name): + """ Returns additional information related to short code name.""" + if short_name in stored_urls.keys(): + response = stored_urls[short_name] + return Response(json.dumps(response), status=200, mimetype='application/json') + else: + return "Bad Request.", 400 diff --git a/short_url/api/api.pyc b/short_url/api/api.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26098a401be1917aaf23c574a9b04cb12a87ec92 GIT binary patch literal 3204 zcmb_e-ESL35TCnqoY?tj8cIr_rN@Vm`w-Iy6p1z}A1Sm#RaKn@55|>sy4~38obR05 zJ(AGo0i}NcuSmS|zrf#vc;E?sGZ#AoMO7bcd&jeTyE`*GzxnM2f37ymzkli_bpG-2 z|2dld4MO4PQA*UE=^k~x>9|i_f2s%64W@dHx;4>#N*8Ecr*56bi_~2t&tR`5(mstE zBAawQUM3aLOFUR1RTE{E)B>qGy@a(jQj0Qph18NL>!cc@TxIV%ttLD*|KRha#ooIm zh)CN>o>}b{(JAy{sjW*q=-dLdJv941gd-XuB9C4Wo%xi%N6r@=gFYSjFu?Pl*k#D? zzu4}^+dtmf{`8rtqc4vzpKZ7W{EcIm3>_~>b=G2Ij@`4vym0*_SGt8BiLjqR+q>KQ z*6PCczJ9E;xKL&9fwl)OpY)!dxM7}ce|4;j#8_>6)gEP$_Tq`*)}EY5tj6|#h*P81 z*bw_d-Yit^N~r5l5MdSV8PP!N%AANpo3rPsqCK(pN5g}^aAyGEzp`C%cwu<@ODDEOWp8n18+fy#)e z$uKvGmVi?29Mal{2YqD*#@Py+Y^;3;0RXb|Jy6@gp1T8te>^GfZ=oZHTBph|ALb}G znV=Lt4|IjjRh}qk(g$J(Za81B+%@daPH|59)mcd$Y)&1dAc0EkA0Ou)n*9r6hSXE& z;EUkAC?7ro6p$Q10f`#b=uE&|j0vG(1mj!CSFz9Ox#-*ew1c&WBU0b+3g3Kk%e>vLkzL`| z&S^kyWg1Mts8XOjkR3Sg3c7x$CB^Un;fIk<&tWWu|6dd{lY~b@lMKTnlcr&zCxx~; za}4`5&julixE(R|NG0=pdjq`=6HzG34d$kWj@3zMvaqz;`~q0oH!$mIp_4pQ#+f_| z_hXZ4Wq!p&AvWwRoR(m%16{;Ux@l}(zqIi{gX1xk2}Qa{cbPdU5bI)BTJr}#UxFB$ zv34gDZJC|o$s{#N%r-wB**p{6zMj+7)K_`lVw^`@c4By-U7u~p;fTU?q{{KcM(kL` zP>i^uBHmTjC`9YnVSZM+B+HK+&oDOM&kGQL#CJ`EMlD9OTs=5OhqMTRBKJg@K4OCH z8>h#zE%GKe)tu{&J)7MaoO$~Z1o{LTlE+5ZFmbo;4OQb_%{8fzgpcM zQ(D~PwFLxfX;zcCJ&F-`F}Q_#+^R|kAisVwxXWcLmCKbQg*ts0(5|Y{XkoU+f)p*T z{C!-wsG<(8_)beMrB7fR)!@n^v!NU68NS2V+B`7~!qDYmRVD)MEFSB2^cEJP_>hz9 z5S9_}IIjS9H++8QGEiAnJ;4nGxDa;Ha}yux*0Ej))Ih7!UZpUZ$!6Pgb)Ie z3KSLlUc3XaYmQw>%Ax==s_K|?^4TwjIDobbRYD*nea+hlZhG(dvwM%DkOZ^%e}eB# zvxDR!l_-8B5sb#XCH$*!BO9d^ zUgmd*=xvBg$>H*>yfR~4;Z=!B#?Yha1`B?@kTgYHBGCujMVsSW8lMX1gZa;XgN|hv z$lvgq-Ys89KjM0e81Uyz$*(Due~c};w)erY2SPByWD<;EDYl6*xWDC{L)KUC9Fee| zK$Tq(E{i^7jRTBsLtI2E+T31BcgMLZQ+=Q7&vF`9yysW-yL+P}{U B%2@ya literal 0 HcmV?d00001 diff --git a/short_url/app/__init__.py b/short_url/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/short_url/app/__init__.pyc b/short_url/app/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c69ce7f24e8e8a80a60104ae77765040c84a6251 GIT binary patch literal 155 zcmZSn%*$mlzb+=30SXv_v;zv@#i>QbF>XGo zd5J|Sr7(eMah}Psl_qH8Tmye@ufvMF^L5QG4b)4d6^~g a@p=W7B^*G5Y;yBcN^?@}K-LulF#`ZvRwWSt literal 0 HcmV?d00001 diff --git a/short_url/app/service.py b/short_url/app/service.py new file mode 100644 index 0000000..db866c9 --- /dev/null +++ b/short_url/app/service.py @@ -0,0 +1,36 @@ +import json +import api.api +from flask import Flask, request + +app = Flask(__name__) + + +# Error handler when path is not available. +@app.errorhandler(404) +def not_found(): + message = { + 'status': 404, + 'message': 'Not Found: ' + request.url, + } + resp = json.dumps(message) + resp.status_code = 404 + return resp + +# By default, all methods accepts GET requests. We need to specify that short_url() will receive POST requests. +api.api.short_url.methods = ['POST'] + + +def main(): + # Registering main server functions. + app.add_url_rule('/urls', 'urls', view_func=api.api.short_url) + app.add_url_rule('/', 'get_url', view_func=api.api.get_url) + app.add_url_rule('//stats', 'stats', view_func=api.api.get_stats) + app.run(debug=True) + + # server will run indefinitely + while True: + pass + + +if __name__ == "__main__": + main() diff --git a/short_url/app/service.pyc b/short_url/app/service.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cbf4941c155910c03da02327a0772ac8120b74fd GIT binary patch literal 1237 zcmcIjO;6iE5S_Ihl7LYul}ZmiaOfeI!i`D@AtYK(6-5qJrO49C%?`mK{$O`aK?12# zD)kTapY{i|GvkOQr{JFmR>fviThQ$@o`-q17PR7uqU~>Mmu42P8+1PZp#Y>0&IxH8({OLd z;1h7YlJit`J7NOdEpjw0@gnPmFKsgHT73hfVaKQVVN?pIgwS49q=hoO;`R$Xc zDb4=2U?|YJY=W{PAspZtKx1b^rWdn_|+n)EGF^HrUm-TBL^eEseg?KpSnQg>NduW92ntLu~&-Q;83sqcKB z>Xen&7mS)eTucUnP<7SQ;EC!h+5POp@O;bxzPNwh-!)`f-dY}+##Td8I8bU4i&)=f zEc!Dh>MmoQ#~1~AKqSsVsoPba*&41V@QF^}aKK1U9Rt~=rkr4u2?uU+{GuJEE^Izho$&7B z;tkt^ET?Ts!Fz5STq^o+W#!{lWO=zo?}ef-72g{nDcU2i)*RP*z$g=DQn< Date: Sat, 9 Mar 2019 17:00:01 -0300 Subject: [PATCH 2/6] [FL] Removes compiled files. --- short_url/.gitignore | 3 ++- short_url/api/__init__.pyc | Bin 155 -> 0 bytes short_url/api/api.pyc | Bin 3204 -> 0 bytes short_url/app/__init__.pyc | Bin 155 -> 0 bytes 4 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 short_url/api/__init__.pyc delete mode 100644 short_url/api/api.pyc delete mode 100644 short_url/app/__init__.pyc diff --git a/short_url/.gitignore b/short_url/.gitignore index 723ef36..1163cb2 100644 --- a/short_url/.gitignore +++ b/short_url/.gitignore @@ -1 +1,2 @@ -.idea \ No newline at end of file +.idea +*.pyc \ No newline at end of file diff --git a/short_url/api/__init__.pyc b/short_url/api/__init__.pyc deleted file mode 100644 index 82eaffdedc2b6fd894daa52c456c96b049333a5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155 zcmZSn%*$mlzb+=30SXv_v;zv@#i>QbF>XGo zd5J|Sr7(eMah}Psl_qH8Tmye@ufvMF^L74G4b)4d6^~g a@p=W7B^*G5Y;yBcN^?@}K-LulF#`ZuVI>Lx diff --git a/short_url/api/api.pyc b/short_url/api/api.pyc deleted file mode 100644 index 26098a401be1917aaf23c574a9b04cb12a87ec92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3204 zcmb_e-ESL35TCnqoY?tj8cIr_rN@Vm`w-Iy6p1z}A1Sm#RaKn@55|>sy4~38obR05 zJ(AGo0i}NcuSmS|zrf#vc;E?sGZ#AoMO7bcd&jeTyE`*GzxnM2f37ymzkli_bpG-2 z|2dld4MO4PQA*UE=^k~x>9|i_f2s%64W@dHx;4>#N*8Ecr*56bi_~2t&tR`5(mstE zBAawQUM3aLOFUR1RTE{E)B>qGy@a(jQj0Qph18NL>!cc@TxIV%ttLD*|KRha#ooIm zh)CN>o>}b{(JAy{sjW*q=-dLdJv941gd-XuB9C4Wo%xi%N6r@=gFYSjFu?Pl*k#D? zzu4}^+dtmf{`8rtqc4vzpKZ7W{EcIm3>_~>b=G2Ij@`4vym0*_SGt8BiLjqR+q>KQ z*6PCczJ9E;xKL&9fwl)OpY)!dxM7}ce|4;j#8_>6)gEP$_Tq`*)}EY5tj6|#h*P81 z*bw_d-Yit^N~r5l5MdSV8PP!N%AANpo3rPsqCK(pN5g}^aAyGEzp`C%cwu<@ODDEOWp8n18+fy#)e z$uKvGmVi?29Mal{2YqD*#@Py+Y^;3;0RXb|Jy6@gp1T8te>^GfZ=oZHTBph|ALb}G znV=Lt4|IjjRh}qk(g$J(Za81B+%@daPH|59)mcd$Y)&1dAc0EkA0Ou)n*9r6hSXE& z;EUkAC?7ro6p$Q10f`#b=uE&|j0vG(1mj!CSFz9Ox#-*ew1c&WBU0b+3g3Kk%e>vLkzL`| z&S^kyWg1Mts8XOjkR3Sg3c7x$CB^Un;fIk<&tWWu|6dd{lY~b@lMKTnlcr&zCxx~; za}4`5&julixE(R|NG0=pdjq`=6HzG34d$kWj@3zMvaqz;`~q0oH!$mIp_4pQ#+f_| z_hXZ4Wq!p&AvWwRoR(m%16{;Ux@l}(zqIi{gX1xk2}Qa{cbPdU5bI)BTJr}#UxFB$ zv34gDZJC|o$s{#N%r-wB**p{6zMj+7)K_`lVw^`@c4By-U7u~p;fTU?q{{KcM(kL` zP>i^uBHmTjC`9YnVSZM+B+HK+&oDOM&kGQL#CJ`EMlD9OTs=5OhqMTRBKJg@K4OCH z8>h#zE%GKe)tu{&J)7MaoO$~Z1o{LTlE+5ZFmbo;4OQb_%{8fzgpcM zQ(D~PwFLxfX;zcCJ&F-`F}Q_#+^R|kAisVwxXWcLmCKbQg*ts0(5|Y{XkoU+f)p*T z{C!-wsG<(8_)beMrB7fR)!@n^v!NU68NS2V+B`7~!qDYmRVD)MEFSB2^cEJP_>hz9 z5S9_}IIjS9H++8QGEiAnJ;4nGxDa;Ha}yux*0Ej))Ih7!UZpUZ$!6Pgb)Ie z3KSLlUc3XaYmQw>%Ax==s_K|?^4TwjIDobbRYD*nea+hlZhG(dvwM%DkOZ^%e}eB# zvxDR!l_-8B5sb#XCH$*!BO9d^ zUgmd*=xvBg$>H*>yfR~4;Z=!B#?Yha1`B?@kTgYHBGCujMVsSW8lMX1gZa;XgN|hv z$lvgq-Ys89KjM0e81Uyz$*(Due~c};w)erY2SPByWD<;EDYl6*xWDC{L)KUC9Fee| zK$Tq(E{i^7jRTBsLtI2E+T31BcgMLZQ+=Q7&vF`9yysW-yL+P}{U B%2@ya diff --git a/short_url/app/__init__.pyc b/short_url/app/__init__.pyc deleted file mode 100644 index c69ce7f24e8e8a80a60104ae77765040c84a6251..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155 zcmZSn%*$mlzb+=30SXv_v;zv@#i>QbF>XGo zd5J|Sr7(eMah}Psl_qH8Tmye@ufvMF^L5QG4b)4d6^~g a@p=W7B^*G5Y;yBcN^?@}K-LulF#`ZvRwWSt From 89e2159915c5ba028e18ca8995689b145353adaf Mon Sep 17 00:00:00 2001 From: Lenarduzzi Date: Sat, 9 Mar 2019 18:07:52 -0300 Subject: [PATCH 3/6] [FL] Comments were added. Also, new unit test was added. --- short_url/api/api.py | 6 ++++-- short_url/app/service.py | 2 +- short_url/test/test_shorturl.py | 7 +++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/short_url/api/api.py b/short_url/api/api.py index 216b505..b8f1dc9 100644 --- a/short_url/api/api.py +++ b/short_url/api/api.py @@ -12,10 +12,12 @@ def is_valid(short_code): + """ Checks if short_code complies with requirements: alphanumeric and length = 6.""" return re.match(r'[A-Za-z0-9]', short_code) and len(short_code) == 6 def exist(short_code): + """ Checks if short_code is already in use.""" return short_code in stored_urls.keys() @@ -29,7 +31,6 @@ def short_url(): """ Relates a short alphanumeric code which will represents a long url.""" content = request.get_json() print json.dumps(content) - short_code = "" # checks if code is missing. If yes, a new code is generated. if 'code' in content.keys(): # validate short code @@ -44,13 +45,14 @@ def short_url(): short_code = generate_random_code() print short_code - # stores url into a dictionary. + # creates new element new_url_item = { 'url': content['url'], 'created_at': datetime.datetime.utcnow().isoformat(), 'last_usage': datetime.datetime.utcnow().isoformat(), 'usage_count': 0} + # stores new element stored_urls[short_code] = new_url_item response = {'code': short_code} diff --git a/short_url/app/service.py b/short_url/app/service.py index db866c9..f609e83 100644 --- a/short_url/app/service.py +++ b/short_url/app/service.py @@ -21,7 +21,7 @@ def not_found(): def main(): - # Registering main server functions. + """ Registering main server functions. """ app.add_url_rule('/urls', 'urls', view_func=api.api.short_url) app.add_url_rule('/', 'get_url', view_func=api.api.get_url) app.add_url_rule('//stats', 'stats', view_func=api.api.get_stats) diff --git a/short_url/test/test_shorturl.py b/short_url/test/test_shorturl.py index 8c703c1..e4fc3dd 100644 --- a/short_url/test/test_shorturl.py +++ b/short_url/test/test_shorturl.py @@ -8,8 +8,6 @@ class ShortUrlTest(unittest.TestCase): - # initialization logic for the test suite declared in the test module - # code that is executed before all tests in one test run server_pid = 0 @@ -27,6 +25,7 @@ def tearDownClass(cls): child.kill() parent.kill() + # Scenarios def test_valid_short_code(self): response = requests.post("http://localhost:5000/urls", json={'url': 'http://example.com', 'code': 'test01'}) self.assertEqual(response.status_code, 201) @@ -60,5 +59,9 @@ def test_get_url_from_valid_short_code(self): response = requests.get("http://localhost:5000/test04") self.assertEqual(response.text, "Location: http://example4.com") + def test_get_url_from_not_valid_short_code(self): + response = requests.get("http://localhost:5000/test05") + self.assertEqual(response.status_code, 400) + if __name__ == '__main__': unittest.main() From 2da271e71218dff7ba61eb2b20fa5cdc4de97fa7 Mon Sep 17 00:00:00 2001 From: Lenarduzzi Date: Sun, 10 Mar 2019 11:19:01 -0300 Subject: [PATCH 4/6] [FL] Some tests were added for stats feature. --- short_url/test/test_shorturl.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/short_url/test/test_shorturl.py b/short_url/test/test_shorturl.py index e4fc3dd..8bd8d0d 100644 --- a/short_url/test/test_shorturl.py +++ b/short_url/test/test_shorturl.py @@ -1,4 +1,5 @@ import time +import datetime import json import unittest import psutil as psutil @@ -63,5 +64,31 @@ def test_get_url_from_not_valid_short_code(self): response = requests.get("http://localhost:5000/test05") self.assertEqual(response.status_code, 400) + def test_url_stats(self): + response = requests.post("http://localhost:5000/urls", json={'url': 'http://example6.com', 'code': 'test06'}) + created_at = datetime.datetime.utcnow().isoformat() + self.assertEqual(response.status_code, 201) + self.assertDictEqual({'code': 'test06'}, json.loads(response.text)) + + response = requests.get("http://localhost:5000/test06") + time.sleep(2) + response = requests.get("http://localhost:5000/test06") + time.sleep(2) + response = requests.get("http://localhost:5000/test06") + last_usage = datetime.datetime.utcnow().isoformat() + + response = requests.get("http://localhost:5000/test06/stats") + json_resp = json.loads(response.text) + + self.assertEqual(response.status_code, 200) + self.assertEqual(json_resp['url'], "http://example6.com") + self.assertEqual(json_resp['usage_count'], 3) + self.assertTrue(json_resp['created_at'] <= created_at) + self.assertTrue(json_resp['last_usage'] <= last_usage) + + def test_url_stats_with_wrong_short_code(self): + response = requests.get("http://localhost:5000/invalid_code/stats") + self.assertEqual(response.status_code, 400) + if __name__ == '__main__': unittest.main() From 0dd0e93383ce9f21b964fb13fc7ea214154c75d9 Mon Sep 17 00:00:00 2001 From: Lenarduzzi Date: Sun, 10 Mar 2019 11:30:20 -0300 Subject: [PATCH 5/6] [FL] Some comments on README doc were added. --- README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 73d26f9..137b27e 100644 --- a/README.md +++ b/README.md @@ -103,10 +103,35 @@ Content-Type: "application/json" * Not Found: If the `shortcode` cannot be found -## Delivery Steps: +## Delivery Steps: 1. Fork this repo to your own Github account. 2. Implement the functionality, including any instructions to setup and run the application. 3. Submit a PR to the `master` branch of this repository. Thank you and good luck! + +------------------------------------------------------------------------- + +##### About FL solution + +### Solution structure: + +* api: Contains all functions related to urls management +* app: Contains functions about setting up of service +* test: Contains unit tests for training all api functions + + +### How to run... + +## Run Short_URL service + +PyCharm is used for this purpouse + +1. Opens project in PyCharm +2. Performs right click over /app/service.py and select Run + +## Run tests + +1. Opens project in PyCharm +2. Performs right click over /test/test_shorturl.py and select Run From f3db3d64d83cb562764f1e78f4bdce0d2896b93d Mon Sep 17 00:00:00 2001 From: Lenarduzzi Date: Sun, 10 Mar 2019 11:35:22 -0300 Subject: [PATCH 6/6] [FL] Some comments on README doc were added. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 137b27e..a12c518 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ Thank you and good luck! ------------------------------------------------------------------------- -##### About FL solution +## About FL solution ### Solution structure: @@ -122,16 +122,16 @@ Thank you and good luck! * test: Contains unit tests for training all api functions -### How to run... +### How to ... -## Run Short_URL service +#### Run Short_URL service PyCharm is used for this purpouse 1. Opens project in PyCharm 2. Performs right click over /app/service.py and select Run -## Run tests +#### Run tests 1. Opens project in PyCharm 2. Performs right click over /test/test_shorturl.py and select Run