├── .gitignore ├── Application ├── 1.Sensors │ ├── README.md │ ├── distance2.png │ ├── distance3.png │ └── sensors_data1.txt ├── 2.CorrectBrackets │ └── README.md └── 3.RottingApples │ ├── README.md │ ├── day0.jpg │ ├── day100.jpg │ ├── day3.jpg │ └── day7.jpg ├── LICENSE ├── README.md ├── partners ├── hacksoft.png └── hedgeserv.jpg ├── playground ├── closures.py ├── context_managers.py ├── demo.py ├── generators.py ├── mocks │ ├── calendar.py │ └── test_calendar.py ├── packages │ ├── demo.py │ ├── pkg │ │ ├── __init__.py │ │ ├── foo1.py │ │ ├── foo2.py │ │ └── foo3 │ │ │ └── __init__.py │ └── random.py ├── property.py ├── teams_generator.py └── test_demo.py ├── week01 ├── 01.PythonProblems │ ├── README.md │ ├── week1_solutions.py │ └── word_counter.py ├── 02.DiveIntoPython │ ├── README.md │ ├── biggest_palindrome_optimized.py │ └── nokia.jpg ├── 03.FinalRound │ ├── Anagrams │ │ └── README.md │ ├── CreditCard │ │ └── README.md │ ├── GoldbachConjecture │ │ └── README.md │ └── MatrixBombing │ │ └── README.md └── README.md ├── week02 ├── 01.LinuxCommands │ ├── README.md │ └── duhs.py ├── 02.CancellationPolicy │ ├── README.md │ ├── solution.py │ └── tests.py ├── 03.MoreTesting │ └── README.md └── README.md ├── week03 ├── 01.FractionsOOP │ ├── README.md │ ├── fraction.py │ └── test_fraction.py ├── 02.CashDesk │ └── README.md ├── 03.Polynomials │ └── README.md ├── 04.MusicLibrary │ └── README.md ├── 05.BowlingGame │ └── README.md ├── README.md ├── example.json └── json_tutorial.py ├── week04 ├── 01.Mixins │ ├── README.md │ └── solution │ │ ├── mixins.py │ │ └── test_mixins.py ├── 02.BeloteDeclarations │ └── README.md └── README.md ├── week05 ├── 01.DungeonsAndPythons │ └── README.md └── README.md ├── week06 ├── 01.Decorators │ ├── README.md │ └── required.py ├── 02.Generators │ ├── Book.zip │ ├── README.md │ └── chain.py └── README.md ├── week07 ├── 01.ContextManagers │ ├── README.md │ ├── measure_performance.py │ ├── silence_exception.py │ ├── test_measure_performance.py │ └── test_silence_exception.py ├── 02.PipAndVirtualenv │ └── README.md └── README.md ├── week08 ├── 01.Graphs │ └── README.md └── 02.Mocks │ └── README.md ├── week09 ├── 01.SQL-Intro │ ├── README.md │ ├── movies.sql │ └── movies_schema.png ├── 02.SQL-Subqueries-JOINs-Groups │ ├── README.md │ ├── joins_reminder.jpg │ └── tasks │ │ ├── 01.PC │ │ ├── README.md │ │ ├── pc.db │ │ └── pc_schema.png │ │ └── 02.Ships │ │ ├── README.md │ │ ├── ships.db │ │ └── ships_schema.png └── 03.SQL-and-Python │ ├── README.md │ ├── demo │ ├── hash.py │ ├── register_form.py │ ├── setup_users_database_with_hashed_passwords.py │ └── setup_users_database_with_plain_text_passwords.py │ └── tasks │ ├── 01.Business-Card-Catalog │ └── README.md │ ├── 02.Vehicle-Repair-Manager │ ├── README.md │ └── vehicle_repair_management_db_schema.png │ └── 03.Break-the-Cipher │ ├── README.md │ └── secret_keeper.py ├── week10 ├── CinemaReservation │ ├── README.md │ ├── example │ │ └── cinema.db │ ├── hack_cinema │ │ ├── __init__.py │ │ ├── cinema.db │ │ ├── db.py │ │ ├── db_schema │ │ │ ├── __init__.py │ │ │ ├── movies.py │ │ │ └── users.py │ │ ├── index_view.py │ │ ├── movies │ │ │ ├── controllers.py │ │ │ ├── models.py │ │ │ ├── movies_gateway.py │ │ │ ├── queries.py │ │ │ └── views.py │ │ ├── settings.py │ │ └── users │ │ │ ├── __init__.py │ │ │ ├── controllers.py │ │ │ ├── models.py │ │ │ ├── users_gateway.py │ │ │ └── views.py │ └── main.py └── README.md ├── week11 ├── README.md ├── SQLAlchemy.md └── examples │ ├── anonymous_classes.py │ ├── base_tracking.py │ ├── class_deconstruction.py │ ├── class_decorators.py │ ├── declarative.py │ ├── decorators.py │ ├── ducktyping.py │ ├── eval_exec.py │ ├── metaclass.py │ ├── registry.py │ ├── serializer │ ├── __init__.py │ ├── fields.py │ ├── main.py │ └── serializers.py │ └── setattr.py ├── week12 ├── Crawler │ └── README.md └── README.md └── week13 ├── 01.Models-and-Admin └── README.md ├── 02.Views └── README.md └── hackademy ├── education ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20200525_1459.py │ └── __init__.py ├── models.py ├── templates │ ├── courses │ │ ├── base.html │ │ ├── create.html │ │ ├── detail.html │ │ ├── edit_course.html │ │ ├── list.html │ │ ├── new_experiment.html │ │ ├── new_experiment_success.html │ │ └── session.html │ └── index.html ├── tests.py ├── urls.py └── views │ ├── __init__.py │ └── courses.py ├── hackademy ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py └── manage.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /Application/1.Sensors/README.md: -------------------------------------------------------------------------------- 1 | # Сензори за мръсен въздух 2 | 3 | Имате компания, която мери нивата на лош въздух в София. Имате сензори в целия град. Понякога сензорите се чупят или започват да дават неверни резултати. Поради тази причина на повечето места сте сложили повече от 1 сензор. 4 | 5 | Можете да разберете дали даден сензор е в неизправност, ако има много голяма разлика между неговата стойност и стойностите на сензорите, които са му "съседни". 6 | 7 | ## Съседни сензори 8 | 9 | Търсим съседните сензори на (5, 8 ) в радиус от 2 километра. Спрямо схемата на картинката това е само сензор (7,8) 10 | 11 | ![](distance2.png) 12 | 13 | Ако обаче търсим съседните сензори на сензор (5, 8 ) с радиус 3, получаваме сензори (7, 8), (8, 8), (7, 7), (3, 6) и (5, 5) 14 | 15 | ![](distance3.png) 16 | 17 | ## Задачата 18 | 19 | Информацията за сензорите се намира във файл ([тук](sensors_data1.txt) има примерен). Всеки ред във файла репрезентира един сензор с неговите координати и нивото на мръсен въздух, който е измерил. 20 | 21 | На избран от вас език, напишете програма, която чете от файл и изкарва всички възможни неизправни сензори. Името на файла, максималната дистанция, която дефинира близки сензори и максималната позволена грешка между сензорите се получават от стандартния вход. 22 | 23 | Формат на файла: 24 | 25 | ``` 26 | 3,4,100 27 | 3,3,100 28 | 4,3,20 29 | 10,9,200 30 | 9,9,180 31 | 7,6,50 32 | 7,5,300 33 | 1,1,100 34 | ``` 35 | 36 | Всеки ред от файла репрезентира 1 сензор. Първите 2 колони са координатите на сензорите (X,Y), а в последната се намира стойността на сензора за мръсен въздух. 37 | 38 | > Ако няма проблемни сензори, изведете: "All sensors are OK." 39 | 40 | ## Пример 41 | 42 | Input: 43 | 44 | ``` 45 | Filename: sensors_data.txt 46 | Neighbours distance: 1 47 | Max error: 50 48 | ``` 49 | 50 | Output: 51 | 52 | ``` 53 | Please check sensors at: (3,5), (3,6) 54 | ``` 55 | -------------------------------------------------------------------------------- /Application/1.Sensors/distance2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/Application/1.Sensors/distance2.png -------------------------------------------------------------------------------- /Application/1.Sensors/distance3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/Application/1.Sensors/distance3.png -------------------------------------------------------------------------------- /Application/1.Sensors/sensors_data1.txt: -------------------------------------------------------------------------------- 1 | 1,1,100 2 | 1,2,100 3 | 5,5,20 4 | 4,4,80 5 | 3,3,10 6 | 3,5,0 7 | 3,6,200 -------------------------------------------------------------------------------- /Application/2.CorrectBrackets/README.md: -------------------------------------------------------------------------------- 1 | ## Правилни скоби 2 | 3 | Дефинираме "Правилни скоби" по следния начин: 4 | 5 | Правилни скоби са: 6 | 7 | а) `()` - лява скоба `(` до дясна скоба `)` 8 | 9 | б) `(< правилни скоби >)` - лява скоба `(` до правилни скоби, до дясна скоба `)` 10 | 11 | в) `< правилни скоби >< правилни скоби >` - правилни скоби до правилни скоби 12 | 13 | ## Задача 14 | 15 | На избран от вас език, напишете програма, която чете от стандартния вход подаден низ и връща дали този низ е "Правилни скоби." 16 | 17 | ## Пример 1 18 | 19 | Input: 20 | ``` 21 | ()(()) 22 | ``` 23 | Output: 24 | ``` 25 | True 26 | ``` 27 | 28 | ### Описание 29 | 30 | Това са правилни скоби защото: 31 | 1. Спрямо дефиниция `в)`, разделяме въведеният израз `()(())` на две части: `()` и `(())`. Това означава, че ако първата и втората част са "правилни скоби" - то и целия израз е "правилни скоби" 32 | 2. Спрямо дефиниция `a)` разглеждаме първият израз от разделените в точка 1. Изразът `()` е вярно, че е "правилни скоби" спрямо дефиниция `a)`. 33 | 3. Спрямо дефиниция `б)` разгеждаме втората част от изразът от точка 1. Изразът `(())` е възможно да отговаря на `б)` защото започва с лява скоба `(` и завършва на дясна скоба `)`. Тоест ако изразът между първата и последната скоба (именно този "()"), отговаря на дефиницията на "правилни скоби" - то и целия израз отговаря на дефиницията на "правилни скоби". 34 | 4. Спрямо дефиниция `a)` разглеждаме последната отделена част в точка 3. Изразът `()` отговаря на `a)` от дефиницията на "правилни скоби", тоест - И изразът в точка 3 също отговаря на дефиницията. 35 | 36 | С горните съображения можем да кажем, че въведеният израз "()(())" отговаря на дефиницията на "правилни скоби" 37 | 38 | ## Пример 2 39 | 40 | Input: 41 | ``` 42 | ())) 43 | ``` 44 | Output: 45 | ``` 46 | False 47 | ``` 48 | 49 | ### Описание 50 | 51 | 1. Разглеждаме изразът `()))` спрямо дефиниция `б)`, защото започва с лява скоба и завършва на дясна скоба. Това означава, че ако изразът между първата и последната скоба, отговаря на дефиницията на "правилни скоби", то целият израз отговаря. 52 | 2. Разглеждаме изразът `))` останал от точка 1. Той не отговаря на нито една от дефинициите `а)` `б)` или `в)`. Това означава, че все още не сме доказали, дали изразът е "правилни скоби". 53 | 3. Разглеждаме изразът `()))` спрямо дефиниция `в)` - това означава, че го разделяме на 2 части. Ако лявата част и дясната част са "правилни скоби", то и целият израз е "правилни скоби" 54 | 4. Разглеждаме лявата част от точка 3 `()` спрямо дефинициите и виждаме, че отговаря на дефиниция `a)` 55 | 5. Разглеждаме дясната част от точка 3 `))` тя не отговаря на нито една от дефинициите `a)`, `б)` или `в)`. Следователно целият израз не е "правилни скоби" спрямо дефиниция `в)` 56 | -------------------------------------------------------------------------------- /Application/3.RottingApples/README.md: -------------------------------------------------------------------------------- 1 | ## Гниещи ябълки 2 | 3 | В къщата, в която живеете, имате огромно ябълково дърво. Дървото е родило за поредна година и вие събирате реколтата от ябълки в касетки с различна големина. Налага ви се да заминете извън града за няколко дни малко след като сте събрали реколтата. 4 | 5 | ## Задачата 6 | 7 | Знаете, че ако една ябълка е изгнила, след 3 дни всички ябълки около нея ще са изгнили. 8 | 9 | На избран от вас език, напишете програма, която от стандарния вход чете: 10 | 11 | - големината на касетката - N x M (**ЗАБЕЛЕЖКА: Касетките нямат нулев ред или колона.**) 12 | - координатите на изгнилите ябълки 13 | - след колко дни ще се върнете в къщата си 14 | 15 | Изведете как изглежда касетката след този период. 16 | 17 | - O - свежа ябълка 18 | - X - изгнила ябълка 19 | 20 | ## Пример 21 | 22 | - :green_apple: - свежа ябълка 23 | - :apple: - изгнила ябълка 24 | 25 | В началото: 26 | 27 | ![](day0.jpg) 28 | 29 | След 3 дни: 30 | 31 | ![](day3.jpg) 32 | 33 | След 7 дни: 34 | 35 | ![](day7.jpg) 36 | 37 | След 100 дни: 38 | 39 | ![](day100.jpg) 40 | 41 | ## Примерен I/O 42 | 43 | Входни данни: 44 | 45 | ``` 46 | Enter the size of the box: 20x10 47 | Еnter the coordinates of the rotten apples: (3,8) (16,4) 48 | After how many days will you come back: 10 49 | ``` 50 | 51 | Изход: 52 | 53 | ``` 54 | OOOOOOOOOOOOXXXXXXXO 55 | OOOOOOOOOOOOXXXXXXXO 56 | OOOOOOOOOOOOXXXXXXXO 57 | OOOOOOOOOOOOXXXXXXXO 58 | XXXXXXOOOOOOXXXXXXXO 59 | XXXXXXOOOOOOXXXXXXXO 60 | XXXXXXOOOOOOXXXXXXXO 61 | XXXXXXOOOOOOOOOOOOOO 62 | XXXXXXOOOOOOOOOOOOOO 63 | XXXXXXOOOOOOOOOOOOOO 64 | ``` 65 | -------------------------------------------------------------------------------- /Application/3.RottingApples/day0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/Application/3.RottingApples/day0.jpg -------------------------------------------------------------------------------- /Application/3.RottingApples/day100.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/Application/3.RottingApples/day100.jpg -------------------------------------------------------------------------------- /Application/3.RottingApples/day3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/Application/3.RottingApples/day3.jpg -------------------------------------------------------------------------------- /Application/3.RottingApples/day7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/Application/3.RottingApples/day7.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Hack Bulgaria 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Programming 101 with Python - 2020 2 | 3 | Repo for the course "Programming 101 with Python", starting on 24th of February 2020 4 | 5 | This is going to be the 9th edition of the course. 6 | 7 | ## Partners 8 | 9 | The course is happening thanks to: 10 | 11 | | [![HedgeServ](/partners/hedgeserv.jpg)](https://www.hedgeserv.com/career/) | | [![HackSoft](/partners/hacksoft.png)](https://www.hacksoft.io/) | 12 | | -------------------------------------------------------------------------- | --- | --------------------------------------------------------------- | 13 | 14 | 15 | ## Agenda 16 | 17 | The course is about learning how to write software with Python. It's perfect for people that are going to start as junior developers afterwards. 18 | 19 | Key topics, that are going to be covered in the course: 20 | 21 | - Working with Linux-based operating system. 22 | - The Python programming language. 23 | - Testing. 24 | - Design patterns. 25 | - Using relational databases & SQL & ORMs. 26 | - Working in teams with git & GitHub. 27 | - Implementing client-server applications. 28 | - Communication between applications - formats, message queues, HTTP 29 | - Securing our applications from common vulnerabilities. 30 | -------------------------------------------------------------------------------- /partners/hacksoft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/partners/hacksoft.png -------------------------------------------------------------------------------- /partners/hedgeserv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/partners/hedgeserv.jpg -------------------------------------------------------------------------------- /playground/closures.py: -------------------------------------------------------------------------------- 1 | # def make_multiple(n): 2 | # def multiply(x): 3 | # print(f'{x} * {n} is ', x * n) 4 | 5 | # return multiply 6 | 7 | 8 | # times_three = make_multiple(3) 9 | # times_five = make_multiple(5) 10 | 11 | # del make_multiple 12 | 13 | # times_three(10) # == make_multiple(3)(10) 14 | 15 | # print('Closure of make_multiple', make_multiple.__closure__) 16 | # print('Closure of times_three', times_three.__closure__[0].cell_contents) 17 | # print('Closure of times_five', times_five.__closure__) 18 | 19 | # times_five(100) 20 | # times_five(10) 21 | 22 | # times_three(20) 23 | 24 | 25 | def add_discount(promo=100): 26 | print('In function that returns decorator') 27 | 28 | def inner(func): 29 | print('In decorator') 30 | 31 | def discounted(user, money): 32 | print('closure for: ', func.__name__) 33 | return func(user, money * (promo / 100)) # return value is the return value of `func` 34 | return discounted # return value is closure 35 | return inner # return value is decorator 36 | 37 | 38 | # decorator = add_discount(promo=20) 39 | # print('after add_discount return value') 40 | # pay_phone = decorator(pay_phone) 41 | 42 | 43 | # @shano 44 | @add_discount(promo=20) 45 | def pay_phone(user, money): 46 | print('phone', user, money) 47 | 48 | 49 | # @add_discount() 50 | # def pay_net(user, money): 51 | # print('net', user, money) 52 | 53 | print('Before pay_phone is called') 54 | pay_phone('marto', 100) 55 | # pay_net('marto', 100) 56 | -------------------------------------------------------------------------------- /playground/context_managers.py: -------------------------------------------------------------------------------- 1 | import time 2 | from contextlib import contextmanager, ContextDecorator 3 | 4 | 5 | class FileManager: 6 | def __init__(self, filename, mode='r'): 7 | print('In __init__') 8 | self.filename = filename 9 | self.mode = mode 10 | self.file = None 11 | 12 | def __enter__(self): 13 | print('Entering') 14 | self.file = open(self.filename, self.mode) 15 | return self.file 16 | 17 | def __exit__(self, exc_type, exc_value, exc_traceback): 18 | print('Exiting') 19 | self.file.close() 20 | 21 | 22 | @contextmanager 23 | def file_manager(filename, mode='r'): 24 | try: 25 | file = open(filename, mode) 26 | # ======================== 27 | yield file 28 | # ======================== 29 | except Exception: 30 | pass 31 | finally: 32 | file.close() 33 | 34 | 35 | with FileManager('demo1.py', 'w') as f: 36 | f.write('print("Marto e bok")') 37 | 38 | with file_manager('demo2.py', 'w') as f: 39 | f.write('print("Marto e bok")') 40 | 41 | 42 | class stopwatch(ContextDecorator): 43 | def __enter__(self): 44 | self.start = time.time() 45 | return self 46 | 47 | def __exit__(self, *args): 48 | print('Finished in: ', time.time() - self.start) 49 | 50 | 51 | @stopwatch() 52 | def foo(): 53 | time.sleep(1) 54 | 55 | with stopwatch(): 56 | print('in with block') 57 | time.sleep(1) 58 | 59 | print('outside the with block') 60 | 61 | 62 | foo() 63 | -------------------------------------------------------------------------------- /playground/demo.py: -------------------------------------------------------------------------------- 1 | class A: 2 | pass 3 | 4 | 5 | class B: 6 | pass 7 | 8 | 9 | class C(A, B): 10 | pass 11 | 12 | 13 | class X: 14 | pass 15 | 16 | 17 | class D(C, X, B): 18 | pass 19 | 20 | 21 | print(D.mro()) 22 | -------------------------------------------------------------------------------- /playground/generators.py: -------------------------------------------------------------------------------- 1 | # def fib(): 2 | # print('Initializing generator') 3 | # a, b = 1, 1 4 | 5 | # while a < 100: 6 | # print('Current: ', a) 7 | # yield a 8 | # a, b = b, a + b 9 | 10 | 11 | # fib_generator = fib() 12 | 13 | # next(fib_generator) 14 | # next(fib_generator) 15 | # next(fib_generator) 16 | # next(fib_generator) 17 | 18 | # print('=========') 19 | 20 | # new_fib_generator = fib_generator 21 | 22 | # for _ in new_fib_generator: 23 | # pass 24 | 25 | # print('=========') 26 | 27 | # for _ in fib_generator: 28 | # pass 29 | 30 | 31 | def complex_generator(): 32 | items = [1, 2, 3, 4, 5, 6, 7, 8] 33 | wtf = 1 34 | 35 | for item in items: 36 | print('Yielding: ', item) 37 | value = yield item 38 | 39 | # return 'Opa.' 40 | 41 | if (item == 8): 42 | print('No more ...') 43 | print(f'Last value is {value}') 44 | 45 | # value is None when the `next` is used a.k.a the first time the generator is triggered for sure. 46 | if value is not None: 47 | wtf += item + value 48 | 49 | print(wtf) 50 | 51 | return 'The end of the complex_generator' 52 | 53 | 54 | gen = complex_generator() 55 | 56 | next(gen) 57 | next(gen) 58 | 59 | gen.send(10) # Sends 10 to the generator and restarts it. Cannot be used if the generator is not started!!! 60 | gen.send(100) 61 | 62 | # gen.close() Will close the generator and the next time you try to use the generator 63 | 64 | # for i in range(4): 65 | for i in range(5): 66 | gen.send(i) 67 | -------------------------------------------------------------------------------- /playground/mocks/calendar.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | 4 | my_datetime = datetime 5 | 6 | 7 | def is_weekday(): 8 | today = my_datetime.today() 9 | return (0 <= today.weekday() < 5) 10 | -------------------------------------------------------------------------------- /playground/mocks/test_calendar.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | from datetime import datetime 4 | 5 | from calendar import is_weekday 6 | 7 | 8 | class CalendarTests(unittest.TestCase): 9 | @patch('calendar.my_datetime', autospec=True) 10 | def test_sunday_is_not_weekday(self, datetime_mock): 11 | sunday = datetime(year=2020, month=4, day=26) 12 | datetime_mock.today.return_value = sunday 13 | 14 | self.assertFalse(is_weekday()) 15 | 16 | 17 | if __name__ == '__main__': 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /playground/packages/demo.py: -------------------------------------------------------------------------------- 1 | import pkg 2 | 3 | from pkg import foo2 4 | 5 | from random import * 6 | 7 | print(pkg.foo1.x, foo2.y) 8 | 9 | print('rand int: ', randint(0, 11)) 10 | -------------------------------------------------------------------------------- /playground/packages/pkg/__init__.py: -------------------------------------------------------------------------------- 1 | print('In pkg init...') 2 | 3 | import pkg.foo1 4 | print('foo1: ', foo1) 5 | 6 | 7 | from . import foo1 8 | print('foo1: ', foo1) 9 | 10 | 11 | from pkg import foo1 12 | print('foo1: ', foo1) 13 | 14 | 15 | # from pkg import foo3 16 | -------------------------------------------------------------------------------- /playground/packages/pkg/foo1.py: -------------------------------------------------------------------------------- 1 | x = 100 -------------------------------------------------------------------------------- /playground/packages/pkg/foo2.py: -------------------------------------------------------------------------------- 1 | from .foo1 import x 2 | 3 | y = 1000 4 | -------------------------------------------------------------------------------- /playground/packages/pkg/foo3/__init__.py: -------------------------------------------------------------------------------- 1 | # from ..foo2 import x 2 | from pkg.foo2 import x 3 | 4 | print('In foo2 init: ', x) 5 | -------------------------------------------------------------------------------- /playground/packages/random.py: -------------------------------------------------------------------------------- 1 | def randint(x, y=100): 2 | return 42 3 | -------------------------------------------------------------------------------- /playground/property.py: -------------------------------------------------------------------------------- 1 | class Temperature: 2 | def __init__(self, value): 3 | self.degrees = float(value) 4 | 5 | @property 6 | def farenheit(self): 7 | return (self.degrees * 1.8) + 32 8 | 9 | @farenheit.setter 10 | def farenheit(self, value): 11 | self.degrees = (value - 32) / 1.8 12 | 13 | 14 | t = Temperature(100) 15 | print(t.degrees, t.farenheit) 16 | 17 | t.farenheit = 100 18 | print(t.degrees, t.farenheit) 19 | -------------------------------------------------------------------------------- /playground/teams_generator.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | from pprint import pprint 4 | 5 | STUDENT_NAMES = [ 6 | 'Tanya Budinova', 7 | 'Иван Георгиев Гочев', 8 | 'Ангелина Кръстанова', 9 | 'Мария-Магдалена Желязкова', 10 | 'Ралица Шиндарова', 11 | 'Антонио Георгиев', 12 | 'Silviya Vasileva Vasileva', 13 | 'Християна Малинова', 14 | 'Николай Колев Нешев', 15 | 'Цветомир Цветков', 16 | 'Петър Дамянов', 17 | 'Георги Къков', 18 | 'Николай Веселинов Късев', 19 | 'Антони Стоев', 20 | 'Златина Чолакова', 21 | 'Снежина Лъчезарова Цанкова', 22 | 'Димитър Николов', 23 | 'Емилиан Спасов', 24 | 'Йоанна Йотова', 25 | 'Боян Бонев', 26 | 'Георги Куклев', 27 | 'Йоан Джелекарски', 28 | 'Бойко Георгиев', 29 | 'Redjep ', 30 | 'Никола Методиев', 31 | ] 32 | 33 | TEAM_NAMES = [ 34 | 'Panda', 35 | 'Dog', 36 | 'Cat', 37 | 'Bear', 38 | # 'Horse', 39 | 'Elephant', 40 | 'Tiger', 41 | 'Shark', 42 | 'Bat', 43 | 'Wolf', 44 | 'Crocodile', 45 | 'Lion' 46 | ] 47 | 48 | 49 | def generate_teams(): 50 | student_names = STUDENT_NAMES[:] 51 | 52 | for _ in range(20): 53 | random.shuffle(student_names) 54 | 55 | teams = [student_names[i:i + 2] for i in range(0, len(student_names), 2)] 56 | 57 | # The last team is from 3 students 58 | teams[-2].extend(teams[-1]) 59 | del teams[-1] 60 | 61 | return { 62 | team_name: teams[index] 63 | for index, team_name in enumerate(TEAM_NAMES) 64 | } 65 | 66 | 67 | def order_teams_for_final_projects(): 68 | teams = TEAM_NAMES[:] 69 | 70 | for _ in range(50): 71 | random.shuffle(teams) 72 | 73 | return list(enumerate(teams)) 74 | 75 | 76 | if __name__ == '__main__': 77 | while True: 78 | result = order_teams_for_final_projects() 79 | pprint(result) 80 | time.sleep(0.1) 81 | -------------------------------------------------------------------------------- /playground/test_demo.py: -------------------------------------------------------------------------------- 1 | class TestDemo: 2 | print('testing') 3 | -------------------------------------------------------------------------------- /week01/01.PythonProblems/week1_solutions.py: -------------------------------------------------------------------------------- 1 | def sum_of_digits(n): 2 | result = 0 3 | 4 | if n < 0: 5 | n = -1 * n 6 | 7 | while n > 0: 8 | result += n % 10 9 | n = n // 10 10 | 11 | return result 12 | 13 | 14 | def sum2_of_digits(n): 15 | n_str = str(n) 16 | if n_str[0] == '-': 17 | n_str = n_str[1::] 18 | 19 | return sum([int(i) for i in n_str]) 20 | 21 | 22 | def sum3_of_digits(n): 23 | n_str = str(abs(n)) 24 | return sum([int(i) for i in n_str]) 25 | 26 | 27 | def to_digits(digits): 28 | return list(map(int, list(str(digits)))) 29 | 30 | 31 | def to_digits2(digits): 32 | return [int(i) for i in str(digits)] 33 | 34 | 35 | def to_number(digits): 36 | number_str = "".join([str(d) for d in digits]) 37 | return int(number_str) 38 | 39 | 40 | # print(sum_of_digits(1325132435356)) 41 | # print(sum2_of_digits(1325132435356)) 42 | 43 | # print(to_digits(123)) 44 | # print(to_digits2(123)) 45 | 46 | print(to_number([21, 2, 33])) 47 | -------------------------------------------------------------------------------- /week01/01.PythonProblems/word_counter.py: -------------------------------------------------------------------------------- 1 | def is_palindrome(word): 2 | return word == word[::-1] 3 | 4 | 5 | def get_word_occurences(word, text): 6 | if is_palindrome(word): 7 | return text.count(word) 8 | 9 | return text.count(word) + text[::-1].count(word) 10 | 11 | 12 | def build_matrix(rows, cols): 13 | matrix = [] 14 | rows_inputted = 0 15 | print('Enter matrix: ') 16 | 17 | while rows_inputted < rows: 18 | row_input = input() 19 | 20 | row = row_input.strip().split(' ') 21 | if len(row) != cols: 22 | return 'Wrong input.' 23 | 24 | matrix.append(row) 25 | rows_inputted += 1 26 | 27 | return matrix 28 | 29 | 30 | def word_occurances_in_rows(matrix, word): 31 | word_occurances = 0 32 | 33 | for row in matrix: 34 | word_occurances += get_word_occurences(word, ''.join(row)) 35 | 36 | return word_occurances 37 | 38 | 39 | def word_occurances_in_cols(matrix, word): 40 | word_occurances = 0 41 | cols_count = len(matrix[0]) 42 | 43 | for i in range(cols_count): 44 | column = [] 45 | for row in matrix: 46 | column.append(row[i]) 47 | 48 | word_occurances += get_word_occurences(word, ''.join(column)) 49 | 50 | return word_occurances 51 | 52 | 53 | def word_occurances_in_right_diagonals(matrix, word): 54 | word_occurances = 0 55 | rows_count = len(matrix) 56 | cols_count = len(matrix[0]) 57 | 58 | for c in range(rows_count + cols_count - 1): 59 | diagonal = [] 60 | for i in range(rows_count): 61 | for j in range(cols_count): 62 | if i + j == c: 63 | diagonal.append(matrix[i][j]) 64 | 65 | word_occurances += get_word_occurences(word, ''.join(diagonal)) 66 | 67 | return word_occurances 68 | 69 | 70 | def word_occurances_in_left_diagonals(matrix, word): 71 | word_occurances = 0 72 | rows_count = len(matrix) 73 | cols_count = len(matrix[0]) 74 | 75 | for c in range(1 - cols_count, rows_count): 76 | diagonal = [] 77 | for i in range(rows_count): 78 | for j in reversed(range(cols_count)): 79 | if j - i == c: 80 | diagonal.append(matrix[i][j]) 81 | 82 | word_occurances += get_word_occurences(word, ''.join(diagonal)) 83 | 84 | return word_occurances 85 | 86 | 87 | def word_counter(): 88 | word = input('Enter word: ') 89 | size = input('Enter matrix size (format: N M): ') 90 | 91 | n = int(size.split(' ')[0]) 92 | m = int(size.split(' ')[1]) 93 | 94 | if len(word) > min([n, m]): 95 | return 'Invalid number of rows or columns!' 96 | 97 | matrix = build_matrix(n, m) 98 | 99 | word_occurances = word_occurances_in_rows(matrix, word) 100 | word_occurances += word_occurances_in_cols(matrix, word) 101 | word_occurances += word_occurances_in_right_diagonals(matrix, word) 102 | word_occurances += word_occurances_in_left_diagonals(matrix, word) 103 | 104 | return word_occurances 105 | 106 | 107 | if __name__ == '__main__': 108 | print(word_counter()) 109 | -------------------------------------------------------------------------------- /week01/02.DiveIntoPython/README.md: -------------------------------------------------------------------------------- 1 | # Python Problems 2 | 3 | - [Gas Stations](#gas-stations) 4 | - [Signature ](#signature-) 5 | - [Test examples](#test-examples) 6 | - [Is Number Balanced](#is-number-balanced) 7 | - [Signature ](#signature--1) 8 | - [Test examples](#test-examples-1) 9 | - [Increasing and Decreasing Sequences](#increasing-and-decreasing-dequences) 10 | - [Signature ](#signature--2) 11 | - [Test examples](#test-examples-2) 12 | - [Largest Palindrome](#largest-palindrome) 13 | - [Signature ](#signature--3) 14 | - [Test examples](#test-examples-3) 15 | - [Sum all numbers in a given string](#sum-all-numbers-in-a-given-string) 16 | - [Signature ](#signature--4) 17 | - [Test examples](#test-examples-4) 18 | - [Birthday Ranges](#birthday-ranges) 19 | - [Signature ](#signature--5) 20 | - [Test examples](#test-examples-5) 21 | - [100 SMS](#100-sms) 22 | - [Signature ](#signature--6) 23 | - [Test examples](#test-examples-6) 24 | 25 | In a file called `week2_solutions.py`, solve the following problems: 26 | 27 | ## Gas Stations 28 | 29 | --- 30 | 31 | We are implementing a smart GPS software. 32 | 33 | - We are taking a long trip from Sofia to Bourgas and we know the distance between the two cities. It is a positive integer and we mark it as `distance`. 34 | 35 | - We know how much our car can ride with a full tank of gas. It is a positive integer in kilometers. We mark it as `tank_size`. 36 | 37 | - We have a list of gas stations. We know the distance between Sofia and the current gas station. `stations = [50, 80, 110, 180, 220, 290]` Notice, the list is sorted! 38 | 39 | By using this information we will implement a function that returns the shortest `list` of gas stations that we have to visit in order to travel from Sofia to Bourgas. We allways start with a full tank_size! 40 | 41 | ### Signature 42 | 43 | ```python 44 | def gas_stations(distance, tank_size, stations): 45 | pass 46 | ``` 47 | 48 | ### Test Example 49 | 50 | ```python 51 | >>> gas_stations(320, 90, [50, 80, 140, 180, 220, 290]) 52 | [80, 140, 220, 290] 53 | >>> gas_stations(390, 80, [70, 90, 140, 210, 240, 280, 350]) 54 | [70, 140, 210, 280, 350] 55 | ``` 56 | 57 | ## Is Number Balanced 58 | 59 | A number is called balanced, if we take the middle of it and the sums of the left and right parts are equal. 60 | 61 | For example, the number `1238033` is balanced, because it's left part is `123` and right part is `033`. 62 | 63 | We have: `1 + 2 + 3 = 0 + 3 + 3 = 6`. 64 | 65 | - A number with only one digit is always balanced! 66 | 67 | ### Signature 68 | 69 | ```python 70 | def is_number_balanced(number): 71 | pass 72 | ``` 73 | 74 | ### Test Examples 75 | 76 | ```python 77 | >>> is_number_balanced(9) 78 | True 79 | >>> is_number_balanced(4518) 80 | True 81 | >>> is_number_balanced(28471) 82 | False 83 | >>> is_number_balanced(1238033) 84 | True 85 | ``` 86 | 87 | ## Increasing and Decreasing Sequences 88 | 89 | Implement a function, called `increasing_or_decreasing(seq)` where the `seq` parameter is a `list` of integers. 90 | 91 | ### Signature 92 | 93 | ```python 94 | def increasing_or_decreasing(seq): 95 | pass 96 | ``` 97 | 98 | The function should return `Up!`, if the given sequence is monotonously increasing. 99 | If monotonously decreasing return `Down!` . 100 | If both of the conditions are not satisfied, then return `False`. 101 | 102 | And before you skip this problem, because of the math terminology, let me explain: 103 | 104 | **A sequence is monotonously increasing if for every two elements `a` and `b`, that are next to each other (`a` is before `b`), we have `a` < `b`.** 105 | 106 | For example, `[1,2,3,4,5]` is monotonously increasing, but `[1,2,3,4,5,1]` is not. 107 | 108 | ### Test Examples 109 | 110 | ```python 111 | >>> increasing_or_decreasing([1,2,3,4,5]) 112 | Up! 113 | >>> increasing_or_decreasing([5,6,-10]) 114 | False 115 | >>> increasing_or_decreasing([1,1,1,1]) 116 | False 117 | >>> increasing_or_decreasing([9,8,7,6]) 118 | Down! 119 | ``` 120 | 121 | ## Largest Palindrome 122 | 123 | Implement a function `get_largest_palindrome`, which returns the largest palindrome smaller than `n`. Given number `n` can also be a palindrome. 124 | 125 | ### Signature 126 | 127 | ```python 128 | def get_largest_palindrome(n): 129 | pass 130 | ``` 131 | 132 | ### Test Examples 133 | 134 | ```python 135 | >>> get_largest_palindrome(99) 136 | 88 137 | >>> get_largest_palindrome(252) 138 | 242 139 | >>> get_largest_palindrome(994687) 140 | 994499 141 | >>> get_largest_palindrome(754649) 142 | 754457 143 | ``` 144 | 145 | ## Sum all numbers in a given string 146 | 147 | You are given a string, where there can be numbers. Return the sum of all numbers in that string(**not digits, numbers**). 148 | 149 | ### Signature 150 | 151 | ```python 152 | def sum_of_numbers(input_string): 153 | pass 154 | ``` 155 | 156 | ### Test Examples 157 | 158 | ```python 159 | >>> sum_of_numbers("ab125cd3") 160 | 128 161 | >>> sum_of_numbers("ab12") 162 | 12 163 | >>> sum_of_numbers("ab") 164 | 0 165 | >>> sum_of_numbers("1101") 166 | 1101 167 | >>> sum_of_numbers("1111O") 168 | 1111 169 | >>> sum_of_numbers("1abc33xyz22") 170 | 56 171 | >>> sum_of_numbers("0hfabnek") 172 | 0 173 | ``` 174 | 175 | ## Birthday Ranges 176 | 177 | Implement a function that calculates how many people are born in a range of `start` and `end` date(`end` is included in the range). The input parameters are: 178 | 179 | - `birthdays` - a list of integers, which are in the range from 1 to 365 inclusive. 180 | - `ranges` - a list of tuples, where each tuple has only two integer values(the first one represents the `start` date and the second - `end` date). All values are in the range from 1 to 365 inclusive. 181 | 182 | ### Signature 183 | 184 | ```python 185 | def birthday_ranges(birthdays, ranges): 186 | pass 187 | ``` 188 | 189 | Calculate, for each tuple, how many people are born in that between the `start` and `end` date. 190 | 191 | ### Test Examples 192 | 193 | ```python 194 | >>> birthday_ranges([1, 2, 3, 4, 5], [(1, 2), (1, 3), (1, 4), (1, 5), (4, 6)]) 195 | [2, 3, 4, 5, 2] 196 | >>> birthday_ranges([5, 10, 6, 7, 3, 4, 5, 11, 21, 300, 15], [(4, 9), (6, 7), (200, 225), (300, 365)]) 197 | [5, 2, 0, 1] 198 | ``` 199 | 200 | ## 100 SMS 201 | 202 | A long time ago, before the smartphones, when you had to write some messages, the keypads looked like that: 203 | 204 | ![Nokia 3310 Keypad](nokia.jpg) 205 | 206 | For example, on such keypad, if you want to write **Ruby**, you had to press the following sequence of numbers: 207 | 208 | ``` 209 | 7778822999 210 | ``` 211 | 212 | Each key contains some letters from the alphabet. And by pressing that key, you rotate the letters until you get to your desired one. 213 | 214 | It's time to implement some encode / decode functions for the old keypads! 215 | 216 | First, implement a function that takes a list of integers - the sequence of numbers that have been pressed. The returned value should be the corresponding string of the message. 217 | 218 | ### Signature: 219 | 220 | ```python 221 | def numbers_to_message(pressed_sequence): 222 | pass 223 | ``` 224 | 225 | There are some special rules: 226 | 227 | - If you press `1`, the next letter is going to be capitalized 228 | - If you press `0`, this will insert a single white-space 229 | - If you press a number and wait for a few seconds, the special breaking number `-1` enters the sequence. This is the way to write different letters from only one keypad! 230 | 231 | ### Test examples: 232 | 233 | ```python 234 | >>> numbers_to_message([2, -1, 2, 2, -1, 2, 2, 2]) 235 | "abc" 236 | >>> numbers_to_message([2, 2, 2, 2]) 237 | "a" 238 | >>> numbers_to_message([1, 4, 4, 4, 8, 8, 8, 6, 6, 6, 0, 3, 3, 0, 1, 7, 7, 7, 7, 7, 2, 6, 6, 3, 2]) 239 | "Ivo e Panda" 240 | ``` 241 | 242 | Now it is time to convert the message to a sequence of numbers. This function takes a string - the `message` and returns the **minimal** keystrokes that you need to write that `message` 243 | 244 | ### Signature: 245 | 246 | ```python 247 | def message_to_numbers(message): 248 | pass 249 | ``` 250 | 251 | ### Test examples: 252 | 253 | ```python 254 | >>> message_to_numbers("abc") 255 | [2, -1, 2, 2, -1, 2, 2, 2] 256 | >>> message_to_numbers("a") 257 | [2] 258 | >>> message_to_numbers("Ivo e Panda") 259 | [1, 4, 4, 4, 8, 8, 8, 6, 6, 6, 0, 3, 3, 0, 1, 7, 2, 6, 6, 3, 2] 260 | >>> message_to_numbers("aabbcc") 261 | [2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, 2, -1, 2, 2, 2] 262 | ``` 263 | -------------------------------------------------------------------------------- /week01/02.DiveIntoPython/biggest_palindrome_optimized.py: -------------------------------------------------------------------------------- 1 | def is_palindrome(num): 2 | return str(num) == str(num)[::-1] 3 | 4 | 5 | def to_number(lst): 6 | s = [str(x) for x in lst] 7 | s = ''.join(s) 8 | s = int(s) 9 | return s 10 | 11 | 12 | def to_list(num): 13 | return [int(x) for x in str(num)] 14 | 15 | 16 | def get_biggest_palindrome(num): 17 | if num <= 11: 18 | raise ValueError('There are no palindromes lower than 11.') 19 | 20 | if (is_palindrome(num)): 21 | num -= 1 22 | 23 | num_list = to_list(num) 24 | length = len(num_list) 25 | 26 | if num_list[0] == 1 and all(x == 0 for x in num_list[1:]): 27 | return num - 1 28 | 29 | pivot = [] 30 | if length % 2 != 0: 31 | pivot = [num_list[length // 2]] 32 | 33 | left = num_list[:length // 2] 34 | right = num_list[(length // 2) + bool(pivot):] 35 | 36 | left_number = to_number(left) 37 | right_number = to_number(right) 38 | 39 | if (left_number < right_number): 40 | right = left[::-1] 41 | else: 42 | right = left[::-1] 43 | 44 | new = left + pivot + right 45 | 46 | if (to_number(new) > num): 47 | left_number -= 1 48 | 49 | if (pivot): 50 | pivot[0] -= 1 51 | if (pivot[0]) < 0: 52 | pivot[0] = 9 53 | 54 | new_left = to_list(left_number) 55 | if len(new_left) < len(left): 56 | pivot = [9] 57 | 58 | left = new_left[:] 59 | right = left[::-1] 60 | 61 | return to_number(left + pivot + right) 62 | 63 | 64 | print(get_biggest_palindrome(7550645)) 65 | -------------------------------------------------------------------------------- /week01/02.DiveIntoPython/nokia.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week01/02.DiveIntoPython/nokia.jpg -------------------------------------------------------------------------------- /week01/03.FinalRound/Anagrams/README.md: -------------------------------------------------------------------------------- 1 | # Are two words anagrams? 2 | 3 | For anagrams, check here - https://en.wikipedia.org/wiki/Anagram 4 | 5 | For example, `listen` and `silent` are anagrams. 6 | 7 | The program should read two words from the standard input and output: 8 | 9 | * `ANAGRAMS` if the words are anagrams of each other 10 | * `NOT ANAGRAMS` if the two words are not anagrams of each other 11 | 12 | **Consider lowering the case of the two words since the case does not matter. `SILENT` and `listen` are also anagrams.** 13 | 14 | ## Examples 15 | --- 16 | 17 | Input: 18 | 19 | ``` 20 | TOP_CODER COTO_PRODE 21 | ``` 22 | 23 | Output: 24 | 25 | ``` 26 | NOT ANAGRAMS 27 | ``` 28 | 29 | --- 30 | 31 | Input: 32 | 33 | ``` 34 | kilata cvetelina_yaneva 35 | ``` 36 | 37 | Output: 38 | 39 | ``` 40 | NOT ANAGRAMS 41 | ``` 42 | 43 | Also, should not make songs together. 44 | 45 | --- 46 | 47 | Input: 48 | 49 | ``` 50 | BRADE BEARD 51 | ``` 52 | 53 | Output: 54 | 55 | ``` 56 | ANAGRAMS 57 | ``` 58 | -------------------------------------------------------------------------------- /week01/03.FinalRound/CreditCard/README.md: -------------------------------------------------------------------------------- 1 | # Credit card validation 2 | 3 | Implement a function, called `is_credit_card_valid(number)`, which returns True/False based on the following algorithm: 4 | 5 | * Each credit card number must contain odd count of digits. 6 | * We transform the number with the following steps (based on the fact that we start from index 0) 7 | - All digits, read from right to left, at even positions (index), **remain the same.** 8 | - Every digit, read from right to left, at odd position is replaced by the result, that is obtained from doubling the given digit. 9 | * After the transformation, we find the sum of all digits in the transformed number. 10 | * The number is valid, if the sum is divisible by 10. 11 | 12 | For example: `79927398713` is valid, bacause: 13 | 14 | * When we double and replace all digits at odd position we get: `7 (18 = 2 * 9) 9 (4 = 2 * 2) 7 (6 = 2 * 3) 9 (16 = 2 * 8) 7 (2 = 2 * 1) 3` 15 | * The sum of all digits of the new number is 70, which is divisible by 10 => the card number is valid. 16 | 17 | More examples: 18 | 19 | * `79927398713` is a valid number 20 | * `79927398715` is invalid number 21 | -------------------------------------------------------------------------------- /week01/03.FinalRound/GoldbachConjecture/README.md: -------------------------------------------------------------------------------- 1 | # Goldbach Conjecture 2 | 3 | Implement a function, called `goldbach(n)` which returns a list of tuples, that is the goldbach conjecture for the given number `n`. 4 | 5 | The Goldbach Conjecture states: 6 | 7 | > Every even integer greater than 2 can be expressed as the sum of two primes. 8 | 9 | Keep in mind that there can be more than one combination of two primes, that sums up to the given number. 10 | 11 | **The result should be sorted by the first item in the tuple.** 12 | 13 | For example: 14 | 15 | - `4 = 2 + 2` 16 | - `6 = 3 + 3` 17 | - `8 = 3 + 5` 18 | - `10 = 3 + 7 = 5 + 5` 19 | - `100 = 3 + 97 = 11 + 89 = 17 + 83 = 29 + 71 = 41 + 59 = 47 + 53` 20 | 21 | ## Signature 22 | 23 | ```python 24 | def goldbach(n): 25 | pass 26 | ``` 27 | 28 | ## Test examples 29 | 30 | ```python 31 | >>> goldbach(4) 32 | [(2,2)] 33 | >>> goldbach(6) 34 | [(3,3)] 35 | >>> goldbach(8) 36 | [(3,5)] 37 | >>> goldbach(10) 38 | [(3,7), (5,5)] 39 | >>> goldbach(100) 40 | [(3, 97), (11, 89), (17, 83), (29, 71), (41, 59), (47, 53)] 41 | ``` 42 | -------------------------------------------------------------------------------- /week01/03.FinalRound/MatrixBombing/README.md: -------------------------------------------------------------------------------- 1 | ## Matrix Bombing 2 | 3 | You are givn a `NxM` matrix of integer numbers. 4 | 5 | We can drop a bomb at any place in the matrix, which has the following effect: 6 | 7 | - All of the **3 to 8 neighbours** (depending on where you hit!) of the target are reduced by the value of the target. 8 | - Numbers can be reduced only to 0 - they cannot go to negative. 9 | 10 | For example, if we have the following matrix: 11 | 12 | ``` 13 | 10 10 10 14 | 10 9 10 15 | 10 10 10 16 | ``` 17 | 18 | and we drop bomb at `9`, this will result in the following matrix: 19 | 20 | ``` 21 | 1 1 1 22 | 1 9 1 23 | 1 1 1 24 | ``` 25 | 26 | Implement a function called `matrix_bombing_plan(m)`. 27 | 28 | The function should return a dictionary where keys are positions in the matrix, represented as tuples, and values are the total sum of the elements of the matrix, after the bombing at that position. 29 | 30 | The positions are the standard indexes, starting from `(0, 0)` 31 | 32 | For example if we have the following matrix: 33 | 34 | ``` 35 | 1 2 3 36 | 4 5 6 37 | 7 8 9 38 | ``` 39 | 40 | and run the function, we will have: 41 | 42 | ```python 43 | {(0, 0): 42, 44 | (0, 1): 36, 45 | (0, 2): 37, 46 | (1, 0): 30, 47 | (1, 1): 15, 48 | (1, 2): 23, 49 | (2, 0): 29, 50 | (2, 1): 15, 51 | (2, 2): 26} 52 | ``` 53 | -------------------------------------------------------------------------------- /week01/README.md: -------------------------------------------------------------------------------- 1 | # Week 01 - Introduction to course & Python 2 | 3 | - [Intro slides](https://slides.com/hackbulgaria/python101-9th-opening) 4 | - [Exceptions slides](https://slides.com/hackbulgaria/python101-9th-exceptions) 5 | 6 | ## Data Structures 7 | 8 | Here are the basic data structures, which will help you to solve our problems. Test them in the **REPL**. 9 | 10 | All of the information you need can be found in the [docs](https://docs.python.org/3.8/tutorial/datastructures.html#data-structures)! 11 | 12 | ### List 13 | 14 | It does not work like a traditional array. It is **heterogeneous**! This means it can store elements with different types in one list. 15 | 16 | ```python 17 | things = [1 , 2, 'Ivo', [8, 'Rado']] 18 | ``` 19 | 20 | We can iterate: 21 | 22 | ```python 23 | for thing in things: 24 | print(thing) 25 | ``` 26 | 27 | This outputs: 28 | 29 | ``` 30 | 1 31 | 2 32 | Ivo 33 | [8, 'Rado'] 34 | ``` 35 | 36 | ### Dictionaries 37 | 38 | Dictionaries are also known as hash tables / associative arrays. 39 | 40 | They are used for key-value mapping. 41 | 42 | **That's one of the most used & powerful data structures in Python.** 43 | 44 | ```python 45 | youtube_views = { 46 | 'Gangnam Style': 2096709806, 47 | 'Baby': 1091538504, 48 | 'Waka Waka': 746709408, 49 | } 50 | 51 | print(youtube_views['Waka Waka']) # 746709408 52 | ``` 53 | 54 | Values are added by assigning them to `keys`. 55 | 56 | ```python 57 | youtube_views['Wrecking Ball'] = 709604432 58 | ``` 59 | 60 | If that `key` already exists, the value held by that key will be replaced. 61 | 62 | ```python 63 | youtube_views['Wrecking Ball'] = 85 64 | 65 | print(youtube_views) 66 | # {'Wrecking Ball': 85, 'Gangnam Style': 2096709806, 'Waka Waka': 746709408, 'Baby': 1091538504} 67 | ``` 68 | 69 | ### Set 70 | 71 | A set is an unordered collection with no duplicate elements. 72 | 73 | Basic usage includes membership testing and eliminating duplicate entries. 74 | 75 | Set objects also support mathematical operations like `union`, `intersection`, `difference`, and `symmetric difference`. 76 | 77 | ```python 78 | sports = set() 79 | sports.add('volleyball') 80 | sports.add('football') 81 | sports.add('basketball') 82 | 83 | print('volleyball' in sports) # True 84 | print('tenis' in sports) # False 85 | ``` 86 | 87 | Sets are perfect for searching elements with `in` since they can find them in constant time, in comparison to `O(n)` for `lists`. 88 | 89 | ### Tuple 90 | 91 | Tuple are fixed-sized lists, which are **immutable**. 92 | 93 | ```python 94 | super_heroes = ('Hackman', 'Spiderman', 'Hulk') 95 | 96 | super_heroes[1] = 'Spindi' 97 | ``` 98 | 99 | This will result in the following error: 100 | 101 | ``` 102 | Traceback (most recent call last): 103 | File "", line 1, in 104 | TypeError: 'tuple' object does not support item assignment 105 | ``` 106 | 107 | ### List Comprehension 108 | 109 | Python has a nice syntax for list / dict & set operations, called "comprehensions". 110 | 111 | For example, if we have the following cycle: 112 | 113 | ```python 114 | numbers = [] 115 | 116 | for x in range(4, 10): 117 | numbers.append(x) 118 | ``` 119 | 120 | We can rewrite this as follows: 121 | 122 | ```python 123 | numbers = [x for x in range(4, 10)] 124 | ``` 125 | 126 | We can also make different operations with the element, we append in list: 127 | 128 | ```python 129 | squares = [x**2 for x in range(10)] 130 | print(squares) 131 | # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 132 | ``` 133 | -------------------------------------------------------------------------------- /week02/01.LinuxCommands/README.md: -------------------------------------------------------------------------------- 1 | ## 1. Implement the `cat` command - Print file contents 2 | 3 | In Linux, there is a very useful command, called `cat`: 4 | 5 | ``` 6 | $ cat file.txt 7 | This is some file 8 | And cat is printing it's contents 9 | ``` 10 | 11 | Implement a Python script, called `cat.py` that takes one argument - a filename and prints the contents of that file to the console. 12 | 13 | ### Boilerplate: 14 | 15 | ```python 16 | # cat.py 17 | import sys 18 | 19 | def cat(arguments): 20 | pass 21 | 22 | 23 | def main(): 24 | pass 25 | 26 | if __name__ == '__main__': 27 | main() 28 | ``` 29 | 30 | ### Test Examples 31 | 32 | If we have `file.txt` in the same directory with `cat.py`, and `file.txt` content is: 33 | 34 | ``` 35 | Python is an awesome language! 36 | You should try it. 37 | ``` 38 | 39 | This is the result: 40 | 41 | ``` 42 | $ python cat.py file.txt 43 | Python is an awesome language! 44 | You should try it. 45 | ``` 46 | 47 | ## 2. Cat multiple files 48 | 49 | Implement a Python script, called `cat2.py` that takes multiple arguments - filenames and prints the contents of all files to the console, in the order of the arguments. 50 | 51 | **The number of the files that are given as arguments is unknown.** 52 | 53 | There should be a newline between every two files that are printed. 54 | 55 | ### Boilerplate 56 | 57 | ```python 58 | # cat2.py 59 | import sys 60 | 61 | 62 | def cat2(arguments): 63 | pass 64 | 65 | 66 | def main(): 67 | pass 68 | 69 | if __name__ == '__main__': 70 | main() 71 | ``` 72 | 73 | ### Examples 74 | 75 | If we have two files - `file1.txt` and `file2.txt` in the same directory with `cat2.py` and: 76 | 77 | **file1.txt:** 78 | 79 | ``` 80 | Python is an awesome language! 81 | You should try it. 82 | ``` 83 | 84 | **file2.txt:** 85 | 86 | ``` 87 | Also, you can use Python at a lot of different places! 88 | ``` 89 | 90 | This has to be the result: 91 | 92 | ``` 93 | $ python cat2.py file1.txt file2.txt 94 | Python is an awesome language! 95 | You should try it. 96 | 97 | Also, you can use Python at a lot of different places! 98 | ``` 99 | 100 | ## 3. Generate file with random integers 101 | 102 | Implement a Python script, called `generate_numbers.py` that takes two arguments - a `filename` and a positive integer `n`. 103 | 104 | The script should create a file with the `filename` and writes `n` random integers in it, separated by `" "`. 105 | 106 | For random integers, you can use: 107 | 108 | ```python 109 | from random import randint 110 | print(randint(1, 1000)) 111 | ``` 112 | 113 | ### Boilerplate 114 | 115 | ```python 116 | # generate_numbers.py 117 | import sys 118 | from random import randint 119 | 120 | 121 | def generate_numbers(filename, numbers): 122 | pass 123 | 124 | 125 | def main(): 126 | pass 127 | 128 | if __name__ == '__main__': 129 | main() 130 | ``` 131 | 132 | ### Examples 133 | 134 | ``` 135 | $ python generate_numbers.py numbers.txt 100 136 | $ cat numbers.txt 137 | 612 453 555 922 120 840 173 98 994 461 392 739 982 598 610 205 13 604 304 591 830 313 534 47 945 26 975 338 204 51 299 611 699 712 544 868 2 80 472 101 396 744 950 561 378 553 777 248 53 900 209 817 546 12 920 219 38 483 176 566 719 196 240 638 812 630 315 209 774 768 505 268 358 39 783 78 94 293 187 661 743 89 768 549 106 837 687 992 422 30 897 174 844 148 88 472 808 598 341 749 138 | ``` 139 | 140 | ## 4. Sum integers from file 141 | 142 | Implement a Python script, called `sum_numbers.py` that takes one argument - a `filename` which has integers, separated by `" "`. 143 | 144 | The script should print the sum of all integers in that file. 145 | 146 | ### Examples 147 | 148 | If we use the generated file Problem 3: 149 | 150 | ``` 151 | $ python sum_numbers.py numbers.txt 152 | 47372 153 | ``` 154 | 155 | ## 5. Implement an alternative to du -h command 156 | 157 | In Linux, if we want to know the size of a directory, we use the `du` command. For example: 158 | 159 | ``` 160 | $ du -hs /home/rositsazz/code 161 | 2,3G /home/rositsazz/code 162 | ``` 163 | 164 | - `-h` flag is for "human readable" which means we get the size in gigabytes, not bytes. 165 | - `-s` flag is for silent. We don't want to print every file that we go through. 166 | 167 | In a file called `duhs.py`, implement the logic of `du -hs /some/path`, where `/some/path` is obtained as an argument to the file. 168 | 169 | Example usage: 170 | 171 | ``` 172 | $ python duhs.py /home/rositsazz/code 173 | /home/rositsazz/code size is: 2.3G 174 | ``` 175 | 176 | **THIS IS NOT THE SOLUTION WE WANT:** 177 | 178 | ```python 179 | from subprocess import call 180 | import sys 181 | 182 | path = sys.argv[1] 183 | 184 | call(["du", "-s", "-h", path]) 185 | ``` 186 | 187 | ### Hints 188 | 189 | - Check the [`os`](https://docs.python.org/3.7/library/os.html) python module. 190 | - Many of the methods raise errors. Handle them properly!: 191 | 192 | ## 6. Count characters, words or lines 193 | 194 | Implement a Python script, called `wc.py` that takes two arguments: 195 | 196 | - A command, that can be one of the following : `chars`, `words`, `lines` 197 | - A filename 198 | 199 | The script should output, according to the command, the following: 200 | 201 | - For the command `chars`, the number of characters in the file 202 | - For the command `words`, the number of words in the file 203 | - For the command `lines`, the number of lines in the file 204 | 205 | ### Examples 206 | 207 | Lets have the following text: 208 | 209 | **story.txt:** 210 | 211 | ``` 212 | Now indulgence dissimilar for his thoroughly has terminated. Agreement offending commanded my an. Change wholly say why eldest period. Are projection put celebrated particular unreserved joy unsatiable its. In then dare good am rose bred or. On am in nearer square wanted. 213 | 214 | Of resolve to gravity thought my prepare chamber so. Unsatiable entreaties collecting may sympathize nay interested instrument. If continue building numerous of at relation in margaret. Lasted engage roused mother an am at. Other early while if by do to. Missed living excuse as be. Cause heard fat above first shall for. My smiling to he removal weather on anxious. 215 | 216 | Ferrars all spirits his imagine effects amongst neither. It bachelor cheerful of mistaken. Tore has sons put upon wife use bred seen. Its dissimilar invitation ten has discretion unreserved. Had you him humoured jointure ask expenses learning. Blush on in jokes sense do do. Brother hundred he assured reached on up no. On am nearer missed lovers. To it mother extent temper figure better. 217 | 218 | ``` 219 | 220 | **Print the chars:** 221 | 222 | ``` 223 | $ python wc.py chars story.txt 224 | 1032 225 | ``` 226 | 227 | **Print the words:** 228 | 229 | ``` 230 | $ python wc.py words story.txt 231 | 166 232 | ``` 233 | 234 | **Print the lines:** 235 | 236 | ``` 237 | $ python wc.py lines story.txt 238 | 5 239 | ``` 240 | 241 | ## Reduce file path 242 | 243 | A file path in a Unix OS looks like this - `/home/rositsazz/courses/Programming101-Python/week01` 244 | 245 | We start from the root - `/` and we navigate to the destination fodler. 246 | 247 | But there is a problem - if we have `..` and `.` in our file path, it's not clear where we are going to end up. 248 | 249 | - `..` means to go back one directory 250 | - `.` means to stay in the same directory 251 | - we can have more then one `/` between the directories - `/home//code` 252 | 253 | So for example : `/home//rositsazz/courses/./Programming101-Python/week01/../` reduces to `/home/rositsazz/courses/Programming101-Python/`. 254 | 255 | Implement a function, called `reduce_file_path(path)` which takes a string and returns the reduced version of the path. 256 | 257 | - Every `..` means that we have to go one directory back 258 | - Every `.` means that we are staying in the same directory 259 | - Every extra `/` is unnecessary 260 | - Always remove the last `/` 261 | 262 | ### Signature 263 | 264 | ```python 265 | def reduce_file_path(path): 266 | pass 267 | ``` 268 | 269 | ### Test examples 270 | 271 | ```python 272 | >>> reduce_file_path("/") 273 | "/" 274 | >>> reduce_file_path("/srv/../") 275 | "/" 276 | >>> reduce_file_path("/srv/www/htdocs/wtf/") 277 | "/srv/www/htdocs/wtf" 278 | >>> reduce_file_path("/srv/www/htdocs/wtf") 279 | "/srv/www/htdocs/wtf" 280 | >>> reduce_file_path("/srv/./././././") 281 | "/srv" 282 | >>> reduce_file_path("/etc//wtf/") 283 | "/etc/wtf" 284 | >>> reduce_file_path("/etc/../etc/../etc/../") 285 | "/" 286 | >>> reduce_file_path("//////////////") 287 | "/" 288 | >>> reduce_file_path("/../") 289 | "/" 290 | ``` 291 | -------------------------------------------------------------------------------- /week02/01.LinuxCommands/duhs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | DIR_INIT_SIZE = 2 * 4096 5 | 6 | 7 | def get_file_disk_usage(filepath): 8 | blocks_used = (os.stat(filepath).st_size // 4096) + 1 9 | return blocks_used * 4 # KB 10 | 11 | 12 | def get_files_size(path): 13 | if os.path.isfile(path): 14 | return get_file_disk_usage(path) 15 | 16 | total = 0 17 | 18 | if os.path.isdir(path): 19 | for inner in os.scandir(path): 20 | if inner.is_file(): 21 | total += os.stat(inner).st_size 22 | if inner.is_dir(): 23 | total += get_files_size(inner) 24 | 25 | return total 26 | 27 | 28 | def get_dirs_size(path): 29 | counter = 0 30 | if os.path.isdir(path): 31 | counter = sum([1 for _ in os.walk(path)]) 32 | 33 | return counter * DIR_INIT_SIZE 34 | 35 | 36 | def duhs(path): 37 | dirs_size = get_dirs_size(path) 38 | files_size = get_files_size(path) 39 | 40 | return dirs_size + files_size 41 | 42 | 43 | def main(): 44 | size = duhs(sys.argv[1]) 45 | print(f'{size // 1024}K') 46 | 47 | return size 48 | 49 | 50 | if __name__ == '__main__': 51 | main() 52 | -------------------------------------------------------------------------------- /week02/02.CancellationPolicy/README.md: -------------------------------------------------------------------------------- 1 | # Cancellation Policy 2 | 3 | You are a co-working space and you have many floors, rooms and desks. 4 | Your customers can book rooms and desks for their needs. 5 | If a customer decides to cancel his or her booking just before it starts we may need to fee him. 6 | 7 | The cancellation policy conditions are in the given format: 8 | 9 | ``` 10 | [ 11 | { hours: 24, percent: 10 }, 12 | { hours: 12, percent: 50 }, 13 | { hours: 6, percent: 80 }, 14 | { percent: 100 } 15 | ] 16 | ``` 17 | 18 | The above example means: You will be feed with: 19 | 20 | - 10% if you cancel your booking more than 24 hours before it starts 21 | - 50% if you cancel it for less or equal than 24 hours and more than 12 hours before it starts 22 | - 80% if you cancel it for less or equal than 12 hours and more than 6 hours before it starts 23 | - 100% if you cancel it for less or equal than 6 hours before it starts. 24 | 25 | Notes: 26 | 27 | - Only the dictionary that has no `hours` key is required 28 | - The max `hours` value is 24 29 | - The cancellation conditions may not be ordered. 30 | 31 | Using TDD, implement a program that calculates the price that a customer needs to pay if he/she wants to cancel his/her booking **now**. 32 | -------------------------------------------------------------------------------- /week02/02.CancellationPolicy/solution.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | 3 | 4 | def validate_conditions(conditions): 5 | counter = 0 6 | 7 | for condition in conditions: 8 | if not condition.get('hours'): 9 | counter += 1 10 | if condition.get('hours', 0) > 24: 11 | raise ValueError('Hours cannot be > 24.') 12 | 13 | if counter != 1: 14 | raise ValueError('Invalid conditions.') 15 | 16 | 17 | def ensure_conditions(conditions): 18 | for condition in conditions: 19 | if 'hours' not in condition.keys(): 20 | condition['hours'] = 0 21 | 22 | return conditions 23 | 24 | 25 | def pair_conditions(conditions): 26 | result = [] 27 | 28 | for index in range(len(conditions) - 1): 29 | left = conditions[index] 30 | right = conditions[index + 1] 31 | 32 | result.append((left, right)) 33 | 34 | return result 35 | 36 | 37 | def get_current_condition(pairs, start, now): 38 | for pair in pairs: 39 | lower_condition, higher_condition = pair 40 | 41 | lower_date = start - timedelta(hours=lower_condition['hours']) 42 | upper_date = start - timedelta(hours=higher_condition['hours']) 43 | 44 | if (now >= lower_date and now < upper_date): 45 | return higher_condition['percent'] 46 | 47 | return pairs[0][0]['percent'] 48 | 49 | 50 | def get_cancellation_fee(price, percent): 51 | return price * (percent / 100) 52 | 53 | 54 | def sort_conditions(conditions): 55 | return sorted(conditions, key=lambda c: c['hours'], reverse=True) 56 | 57 | 58 | def get_cancellation_policy( 59 | conditions, 60 | price, 61 | start, 62 | now 63 | ): 64 | assert now < start, 'Invalid booking start.' 65 | validate_conditions(conditions) 66 | 67 | ensured_conditions = ensure_conditions(conditions) 68 | 69 | if (len(ensured_conditions)) == 1: 70 | return get_cancellation_fee(price, ensured_conditions[0]['percent']) 71 | 72 | sorted_conditions = sort_conditions(ensured_conditions) 73 | paired_conditions = pair_conditions(sorted_conditions) 74 | 75 | current = get_current_condition(paired_conditions, start, now) 76 | 77 | return get_cancellation_fee(price, current) 78 | 79 | 80 | def main(): 81 | now = datetime.now() 82 | booking_start = now + timedelta(hours=10) 83 | price = 1000 84 | conditions = [ 85 | {'hours': 24, 'percent': 10}, 86 | {'hours': 12, 'percent': 50}, 87 | {'hours': 6, 'percent': 80}, 88 | {'percent': 100} 89 | ] 90 | 91 | result = get_cancellation_policy( 92 | conditions, 93 | price, 94 | booking_start, 95 | now 96 | ) 97 | print(result) 98 | 99 | 100 | if __name__ == '__main__': 101 | main() 102 | -------------------------------------------------------------------------------- /week02/02.CancellationPolicy/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from datetime import datetime, timedelta 3 | from solution_interface import ( 4 | validate_conditions, 5 | ensure_conditions, 6 | pair_conditions, 7 | get_cancellation_policy, 8 | get_current_condition, 9 | sort_conditions 10 | ) 11 | 12 | 13 | class TestValidateConditions(unittest.TestCase): 14 | def test_validation_passes_with_valid_conditions(self): 15 | conditions = [ 16 | {'hours': 10, 'percent': 10}, 17 | {'percent': 100} 18 | ] 19 | 20 | validate_conditions(conditions) 21 | 22 | def test_raises_exception_if_all_conditions_have_hours(self): 23 | conditions = [ 24 | {'hours': 10, 'percent': 10} 25 | ] 26 | exc = None 27 | 28 | # ACT 29 | try: 30 | validate_conditions(conditions) 31 | except Exception as err: 32 | exc = err 33 | 34 | # ASSERTS 35 | self.assertIsNotNone(exc) 36 | self.assertEqual(str(exc), 'Invalid conditions.') 37 | 38 | def test_raises_exception_if_more_than_one_condition_with_no_hours(self): 39 | conditions = [ 40 | {'hours': 10, 'percent': 10000}, 41 | {'percent': 10}, 42 | {'percent': 100} 43 | ] 44 | exc = None 45 | 46 | # ACT 47 | try: 48 | validate_conditions(conditions) 49 | except Exception as err: 50 | exc = err 51 | 52 | # ASSERTS 53 | self.assertIsNotNone(exc) 54 | self.assertEqual(str(exc), 'Invalid conditions.') 55 | 56 | def test_raises_exception_if_hours_bigger_than_24(self): 57 | # ARRANGE 58 | conditions = [ 59 | {'hours': 72, 'percent': 10000}, 60 | {'percent': 10}, 61 | ] 62 | exc = None 63 | 64 | # ACT 65 | try: 66 | validate_conditions(conditions) 67 | except Exception as err: 68 | exc = err 69 | 70 | # ASSERTS 71 | self.assertIsNotNone(exc) 72 | self.assertEqual(str(exc), 'Hours cannot be > 24.') 73 | 74 | 75 | class TestEnsureConditions(unittest.TestCase): 76 | def test_all_conditions_have_hours_after_ensuring(self): 77 | cond1 = {'hours': 10, 'percent': 10} 78 | cond2 = {'percent': 100} 79 | conditions = [cond1, cond2] 80 | 81 | ensure_conditions(conditions) 82 | 83 | self.assertEqual(cond1['hours'], 10) 84 | self.assertEqual(cond2['hours'], 0) 85 | 86 | 87 | class TestPairConditions(unittest.TestCase): 88 | def test_pair_conditions_with_two_elements(self): 89 | conditions = [{'hours': 10, 'percent': 20}, {'hours': 0, 'percent': 50}] 90 | 91 | result = pair_conditions(conditions) 92 | 93 | expected = [({'hours': 10, 'percent': 20}, {'hours': 0, 'percent': 50})] 94 | self.assertEqual(result, expected) 95 | 96 | def test_pair_conditions_with_even_number_of_elements(self): 97 | conditions = [ 98 | {'hours': 24, 'percent': 0}, 99 | {'hours': 18, 'percent': 20}, 100 | {'hours': 12, 'percent': 50}, 101 | {'hours': 0, 'percent': 100} 102 | ] 103 | 104 | result = pair_conditions(conditions) 105 | 106 | expected = [ 107 | ({'hours': 24, 'percent': 0}, {'hours': 18, 'percent': 20}), 108 | ({'hours': 18, 'percent': 20}, {'hours': 12, 'percent': 50}), 109 | ({'hours': 12, 'percent': 50}, {'hours': 0, 'percent': 100}) 110 | ] 111 | 112 | self.assertEqual(result, expected) 113 | 114 | def test_pair_conditions_with_odd_number_of_elements(self): 115 | conditions = [ 116 | {'hours': 24, 'percent': 0}, 117 | {'hours': 18, 'percent': 20}, 118 | {'hours': 12, 'percent': 50}, 119 | {'hours': 6, 'percent': 80}, 120 | {'hours': 0, 'percent': 100} 121 | ] 122 | 123 | result = pair_conditions(conditions) 124 | 125 | expected = [ 126 | ({'hours': 24, 'percent': 0}, {'hours': 18, 'percent': 20}), 127 | ({'hours': 18, 'percent': 20}, {'hours': 12, 'percent': 50}), 128 | ({'hours': 12, 'percent': 50}, {'hours': 6, 'percent': 80}), 129 | ({'hours': 6, 'percent': 80}, {'hours': 0, 'percent': 100}) 130 | ] 131 | 132 | self.assertEqual(result, expected) 133 | 134 | 135 | class TestGetCurrentCondition(unittest.TestCase): 136 | def test_with_current_date_before_min_condition_date_should_return_min_condition_percent(self): 137 | conditions = [ 138 | ({'hours': 24, 'percent': 0}, {'hours': 18, 'percent': 20}), 139 | ({'hours': 18, 'percent': 20}, {'hours': 12, 'percent': 50}), 140 | ({'hours': 12, 'percent': 50}, {'hours': 6, 'percent': 80}), 141 | ({'hours': 6, 'percent': 80}, {'hours': 0, 'percent': 100}) 142 | ] 143 | booking_start = datetime.now() 144 | now = booking_start - timedelta(hours=100) 145 | 146 | result = get_current_condition(conditions, booking_start, now) 147 | 148 | self.assertEqual(result, 0) 149 | 150 | def test_with_current_date_in_condition_interval_should_return_higher_condition_percent(self): 151 | conditions = [ 152 | ({'hours': 24, 'percent': 0}, {'hours': 18, 'percent': 20}), 153 | ({'hours': 18, 'percent': 20}, {'hours': 12, 'percent': 50}), 154 | ({'hours': 12, 'percent': 50}, {'hours': 6, 'percent': 80}), 155 | ({'hours': 6, 'percent': 80}, {'hours': 0, 'percent': 100}) 156 | ] 157 | booking_start = datetime.now() 158 | now = booking_start - timedelta(hours=10) 159 | 160 | result = get_current_condition(conditions, booking_start, now) 161 | 162 | self.assertEqual(result, 80) 163 | 164 | def test_with_current_date_equal_to_condition_hours_should_return_interval_upper_boundary_percent(self): 165 | conditions = [ 166 | ({'hours': 24, 'percent': 0}, {'hours': 18, 'percent': 20}), 167 | ({'hours': 18, 'percent': 20}, {'hours': 12, 'percent': 50}), 168 | ({'hours': 12, 'percent': 50}, {'hours': 6, 'percent': 80}), 169 | ({'hours': 6, 'percent': 80}, {'hours': 0, 'percent': 100}) 170 | ] 171 | booking_start = datetime.now() 172 | now = booking_start - timedelta(hours=6) 173 | 174 | result = get_current_condition(conditions, booking_start, now) 175 | 176 | self.assertEqual(result, 100) 177 | 178 | 179 | class TestGetCancellationPolicy(unittest.TestCase): 180 | def test_with_now_equal_to_booking_start_should_raise_error(self): 181 | conditions = [{'percent': 50}] 182 | booking_start = datetime.now() 183 | now = booking_start 184 | exc = None 185 | 186 | try: 187 | get_cancellation_policy(conditions, 10, booking_start, now) 188 | except Exception as err: 189 | exc = err 190 | 191 | self.assertIsNotNone(exc) 192 | self.assertEqual(str(exc), 'Invalid booking start.') 193 | 194 | def test_with_now_later_than_booking_start_should_raise_error(self): 195 | conditions = [{'percent': 50}] 196 | booking_start = datetime.now() 197 | now = booking_start + timedelta(hours=1) 198 | exc = None 199 | 200 | try: 201 | get_cancellation_policy(conditions, 10, booking_start, now) 202 | except Exception as err: 203 | exc = err 204 | 205 | self.assertIsNotNone(exc) 206 | self.assertEqual(str(exc), 'Invalid booking start.') 207 | 208 | def test_cancellation_fee_with_only_one_condition(self): 209 | conditions = [{'percent': 50}] 210 | price = 100 211 | booking_start = datetime.now() 212 | now = booking_start - timedelta(hours=100) 213 | 214 | result = get_cancellation_policy(conditions, price, booking_start, now) 215 | 216 | self.assertEqual(result, 50.0) 217 | 218 | def test_cancellation_fee_with_several_conditions_and_now_in_them(self): 219 | conditions = [ 220 | {'hours': 24, 'percent': 10}, 221 | {'hours': 12, 'percent': 50}, 222 | {'hours': 6, 'percent': 80}, 223 | {'percent': 100} 224 | ] 225 | price = 100 226 | booking_start = datetime.now() 227 | now = booking_start - timedelta(hours=10) 228 | 229 | result = get_cancellation_policy(conditions, price, booking_start, now) 230 | 231 | self.assertEqual(result, 80.0) 232 | 233 | def test_cancellation_fee_with_several_conditions_and_now_in_them_and_decimal_percent(self): 234 | conditions = [ 235 | {'hours': 24, 'percent': 10}, 236 | {'hours': 12, 'percent': 50}, 237 | {'hours': 6, 'percent': 65.5}, 238 | {'percent': 100} 239 | ] 240 | price = 200 241 | booking_start = datetime.now() 242 | now = booking_start - timedelta(hours=10) 243 | 244 | result = get_cancellation_policy(conditions, price, booking_start, now) 245 | 246 | self.assertEqual(result, price * (65.5 / 100)) 247 | 248 | 249 | class TestSortConditions(unittest.TestCase): 250 | def test_conditions_are_sorted_in_descending_order(self): 251 | conditions = [ 252 | {'hours': 12, 'percent': 50}, 253 | {'hours': 0, 'percent': 100}, 254 | {'hours': 6, 'percent': 80}, 255 | {'hours': 24, 'percent': 10}, 256 | ] 257 | 258 | result = sort_conditions(conditions) 259 | 260 | expected = [ 261 | {'hours': 24, 'percent': 10}, 262 | {'hours': 12, 'percent': 50}, 263 | {'hours': 6, 'percent': 80}, 264 | {'hours': 0, 'percent': 100}, 265 | ] 266 | 267 | self.assertEqual(result, expected) 268 | 269 | 270 | if __name__ == '__main__': 271 | unittest.main() 272 | -------------------------------------------------------------------------------- /week02/03.MoreTesting/README.md: -------------------------------------------------------------------------------- 1 | ## Python sort 2 | 3 | Implement a function, called `my_sort` that takes 3 arguments. The arguments are: 4 | 5 | - `iterable` - list or tuple that has to be sorted. Preserve the type in the return value. **Must have default value** 6 | - `ascending` - boolean that controls the sorting order. **Must have default value** 7 | - `key` - string that serves as a lookup key if the `iterable` argument is a list of dictionaries. **Must have default value** 8 | 9 | The function should return the given iterable sorted in order that is controlled by the `ascending` value. 10 | 11 | **NOTE: You should NOT use Python's builtin list.sort() or sorted()** 12 | 13 | ### Test examples 14 | 15 | ``` 16 | >>> my_sort([]) 17 | [] 18 | >>> my_sort((10,8,9,10,100)) 19 | (8, 9, 10, 10, 100) 20 | >>> my_sort([10, 8, 9, 10, 100], False) 21 | [100, 10, 10, 9, 8] 22 | >>> my_sort(iterable=[{'name': 'Marto', 'age': 24}, {'name': 'Ivo', 'age': 27}, {'name': 'Sashko', 'age': 25}], key='age') 23 | [{'name': 'Marto', 'age': 24}, {'name': 'Sashko', 'age': 25}, {'name': 'Ivo', 'age': 27}] 24 | ``` 25 | 26 | ## Simplify fractions 27 | 28 | Implement a function, called `simplify_fraction(fraction)` that takes a tuple of the form `(nominator, denominator)` and simplifies the fraction. 29 | 30 | The function should return the fraction in it's irreducible form. 31 | 32 | For example, a fraction `3/9` can be reduced by dividing both the nominator and the denominator by 3. We end up with `1/3` which is irreducible. 33 | 34 | ### Test examples 35 | 36 | ``` 37 | >>> simplify_fraction((3,9)) 38 | (1,3) 39 | >>> simplify_fraction((1,7)) 40 | (1,7) 41 | >>> simplify_fraction((4,10)) 42 | (2,5) 43 | >>> simplify_fraction((462,63)) 44 | (22,3) 45 | ``` 46 | 47 | ## Collect fractions 48 | 49 | Implement a function, called `collect_fractions(fractions)` where `fractions` is a list of tuples of the form `(nominator, denominator)`. 50 | 51 | Both the nominator and the denominator are integers. 52 | The function should return the sum of the fractions. 53 | 54 | ### Test examples 55 | 56 | ``` 57 | >>> collect_fractions([(1, 4), (1, 2)]) 58 | (3, 4) 59 | >>> collect_fractions([(1, 7), (2, 6)]) 60 | (10,21) 61 | ``` 62 | 63 | ## Sort array of fractions 64 | 65 | Implement a function, called `sort_fractions(fractions, ascending = True)` where `fractions` is a list of tuples of the form `(nominator, denominator)` and `ascending` is boolean that controls the sorting direction. 66 | 67 | Both the nominator and the denominator are integers. 68 | 69 | The function should return the list, sorted depending on the `ascending` value. 70 | 71 | ### Test examples 72 | 73 | ``` 74 | >>> sort_fractions([(2, 3), (1, 2)]) 75 | [(1, 2), (2, 3)] 76 | >>> sort_fractions([(2, 3), (1, 2), (1, 3)]) 77 | [(1, 3), (1, 2), (2, 3)] 78 | >>> sort_fractions([(5, 6), (22, 78), (22, 7), (7, 8), (9, 6), (15, 32)]) 79 | [(22, 78), (15, 32), (5, 6), (7, 8), (9, 6), (22, 7)] 80 | ``` 81 | -------------------------------------------------------------------------------- /week02/README.md: -------------------------------------------------------------------------------- 1 | # Week 02 - Introduction to course & Python 2 | 3 | - [Files and Commands slides](https://slides.com/hackbulgaria/python101-9th-files#/) 4 | - [Unit Tests & TDD](https://slides.com/hackbulgaria/python101-9th-tests/#/) 5 | - [Function Arguments](https://slides.com/hackbulgaria/function-arguments/#/) 6 | -------------------------------------------------------------------------------- /week03/01.FractionsOOP/README.md: -------------------------------------------------------------------------------- 1 | # Fractions OOP 2 | 3 | Remember the Fractions problems you had to solve two weeks ago ([link](https://github.com/HackBulgaria/Programming-101-Python-2020-Spring/tree/master/week02/03.MoreTesting#simplify-fractions))? You will have to rewrite them today using your new OOP knowledge. 4 | 5 | 0. Preserve the functions from your previous solutions. 6 | 1. Reimplement them using TDD and OOP. 7 | 1. You will need to have a Fraction class. 8 | 1. Your old tests may need to be refactored. 9 | 1. You will have to override some dunders in order to collect and sort fractions. 10 | 1. We will expect to have "1/3" (for example) when we try to case our fraction to a string. 11 | -------------------------------------------------------------------------------- /week03/01.FractionsOOP/fraction.py: -------------------------------------------------------------------------------- 1 | from math import gcd 2 | 3 | 4 | class Fraction: 5 | def __init__(self, numerator, denominator): 6 | assert denominator >= 1, 'Zero or negative denominator.' 7 | 8 | self.numerator = numerator 9 | self.denominator = denominator 10 | 11 | def __str__(self): 12 | return f'{self.numerator}/{self.denominator}' 13 | 14 | def __repr__(self): 15 | return f'Fraction {self}' 16 | 17 | def __eq__(self, other): 18 | return self.numerator / self.denominator == other.numerator / other.denominator 19 | 20 | def __add__(self, other): 21 | numerator = (self.numerator * other.denominator) + (other.numerator * self.denominator) 22 | denominator = self.denominator * other.denominator 23 | 24 | return Fraction(numerator, denominator).simplify() 25 | 26 | def __lt__(self, other): 27 | return self.numerator / self.denominator < other.numerator / other.denominator 28 | 29 | def simplify(self): 30 | divider = gcd(self.numerator, self.denominator) 31 | 32 | return Fraction(self.numerator // divider, self.denominator // divider) 33 | -------------------------------------------------------------------------------- /week03/01.FractionsOOP/test_fraction.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from fraction import Fraction 3 | 4 | 5 | class TestFraction(unittest.TestCase): 6 | def test_cannot_instantiate_fraction_with_zero_denominator(self): 7 | exception = None 8 | 9 | try: 10 | Fraction(1, 0) 11 | except AssertionError as exc: 12 | exception = exc 13 | 14 | self.assertIsNotNone(exception) 15 | 16 | def test_fractions_string_representation_is_as_expected_one(self): 17 | fraction1 = Fraction(1, 3) 18 | fraction2 = Fraction(-1, 3) 19 | fraction3 = Fraction(2, 4) 20 | 21 | self.assertEqual(str(fraction1), '1/3') 22 | self.assertEqual(str(fraction2), '-1/3') 23 | self.assertEqual(str(fraction3), '2/4') 24 | 25 | def test_fractions_equalization_with_equal_fractions(self): 26 | fraction1 = Fraction(1, 5) 27 | fraction2 = Fraction(1, 5) 28 | 29 | self.assertTrue(fraction1 == fraction2, 'Fractions are not equal') 30 | 31 | def test_fractions_equalization_with_equal_nonsimpified_fractions(self): 32 | fraction1 = Fraction(1, 3) 33 | fraction2 = Fraction(3, 9) 34 | 35 | self.assertTrue(fraction1 == fraction2, 'Fractions are not equal') 36 | 37 | def test_simplified_fraction_is_preserved_after_simplification(self): 38 | fraction = Fraction(1, 5) 39 | 40 | expected = Fraction(1, 5) 41 | 42 | self.assertEqual(fraction.simplify(), expected) 43 | 44 | def test_fraction_is_simplified_as_expected(self): 45 | fraction = Fraction(10, 50) 46 | 47 | expected = Fraction(1, 5) 48 | 49 | self.assertEqual(fraction.simplify(), expected) 50 | 51 | def test_addition_fractions_works_correct_for_non_simplifiable_result_with_equal_denominator(self): 52 | fraction1 = Fraction(1, 5) 53 | fraction2 = Fraction(2, 5) 54 | 55 | result = fraction1 + fraction2 56 | 57 | self.assertEqual(result.numerator, 3) 58 | self.assertEqual(result.denominator, 5) 59 | 60 | def test_addition_fractions_works_correct_for_non_simplifiable_result_with_non_equal_denominator(self): 61 | fraction1 = Fraction(1, 7) 62 | fraction2 = Fraction(2, 6) 63 | 64 | result = fraction1 + fraction2 65 | 66 | self.assertEqual(result.numerator, 10) 67 | self.assertEqual(result.denominator, 21) 68 | 69 | 70 | if __name__ == '__main__': 71 | unittest.main() 72 | -------------------------------------------------------------------------------- /week03/02.CashDesk/README.md: -------------------------------------------------------------------------------- 1 | # The Cash Desk Problem 2 | 3 | We are going to train our OOP skill by implementing a few classes, which will represent a cash desk. 4 | 5 | The cash desk will do the following things: 6 | 7 | - Take money as single bills 8 | - Take money as batches (пачки!) 9 | - Keep a total count 10 | - Tell us some information about the bills it has 11 | 12 | ## The Bill class 13 | 14 | Create a class, called `Bill` which takes one parameter to its constructor - the `amount` of the bill - an integer. 15 | 16 | This class will only have **dunders** so you wont be afraid of them anymore! 17 | 18 | The class should implement: 19 | 20 | - `__str__` and `__repr__` 21 | - `__int__` 22 | - `__eq__` and `__hash__` 23 | - If amount is negative number, raise an `ValueError` error. 24 | - If type of amount isn't `int`, raise an `TypeError` error. 25 | 26 | Here is an example usage: 27 | 28 | ```python 29 | from cashdesk import Bill 30 | 31 | a = Bill(10) 32 | b = Bill(5) 33 | c = Bill(10) 34 | 35 | int(a) # 10 36 | str(a) # "A 10$ bill" 37 | print(a) # A 10$ bill 38 | 39 | a == b # False 40 | a == c # True 41 | 42 | money_holder = {} 43 | 44 | money_holder[a] = 1 # We have one 10% bill 45 | 46 | if c in money_holder: 47 | money_holder[c] += 1 48 | 49 | print(money_holder) # { "A 10$ bill": 2 } 50 | ``` 51 | 52 | ## The BatchBill class 53 | 54 | We are going to implement a class, which represents more than one bill. A `BatchBill`! 55 | 56 | The class takes a list of `Bills` as the single constructor argument. 57 | 58 | The class should have the following methods: 59 | 60 | - `__len__(self)` - returns the number of `Bills` in the batch 61 | - `total(self)` - returns the total amount of all `Bills` in the batch 62 | 63 | We should be able to iterate the `BatchBill` class with a for-loop. 64 | 65 | Here is an example: 66 | 67 | ```python 68 | from cashdesk import Bill, BillBatch 69 | 70 | values = [10, 20, 50, 100] 71 | bills = [Bill(value) for value in values] 72 | 73 | batch = BillBatch(bills) 74 | 75 | for bill in batch: 76 | print(bill) 77 | 78 | # A 10$ bill 79 | # A 20$ bill 80 | # A 50$ bill 81 | # A 100$ bill 82 | ``` 83 | 84 | In order to do that, you need to implement the following method: 85 | 86 | ```python 87 | def __getitem__(self, index): 88 | pass 89 | ``` 90 | 91 | ## The CashDesk classs 92 | 93 | Finally, implement a `CashDesk` class, which has the following methods: 94 | 95 | - `take_money(money)`, where `money` can be either `Bill` or `BatchBill` class 96 | - `total()` - returns the total amount of money currenly in the desk 97 | - `inspect()` - prints a table representation of the money - for each bill, how many copies of it we have. 98 | 99 | For example: 100 | 101 | ```python 102 | from cashdesk import Bill, BillBatch, CashDesk 103 | 104 | values = [10, 20, 50, 100, 100, 100] 105 | bills = [Bill(value) for value in values] 106 | 107 | batch = BillBatch(bills) 108 | 109 | desk = CashDesk() 110 | 111 | desk.take_money(batch) 112 | desk.take_money(Bill(10)) 113 | 114 | print(desk.total()) # 390 115 | desk.inspect() 116 | 117 | # We have a total of 390$ in the desk 118 | # We have the following count of bills, sorted in ascending order: 119 | # 10$ bills - 2 120 | # 20$ bills - 1 121 | # 50$ bills - 1 122 | # 100$ bills - 3 123 | 124 | ``` 125 | -------------------------------------------------------------------------------- /week03/03.Polynomials/README.md: -------------------------------------------------------------------------------- 1 | # Polynomials and Derivates 2 | 3 | ## Polynomials 4 | 5 | In math, [a polynomial](https://en.wikipedia.org/wiki/Polynomial) is a function, defined like that: 6 | 7 | ``` 8 | f(x) = Cn*x^n + Cn-1*x^n-1 + ... + c1*x^1 + c0 9 | ``` 10 | 11 | where: 12 | 13 | - `Cn` to `C0` are coefficients. **We are going to work with positive integers for coefficients.** 14 | - `x` is the variable and `x^n` means x to the power of `n` 15 | - If the given coefficient is equal to `1`, it can be omitted. 16 | 17 | Here are few examples: 18 | 19 | ``` 20 | f(x) = 2x^3 + 3x + 1 21 | f'(x) = 6x + 3 22 | ``` 23 | 24 | ## Derivatives 25 | 26 | A derivative of a polynomial function is easily calculated. The only thing that we need to know is how to take derivative from each member of the polynomial function. 27 | 28 | Here is the general rule for taking derivative of a function in the following form: 29 | 30 | ``` 31 | f(x) = c * x^n 32 | f'(x) = n * c * x^(n - 1) 33 | ``` 34 | 35 | Where `c` and `n` are integers and `f'(x)` denotes the derivative of `f(x)` 36 | 37 | There are two corner cases: 38 | 39 | Taking derivative of `x` to the power of 1. 40 | 41 | ``` 42 | f(x) = c * x 43 | f'(x) = c 44 | ``` 45 | 46 | and taking derivatives of constants: 47 | 48 | ``` 49 | f(x) = c 50 | f'(x) = 0 51 | ``` 52 | 53 | So if we want to take the derivative of a polynomial function, we just apply that rule to every member of the polynom: 54 | 55 | ``` 56 | f(x) = 2x^3 + 3x + 1 57 | f'(x) = 6*x^2 + 3 58 | ``` 59 | 60 | ## Your task 61 | 62 | Using your OO knowledge, implement a program that takes a string, representing a polynomial function and returns / prints the derivative of that polynomial function. 63 | 64 | Few examples: 65 | 66 | ``` 67 | $ python3 solution.py '2*x^3+x' 68 | Derivative of f(x) = 2*x^3 + x is: 69 | f'(x) = 6*x^2 + 1 70 | ``` 71 | 72 | ``` 73 | $ python3 solution.py '1' 74 | The derivative of f(x) = 1 is: 75 | f'(x) = 0 76 | ``` 77 | 78 | ``` 79 | $ python3 solution.py 'x^4+10*x^3' 80 | The derivative of f(x) = x^4 + 10*x^3 is: 81 | f'(x) = 4*x^3 + 30*x^2 82 | ``` 83 | 84 | Few things to keep in mind: 85 | 86 | ``` 87 | $ python3 solution.py '1+x^2' 88 | The derivative of f(x) = x^2 + 1 is: 89 | f'(x) = 2x 90 | ``` 91 | 92 | And 93 | 94 | ``` 95 | $ python3 solution.py '3x^2' 96 | The derivative of f(x) = 3x^2 is: 97 | f'(x) = 6*x 98 | ``` 99 | 100 | Don't bother checking if the polynomial is correct for it's variable. It's always going to be the same ( for example `x`) 101 | 102 | ## Hints 103 | 104 | Do not forget to test your implementation. 105 | Of course, think wisely and create your program in a way that is independent from the console input/output. 106 | -------------------------------------------------------------------------------- /week03/04.MusicLibrary/README.md: -------------------------------------------------------------------------------- 1 | # A Music Library 2 | 3 | We are going to implement a music library + crawler for the music on our computer. 4 | 5 | ## Songs 6 | 7 | First, we are going to need a `Song` class which we want to initialize like that: 8 | 9 | ```python 10 | s = Song(title="Odin", artist="Manowar", album="The Sons of Odin", length="3:44") 11 | ``` 12 | 13 | **Length can have hours!:** 14 | 15 | Those are all valid lengths: 16 | 17 | - `"3:44"` 18 | - `"1:30:44"` 19 | 20 | The methods we want for our `Song` are: 21 | 22 | - `__str__` - should be able to turn the song into the following string: `"{artist} - {title} from {album} - {length}"` 23 | - Our `Song` should be hashabe! Implement `__eq__` and `__hash__` 24 | - `length(seconds=True)` should return the length in seconds. 25 | - `length(minutes=True)` should return the length in minutes (omit the seconds) 26 | - `length(hours=True)` should return the length in hours (omit minutes and seconds that does not add up to a full hour) 27 | - `length()` should return the string representation of the length 28 | 29 | ## Playlist class 30 | 31 | We are going to implement a collection for our songs. 32 | 33 | The playlist should be initialized with a name: 34 | 35 | ```python 36 | code_songs = Playlist(name="Code", repeat=True, shuffle=True) 37 | ``` 38 | 39 | - `repeat` should be defaulted to `False` 40 | - `shuffle` should be defaulted to `False` 41 | 42 | The `Playlist` should behave like that: 43 | 44 | - `add_song(song)` and `remove_song(song)` are self explanatory. 45 | - `add_songs(songs)` should add a list of `songs`. 46 | - `total_length()` should return a string representation of tha total length of all songs in the playlist 47 | - `artists()` - returns a histogram of all artists in the playlist. For each artist, we must have the count of songs in the playlist. 48 | - `next_song()` should return a `Song` instance from the order of the playlist. If `repeat=True`, when our playlist reaches the end, it should loop back again at the beginning. If `shuffle=True`, everytime we call `next_song()`, we should get a different song. **Make it randomize so it wont repeat a played song unless every song from the playlist has been played!** 49 | 50 | ## Saving and Loading the playlist 51 | 52 | > JSON is a format which is really handy if we want to represent objects. It looks like Python dictionaties. You can check this [tutorial](https://github.com/HackBulgaria/Programming-101-Python-2020-Spring/blob/master/week03/json_tutorial.py) for working JSON files. 53 | 54 | We should be able to save and load a playlist to a JSON file. Use the [json](https://docs.python.org/3/library/json.html) module from python. 55 | 56 | - `save()` should be a instance method, that saves the playlist to a JSON filed, which has the name of the playlist. If the name has whitespaces in it, replace them with `"-"` - the so-called dasherize. 57 | - `load(path)` should return a new `Playlist` instance with all songs from the file. 58 | 59 | Example usage: 60 | 61 | ```python 62 | code = Playlist("For Code") 63 | # ... adding songs ... 64 | 65 | # Saves to For-Code.json 66 | code.save() 67 | ``` 68 | 69 | Later on: 70 | 71 | ```python 72 | code = Playlist.load("For-Code.json") 73 | code.name == "For Code" # True 74 | ``` 75 | 76 | Save the `*.json` files in a specified folder, for instance, called `playlist-data`. Make the `load` function look there too. 77 | -------------------------------------------------------------------------------- /week03/05.BowlingGame/README.md: -------------------------------------------------------------------------------- 1 | ## Bowling Game 2 | 3 | ### Game Basics 4 | 5 | One game of bowling consists of 10 frames. The minimum score for a game is 0 and a maximum is 300. 6 | Each frame consists of two chances to knock down ten pins. In the bowling game we use "pins", instead of "points". 7 | 8 | - Strikes - 9 | Knocking down all 10 pins on your first ball is called a strike, denoted by an "X" on the score sheet. A strike is worth 10, plus the value of your next 2 rolls. 10 | At minimum, your score for a frame in which you throw a strike will be 10 (10+0+0). 11 | At best, your next two shots will be strikes, and the frame will be worth 30 (10+10+10). 12 | Say you throw a strike in the first frame. Technically, you don't have a score yet. 13 | You need to throw two more balls to figure out your total score for the frame. 14 | In the second frame, you throw a 6 on your first ball and a 2 on your second ball. Your score for the first frame will be 18 (10+6+2). 15 | - Spares - 16 | If it takes two shots to knock down all ten pins, it's called a spare, denoted by "N /". A spare is worth 10, plus the value of your next roll. 17 | Say you throw a spare in your first frame. Then, in your first ball of the second frame, you throw a 7. 18 | Your score for the first frame will be 17 (10+7). 19 | The maximum score for a frame in which you get a spare is 20 (a spare followed by a strike), and the minimum is 10. 20 | - Open Frames - 21 | If, after 2 shots, at least 1 pin is still standing, it's called an open frame. If you don't get a strike or a spare in a frame, your score is the total number of pins you knock down. If you knock down 3 pins on your first ball and 2 on your second, your score for that frame is 5. 22 | - The Tenth Frame - 23 | In the sample score, three shots were thrown in the tenth frame. This is because of the bonuses awarded for strikes and spares. If you throw a strike on your first ball in the tenth frame, you need two more shots to determine the total value of the strike. If you throw a spare on your first two balls in the tenth frame, you need one more shot to determine the total value of the spare. This is called a fill ball. 24 | If you throw an open frame in the tenth frame, you won't get a third shot. The only reason the third shot exists is to determine the full value of a strike or spare. 25 | **Use OOP and follow the TDD to implement the Bowling game** 26 | - Help - 27 | You can use this [calculator](https://www.bowlinggenius.com/) to understand the rules better. 28 | 29 | ```python3.6 30 | game = BowlingGame([1, 4, 4, 5, 6, 3, 5, 1, 1, 0, 1, 7, 3, 6, 4, 3, 2, 1, 6, 2]) 31 | game.result # 65 32 | game = BowlingGame([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ) 33 | game.result # 0 34 | game = BowlingGame([10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]) 35 | game.result # 300 36 | game = BowlingGame([5, 1, 1, 0, 1, 7, 3, 6, 4, 3, 2, 1, 6]) 37 | game.result # invalid number of frames 38 | ``` 39 | -------------------------------------------------------------------------------- /week03/README.md: -------------------------------------------------------------------------------- 1 | # Week 03 - OOP & git 2 | 3 | - [OOP Part 1 slides](https://slides.com/hackbulgaria/python101-9th-oop1#/) 4 | - [OOP Part 2 slides](https://slides.com/hackbulgaria/python101-9th-oop2#/) 5 | - [Git & GitHub slides](https://slides.com/hackbulgaria/python-101-9th-git#/) 6 | -------------------------------------------------------------------------------- /week03/example.json: -------------------------------------------------------------------------------- 1 | {"name": "Marto", "age": 24, "interests": ["sport", "AI"]} -------------------------------------------------------------------------------- /week03/json_tutorial.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | my_object = {'name': 'Marto', 'age': 24, 'interests': ['sport', 'AI']} 4 | 5 | with open('example.json', 'w') as f: 6 | # Saves my_object in the example.json file 7 | json.dump(my_object, f) 8 | 9 | with open('example.json', 'r') as f: 10 | # Reads example.json file 11 | my_read_object = json.load(f) 12 | print(my_object) 13 | print(type(my_object)) 14 | -------------------------------------------------------------------------------- /week04/01.Mixins/README.md: -------------------------------------------------------------------------------- 1 | # Serializing & Deserializing from/to JSON and XML 2 | 3 | ## JSON & XML 4 | 5 | [JSON](https://en.wikipedia.org/wiki/JSON) and [XML](https://en.wikipedia.org/wiki/XML) are file formats. They serve for communication between systems and interfaces. 6 | 7 | ## Serialization & Deserialization 8 | 9 | Serialization is the process of translating an object into a primitive type. Deserialization is the opposite operation. 10 | 11 | ## Your task 12 | 13 | All the classes from today should now how to serailize & deserialize themselves into JSON and XML. 14 | You need to create two mixins called `Jsonable` and `Xmlable`. 15 | They should have the following methods: 16 | 17 | - `Jsonable`: 18 | - `to_json(indent)`. Default value `indent=4`. Returns JSON string representing the current object. 19 | - `from_json(json_string)`. Returns object instance of the current class with attributes from the passed argument. 20 | - `Xmlable` with methods: 21 | - `to_xml()`. Returns XML string representing the current object. 22 | - `from_xml(xml_string)`. Returns object instance of the current class with attributes from the passed argument. 23 | 24 | > NOTE: What should `from_json` and `from_xml` methods be? 25 | 26 | ### Example 27 | 28 | #### Serialization 29 | 30 | ```python 31 | p = Panda(name='Marto') 32 | p.to_json() 33 | ''' 34 | { 35 | "dict": { 36 | "name": "Marto" 37 | }, 38 | "type": "Panda" 39 | } 40 | ''' 41 | p.to_xml() 42 | 'Ivo' 43 | ``` 44 | 45 | #### Deserialization 46 | 47 | ```python 48 | p = Panda(name='marto') 49 | json_string = p.to_json() 50 | xml_string = p.to_xml() 51 | 52 | p1 = Panda.from_json(json_string) 53 | p2 = Panda.from_xml(xml_string) 54 | 55 | assert p == p1 56 | assert p == p2 57 | ``` 58 | 59 | #### Validation 60 | 61 | Make sure you are `from_json` and `from_xml` return objects from the correct class. Raise `ValueError` instead. 62 | 63 | ```python 64 | person = Person('Pesho') 65 | Panda.from_json(person.to_json()) # ValueError 66 | ``` 67 | 68 | ### Hints 69 | 70 | - Use TDD! 71 | - For XML, use the standard library - 72 | - For JSON, use the standard library - 73 | 74 | ### Bonus 75 | 76 | Make your program work with nested structures. 77 | 78 | ```python 79 | person = Person('Pesho', panda=Panda('Marto')) 80 | person.to_json() 81 | ''' 82 | { 83 | "dict": { 84 | "name": "Pesho", 85 | "panda": { 86 | "dict": { 87 | "name": "Marto" 88 | }, 89 | "type": "Panda" 90 | } 91 | }, 92 | "type": "Person" 93 | } 94 | ''' 95 | ``` 96 | -------------------------------------------------------------------------------- /week04/01.Mixins/solution/mixins.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class WithSetAttributes: 5 | def __init__(self, **kwargs): 6 | for name, value in kwargs.items(): 7 | setattr(self, name, value) 8 | 9 | 10 | class WithEqualAttributes: 11 | def __eq__(self, other): 12 | return self.__dict__ == other.__dict__ 13 | 14 | 15 | class Jsonable: 16 | def to_json(self, indent=4): 17 | name = self.__class__.__name__ 18 | 19 | attributes = self.__dict__ 20 | 21 | return json.dumps({'type': name, 'dict': attributes}, indent=indent) 22 | 23 | @classmethod 24 | def from_json(cls, json_string): 25 | data = json.loads(json_string) 26 | 27 | class_name = data['type'] 28 | 29 | if class_name != cls.__name__: 30 | raise ValueError('Wrong type.') 31 | 32 | attributes = data['dict'] 33 | 34 | return cls(**attributes) 35 | -------------------------------------------------------------------------------- /week04/01.Mixins/solution/test_mixins.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | 4 | from mixins import Jsonable, WithSetAttributes, WithEqualAttributes 5 | 6 | 7 | class Panda(Jsonable, WithSetAttributes, WithEqualAttributes): 8 | pass 9 | 10 | 11 | class Car(Jsonable, WithSetAttributes, WithEqualAttributes): 12 | pass 13 | 14 | 15 | class TestJsonable(unittest.TestCase): 16 | def test_to_json_returns_empty_json_for_objects_with_no_arguments(self): 17 | panda = Panda() 18 | 19 | result = panda.to_json(indent=4) 20 | expexted = { 21 | 'type': Panda.__name__, 22 | 'dict': {} 23 | } 24 | 25 | self.assertEqual(result, json.dumps(expexted, indent=4)) 26 | 27 | def test_to_json_returns_correct_json_with_arguments(self): 28 | panda = Panda( 29 | name='Marto', 30 | age=20, 31 | weight=100.10, 32 | food=['bamboo', 'grass'], 33 | skills={'eat': 100, 'sleep': 200} 34 | ) 35 | 36 | panda_result = panda.to_json(indent=4) 37 | panda_expexted = { 38 | 'type': Panda.__name__, 39 | 'dict': { 40 | 'name': 'Marto', 41 | 'age': 20, 42 | 'weight': 100.10, 43 | 'food': ['bamboo', 'grass'], 44 | 'skills': {'eat': 100, 'sleep': 200} 45 | } 46 | } 47 | 48 | self.assertEqual(panda_result, json.dumps(panda_expexted, indent=4)) 49 | 50 | def test_to_json_returns_correct_json_with_arguments_of_jsonable_type(self): 51 | panda = Panda(name='Marto', friend=Panda(name='Ivo')) 52 | 53 | panda_result = panda.to_json(indent=4) 54 | panda_expexted = { 55 | 'type': Panda.__name__, 56 | 'dict': { 57 | 'name': 'Marto', 58 | 'friend': { 59 | 'type': Panda.__name__, 60 | 'dict': { 61 | 'name': 'Ivo' 62 | } 63 | } 64 | } 65 | } 66 | 67 | self.assertEqual(panda_result, json.dumps(panda_expexted, indent=4)) 68 | 69 | def test_from_json_with_wrong_class_type(self): 70 | car = Car() 71 | car_json = car.to_json() 72 | 73 | with self.assertRaises(ValueError): 74 | Panda.from_json(car_json) 75 | 76 | def test_from_json_with_no_arguments(self): 77 | car = Car() 78 | car_json = car.to_json() 79 | 80 | result = Car.from_json(car_json) 81 | 82 | self.assertEqual(car, result) 83 | 84 | def test_from_json_with_arguments(self): 85 | panda = Panda( 86 | name='Marto', 87 | age=20, 88 | weight=100.10, 89 | food=['bamboo', 'grass'], 90 | skills={'eat': 100, 'sleep': 200} 91 | ) 92 | panda_json = panda.to_json() 93 | 94 | result = Panda.from_json(panda_json) 95 | 96 | self.assertEqual(panda, result) 97 | 98 | 99 | if __name__ == '__main__': 100 | unittest.main() 101 | -------------------------------------------------------------------------------- /week04/02.BeloteDeclarations/README.md: -------------------------------------------------------------------------------- 1 | # Belote Declarations 2 | 3 | [Belote](https://en.wikipedia.org/wiki/Belote) is one of the most famous card games in Bulgaria. Your task is to implement just part of it - the possible declarations (announcements) in the first trick. 4 | 5 | ## Explanations 6 | 7 | > TL;DR: If you are familiar with the game of Belote this paragraph may be skipped. 8 | 9 | The Belote game is explained well in the above link from Wikipedia. There are a lot of rules but most of them do not apply for the declarations which are our target for this task. Here, I will try to summarize the rules: 10 | 11 | - The game is played with all 4 ranks `(Clubs, Diamonds, Hearts, Spades)` and the suites from 7 to A `(7, 8, 9, 10, J, Q, K, A)`. 12 | - The game is played by 2 teams -> Team X = Player 1 + Player 3; Team Y = Player 2 + Player 4 13 | - Each player has 8 cards 14 | - The announcements are made in consecutive order by the players. 15 | 16 | ### Belote 17 | 18 | - A player can announce "belote" if he has a K and Q from the same rank. 19 | - You can have a "belote" only from the rank that is the same with the game that is played (Ks + Qs is a belote only in a game of Spades) 20 | - In a game of "All trumps" -> you can have belote from all of the ranks 21 | - In a game of "No trumps" -> **there are no belotes** 22 | 23 | ### Consecutive cards 24 | 25 | - A player can announce "tierce" (20 points) if he has 3 consecutive cards from the same rank ( e.g. 7c, 8c, 9c ). 26 | - A player can announce "quarte" (50 points) if he has 4 consecutive cards from the same rank ( e.g. 10h, Jh, Qh, Kh ). 27 | - A player can announce "quinte" (100 points) if he has 5 (or more) consecutive cards from the same rank ( e.g. 8s, 9s, 10s, Js, Qs ). 28 | - In a game of "No trumps" -> **there are no tierces, quartes or quintes** 29 | 30 | ### Carre 31 | 32 | - A player can announce "carre" if he has 4 cards with the same suite ( e.g. Js, Jh, Jd, Jc ). 33 | - **4 7's or 4 8's are not considered carre** 34 | - In a game of "No trumps" -> **there are no carres** 35 | - Pointing: 36 | - carre of 10, Q, K, A = 100 points 37 | - carre of 9's = 150 points 38 | - carre of J's = 200 points 39 | - **NOTE: One card cannot be part from a carre and tierce/quarte/quinte in the same time. It can be part of a belote though.**. Let's say you have the following cards: `['7s', '8s', '9s', '9c', '9d', '9h', 'Kc', 'As']`. In this case the program is expected to output carre of 9's. You can still announce only tierce, but it gives you less points. 40 | 41 | ## Scoring 42 | 43 | The game is won by the team who first make > 150 points (If you have 150 points, the game is not over!) 44 | 45 | For this task, the points of one team will be calculated by summing their points from their announces. 46 | 47 | There are multiple rules connected to the priority of the announces. They apply only to between the teams: 48 | 49 | - The general rule: tierce < quarte < quinte. This means that if Team 1 has tierce and Teams 2 has quinte, the tierce of Team 1 should be excluded from the points. 50 | - If two enemy players have same sequences (e.g. player 1 and player 2 both have tierce) the one that is to a higher rank wins. If the sequences are equal, both of them should be excluded. 51 | - All belotes hold good! 52 | - All carres hold good! 53 | 54 | ### Example rounds 55 | 56 | ``` 57 | Team 1: 7s 8s 9s - tierce 58 | Team 2: 7c 8c 9c 10c - quarte 59 | 60 | Result: Team 1 has no tierce, because it's killed by the quarte of the other team => 0 points. Team 2 has quarte => 50 points 61 | ``` 62 | 63 | ``` 64 | Team 1: Jd Qd Kd - tierce and belote 65 | Team 2: 7c 8c 9c - tierce 66 | 67 | Result: Team 1 has no tierce and belote => 40 points. Team 2 has no tierce, because it's lower than the tierce of the other team => 0 points 68 | 69 | *NOTE: Rules for the belotes and the current played game should be applied* 70 | ``` 71 | 72 | ``` 73 | Team 1: Jd Qd Kd - tierce and belote 74 | Team 2: Js Qs Ks - tierce and belote 75 | 76 | Result: Team 1 has only belote, the tierce is killed as it's equal to the tierce of the other team => 20 points. Team 2 has only belote, the tierce is killed as it's equal to the tierce of the other team => 20 points. 77 | 78 | *NOTE: Rules for the belotes and the current played game should be applied* 79 | ``` 80 | 81 | ``` 82 | Team 1: 10s 10d 10h 10c - carre of 10's 83 | Team 2: 9s 9d 9h 9c - carre of 9's 84 | 85 | Result: Team 1 has carre of 10's => 100 points. Team 2 has care of 9's => 150 points 86 | ``` 87 | 88 | ``` 89 | Team 1: 10s 10d 10h 10c - carre of 10's 90 | Team 2: 7s 8s 9s - tierce 91 | 92 | Result: Team 1 has carre of 10's => 100 points. Team 2 has tierce => 20 points 93 | ``` 94 | 95 | ``` 96 | Team 1: 7s 8s 9s + 9d 10d Jd - 2 tierces 97 | Team 2: 7h 8h 9h - tierce 98 | 99 | Result: Team 1 has 2 tierces => 40 points. Team 2 has no tierce, becase one of the tierces of the other team is higher => 0 points 100 | ``` 101 | 102 | ``` 103 | Team 1: 7s 8s 9s + 9d 10d Jd Qd - tierce & quarte 104 | Team 2: 9c 10c Jc Qc - quarte 105 | 106 | Result: Team 1 has nothing because the quartes are equal and the quarte of the other team kills the tierce => 0 points. Team 2 has no quarte, because the quartes are equal => 0 points 107 | ``` 108 | 109 | ## The task 110 | 111 | You should imitate a game of Belote where the scores are generated only from the declarations of the players. 112 | 113 | Team and player names should inputted from the user. 114 | Example: 115 | 116 | ``` 117 | python3 belote.py 118 | 119 | Team 1 name: Mecheta 120 | Team 2 name: Koteta 121 | 122 | "Mecheta" players: Marto, Rado 123 | "Koteta" players: Gosho, Pesho 124 | ``` 125 | 126 | The two teams should alternate when declaring. When the program is started, the first player is player 1 from team 1 ('Marto'). In other words, the order of the players after the above input is: 'Marto' -> 'Gosho' -> 'Rado' -> 'Pesho'. 127 | After each round, the first player from the last round becomes last: 'Gosho' -> 'Rado' -> 'Pesho' -> 'Marto'. 128 | When a game is won by a team, a random player from this team starts the first round of the next game. 129 | 130 | The points from one round are calculated by summing the points from the declarations of each team. For example, if 'Marto' has tierce (20) and 'Rado' has quarte (50), their result from the round is 70 points. 131 | **NOTE: In the real game of Belote, these points are divided by 10. Don't do this for this task!!!** 132 | 133 | The program should finish when one of the teams win 2 games. 134 | 135 | One game is won by the first team that scores more than 150 points. If both of the teams have more than 150 points, the game is won by the team who has more points. If the points are equal, the game should continue until one of the teams shoot ahead. 136 | 137 | After each round, the points from the current round should be written into the `results.txt` file. 138 | In the following example we will see the expected format of the file: 139 | 140 | ``` 141 | Mecheta | Koteta 142 | ================================= 143 | 20 | 100 144 | 20 + 50 | 100 + 0 145 | ``` 146 | 147 | This is how you should mark when a game is won by a team: 148 | 149 | ``` 150 | Mecheta | Koteta 151 | ================================= 152 | 20 | 100 153 | 20 + 20 | 100 + 20 154 | 40 + 50 | 120 + 0 155 | 90 + 100 | 120 + 50 156 | 190 | 170 157 | ================================= 158 | (1) | (0) 159 | ================================= 160 | ``` 161 | 162 | After each round, the cards and the announcements of each player should be written in `data.json` file. 163 | Let's say we have this round: 164 | 165 | ``` 166 | Marto: ["7s", "8s", "9s", "10c", "Jd", "Qd", "Kh", "As"] # team Mecheta 167 | Gosho: ["7c", "8d", "9c", "10s", "Jh", "Qc", "Kc", "Ad"] # team Koteta 168 | Rado: ["7d", "8c", "9d", "10d", "Js", "Qs", "Kd", "Ac"] # team Mecheta 169 | Pesho: ["7h", "8h", "9h", "10h", "Jc", "Qh", "Ks", "Ah"] # team Koteta 170 | ``` 171 | 172 | ```json 173 | { 174 | "game 1": { 175 | "round 1": { 176 | "contract": "Hearts", 177 | "Mecheta": { 178 | "Marto": { 179 | "cards:": ["7s", "8s", "9s", "10c", "Jd", "Qh", "Kh", "As"], 180 | "announcements": ["belote"], 181 | "points": 20 182 | }, 183 | "Rado": { 184 | "cards:": ["7d", "8c", "9d", "10d", "Js", "Qs", "Kd", "Ac"], 185 | "announcements": [], 186 | "points": 0 187 | } 188 | }, 189 | "Koteta": { 190 | "Gosho": { 191 | "cards:": ["7c", "8d", "9c", "10s", "Jh", "Qc", "Kc", "Ad"], 192 | "announcements": [], 193 | "points": 0 194 | }, 195 | "Pesho": { 196 | "cards:": ["7h", "8h", "9h", "10h", "Jc", "Qd", "Ks", "Ah"], 197 | "announcements": ["quarte"], 198 | "points": 50 199 | } 200 | } 201 | } 202 | // rest of the rounds here .... 203 | } 204 | } 205 | ``` 206 | -------------------------------------------------------------------------------- /week04/README.md: -------------------------------------------------------------------------------- 1 | # Week 04 - Mixins 2 | 3 | - [Multiple Inheritance & Mixins](https://slides.com/hackbulgaria/python-101-9th-mixins#/) 4 | -------------------------------------------------------------------------------- /week05/README.md: -------------------------------------------------------------------------------- 1 | # Week 05 2 | 3 | - [@property](https://github.com/HackBulgaria/Programming-101-Python-2020-Spring/blob/master/playground/property.py) 4 | - [Packages and modules](https://github.com/HackBulgaria/Programming-101-Python-2020-Spring/tree/python-imports) 5 | -------------------------------------------------------------------------------- /week06/01.Decorators/README.md: -------------------------------------------------------------------------------- 1 | ## @accepts 2 | 3 | Make a decorator `@accepts` that takes as many arguments as the function takes. That decorator specify the types of the arguments that your function takes. If any of the arguments does not match the type in the decorator raise a `TypeError` 4 | 5 | ### Examples 6 | 7 | ```python 8 | @accepts(str) 9 | def say_hello(name): 10 | return "Hello, I am {}".format(name) 11 | 12 | say_hello(4) 13 | 14 | TypeError: Argument "name" of say_hello is not str! 15 | ``` 16 | 17 | ```python 18 | @accepts(str, int) 19 | def deposit(name, money): 20 | print("{} sends {} $!".format(name, money)) 21 | 22 | deposit("Marto", 10) 23 | ``` 24 | 25 | Note that this is just a nice example. In real life you don't want use this! 26 | 27 | ## @performance(file_name) 28 | 29 | Make a decorator `@performance` that takes a `file_name` and writes in to this file a log. New line for every call of the decorated function. This decorator should log the time needed for the decorated function to execute. 30 | 31 | ### Example 32 | 33 | ```python 34 | @performance('log.txt') 35 | def something_heavy(): 36 | sleep(2) 37 | return "I am done!" 38 | 39 | something_heavy() 40 | 41 | I am done! 42 | ``` 43 | 44 | And the log file should look like this: 45 | 46 | ``` 47 | get_low was called and took 2.00 seconds to complete 48 | get_low was called and took 2.10 seconds to complete 49 | ``` 50 | 51 | ## @required 52 | 53 | Make a decorator `@required` that makes all decorated methods with throw exception if they are not overrided in the child classes. 54 | 55 | ### Example 56 | 57 | ```python 58 | class Animal: 59 | @required 60 | def eat(self, food): 61 | pass 62 | 63 | 64 | class Panda(Animal): 65 | pass 66 | 67 | p = Panda() 68 | p.eat('bamboo') 69 | 70 | Exception: All classes that inherit from "Animal" must provide "eat" method. 71 | ``` 72 | 73 | ## @silence(file_name) 74 | 75 | Make a decorator `@silence` that accepts a `file_name` as an argument. All functions that are decorated with it should always run successfully. If an error is raised, it should be logged in the given file. 76 | 77 | ```python 78 | @silence('errors.txt') 79 | def foo(x) 80 | if x > 50: 81 | raise ValueError('Omg.') 82 | 83 | foo(10) 84 | foo(100) 85 | 86 | # in errors.txt 87 | Calling `foo` raised an error - ValueError: 'Omg.'. Provided arguments: (100, ). 88 | ``` 89 | -------------------------------------------------------------------------------- /week06/01.Decorators/required.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | def required(method): 5 | def required_method(instance, *args, **kwargs): 6 | method_name = method.__name__ 7 | 8 | if method_name not in instance.__dict__: 9 | raise AttributeError( 10 | f'All classes that inherit from "{instance.__class__.__name__}" must provide "{method_name}" method.' 11 | ) 12 | 13 | return method(instance, *args, **kwargs) 14 | 15 | return required_method 16 | 17 | 18 | class Animal(ABC): 19 | @abstractmethod 20 | def eat(self, food): 21 | pass 22 | 23 | @required 24 | def sleep(self, time=10): 25 | pass 26 | 27 | 28 | class Dog(Animal): 29 | pass 30 | 31 | 32 | sharo = Dog() 33 | # sharo.sleep() 34 | -------------------------------------------------------------------------------- /week06/02.Generators/Book.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week06/02.Generators/Book.zip -------------------------------------------------------------------------------- /week06/02.Generators/README.md: -------------------------------------------------------------------------------- 1 | # Generators 2 | 3 | ## chain 4 | 5 | Implement a function that takes two iterables and returns another one that concatenate the two iterables. 6 | 7 | ```python 8 | def chain(iterable_one, iterable_two): 9 | pass 10 | ``` 11 | 12 | ### Example 13 | 14 | ```python 15 | >>> list(chain(range(0, 4), range(4, 8))) 16 | [0, 1, 2, 3, 4, 5, 6, 7] 17 | ``` 18 | 19 | ## compress 20 | 21 | Implement a function that takes one iterables and one iterable mask. The mask is an collection that contains only `True` or `False` 22 | 23 | This function returns only this objects from the first collection that have `True` on their position in the mask. 24 | 25 | ```python 26 | def compress(iterable, mask): 27 | pass 28 | ``` 29 | 30 | ### Example 31 | 32 | ```python 33 | >>> list(compress(["Ivo", "Rado", "Panda"], [False, False, True])) 34 | ["Panda"] 35 | ``` 36 | 37 | ## cycle 38 | 39 | Implement a function that takes an iterable and returns endless concatenation of it. 40 | 41 | ```python 42 | def cycle(iterable): 43 | pass 44 | ``` 45 | 46 | ```python 47 | >>> endless = cycle(range(0,10)) 48 | for item in endless: 49 | print(item) 50 | ``` 51 | 52 | ## Book Reader 53 | 54 | You have some text files. They represent a book. Our book contains chapters. Each chapter starts with `#` at the beginning of the line. (Markdown book) 55 | 56 | Our book is made of many files. Each file has its number `001.txt, 002.txt, 003.txt` 57 | 58 | Each file may contain one or more chapters. 59 | 60 | [Link to the book](Book.zip) 61 | 62 | Write a program that displays on the console each chapter. You can only move forwards using the `space button`. 63 | 64 | Try not to load the whole book in the memory. Use generator! 65 | 66 | ## Book Generator 67 | 68 | Make a python program that generates books. 69 | 70 | Your program should take the following parameters. 71 | 72 | - Chapters count 73 | - Chapter length range (in words) 74 | 75 | The words should be with random length and random char. The format of the book should be the same as previous task. Try to place some new lines in the chapters at random positions. The whole book must be in one file. 76 | 77 | Try to generate bigger book. Like 1-2G, and try to pass it to the previous program. 78 | 79 | ## Extra Task - Mouse Beep 80 | 81 | Make a generator that returns the current position of your mouse pointer. 82 | 83 | Then make a function that checks if your mouse is at the upper left corner of your screen. If it is your computer should make a beep sound. 84 | 85 | Ask Google for "How to get mouse cords" and "Python beep sound" 86 | -------------------------------------------------------------------------------- /week06/02.Generators/chain.py: -------------------------------------------------------------------------------- 1 | def chain1(iter1, iter2): 2 | res = [] 3 | 4 | for x in iter1: 5 | res.append(x) 6 | 7 | for x in iter2: 8 | res.append(x) 9 | 10 | return res 11 | 12 | 13 | def chain2(iter1, iter2): 14 | res = [] 15 | 16 | res.extend(iter1) 17 | res.extend(iter2) 18 | 19 | return res 20 | 21 | 22 | def chain3(iter1, iter2): 23 | return [*iter1, *iter2] 24 | 25 | 26 | def chain4(iter1, iter2): 27 | res = [*iter1, *iter2] 28 | 29 | for x in res: 30 | yield x 31 | 32 | 33 | def chain5(iter1, iter2): 34 | for x in iter1: 35 | yield x 36 | 37 | for x in iter2: 38 | yield x 39 | 40 | 41 | iter1 = range(4) 42 | iter2 = range(4, 10) 43 | 44 | result = chain5(iter1, iter2) 45 | 46 | print(result) 47 | print(list(result)) 48 | -------------------------------------------------------------------------------- /week06/README.md: -------------------------------------------------------------------------------- 1 | # Week 06 2 | 3 | - [Decorators](https://slides.com/hackbulgaria/python101-9th-decorators/#/) 4 | - [Generators](https://slides.com/hackbulgaria/python101-9th-iterators-generators) 5 | -------------------------------------------------------------------------------- /week07/01.ContextManagers/README.md: -------------------------------------------------------------------------------- 1 | # Context Managers 2 | 3 | **NOTE: All tasks should be implemented using a class context managers and @contextmanager generator!** 4 | 5 | ## Silence errors 6 | 7 | Implement a context manager that silences specific exceptions. It can accept the message of the exception as non-required argument. 8 | 9 | ```python 10 | with silence_exception(ValueError): 11 | # nothing should happen 12 | raise ValueError('Test') 13 | 14 | with silence_exception(ValueError): 15 | # the error should be re-raised since it is not expected. 16 | raise TypeError('Test') 17 | 18 | with silence_exception(ValueError, 'Test'): 19 | # nothing should happen 20 | raise ValueError('Test') 21 | 22 | with silence_exception(ValueError, 'Testing.'): 23 | # the error should be re-raised since it is not expected. 24 | raise ValueError('Test') 25 | ``` 26 | 27 | ## Change Decimal Precision 28 | 29 | Implement a context manager that sets the precision of all Decimals in the with-block. Outside the block, the Decimals must be preserved with their default precision. _Think for raised exceptions!_ 30 | 31 | ```python 32 | with change_precision(2): 33 | print(Decimal('1.123132132') + Decimal('2.23232')) # 3.4 34 | 35 | print(Decimal('1.123132132') + Decimal('2.23232')) # 3.355452132 36 | ``` 37 | 38 | Check this for help: https://docs.python.org/3/library/decimal.html#quick-start-tutorial 39 | 40 | ## Measure Performance 41 | 42 | Implement a context manager that tracks the performance of a code block using "benchmarks". The context manager should print how much time each code block took. When the with-block is finished, the context manager should print how much time the whole block took. 43 | 44 | The benchmarks can accept a string message so it's easier to know what is measured. If there is no message passed - use the index of the benchmark as shown below. 45 | 46 | Each benchmark can optionally restart the context manager time (show below). 47 | 48 | ```python 49 | with measure_performance() as p: 50 | time.sleep(1) 51 | p.benchmark('1st step') 52 | 53 | time.sleep(2) 54 | p.benchmark('2nd step', restart=True) 55 | 56 | time.sleep(3) 57 | p.benchmark() 58 | ``` 59 | 60 | The output is: 61 | 62 | ``` 63 | 1st step: 1.0011475086212158 64 | 2nd step: 3.0017237663269043 65 | Benchmark No.3: 3.003134250640869 66 | Finished for: 6.0052361488342285 67 | ``` 68 | 69 | **NOTE: All tasks should be implemented using a class context managers and @contextmanager generator!** 70 | -------------------------------------------------------------------------------- /week07/01.ContextManagers/measure_performance.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | 5 | class MeasurePerformance: 6 | def __init__(self, filename): 7 | self.filename = filename 8 | self.file = None 9 | self._benchmarks = [] 10 | 11 | def __enter__(self): 12 | self.file = open(self.filename, 'w') 13 | 14 | self.original_start = time.time() 15 | self.start = self.original_start 16 | 17 | return self 18 | 19 | def __exit__(self, exc_type, exc_value, exc_traceback): 20 | if self.file: 21 | if exc_type: 22 | self.file.close() 23 | os.remove(self.filename) 24 | else: 25 | self.file.write('\n'.join(self._benchmarks)) 26 | self.file.write(f'\nFinished for: {int(time.time() - self.original_start)}s') 27 | self.file.close() 28 | 29 | def get_exceeded_time(self): 30 | return int(time.time() - self.start) 31 | 32 | def benchmark(self, msg=None, restart=False): 33 | exceeded_time = self.get_exceeded_time() 34 | 35 | if restart: 36 | self.start = time.time() 37 | 38 | if msg is None: 39 | msg = f'Benchmark No.{len(self._benchmarks) + 1}' 40 | 41 | benchmark = f'{msg}: {exceeded_time}s' 42 | self._benchmarks.append(benchmark) 43 | -------------------------------------------------------------------------------- /week07/01.ContextManagers/silence_exception.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | 3 | 4 | @contextmanager 5 | def silence_exception(exc_type, msg=None): 6 | try: 7 | yield 8 | except exc_type as exc: 9 | if msg is not None and str(exc) != msg: 10 | raise exc 11 | 12 | 13 | class SilenceException: 14 | def __init__(self, exc_type, msg=None): 15 | self.exc_type = exc_type 16 | self.msg = msg 17 | 18 | def __enter__(self): 19 | return self 20 | 21 | def __exit__(self, exc_type, exc_value, exc_traceback): 22 | same_exception_type = self.exc_type == exc_type 23 | correct_message = self.msg is None or str(exc_value) == self.msg 24 | 25 | return same_exception_type and correct_message 26 | -------------------------------------------------------------------------------- /week07/01.ContextManagers/test_measure_performance.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | import time 4 | 5 | from measure_performance import MeasurePerformance 6 | 7 | 8 | class MeasurePerformanceTests(unittest.TestCase): 9 | def setUp(self): 10 | self.filename = 'test_measure_performance.txt' 11 | 12 | def test_get_exceeded_time_returns_correct_value(self): 13 | meter = MeasurePerformance(self.filename) 14 | 15 | meter.start = time.time() - 1 16 | 17 | self.assertEqual(meter.get_exceeded_time(), 1) 18 | 19 | def test_benchmark_measures_correct_time_from_init_to_first_call(self): 20 | with MeasurePerformance(self.filename) as meter: 21 | time.sleep(1) 22 | 23 | self.assertEqual(meter.get_exceeded_time(), 1) 24 | 25 | def test_benchmark_appends_benchmark_using_passed_message(self): 26 | with MeasurePerformance(self.filename) as meter: 27 | time.sleep(1) 28 | 29 | meter.benchmark('test') 30 | 31 | expected = ['test: 1s'] 32 | 33 | self.assertEqual(meter._benchmarks, expected) 34 | 35 | def test_benchmark_appends_benchmark_with_no_message_using_benchmarks_count(self): 36 | with MeasurePerformance(self.filename) as meter: 37 | time.sleep(1) 38 | 39 | meter.benchmark('test') 40 | 41 | time.sleep(1) 42 | meter.benchmark() 43 | 44 | expected = ['test: 1s', 'Benchmark No.2: 2s'] 45 | 46 | self.assertEqual(meter._benchmarks, expected) 47 | 48 | def test_benchmark_does_not_restart_the_start_of_measure_performance_if_not_passed(self): 49 | with MeasurePerformance(self.filename) as meter: 50 | original_start = meter.original_start 51 | 52 | time.sleep(1) 53 | meter.benchmark('test') 54 | 55 | self.assertEqual(meter.original_start, original_start) 56 | 57 | def test_benchmark_restarts_the_start_of_the_measure_performance_if_passed(self): 58 | with MeasurePerformance(self.filename) as meter: 59 | original_start = meter.original_start 60 | 61 | time.sleep(1) 62 | meter.benchmark('test', restart=True) 63 | 64 | self.assertEqual(meter.original_start, original_start) 65 | 66 | def test_benchmark_preserves_correct_execution_times_if_not_restarted(self): 67 | msg = 'test' 68 | 69 | with MeasurePerformance(self.filename) as meter: 70 | time.sleep(1) 71 | 72 | meter.benchmark(msg) 73 | 74 | time.sleep(1) 75 | meter.benchmark(msg, restart=False) 76 | 77 | expected = ['test: 1s', 'test: 2s'] 78 | 79 | self.assertEqual(meter._benchmarks, expected) 80 | 81 | def test_benchmark_preserves_correct_execution_times_if_restarted(self): 82 | msg = 'test' 83 | 84 | with MeasurePerformance(self.filename) as meter: 85 | time.sleep(1) 86 | 87 | meter.benchmark(msg, restart=True) 88 | 89 | time.sleep(1) 90 | meter.benchmark(msg) 91 | 92 | expected = ['test: 1s', 'test: 1s'] 93 | 94 | self.assertEqual(meter._benchmarks, expected) 95 | 96 | def test_context_manager_does_not_create_the_file_if_exception_is_raised(self): 97 | with self.assertRaises(ValueError): 98 | with MeasurePerformance(self.filename): 99 | raise ValueError('Testing') 100 | 101 | self.assertFalse(os.path.exists(self.filename)) 102 | 103 | def test_context_manager_closes_the_file_if_there_is_no_exception(self): 104 | with MeasurePerformance(self.filename): 105 | x = 111 106 | x += 10 107 | 108 | self.assertTrue(os.path.exists(self.filename)) 109 | 110 | # we can open only closed files 111 | test_file = open(self.filename, 'r') 112 | test_file.close() 113 | 114 | def test_context_manager_dumps_correct_info_into_file_when_closed(self): 115 | with MeasurePerformance(self.filename) as meter: 116 | time.sleep(1) 117 | 118 | meter.benchmark('test') 119 | 120 | time.sleep(1) 121 | meter.benchmark() 122 | 123 | expected = 'test: 1s\nBenchmark No.2: 2s\nFinished for: 2s' 124 | 125 | with open(self.filename, 'r') as test_file: 126 | self.assertEqual(expected, test_file.read()) 127 | 128 | def tearDown(self): 129 | try: 130 | os.remove(self.filename) 131 | except FileNotFoundError: 132 | pass 133 | 134 | 135 | if __name__ == '__main__': 136 | unittest.main() 137 | -------------------------------------------------------------------------------- /week07/01.ContextManagers/test_silence_exception.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from silence_exception import silence_exception, SilenceException 4 | 5 | 6 | class SilenceExceptionTests(unittest.TestCase): 7 | def test_silences_passed_exception(self): 8 | exception = None 9 | 10 | try: 11 | with silence_exception(ValueError): 12 | raise ValueError('Testing.') 13 | except Exception as exc: 14 | exception = exc 15 | 16 | self.assertIsNone(exception) 17 | 18 | def test_not_silences_different_exception_from_passed_one(self): 19 | with self.assertRaises(ValueError): 20 | with silence_exception(TypeError): 21 | raise ValueError('Testing.') 22 | 23 | def test_not_silences_passed_exception_outside_context_manager(self): 24 | with self.assertRaises(ValueError, msg='Testing outside with-block'): 25 | with silence_exception(ValueError): 26 | raise ValueError('Testing inside with-block') 27 | 28 | raise ValueError('Testing outside with-block') 29 | 30 | def test_silences_passed_exception_with_correct_message(self): 31 | exception = None 32 | exc_message = 'Testing with msg argument.' 33 | 34 | try: 35 | with silence_exception(ValueError, msg=exc_message): 36 | raise ValueError(exc_message) 37 | except Exception as exc: 38 | exception = exc 39 | 40 | self.assertIsNone(exception) 41 | 42 | def test_not_silences_passed_exception_with_different_message(self): 43 | exc_message = 'Testing with msg argument.' 44 | 45 | with self.assertRaises(ValueError): 46 | with silence_exception(ValueError, msg=exc_message): 47 | raise ValueError(f'{exc_message} - different.') 48 | 49 | def test_not_silences_different_exception_with_same_message(self): 50 | exc_message = 'Testing with msg argument.' 51 | 52 | with self.assertRaises(TypeError): 53 | with silence_exception(ValueError, msg=exc_message): 54 | raise TypeError(exc_message) 55 | 56 | 57 | class SilenceExceptionClassTests(unittest.TestCase): 58 | def test_silences_passed_exception(self): 59 | exception = None 60 | 61 | try: 62 | with SilenceException(ValueError): 63 | raise ValueError('Testing.') 64 | except Exception as exc: 65 | exception = exc 66 | 67 | self.assertIsNone(exception) 68 | 69 | def test_not_silences_different_exception_from_passed_one(self): 70 | with self.assertRaises(ValueError): 71 | with SilenceException(TypeError): 72 | raise ValueError('Testing.') 73 | 74 | def test_not_silences_passed_exception_outside_context_manager(self): 75 | with self.assertRaises(ValueError, msg='Testing outside with-block'): 76 | with SilenceException(ValueError): 77 | raise ValueError('Testing inside with-block') 78 | 79 | raise ValueError('Testing outside with-block') 80 | 81 | def test_silences_passed_exception_with_correct_message(self): 82 | exception = None 83 | exc_message = 'Testing with msg argument.' 84 | 85 | try: 86 | with SilenceException(ValueError, msg=exc_message): 87 | raise ValueError(exc_message) 88 | except Exception as exc: 89 | exception = exc 90 | 91 | self.assertIsNone(exception) 92 | 93 | def test_not_silences_passed_exception_with_different_message(self): 94 | exc_message = 'Testing with msg argument.' 95 | 96 | with self.assertRaises(ValueError): 97 | with SilenceException(ValueError, msg=exc_message): 98 | raise ValueError(f'{exc_message} - different.') 99 | 100 | def test_not_silences_different_exception_with_same_message(self): 101 | exc_message = 'Testing with msg argument.' 102 | 103 | with self.assertRaises(TypeError): 104 | with SilenceException(ValueError, msg=exc_message): 105 | raise TypeError(exc_message) 106 | 107 | 108 | if __name__ == '__main__': 109 | unittest.main() 110 | -------------------------------------------------------------------------------- /week07/02.PipAndVirtualenv/README.md: -------------------------------------------------------------------------------- 1 | # pip and virtualenv 2 | 3 | * [Slides & diagrams](https://whimsical.com/ETDiwVTKDvbkqWUNsBwY7X) 4 | * [https://pypi.org/](https://pypi.org/) 5 | * [https://packaging.python.org/tutorials/installing-packages/](https://packaging.python.org/tutorials/installing-packages/) 6 | * [https://virtualenv.pypa.io/en/stable/index.html](https://virtualenv.pypa.io/en/stable/index.html) 7 | * [https://github.com/pyenv/pyenv](https://github.com/pyenv/pyenv) 8 | 9 | **Your task is going to be:** 10 | 11 | 1. Setup a virtualenv for the course 12 | 2. Install `pytest` 13 | 3. Run some of the tests you have with `py.test` 14 | -------------------------------------------------------------------------------- /week07/README.md: -------------------------------------------------------------------------------- 1 | # Week 07 2 | 3 | - [Context Mangers](https://slides.com/hackbulgaria/python-101-9th-context-managers/#/) 4 | -------------------------------------------------------------------------------- /week08/01.Graphs/README.md: -------------------------------------------------------------------------------- 1 | ## Tasks 2 | 3 | Every dictionary in the following tasks can have another dictionary as value or a iterable of dictionaries. 4 | 5 | ### Task 1 6 | 7 | Implement `deep_find(data, key)` which finds the given `key` in the `data` and returns it's value. 8 | 9 | **NOTE:** Add 2 solutions - one using DFS and one with BFS. 10 | 11 | ### Task 2 12 | 13 | Implement `deep_find_all(data, key)` which finds the given `key` in the `data` and returns array of the found values. 14 | 15 | **NOTE:** Add 2 solutions - one using DFS and one with BFS. 16 | 17 | ### Task 3 18 | 19 | Implement `deep_update(data, key, val)` which updates every occurance of the given `key` in the `data` with `val`. 20 | 21 | ### Task 4 22 | 23 | Implement `deep_apply(func, data)` which applies the given `func` to all **keys** from the given `data`. 24 | 25 | ### Task 5 26 | 27 | Implement `deep_compare(obj1, obj2)` where obj1 and obj2 can be `dict` or `iterable` and compares the given objects. 28 | 29 | ### Task 6 30 | 31 | Implement `schema_validator(schema: List, data: Dict)` which should assert that the given `data` keys are as the given `schema`. 32 | **Notes** 33 | 34 | - `data` is valid only if the given keys from the `schema` are found in the `data`. 35 | - If the `schema` has more or less keys, `data` is invalid. 36 | - If there is a missmatch in the `schema` and the `data` keys, `data` is invalid. 37 | - `schema_validator` should work for N levels of nesting. 38 | 39 | Example `schema`: 40 | 41 | ``` 42 | schema = [ 43 | 'key1', 44 | 'key2', 45 | [ 46 | 'key3', 47 | ['inner_key1', 'inner_key2'] 48 | ] 49 | ] 50 | ``` 51 | 52 | Valid `data`: 53 | 54 | ``` 55 | data = { 56 | 'key1': 'val1', 57 | 'key2': 'val2', 58 | 'key3': { 59 | 'inner_key1': 'val1', 60 | 'inner_key2': 'val2' 61 | } 62 | } 63 | ``` 64 | 65 | Invalid `data`: 66 | 67 | ``` 68 | data = { 69 | 'key1': 'val1', 70 | 'key2': 'val2', 71 | 'key3': { 72 | 'inner_key1': 'val1', 73 | 'inner_key2': 'val2' 74 | }, 75 | 'key4': 'not expected' 76 | } 77 | ``` 78 | 79 | ### Extra task: Frogs 80 | 81 | There's a lake. N lily-pads are in a series on the lake. On the first half pads (starting from the left) are sitting brown frogs (facing right). Then there is an empty pad. The second half pads have 3 green frogs on them (facing left). The frogs want to pass each other, but they can only jump on the empty lilly pad in the direction they are facing or jump over 1 frog on the empty lilly pad behind that frog. 82 | 83 | The configuration can be depicted as such: 84 | 85 | ``` 86 | > > > _ < < < 87 | ``` 88 | 89 | The frogs can only jump to an empty lily-pad to the direction they are facing. 90 | 91 | ``` 92 | > > _ > < < < 93 | ``` 94 | 95 | The frogs can also jump up to 2 lily-pads away. 96 | 97 | ``` 98 | > > < > _ < < 99 | ``` 100 | 101 | In the end, the frogs must come to this configuration: 102 | 103 | ``` 104 | < < < _ > > > 105 | ``` 106 | 107 | Write a program that will print all of the jumps that the frogs must do in order to pass each other 108 | -------------------------------------------------------------------------------- /week08/02.Mocks/README.md: -------------------------------------------------------------------------------- 1 | ## Presentation 2 | 3 | https://slides.com/hackbulgaria/python-101-9th-mocking/ 4 | 5 | ## You should write tests for the following functions 6 | 7 | ### 1 8 | 9 | Remember the [Cancellation policy task](https://github.com/HackBulgaria/Programming-101-Python-2020-Spring/tree/master/week02/02.CancellationPolicy)? Rework the solution and the tests and stop expecting `now` as argument! 10 | 11 | ``` 12 | def get_cancellation_policy(conditions, price, start): 13 | now = datetime.now() 14 | 15 | assert now < start, 'Invalid booking start.' 16 | validate_conditions(conditions) 17 | 18 | ensured_conditions = ensure_conditions(conditions) 19 | 20 | if (len(ensured_conditions)) == 1: 21 | return get_cancellation_fee(price, ensured_conditions[0]['percent']) 22 | 23 | sorted_conditions = sort_conditions(ensured_conditions) 24 | paired_conditions = pair_conditions(sorted_conditions) 25 | 26 | current = get_current_condition(paired_conditions, start, now) 27 | 28 | return get_cancellation_fee(price, current) 29 | ``` 30 | 31 | ### 2 32 | 33 | Remember [Measure Performance task](https://github.com/HackBulgaria/Programming-101-Python-2020-Spring/tree/master/week07/01.ContextManagers#measure-performance)? Rework the tests to test if the context manager **prints the correct values**. 34 | 35 | ### 3 36 | 37 | ``` 38 | from datetime import datetime 39 | 40 | def get_booking_status(booking): 41 | now = datetime.now() 42 | 43 | if booking.cancelled: 44 | return 'Cancelled' 45 | 46 | if booking.is_fully_paid(): 47 | return 'Paid' 48 | 49 | if now < booking.start: 50 | return 'Upcoming' 51 | 52 | return 'Waiting taxes' 53 | ``` 54 | 55 | ### 4 56 | 57 | ``` 58 | from random import randint 59 | 60 | def big_possitive_pow(): 61 | x = randint(100, 10000) 62 | y = randint(-10, 100) 63 | 64 | if y < 1: 65 | raise ValueError('Try again.') 66 | 67 | return x ** y 68 | ``` 69 | 70 | ### 5 71 | 72 | You don't need to install the libraries! 73 | 74 | ``` 75 | import requests 76 | from bs4 import BeautifulSoup 77 | 78 | 79 | def get_url_title(*, url): 80 | try: 81 | # Pretend to be a bot. 82 | headers = {'User-Agent': 'Testbot'} 83 | response = requests.get(url, headers=headers, timeout=10) 84 | except RequestException as request_exception: 85 | raise Exception(f'Couldn\'t request {url}. This was the exception {request_exception}') 86 | 87 | if not response.ok: 88 | raise Exception(f'Response for {url} was not ok.') 89 | 90 | soup = BeautifulSoup(response.content, 'html.parser') 91 | title_tag = soup.title 92 | 93 | if title_tag 94 | return title_tag.string 95 | 96 | return '' 97 | ``` 98 | -------------------------------------------------------------------------------- /week09/01.SQL-Intro/README.md: -------------------------------------------------------------------------------- 1 | # Introduction to Databases and SQL 2 | 3 | Presentation: https://slides.com/hackbulgaria/python-101-9th-sql/ 4 | 5 | ## Task 1: Tables, tables everywhere! SELECT, UPDATE, INSERT, DELETE 6 | 7 | Now, we know that our languages table looks like this: 8 | 9 | | id | language | answer | answered | guide | 10 | | ------------- |:-------------:| --- | --- |-----:| 11 | 1|Python|google|0|A folder named Python was created. Go there and fight with program.py! 12 | 2|Go|200 OK|0|A folder named Go was created. Go there and try to make Google Go run. 13 | 3|Java|object oriented programming|0|A folder named Java was created. Can you handle the class? 14 | 4|Haskell|Lambda|0|Something pure has landed. Go to Haskell folder and see it! 15 | 5|C#|NDI=|0|Do you see sharp? Go to the C# folder and check out. 16 | 6|Ruby|https://www.ruby-lang.org/bg/|0|Ruby, ruby, rubyyy, aaahaaaahaa! (music). Go to Ruby folder! 17 | 7|C++|header files|0|Here be dragons! It's C++ time. Go to the C++ folder. 18 | 8|JavaScript|Douglas Crockford|0|NodeJS time. Go to JavaScript folder and Node your way! 19 | 20 | Your task is to write queries for: 21 | 22 | * Create database 23 | * Create table `Languages` with columns and attributes with the correct types 24 | * Insert data 25 | * Add new column `rating` which is number from 1 to 9. Insert values for every language. 26 | * For few languages (`Python` and `Go`) update answered value from 0 to 1 27 | * Select languages which answer is `200 OK` or `Lambda`. 28 | 29 | ## Task 2: Database schema 30 | ![Schema](movies_schema.png) 31 | 32 | ## First Queries 33 | 1. Напишете заявка, която извежда адреса на студио ‘MGM’ 34 | 2. Напишете заявка, която извежда рождената дата на актрисата `Kim Basinger` 35 | 3. Напишете заявка, която извежда имената всички продуценти на филми с 36 | нетни активи (networth) над 10 000 000 долара 37 | 4. Напишете заявка, която извежда имената на всички актьори, които са 38 | мъже или живеят на Prefect Rd 39 | 5. Добавате нова филмова звезда 'Zahari Baharov', с адрес и рожденна дата по ваш избор. 40 | 6. Изтрийте всички студия, които имат в адреса си числото 5. 41 | 7. Променете студио да бъде "Fox" на тези филми, които в имената си имат 'star. 42 | 43 | 44 | ## Relations 45 | 46 | 1. Напишете заявка, която извежда имената на актьорите мъже участвали в ‘Terms 47 | 2. of Endearment’ 48 | 3. Напишете заявка, която извежда имената на актьорите участвали във филми 49 | продуцирани от ‘MGM’през 1995 г. 50 | 4. Добавете колона "име на президент"на таблицата Студио и съответно и задайте стойности.Напишете заявка, която извежда името на президента на ‘MGM’ 51 | -------------------------------------------------------------------------------- /week09/01.SQL-Intro/movies.sql: -------------------------------------------------------------------------------- 1 | ----- Tables ----- 2 | 3 | DROP TABLE IF EXISTS MOVIEEXEC; 4 | DROP TABLE IF EXISTS MOVIE; 5 | DROP TABLE IF EXISTS MOVIESTAR; 6 | DROP TABLE IF EXISTS STARSIN; 7 | DROP TABLE IF EXISTS STUDIO; 8 | 9 | CREATE TABLE MOVIEEXEC ( 10 | CERT INTEGER NOT NULL PRIMARY KEY, 11 | NAME CHAR(30), 12 | ADDRESS VARCHAR(255), 13 | NETWORTH INTEGER 14 | ); 15 | 16 | CREATE TABLE STUDIO ( 17 | NAME CHAR(50) NOT NULL PRIMARY KEY, 18 | ADDRESS VARCHAR(255), 19 | PRESC INTEGER 20 | ); 21 | 22 | CREATE TABLE MOVIE ( 23 | TITLE VARCHAR(255) NOT NULL, 24 | YEAR INTEGER NOT NULL, 25 | LENGTH INTEGER, 26 | INCOLOR CHAR(1), 27 | STUDIONAME CHAR(50), 28 | PRODUCER INTEGER, 29 | PRIMARY KEY( TITLE, YEAR), 30 | FOREIGN KEY(PRODUCER) REFERENCES MOVIEEXEC(CERT), 31 | FOREIGN KEY(STUDIONAME) REFERENCES STUDIO(NAME) 32 | ); 33 | 34 | CREATE TABLE MOVIESTAR ( 35 | NAME CHAR(30) NOT NULL PRIMARY KEY, 36 | ADDRESS VARCHAR(255), 37 | GENDER CHAR(1), 38 | BIRTHDATE DATE 39 | ); 40 | 41 | CREATE TABLE STARSIN ( 42 | MOVIETITLE VARCHAR(255) NOT NULL, 43 | MOVIEYEAR INTEGER NOT NULL, 44 | STARNAME CHAR(30) NOT NULL, 45 | FOREIGN KEY(MOVIETITLE) REFERENCES MOVIE(TITLE), 46 | FOREIGN KEY(STARNAME) REFERENCES MOVIESTAR(NAME), 47 | PRIMARY KEY(MOVIETITLE, MOVIEYEAR, STARNAME) 48 | ); 49 | 50 | ----- Constraints ----- 51 | INSERT INTO STUDIO (NAME, ADDRESS, PRESC) 52 | VALUES ('Disney','USA, 234534 CF, World',4); 53 | 54 | INSERT INTO STUDIO (NAME, ADDRESS, PRESC) 55 | VALUES ('Fox','USA, 234576 CF, Fox Str',3); 56 | 57 | INSERT INTO STUDIO (NAME, ADDRESS, PRESC) 58 | VALUES ('MGM','USA, 127823 NY, 8th Str',1); 59 | 60 | INSERT INTO STUDIO (NAME, ADDRESS, PRESC) 61 | VALUES ('Paramount','USA, 986745 CF, Para Str',1); 62 | 63 | INSERT INTO STUDIO (NAME, ADDRESS, PRESC) 64 | VALUES ('USA Entertainm.','USA, 213243 CF, Uni Str',3); 65 | 66 | INSERT INTO STUDIO (NAME, ADDRESS, PRESC) 67 | VALUES ('Warner Bros','USA, 324235 NY, Bacon Str',2); 68 | 69 | INSERT INTO MOVIEEXEC (NAME, ADDRESS, CERT, NETWORTH) 70 | VALUES ('George Lucas', 'Oak Rd.', 555, 200000000); 71 | 72 | INSERT INTO MOVIEEXEC (NAME, ADDRESS, CERT, NETWORTH) 73 | VALUES ('Ted Turner', 'Turner Av.', 333, 125000000); 74 | 75 | INSERT INTO MOVIEEXEC (NAME, ADDRESS, CERT, NETWORTH) 76 | VALUES ('Stephen Spielberg', '123 ET road', 222, 100000000); 77 | 78 | INSERT INTO MOVIEEXEC (NAME, ADDRESS, CERT, NETWORTH) 79 | VALUES ('Merv Griffin', 'Riot Rd.', 199, 112000000); 80 | 81 | INSERT INTO MOVIEEXEC (NAME, ADDRESS, CERT, NETWORTH) 82 | VALUES ('Calvin Coolidge', 'Fast Lane', 123, 20000000); 83 | 84 | INSERT INTO MOVIESTAR 85 | VALUES ('Jane Fonda', 'Turner Av.', 'F', '1977-07-07'); 86 | 87 | INSERT INTO MOVIESTAR 88 | VALUES ('Alec Baldwin', 'Baldwin Av.', 'M', '1977-07-06'); 89 | 90 | INSERT INTO MOVIESTAR 91 | VALUES ('Kim Basinger', 'Baldwin Av.', 'F', '1979-07-05'); 92 | 93 | INSERT INTO MOVIESTAR 94 | VALUES ('Harrison Ford', 'Prefect Rd.', 'M', '1955-05-05'); 95 | 96 | INSERT INTO MOVIESTAR 97 | VALUES ('Debra Winger', 'A way', 'F', '1978-06-05'); 98 | 99 | INSERT INTO MOVIESTAR 100 | VALUES ('Jack Nicholson', 'X path', 'M', '1949-05-05'); 101 | 102 | INSERT INTO MOVIE 103 | VALUES ('Pretty Woman', 1990, 119, 'y', 'Disney', 199); 104 | 105 | INSERT INTO MOVIE 106 | VALUES ('The Man Who Wasn''t There', 2001, 116, 'N', 'USA Entertainm.', 107 | 555); 108 | 109 | INSERT INTO MOVIE 110 | VALUES ('Logan''s run', 1976, NULL, 'Y', 'Fox', 333); 111 | 112 | INSERT INTO MOVIE 113 | VALUES ('Star Wars', 1977, 124, 'Y', 'Fox', 555); 114 | 115 | INSERT INTO MOVIE 116 | VALUES ('Empire Strikes Back', 1980, 111, 'Y', 'Fox', 555); 117 | 118 | INSERT INTO MOVIE 119 | VALUES ('Star Trek', 1979, 132, 'Y', 'Paramount', 222); 120 | 121 | INSERT INTO MOVIE 122 | VALUES ('Star Trek: Nemesis', 2002, 116, 'Y', 'Paramount', 123); 123 | 124 | INSERT INTO MOVIE 125 | VALUES ('Terms of Endearment', 1983, 132, 'Y', 'MGM', 123); 126 | 127 | INSERT INTO MOVIE 128 | VALUES ('The Usual Suspects', 1995, 106, 'Y', 'MGM', 199); 129 | 130 | INSERT INTO MOVIE 131 | VALUES ('Gone With the Wind', 1938, 238, 'Y', 'MGM', 123); 132 | 133 | INSERT INTO STARSIN 134 | VALUES ('Star Wars', 1977, 'Kim Basinger'); 135 | 136 | INSERT INTO STARSIN 137 | VALUES ('Star Wars', 1977, 'Alec Baldwin'); 138 | 139 | INSERT INTO STARSIN 140 | VALUES ('Star Wars', 1977, 'Harrison Ford'); 141 | 142 | INSERT INTO STARSIN 143 | VALUES ('Empire Strikes Back', 1980, 'Harrison Ford'); 144 | 145 | INSERT INTO STARSIN 146 | VALUES ('The Usual Suspects', 1995, 'Jack Nicholson'); 147 | 148 | INSERT INTO STARSIN 149 | VALUES ('Terms of Endearment', 1983, 'Jane Fonda'); 150 | 151 | INSERT INTO STARSIN 152 | VALUES ('Terms of Endearment', 1983, 'Jack Nicholson'); 153 | -------------------------------------------------------------------------------- /week09/01.SQL-Intro/movies_schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week09/01.SQL-Intro/movies_schema.png -------------------------------------------------------------------------------- /week09/02.SQL-Subqueries-JOINs-Groups/README.md: -------------------------------------------------------------------------------- 1 | # SQL - Subqueries, JOINs, Groups 2 | 3 | Presentation: https://slides.com/hackbulgaria/sql-queries-joins-groups/ 4 | 5 | 6 | ## Useful links 7 | 8 | ### JOINS 9 | 10 | ![joins reminder image](joins_reminder.jpg) 11 | 12 | 13 | - http://www.sql-join.com 14 | - INNER vs OUTER JOIN - https://www.diffen.com/difference/Inner_Join_vs_Outer_Join 15 | 16 | ### GROUP BY & aggregations 17 | 18 | - https://www.w3schools.com/sql/sql_groupby.asp 19 | - https://www.youtube.com/watch?v=OM9ux-xiriU 20 | 21 | ## TOOLING 22 | 23 | - Sqlite3 - https://linuxhint.com/install_sqlite_browser_ubuntu_1804/ 24 | - Nice wrapper with autocomplete3 - https://litecli.com/ 25 | -------------------------------------------------------------------------------- /week09/02.SQL-Subqueries-JOINs-Groups/joins_reminder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week09/02.SQL-Subqueries-JOINs-Groups/joins_reminder.jpg -------------------------------------------------------------------------------- /week09/02.SQL-Subqueries-JOINs-Groups/tasks/01.PC/README.md: -------------------------------------------------------------------------------- 1 | ## За базата от данни PC 2 | ![Schema](pc_schema.png) 3 | 4 | 5 | • Напишете заявка, която извежда средната скорост на компютрите 6 | 7 | • Напишете заявка, която извежда средния размер на екраните на лаптопите за 8 | всеки производител. 9 | 10 | • Напишете заявка, която извежда средната скорост на лаптопите с цена над 1000. 11 | 12 | • Напишете заявка, която извежда средната цена на компютрите според различните им hd. 13 | 14 | • Напишете заявка, която извежда средната цена на компютрите за всяка скорост по-голяма от 500. 15 | 16 | • Напишете заявка, която извежда средната цена на компютрите произведени от производител ‘A’. 17 | 18 | • Напишете заявка, която извежда средната цена на компютрите и лаптопите за производител ‘B’ 19 | 20 | • Напишете заявка, която извежда производителите, които са произвели поне по 3 различни модела компютъра. Помислете каква заявка можете да напишете за да сте сигурни в отговора, например да изведете за всеки производител, броя различни модели компютри. 21 | 22 | • Напишете заявка, която извежда производителите на компютрите с най-висока цена. 23 | 24 | •Напишете заявка, която извежда средния размер на диска на тези компютри произведени от производители, които произвеждат и принтери. 25 | -------------------------------------------------------------------------------- /week09/02.SQL-Subqueries-JOINs-Groups/tasks/01.PC/pc.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week09/02.SQL-Subqueries-JOINs-Groups/tasks/01.PC/pc.db -------------------------------------------------------------------------------- /week09/02.SQL-Subqueries-JOINs-Groups/tasks/01.PC/pc_schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week09/02.SQL-Subqueries-JOINs-Groups/tasks/01.PC/pc_schema.png -------------------------------------------------------------------------------- /week09/02.SQL-Subqueries-JOINs-Groups/tasks/02.Ships/README.md: -------------------------------------------------------------------------------- 1 | ## За базата от данни SHIPS 2 | ![Schema](ships_schema.png) 3 | 4 | •Напишете заявка, която за всеки кораб извежда името му, държавата, броя 5 | оръдия и годината на пускане (launched). 6 | 7 | •Повторете горната заявка като този път включите в резултата и класовете, които 8 | нямат кораби, но съществуват кораби със същото име като тяхното. 9 | 10 | • Напишете заявка, която извежда имената на корабите, участвали в битка от 1942г. 11 | 12 | • За всяка страна изведете имената на корабите, които никога не са участвали в битка. 13 | -------------------------------------------------------------------------------- /week09/02.SQL-Subqueries-JOINs-Groups/tasks/02.Ships/ships.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week09/02.SQL-Subqueries-JOINs-Groups/tasks/02.Ships/ships.db -------------------------------------------------------------------------------- /week09/02.SQL-Subqueries-JOINs-Groups/tasks/02.Ships/ships_schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week09/02.SQL-Subqueries-JOINs-Groups/tasks/02.Ships/ships_schema.png -------------------------------------------------------------------------------- /week09/03.SQL-and-Python/README.md: -------------------------------------------------------------------------------- 1 | # SQL - Subqueries, JOINs, Groups 2 | 3 | Presentation: https://slides.com/hackbulgaria/sql-python-security-issues#/ 4 | -------------------------------------------------------------------------------- /week09/03.SQL-and-Python/demo/hash.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | 4 | def make_it_secret_md5(password, count): 5 | for i in range(count): 6 | password = hashlib.md5(password.encode()).hexdigest() 7 | 8 | return password 9 | 10 | 11 | 12 | def make_it_secret_sha512(password, count): 13 | for i in range(count): 14 | password = hashlib.sha512(password.encode()).hexdigest() 15 | 16 | return password 17 | -------------------------------------------------------------------------------- /week09/03.SQL-and-Python/demo/register_form.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | 4 | def create_user(*, username, password): 5 | connection = sqlite3.connect('users_with_plain_passwords.db') 6 | cursor = connection.cursor() 7 | insert_query = f''' 8 | INSERT INTO users (username, password) 9 | VALUES ( ? , ? ) 10 | ''' 11 | cursor.execute(insert_query, (username, password)) 12 | connection.commit() 13 | connection.close() 14 | 15 | 16 | def main(): 17 | while True: 18 | username = input('Username: ') 19 | password = input('Password: ') 20 | 21 | create_user(username=username, password=password) 22 | 23 | print('User registered successfully!!') 24 | 25 | 26 | if __name__ == '__main__': 27 | main() 28 | -------------------------------------------------------------------------------- /week09/03.SQL-and-Python/demo/setup_users_database_with_hashed_passwords.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import hashlib 3 | 4 | 5 | def make_it_secret(password): 6 | return hashlib.sha512(password.encode()).hexdigest() 7 | 8 | 9 | def create_users_table(): 10 | connection = sqlite3.connect('users_with_hashed_passwords.db') 11 | cursor = connection.cursor() 12 | query = ''' 13 | CREATE TABLE IF NOT EXISTS users ( 14 | id INTEGER PRIMARY KEY AUTOINCREMENT, 15 | username VARCHAR(50), 16 | password VARCHAR(100), 17 | plain_text_password VARCHAR(100) 18 | ) 19 | ''' 20 | cursor.execute(query) 21 | connection.commit() 22 | connection.close() 23 | 24 | 25 | def populate_users_table(): 26 | users_data = [] 27 | 28 | for i in range(100): 29 | plain_password = f'password_{i}' 30 | password = make_it_secret(plain_password) 31 | users_data.append( 32 | f'("user_{i}", "{password}", "{plain_password}")' 33 | ) 34 | 35 | for i in range(100, 130): 36 | plain_password = 'qwerty' 37 | password = make_it_secret(plain_password) 38 | users_data.append( 39 | f'("user_{i}", "{password}", "{plain_password}")' 40 | ) 41 | 42 | connection = sqlite3.connect('users_with_hashed_passwords.db') 43 | cursor = connection.cursor() 44 | query = f''' 45 | INSERT INTO users (username, password, plain_text_password) 46 | VALUES {','.join(users_data)} 47 | ''' 48 | cursor.execute(query) 49 | connection.commit() 50 | connection.close() 51 | 52 | 53 | 54 | def main(): 55 | create_users_table() 56 | populate_users_table() 57 | 58 | 59 | if __name__ == '__main__': 60 | main() 61 | -------------------------------------------------------------------------------- /week09/03.SQL-and-Python/demo/setup_users_database_with_plain_text_passwords.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | 4 | def create_users_table(): 5 | connection = sqlite3.connect('users_with_plain_passwords.db') 6 | cursor = connection.cursor() 7 | query = ''' 8 | CREATE TABLE IF NOT EXISTS users ( 9 | id INTEGER PRIMARY KEY AUTOINCREMENT, 10 | username VARCHAR(50), 11 | password VARCHAR(100) 12 | ) 13 | ''' 14 | cursor.execute(query) 15 | connection.commit() 16 | connection.close() 17 | 18 | 19 | def populate_users_table(): 20 | users_data = [] 21 | 22 | for i in range(100): 23 | users_data.append( 24 | f'("user_{i}", "password_{i}")' 25 | ) 26 | 27 | connection = sqlite3.connect('users_with_plain_passwords.db') 28 | cursor = connection.cursor() 29 | query = f''' 30 | INSERT INTO users (username, password) 31 | VALUES {','.join(users_data)} 32 | ''' 33 | cursor.execute(query) 34 | connection.commit() 35 | connection.close() 36 | 37 | 38 | def main(): 39 | create_users_table() 40 | populate_users_table() 41 | 42 | 43 | if __name__ == '__main__': 44 | main() 45 | -------------------------------------------------------------------------------- /week09/03.SQL-and-Python/tasks/01.Business-Card-Catalog/README.md: -------------------------------------------------------------------------------- 1 | # Business Card Catalog 2 | 3 | 4 | Your task is to create a Business Card Catalog 5 | 6 | You need to create one table: `User` with the following properties: 7 | 8 | - `id` - primary key (unique, not nullable, with autoincrement...) 9 | - `full_name` - unique string (not nullable) 10 | - `email` - unique string (not nullable) 11 | - `age` - integer (not nullable) 12 | - `phone` - string (not nullable) 13 | - `additional_info` - text 14 | 15 | 16 | ## Requirements 17 | 18 | ### 1. Supported actions 19 | 20 | - `add` - to insert new business card 21 | - `list` - to list all business cards 22 | - `delete` - to delete a certain business card 23 | - `get` - display full information for a certain business card 24 | - `help` - to list all available options 25 | 26 | 27 | ### 2. Interface 28 | 29 | ``` 30 | $ python main.py 31 | 32 | Hello! This is your business card catalog. What would you like? (enter "help" to list all available options) 33 | >> Enter command: help 34 | ############# 35 | ###Options### 36 | ############# 37 | 38 | 1. `add` - insert new business card 39 | 2. `list` - list all business cards 40 | 3. `delete` - delete a certain business card (`ID` is required) 41 | 4. `get` - display full information for a certain business card (`ID` is required) 42 | 5. `help` - list all available options 43 | ``` 44 | 45 | #### 2.1 `add` 46 | 47 | ``` 48 | >>> Enter command: add 49 | Enter user name: ... # type here 50 | Enter email: ... # type here 51 | Enter age: ... # type here 52 | Enter phone: ... # type here 53 | Enter addional info (optional): ... # type here 54 | ``` 55 | 56 | #### 2.2 `list` 57 | 58 | ``` 59 | >>> Enter command: list 60 | 61 | ############# 62 | ###Contacts### 63 | ############# 64 | 65 | 1. ID: 1, Email: i.donchev@hacksoft.io, Full name: Ivaylo Donchev 66 | 67 | 2. ID: 2, Email: rositsa@hacksoft.io, Full name: Rositsa Zlateva 68 | ``` 69 | 70 | 71 | #### 2.3 `get` 72 | 73 | ``` 74 | >>> Enter command: get 75 | 76 | Enter id: ... # type here 77 | 78 | Contact info: 79 | 80 | ############### 81 | Id: 1, 82 | Full name: Ivaylo Donchev 83 | Email: i.donchev@hacksoft.io 84 | Age: 23 85 | Phone: 088...... 86 | Additional info: ... 87 | ############## 88 | ``` 89 | 90 | 91 | #### 2.4 `delete` 92 | 93 | ``` 94 | >>> Enter command: delete 95 | 96 | Enter id: ... # type here 97 | 98 | Following contact is deleted successfully: 99 | 100 | ############### 101 | Id: 1, 102 | Full name: Ivaylo Donchev 103 | Email: i.donchev@hacksoft.io 104 | Age: 23 105 | Phone: 088...... 106 | Additional info: ... 107 | ############## 108 | ``` 109 | -------------------------------------------------------------------------------- /week09/03.SQL-and-Python/tasks/02.Vehicle-Repair-Manager/README.md: -------------------------------------------------------------------------------- 1 | # Vehicle Repair Manager 2 | 3 | Your task is to create a Vehicle Management System. 4 | For this application you need to create `vehicle_management.db` and several tables. 5 | 6 | - BaseUser 7 | - Client 8 | - Mechanic 9 | - Vehicle 10 | - Service 11 | - Mechanic_services 12 | - Vehicle_repair 13 | 14 | Take a look at the database schema to understand the structure and recreate it locally on your machines. 15 | 16 | ### Example GUI for Customer 17 | 18 | ```python 19 | $ python vehicle_management.py 20 | Hello! 21 | Provide user name: 22 | >>> Roza 23 | Unknown user! 24 | Would you like to create new user? 25 | >>> yes 26 | Are you a client or Mechanic? 27 | >>> Client 28 | Provide user_name: 29 | >>> Roza 30 | Provide phone_number: 31 | >>> 0888 88 88 88 32 | Provide email: 33 | >>> roza@roza.com 34 | Provide address: 35 | >>> Sofia, Hack Bulgaria 36 | 37 | Thank you, Roza! 38 | Welcome to Vehicle Services! 39 | Next time you try to login, provide your user_name! 40 | 41 | You can choose from the following commands: 42 | list_all_free_hours 43 | list_free_hours 44 | save_repair_hour 45 | update_repair_hour 46 | delete_repair_hour 47 | add_vehicle 48 | update_vehicle 49 | delete_vehicle 50 | exit 51 | ``` 52 | 53 | ### Example GUI for Customer 54 | 55 | ```python 56 | $ python vehicle_management.py 57 | Hello! 58 | Provide user name: 59 | >>> Roza 60 | 61 | Hello, Roza! 62 | You can choose from the following commands: 63 | list_all_free_hours 64 | list_free_hours 65 | save_repair_hour 66 | update_repair_hour 67 | delete_repair_hour 68 | add_vehicle 69 | update_vehicle 70 | delete_vehicle 71 | exit 72 | 73 | command> :list_all_free_hours 74 | +----+-------------+-------+ 75 | | id | date | start_hour | 76 | +----+---------------------+ 77 | | 1 | 24-05-2018 | 10:00 | 78 | | 2 | 24-05-2018 | 10:40 | 79 | | 3 | 25-05-2018 | 16:00 | 80 | | 4 | 27-05-2018 | 11:20 | 81 | +----+-------------+-------+ 82 | 83 | Hello, Roza! 84 | You can choose from the following commands: 85 | list_all_free_hours 86 | list_free_hours 87 | save_repair_hour 88 | update_repair_hour 89 | delete_repair_hour 90 | list_personal_vehicles 91 | add_vehicle 92 | update_vehicle 93 | delete_vehicle 94 | exit 95 | 96 | command> :add_vehicle 97 | Vehicle category: 98 | >>> Automobile 99 | Vehicle make: 100 | >>> Audi 101 | Vehicle model: 102 | >>> A3 103 | Vehicle register number: 104 | >>> X 8888 XX 105 | Vehicle gear box: 106 | >>> Manual 107 | 108 | Thank you! You added new personal vehicle! 109 | 110 | Hello, Roza! 111 | You can choose from the following commands: 112 | list_all_free_hours 113 | list_free_hours 114 | save_repair_hour 115 | update_repair_hour 116 | delete_repair_hour 117 | list_personal_vehicles 118 | add_vehicle 119 | update_vehicle 120 | delete_vehicle 121 | exit 122 | 123 | command> :save_repair_hour 1 124 | Choose Vehicle to repair: 125 | +----+------------------------------------+ 126 | | id | Vehicle | 127 | +----+------------------------------------+ 128 | | 1 | Audi A3 with RegNumber: X 8888 XX | 129 | +----+------------------------------------+ 130 | >>> 1 131 | Choose Service: 132 | +----+------------------------------------+ 133 | | id | Service | 134 | +----+------------------------------------+ 135 | | 1 | Oil Change | 136 | | 2 | Tire Change | 137 | +----+------------------------------------+ 138 | >>> 2 139 | 140 | Thank you! You saved an hour on 24-05-2018 at 10:00 for Tire Change! 141 | Vehicle: Audi A3 with RegNumber: X 8888 XX 142 | ``` 143 | 144 | ### Example GUI for Mechanic 145 | 146 | ```python 147 | $ python vehicle_management.py 148 | Hello! 149 | Provide user name: 150 | >>> Mechanic Panda 151 | 152 | Hello, Mechanic Panda! 153 | You can choose from the following commands: 154 | list_all_free_hours 155 | list_free_hours 156 | list_all_busy_hours 157 | list_busy_hours 158 | add_new_repair_hour 159 | add_new_service 160 | update_repair_hour 161 | exit 162 | 163 | command> :update_repair_hour 4 164 | 165 | On 23-05-2018 at 10:30: 166 | Client: Carol Danvers 167 | Vehicle: Audi A6 Saloon 168 | Current Bill: 20$ 169 | Choose one of the following: 170 | 1 - change start hour 171 | 2 - change bill 172 | 3 - return to main menu 173 | 174 | >>> 2 175 | Current Bill is: 20$ 176 | New Bill: 177 | >>> 27.5$ 178 | 179 | On 23-05-2018 at 10:30: 180 | Client: Carol Danvers 181 | Vehicle: Audi A6 Saloon 182 | Current Bill: 27.5$ 183 | Choose one of the following: 184 | 1 - change start hour 185 | 2 - change bill 186 | 3 - return to main menu 187 | 188 | >>> 3 189 | 190 | Hello, Mechanic Panda! 191 | You can choose from the following commands: 192 | list_all_free_hours 193 | list_free_hours 194 | list_all_busy_hours 195 | list_busy_hours 196 | add_new_repair_hour 197 | add_new_service 198 | update_repair_hour 199 | exit 200 | 201 | command> :add_new_repair_hour 202 | Repair hour date: 203 | >>> 26-05-2018 204 | Start Hour: 205 | >>> 13:20 206 | 207 | Your created new repair_hour! 208 | +----+-------------+-------+ 209 | | id | date | start_hour | 210 | +----+---------------------+ 211 | | 1 | 24-05-2018 | 10:00 | 212 | | 2 | 24-05-2018 | 10:40 | 213 | | 3 | 25-05-2018 | 16:00 | 214 | | 5 | 26-05-2018 | 13:20 | 215 | | 4 | 27-05-2018 | 11:20 | 216 | +----+-------------+-------+ 217 | 218 | Hello, Mechanic Panda! 219 | You can choose from the following commands: 220 | list_all_free_hours 221 | list_free_hours 222 | list_all_busy_hours 223 | list_busy_hours 224 | add_new_repair_hour 225 | add_new_service 226 | update_repair_hour 227 | exit 228 | 229 | command> :add_new_service 230 | Provide New service name: 231 | >>> Oil Change 232 | ``` 233 | -------------------------------------------------------------------------------- /week09/03.SQL-and-Python/tasks/02.Vehicle-Repair-Manager/vehicle_repair_management_db_schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week09/03.SQL-and-Python/tasks/02.Vehicle-Repair-Manager/vehicle_repair_management_db_schema.png -------------------------------------------------------------------------------- /week09/03.SQL-and-Python/tasks/03.Break-the-Cipher/README.md: -------------------------------------------------------------------------------- 1 | # Break the Cipher (bonus task) 2 | 3 | 4 | The `secret_keeper.py` file has 1 public function - `make_it_secret('publicmessage')` 5 | It accepts a public message and gives a secret one. 6 | 7 | So in order to decrypt some message you need to generate all combinations and encrypt them with this function and then compare the encrypted values. If they are equal you've successfully decrypted the message. 8 | 9 | Example: 10 | 11 | ``` 12 | >>> make_it_secret('publicword') 13 | '0da537d4cad7299cbbd9905932463537' 14 | 15 | # Our encrypted message is not '0da537d4cad7299cbbd9905932463537' 16 | 17 | >>> make_it_secret('not_successful_attempt') == '0da537d4cad7299cbbd9905932463537' 18 | False 19 | 20 | 21 | >>> make_it_secret('publicword') == '0da537d4cad7299cbbd9905932463537' 22 | True 23 | 24 | # You decrypted the message 25 | ``` 26 | 27 | ## Task 28 | 29 | Your task is simple - *decrypt the following message*: 30 | 31 | ``` 32 | f0aa25cd443edb4cacf75bb24c5ad303 e641386ea86d75a37a79d7a8ca142103 e902668782c8ff35c741a60abb2ee751 b88cb6ce3803814cbe6b4f621210693c 33 | ef9fcdb53e4e10b12bfcd5e9e78135dc cae8d14edd025e72c59dbab6f378c95a 60b36cd3c72aa7c0ddbc69436d7eca96 8fc42c6ddf9966db3b09e84365034357 34 | 74459ca3cf85a81df90da95ff6e7a207 2cb9df9898e55fd0ad829dc202ddbd1c c13367945d5d4c91047b3b50234aa7ab be5d5d37542d75f93a87094459f76678 35 | caf98268abd13bb8ed384da0313e2dd6 8534a9e46af4ac17812152f8b21e3ffd 0cc175b9c0f1b6a831c399e269772661 23678db5efde9ab46bce8c23a6d91b50 36 | 1822266030c65a00f35ba3836cd61158 aab9e1de16f38176f86d7a92ba337a8d ebdacc3da0a7c89897c9433855c6cd1d 4d643b1bd384922ca968749b93b81db5 37 | a2a551a6458a8de22446cc76d639a9e9 1ced92c2ce3df0ae7a634022e7455469 cc5c0452c1308f585de22b4afc7723f7 6e57d6c47d23024e41f4a1aac73a3ea9 38 | a170c4cb1ae103745ec02e015cb86727 dd7536794b63bf90eccfd37f9b147d7f 92159805cf28ee78e13c41ebbbb1aeb4 639bae9ac6b3e1a84cebb7b403297b79 39 | 0cc175b9c0f1b6a831c399e269772661 efa35b82d8622819a31a8bc9208a076f 9942d8472ee432bb4179199f5beae701 9e925e9341b490bfd3b4c4ca3b0c1ef2 40 | cebd65946444e5cd3e861a2dbf69e221 030c76e4f0ed31202379e6df29def1d6 326577fbe6d73973bd67437829bf9301 bb90e57a80b6817ef63ea3b42f948a0a 41 | f9da66cdef69f6ffdc642a29a29eae93 18218139eec55d83cf82679934e5cd75 7f021a1415b86f2d013b2618fb31ae53 46c48bec0d282018b9d167eef7711b2c 42 | b2700999997b51194434005c3d95e619 9f31730024c592d8aa6c3e567e9895dd c67fd61e3b7d35f07e3ca92c8a84a5ab be5d5d37542d75f93a87094459f76678 43 | 99be1ee67a0137092d3d112c0620c552 9dfc8dce7280fd49fc6e7bf0436ed325 e7cbf67460e47dea4b13e81304850d5f 44 | ``` 45 | 46 | ## Limitations: 47 | 48 | 1. The maximum length of each word is **5 symbols**. 49 | 2. Allowed characters 50 | - lowercased letters - `a-z` 51 | - uppercased letters - `A-Z` 52 | - digits - `0-9` 53 | - only following special charachters - `!`, `.`, `<` 54 | 55 | 56 | ## Requirements: 57 | 58 | 1. Create a database table `CipherBreaker` with the following columns: 59 | 60 | - `id` - primary key, not null, unique, auto increment 61 | - `message` - text (this is the public message) 62 | - `encrypted_message` - text (this is the public message **after** you call `make_it_secret`) 63 | 64 | 2. After you try a new string you should store it into the `CipherBreaker` table 65 | 66 | 3. When you try to decrypt a new value (like '0da537d4cad7299cbbd9905932463537') you should first check in `CipherBreaker` if you've already decrypted it ;). That's why you need **step 2** 67 | 68 | 4. If you stop the program while running(kill the process) and then run it again it should continue decrypting from the same place (and not from the beginning) 69 | 70 | 71 | **Here's the deal:** If you send the decrypted message with a working solution(see the requirements), you'll get a beer at the end of the course. Deal ? 72 | 73 | NOTE: The encryption will take some time. If you make a working solution (if it starts decrypting the message and meets the requirements), you'll get a beer anyway :) 74 | 75 | Additional NOTE: Due the Covid-19 pandemic, you'll get the beer as we meet at the end of the course (hopefully) :( 76 | -------------------------------------------------------------------------------- /week09/03.SQL-and-Python/tasks/03.Break-the-Cipher/secret_keeper.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | 4 | def make_it_secret(message): 5 | return hashlib.md5(message.encode()).hexdigest() 6 | -------------------------------------------------------------------------------- /week10/CinemaReservation/README.md: -------------------------------------------------------------------------------- 1 | # Hack Bulgaria Cinema Reservation System 2 | 3 | We are going to the cinema! But the reservation systems are down and the cinema officials don't let people enter without reservations. So we offered to make them a new reservation system that will allow us to go and watch the newest movies. 4 | 5 | ## Problem 0 - The database 6 | 7 | No complex stuff here. Just a few simple tables: 8 | 9 | ### Movies 10 | 11 | | id | name | rating | 12 | | --- | :------------------------------ | :----: | 13 | | 1 | The Hunger Games: Catching Fire | 7.9 | 14 | | 2 | Wreck-It Ralph | 7.8 | 15 | | 3 | Her | 8.3 | 16 | 17 | ### Projections 18 | 19 | | id | movie_id | type | date | time | 20 | | --- | -------- | :--: | :--------: | :---: | 21 | | 1 | 1 | 3D | 2020-04-01 | 19:10 | 22 | | 2 | 1 | 2D | 2020-04-01 | 19:00 | 23 | | 3 | 1 | 4DX | 2020-04-02 | 21:00 | 24 | | 4 | 3 | 2D | 2020-04-05 | 20:20 | 25 | | 5 | 2 | 3D | 2020-04-02 | 22:00 | 26 | | 6 | 2 | 2D | 2020-04-02 | 19:30 | 27 | 28 | ### Users 29 | 30 | | id | username | password | 31 | | --- | ----------------- | :----------: | 32 | | 1 | Martin Angelov | **\*\*\*\*** | 33 | | 2 | Ivo Donchev | **\*\*\*\*** | 34 | | 3 | Radoslav Georgiev | **\*\*\*\*** | 35 | | 4 | Rositza Zlateva | **\*\*\*\*** | 36 | 37 | Passwords should be with length **at least 8 symbols, 1 capital letter and a special symbol**. Also, don't forget to **hash** the passwords! 38 | 39 | ### Reservations 40 | 41 | | id | user_id | projection_id | row | col | 42 | | --- | ------- | ------------- | :-: | :-: | 43 | | 1 | 3 | 1 | 2 | 1 | 44 | | 2 | 3 | 1 | 3 | 5 | 45 | | 3 | 3 | 1 | 7 | 8 | 46 | | 4 | 2 | 3 | 1 | 1 | 47 | | 5 | 2 | 3 | 1 | 2 | 48 | | 6 | 5 | 5 | 2 | 3 | 49 | | 7 | 6 | 5 | 2 | 4 | 50 | 51 | ### Things to note 52 | 53 | - For each projection we assume the hall to be a 10x10 matrix. 54 | - All data presented here is just an example. If you want, you can make up your own data. For example, implement a ticketing system after you have the reservations in place. 55 | 56 | ## Problem 1 - The CLI (Command-Line Interface) 57 | 58 | Our cinema reservation system will be very simple. We will use the console for user interactions: 59 | 60 | - `show movies` - print all movies ORDERed BY rating 61 | - `show movie projections []` - print all projections of a given movie for the given date (date is optional). 62 | 63 | 1. ORDER the results BY date 64 | 2. For each projection, show the total number of spots available. 65 | 66 | - `make reservation` - It's showtime! 67 | 68 | 1. Check if user is logged 69 | 2. If not, first action is to log the user into the system. 70 | 3. If already logged, the user can make a reservation 71 | 4. Make him choose how many seats he want to reserve 72 | 5. Call `show movies` and make the user choose a movie by id 73 | 6. Call `show movie projections` for the chosen `` and make the user choose a projection 74 | 75 | - _If the available spots for a projection are less than the number of reservations needed, print an appropriate message and stay at step 6_; 76 | 77 | 7. Show all available seats 78 | 8. Make the user choose his seats - validate the input 79 | 9. Format the reservation appropriately and show it to the user 80 | 10. On `finalize`, save all the info and wish a happy cinema! 81 | 11. **At each step, allow for `cancel` option.** 82 | 83 | - On `cancel reservation ` - disintegrate given person's reservation (**NOTE**: reservations cannot be so easily removed) 84 | - On `exit` - close Pandora's Box before it's too late. 85 | - On `help` - show a list of valid options 86 | 87 | ### Things to note 88 | 89 | - Reuse code and write tests 90 | - Try not to build everything in one place. 91 | - Make use of the following techniques : **OOP, TDD, SQL, MVC**. 92 | 93 | ## Problem 2 - Decorators 94 | 95 | We want you to implement a decorator for atomic transactions on each of the query you execute with the cinema application! 96 | 97 | ```python 98 | @atomic 99 | def show_movies(): 100 | pass 101 | ``` 102 | 103 | We want to make sure the user is logged in in order to use the system. 104 | 105 | ```python 106 | @login_required 107 | def show_movies(): 108 | pass 109 | ``` 110 | 111 | Log each reservation in the system in a file. 112 | 113 | ```python 114 | 115 | @log_info 116 | def finalize(): 117 | pass 118 | ``` 119 | 120 | ## Examples 121 | 122 | ### Show movies 123 | 124 | ``` 125 | > show movies 126 | Current movies: 127 | [1] - The Hunger Games: Catching Fire (7.9) 128 | [2] - Wreck-It Ralph (7.8) 129 | [3] - Her (8.3) 130 | ``` 131 | 132 | ### Show movie projections 133 | 134 | ``` 135 | > show movie projections 2 136 | Projections for movie 'Wreck-It Ralph': 137 | [5] - 2020-04-02 19:30 (2D) 138 | [6] - 2020-04-02 22:00 (3D) 139 | > show movie projections 1 2020-04-01 140 | Projections for movie 'The Hunger Games: Catching Fire' on date 2020-04-01: 141 | [1] - 19:00 (3D) 142 | [2] - 19:10 (2D) 143 | ``` 144 | 145 | ### Make a reservation 146 | 147 | ``` 148 | > make reservation 149 | You need to a user in the system to make reservations! 150 | Username: Rositsa Zlateva 151 | Password: 152 | Hello, Rositsa Zlateva 153 | ............ 154 | ``` 155 | 156 | ### Make a reservation 157 | 158 | ``` 159 | > make reservation 160 | Hello, Rositsa Zlateva 161 | Step 1 (User): Choose number of tickets> 2 162 | Current movies: 163 | [1] - The Hunger Games: Catching Fire (7.9) 164 | [2] - Wreck-It Ralph (7.8) 165 | [3] - Her (8.3) 166 | Step 2 (Movie): Choose a movie> 2 167 | Projections for movie 'Wreck-It Ralph': 168 | [5] - 2020-04-02 19:30 (2D) - 98 spots available 169 | [6] - 2020-04-02 22:00 (3D) - 100 spots availabe 170 | Step 3 (Projection): Choose a projection> 5 171 | Available seats (marked with a dot): 172 | 1 2 3 4 5 6 7 8 9 10 173 | 1 . . . . . . . . . . 174 | 2 . . X X . . . . . . 175 | 3 . . . . . . . . . . 176 | 4 . . . . . . . . . . 177 | 5 . . . . . . . . . . 178 | 6 . . . . . . . . . . 179 | 7 . . . . . . . . . . 180 | 8 . . . . . . . . . . 181 | 9 . . . . . . . . . . 182 | 10 . . . . . . . . . . 183 | Step 4 (Seats): Choose seat 1> (2,3) 184 | This seat is already taken! 185 | Step 4 (Seats): Choose seat 1> (15, 16) 186 | Lol...NO! 187 | Step 4 (Seats): Choose seat 1> (7,8) 188 | Step 4 (Seats): Choose seat 2> (7,7) 189 | This is your reservation: 190 | Movie: Wreck-It Ralph (7.8) 191 | Date and Time: 2020-04-02 19:30 (2D) 192 | Seats: (7,7), (7.8) 193 | Step 5 (Confirm - type 'finalize') > finalize 194 | Thanks. 195 | ``` 196 | 197 | ## DISCLAIMER 198 | 199 | Customize it! Think of some add-ons and extras and add it to the task! 200 | -------------------------------------------------------------------------------- /week10/CinemaReservation/example/cinema.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week10/CinemaReservation/example/cinema.db -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week10/CinemaReservation/hack_cinema/__init__.py -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/cinema.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week10/CinemaReservation/hack_cinema/cinema.db -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | from .settings import DB_NAME 4 | 5 | 6 | class Database: 7 | def __init__(self): 8 | self.connection = sqlite3.connect(DB_NAME) 9 | self.cursor = self.connection.cursor() 10 | -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/db_schema/__init__.py: -------------------------------------------------------------------------------- 1 | from .users import CREATE_USERS 2 | from .movies import CREATE_MOVIES 3 | -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/db_schema/movies.py: -------------------------------------------------------------------------------- 1 | CREATE_MOVIES = ''' 2 | CREATE TABLE IF NOT EXISTS movies( 3 | id INTEGER PRIMARY KEY, 4 | title VARCHAR(30) NOT NULL, 5 | year INTEGER NOT NULL CHECK(year BETWEEN 2019 and 2022), 6 | rating REAL CHECK(rating BETWEEN 0 and 10), 7 | UNIQUE(title, year) 8 | ); 9 | ''' -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/db_schema/users.py: -------------------------------------------------------------------------------- 1 | # TODO: Hash the passwords 2 | CREATE_USERS = ''' 3 | CREATE TABLE IF NOT EXISTS users ( 4 | id integer PRIMARY KEY NOT NULL, 5 | email varchar(50) UNIQUE, 6 | password varchar(50) 7 | ); 8 | ''' 9 | -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/index_view.py: -------------------------------------------------------------------------------- 1 | from .users.views import UserViews 2 | 3 | 4 | def welcome(): 5 | print('Welcome to HackCinema!') 6 | command = int(input('Choose a command:\n 1 - log in\n 2 - sign up\n Input: ')) 7 | user_views = UserViews() 8 | 9 | if command == 1: 10 | return user_views.login() 11 | 12 | if command == 2: 13 | return user_views.signup() 14 | 15 | raise ValueError(f'Unknown command {command}.') 16 | -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/movies/controllers.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week10/CinemaReservation/hack_cinema/movies/controllers.py -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/movies/models.py: -------------------------------------------------------------------------------- 1 | class MovieModel: 2 | def __init__(self, *, id, title, year, rating): 3 | self.id = id 4 | self.title = title 5 | self.year = year 6 | self.rating = rating 7 | -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/movies/movies_gateway.py: -------------------------------------------------------------------------------- 1 | from hack_cinema.db import Database 2 | 3 | from .models import Movie 4 | from .queries import ( 5 | CREATE_MOVIE, 6 | GET_ALL_MOVIES, 7 | GET_MOVIE, 8 | DELETE_MOVIE 9 | ) 10 | 11 | 12 | class MovieGateway: 13 | def __init__(self): 14 | self.db = Database() 15 | self.model = Movie 16 | 17 | def create(self, title, year, rating): 18 | self.db.cursor.execute(CREATE_MOVIE, (title, year, rating)) 19 | 20 | movie_id = self.db.cursor.lastrowid 21 | return self.get(movie_id) 22 | 23 | def get(self, id): 24 | self.db.cursor.execute(GET_MOVIE, (id, )) 25 | movie_row = self.db.cursor.fetchone() 26 | self.db.connection.commit() 27 | 28 | if not movie_row: 29 | raise ValueError(f'Movie with {id} not found.') 30 | 31 | return self.model(*movie_row) 32 | 33 | def all(self): 34 | self.db.cursor.execute(GET_ALL_MOVIES) 35 | movie_rows = self.db.cursor.fetchall() 36 | 37 | self.db.connection.commit() 38 | 39 | return [self.model(*row) for row in movie_rows] 40 | 41 | def filter(self, **kwargs): 42 | # TODO: Build where clauses here 43 | pass 44 | 45 | def delete(self, id): 46 | movie = self.get(id) # make sure the movie exists before deleting it 47 | 48 | self.db.cursor.execute(DELETE_MOVIE, (id, )) 49 | self.db.connection.commit() 50 | 51 | return movie 52 | -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/movies/queries.py: -------------------------------------------------------------------------------- 1 | GET_ALL_MOVIES = 'SELECT * FROM movies' 2 | 3 | CREATE_MOVIE = ''' 4 | INSERT INTO movies (title, year, rating) 5 | VALUES (?, ?, ?); 6 | ''' 7 | 8 | GET_MOVIE = ''' 9 | SELECT * 10 | FROM movies 11 | WHERE id=? 12 | ''' 13 | 14 | DELETE_MOVIE = 'DELETE FROM movies WHERE id=?' 15 | -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/movies/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week10/CinemaReservation/hack_cinema/movies/views.py -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/settings.py: -------------------------------------------------------------------------------- 1 | DB_NAME = 'cinema.db' 2 | -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week10/CinemaReservation/hack_cinema/users/__init__.py -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/users/controllers.py: -------------------------------------------------------------------------------- 1 | from .users_gateway import UserGateway 2 | 3 | 4 | class UserContoller: 5 | def __init__(self): 6 | self.users_gateway = UserGateway() 7 | 8 | def create_user(self, email, password): 9 | user = self.users_gateway.create(email=email, password=password) 10 | 11 | return user 12 | 13 | def login_user(self, email, password): 14 | pass 15 | -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/users/models.py: -------------------------------------------------------------------------------- 1 | class UserModel: 2 | def __init__(self, *, id, email, password): 3 | self.id = id 4 | self.email = email 5 | self.password = password 6 | 7 | @staticmethod 8 | def validate(email, password): 9 | # TODO: Implement a validation -> Raise an error 10 | pass 11 | -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/users/users_gateway.py: -------------------------------------------------------------------------------- 1 | from ..db import Database 2 | from .models import UserModel 3 | 4 | 5 | class UserGateway: 6 | def __init__(self): 7 | self.model = UserModel 8 | self.db = Database() 9 | 10 | def create(self, *, email, password): 11 | self.model.validate(email, password) 12 | 13 | self.db.cursor.execute() # TODO: create user query 14 | 15 | # TODO: What whould I return? 16 | 17 | def all(self): 18 | raw_users = self.db.cursor.execute() # TODO: Select all users 19 | 20 | return [self.model(**row) for row in raw_users] 21 | -------------------------------------------------------------------------------- /week10/CinemaReservation/hack_cinema/users/views.py: -------------------------------------------------------------------------------- 1 | from .controllers import UserContoller 2 | 3 | 4 | class UserViews: 5 | def __init__(self): 6 | self.controller = UserContoller() 7 | 8 | def login(self): 9 | pass 10 | 11 | def signup(self): 12 | email = input('Email: ') 13 | password = input('Password: ') 14 | 15 | self.controller.create_user(email=email, password=password) 16 | -------------------------------------------------------------------------------- /week10/CinemaReservation/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from hack_cinema.db import Database 4 | from hack_cinema.db_schema import CREATE_USERS, CREATE_MOVIES 5 | 6 | from hack_cinema.index_view import welcome 7 | 8 | 9 | class Application: 10 | @classmethod 11 | def build(self): 12 | db = Database() 13 | db.cursor.execute(CREATE_USERS) 14 | db.cursor.execute(CREATE_MOVIES) 15 | # TODO: Build rest of the tables 16 | 17 | db.connection.commit() 18 | db.connection.close() 19 | 20 | print('Done.') 21 | 22 | @classmethod 23 | def seed(self): 24 | # TODO: Seed with inistial data - consider using another command for this 25 | pass 26 | 27 | @classmethod 28 | def start(self): 29 | welcome() 30 | 31 | 32 | if __name__ == '__main__': 33 | command = sys.argv[1] 34 | 35 | if command == 'build': 36 | Application.build() 37 | elif command == 'seed': 38 | Application.start() 39 | elif command == 'start': 40 | Application.start() 41 | else: 42 | raise ValueError(f'Unknown command {command}. Valid ones are "build", "seed" and "start"') 43 | -------------------------------------------------------------------------------- /week10/README.md: -------------------------------------------------------------------------------- 1 | ## MVC 2 | 3 | https://whimsical.com/N8yXvUnG6y5Bhgxs28mYDV 4 | -------------------------------------------------------------------------------- /week11/README.md: -------------------------------------------------------------------------------- 1 | # Week 11 2 | 3 | * [Python Metaprogramming](https://slides.com/hackbulgaria/python-metaprogramming-eba69f) 4 | -------------------------------------------------------------------------------- /week11/examples/anonymous_classes.py: -------------------------------------------------------------------------------- 1 | def create_mock_object(**kwargs): 2 | cls = type('', (object, ), {}) 3 | obj = cls() 4 | 5 | for key, value in kwargs.items(): 6 | setattr(obj, key, value) 7 | 8 | return obj 9 | -------------------------------------------------------------------------------- /week11/examples/base_tracking.py: -------------------------------------------------------------------------------- 1 | class basetracking(type): 2 | def __new__(cls, name, bases, clsdict): 3 | clsobj = super().__new__(cls, name, bases, clsdict) 4 | 5 | if not hasattr(cls, 'registry'): 6 | cls.registry = set() 7 | 8 | cls.registry.add(clsobj) 9 | 10 | return clsobj 11 | 12 | 13 | class Base(metaclass=basetracking): 14 | pass 15 | 16 | 17 | class A(Base): 18 | pass 19 | 20 | 21 | class B(Base): 22 | pass 23 | 24 | 25 | class C(A, B): 26 | pass 27 | 28 | print(Base.registry) 29 | -------------------------------------------------------------------------------- /week11/examples/class_deconstruction.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | 4 | body = ''' 5 | import abc 6 | 7 | def __init__(self, name): 8 | self.name = name 9 | print(collections) 10 | 11 | def be_panda(self): 12 | print('Bamboo & sleep') 13 | ''' 14 | 15 | clsname = 'Panda' 16 | bases = (object, ) 17 | 18 | clsdict = type.__prepare__(clsname, bases) 19 | 20 | exec(body, globals(), clsdict) 21 | 22 | Panda = type(clsname, bases, clsdict) 23 | 24 | p = Panda('Ivo') 25 | print(p.name) 26 | p.be_panda() 27 | -------------------------------------------------------------------------------- /week11/examples/class_decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | 4 | def debug(func): 5 | fname = func.__qualname__ 6 | 7 | @wraps(func) 8 | def wrapper(*arg, **kwargs): 9 | print('Calling {}'.format(fname)) 10 | 11 | return func(*arg, **kwargs) 12 | 13 | return wrapper 14 | 15 | 16 | def debugmethods(cls): 17 | for attr, value in vars(cls).items(): 18 | if callable(value): 19 | setattr(cls, attr, debug(value)) 20 | 21 | return cls 22 | 23 | 24 | @debugmethods 25 | class Panda: 26 | 27 | def be_panda(self): 28 | print('Being panda') 29 | 30 | def awesome(self): 31 | print('Pandas are awesome!') 32 | 33 | 34 | if __name__ == '__main__': 35 | p = Panda() 36 | p.be_panda() 37 | p.awesome() 38 | -------------------------------------------------------------------------------- /week11/examples/declarative.py: -------------------------------------------------------------------------------- 1 | class DeclarativeMeta(type): 2 | def __new__(cls, name, bases, clsdict): 3 | fields = {} 4 | 5 | for attr, value in clsdict.items(): 6 | if not callable(value) and not attr.startswith('__'): 7 | fields[attr] = value 8 | 9 | for attr, _ in fields.items(): 10 | clsdict.pop(attr) 11 | 12 | clsdict['_declared_fields'] = fields 13 | 14 | print(name, fields, clsdict, "=======================") 15 | clsobj = super().__new__(cls, name, bases, clsdict) 16 | 17 | return clsobj 18 | 19 | class Base(metaclass=DeclarativeMeta): 20 | def __init__(self): 21 | for attr, value in self._declared_fields.items(): 22 | setattr(self, attr, value) 23 | 24 | # def __getattr__(self, name): 25 | # if name in self._declared_fields: 26 | # return self._declared_fields[name] 27 | 28 | # return object.__getattribute__(self, name) 29 | 30 | 31 | class Panda(Base): 32 | a = 5 33 | b = 6 34 | 35 | 36 | p1 = Panda() 37 | p2 = Panda() 38 | 39 | print(p1.a) 40 | print(vars(p1)) 41 | print(p2.b) 42 | print(p1.panda) 43 | -------------------------------------------------------------------------------- /week11/examples/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | 4 | def debug(func): 5 | fname = func.__qualname__ 6 | 7 | @wraps(func) 8 | def wrapper(*arg, **kwargs): 9 | print('Calling {}'.format(fname)) 10 | 11 | return func(*arg, **kwargs) 12 | 13 | return wrapper 14 | 15 | 16 | def debugmethods(cls): 17 | for attr, value in vars(cls).items(): 18 | if callable(value): 19 | setattr(cls, attr, debug(value)) 20 | 21 | return cls 22 | 23 | @debugmethods 24 | class Panda: 25 | def be_panda(self): 26 | print('Being panda') 27 | 28 | def awesome(self): 29 | print('Pandas are awesome') 30 | 31 | 32 | p = Panda() 33 | p.be_panda() 34 | p.awesome() 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /week11/examples/ducktyping.py: -------------------------------------------------------------------------------- 1 | class Duck: 2 | def quack(self): 3 | print('Duck quacks!') 4 | 5 | 6 | class Panda: 7 | def quack(self): 8 | print('Panda quacks!') 9 | 10 | 11 | class Person: 12 | def __init__(self, name): 13 | self.quack = name 14 | 15 | 16 | def duck_duck_go(obj): 17 | if hasattr(obj, 'quack')\ 18 | and callable(getattr(obj, 'quack')): 19 | obj.quack() 20 | 21 | 22 | duck_duck_go(Duck()) 23 | duck_duck_go(Panda()) 24 | duck_duck_go(1) 25 | duck_duck_go(Person('Ivo')) 26 | -------------------------------------------------------------------------------- /week11/examples/eval_exec.py: -------------------------------------------------------------------------------- 1 | code = ''' 2 | for i in range(0, 3): 3 | print(i) 4 | ''' 5 | 6 | exec(code) 7 | 8 | expression = '1 + 2*(3**3)' 9 | print(eval(expression)) 10 | -------------------------------------------------------------------------------- /week11/examples/metaclass.py: -------------------------------------------------------------------------------- 1 | from class_decorators import debugmethods 2 | 3 | 4 | class mytype(type): 5 | def __new__(cls, name, bases, clsdict): 6 | def m(self): 7 | print(self) 8 | print('in m') 9 | 10 | clsdict['m'] = m 11 | clsobj = super().__new__(cls, name, bases, clsdict) 12 | 13 | return clsobj 14 | 15 | class mytype2(type): 16 | def __new__(cls, name, bases, clsdict): 17 | print('mytype2') 18 | clsobj = super().__new__(cls, name, bases, clsdict) 19 | 20 | return clsobj 21 | 22 | 23 | class Base(metaclass=mytype): 24 | pass 25 | 26 | 27 | class Base2(metaclass=mytype2): 28 | pass 29 | 30 | 31 | class A(Base2, Base): 32 | pass 33 | 34 | 35 | class B(A): 36 | pass 37 | 38 | 39 | b = B() 40 | b.m() 41 | -------------------------------------------------------------------------------- /week11/examples/registry.py: -------------------------------------------------------------------------------- 1 | class RegistryMeta(type): 2 | def __new__(cls, name, bases, clsdict): 3 | for base in bases: 4 | print(vars(base)) 5 | clsobj = super().__new__(cls, name, bases, clsdict) 6 | print(clsobj) 7 | print('===========') 8 | 9 | if not hasattr(clsobj, 'registry'): 10 | clsobj.registry = [] 11 | 12 | clsobj.registry.append(clsobj) 13 | 14 | return clsobj 15 | 16 | 17 | class Base(metaclass=RegistryMeta): 18 | pass 19 | 20 | 21 | class A(Base): 22 | pass 23 | 24 | 25 | class B(Base): 26 | pass 27 | 28 | 29 | print(Base.registry) 30 | print(A.registry) 31 | print(B.registry) 32 | -------------------------------------------------------------------------------- /week11/examples/serializer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week11/examples/serializer/__init__.py -------------------------------------------------------------------------------- /week11/examples/serializer/fields.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from validate_email import validate_email 3 | 4 | 5 | class Field(metaclass=abc.ABCMeta): 6 | def transform(self, value): 7 | return value 8 | 9 | @abc.abstractmethod 10 | def validate(self, value): 11 | return False 12 | 13 | 14 | class CharField(Field): 15 | def transform(self, value): 16 | return str(value) 17 | 18 | def validate(self, value): 19 | return True 20 | 21 | 22 | class EmailField(CharField): 23 | def validate(self, value): 24 | return validate_email(value) 25 | 26 | 27 | class DateTimeField(Field): 28 | def transform(self, value): 29 | return value.isoformat() 30 | 31 | def validate(self, value): 32 | return True 33 | -------------------------------------------------------------------------------- /week11/examples/serializer/main.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from serializers import Serializer 4 | from fields import CharField, EmailField, DateTimeField 5 | 6 | 7 | class Panda: 8 | def __init__(self, name, email): 9 | self.name = name 10 | self.email = email 11 | self.created_at = datetime.now() 12 | 13 | 14 | class PandaSerializer(Serializer): 15 | name = CharField() 16 | email = EmailField() 17 | created_at = DateTimeField() 18 | 19 | 20 | def main(): 21 | panda = Panda('Ivo', 'ivo@hackbulgaria.com') 22 | serializer = PandaSerializer(instance=panda) 23 | 24 | serializer.is_valid() 25 | print(serializer.data) 26 | 27 | 28 | if __name__ == '__main__': 29 | main() 30 | -------------------------------------------------------------------------------- /week11/examples/serializer/serializers.py: -------------------------------------------------------------------------------- 1 | from fields import Field 2 | 3 | 4 | class DeclarativeSerializerMeta(type): 5 | def __new__(cls, name, bases, clsdict): 6 | fields = {} 7 | 8 | for attr, value in clsdict.items(): 9 | if isinstance(value, Field): 10 | fields[attr] = value 11 | 12 | for attr, _ in fields.items(): 13 | clsdict.pop(attr) 14 | 15 | clsdict['_fields'] = fields 16 | 17 | return super().__new__(cls, name, bases, clsdict) 18 | 19 | 20 | class Serializer(metaclass=DeclarativeSerializerMeta): 21 | def __init__(self, instance): 22 | self._object = instance 23 | self._called_validation = False 24 | 25 | def is_valid(self): 26 | valid = True 27 | 28 | for field_name, field in self._fields.items(): 29 | if not field.validate(getattr(self._object, field_name)): 30 | valid = False 31 | break 32 | 33 | self._called_validation = True 34 | return valid 35 | 36 | @property 37 | def data(self): 38 | if not self._called_validation: 39 | raise Exception('Call .is_valid() before .data') 40 | 41 | return { field_name: field.transform(getattr(self._object, field_name)) 42 | for field_name, field in self._fields.items() } 43 | -------------------------------------------------------------------------------- /week11/examples/setattr.py: -------------------------------------------------------------------------------- 1 | class Panda: 2 | pass 3 | 4 | 5 | def create_panda(attributes): 6 | panda = Panda() 7 | 8 | for key, value in attributes.items(): 9 | setattr(panda, key, value) 10 | 11 | return panda 12 | 13 | p = create_panda({'name': 'Ivo', 'age': 23, 'weight': 80}) 14 | print(p.__dict__) 15 | -------------------------------------------------------------------------------- /week12/Crawler/README.md: -------------------------------------------------------------------------------- 1 | # Crawl the bulgarian web! 2 | 3 | We are goint to make a histogram of the server software that runs bulgarian webpages (that end in .bg) 4 | 5 | ## Crawling vs. API 6 | 7 | We are going to distinguish making **HTTP calls to an API** and **crawling websites**! 8 | 9 | API means that there is a way to get information in a nicely-structured way. Most of the APIs return JSON data. 10 | 11 | For example, GitHub gives us an API: https://api.github.com/users/radorado, which returns JSON. JSON is good because we can easily parse it into data-structures. 12 | 13 | But most of the websites do not provide any API. So we have to work for our information - parsing the HTML structure of the websites. **This is called crawling** 14 | 15 | ## What serves that website? 16 | 17 | In order to understand what is the server, running given web site, we need to make HTTP request (for example GET), and see the response headers. 18 | 19 | **There is a `Server` header which holds a string with the information.** 20 | 21 | For example: 22 | 23 | ``` 24 | $ curl -I https://hackbulgaria.com 25 | HTTP/2 200 26 | content-type: text/html 27 | content-length: 21670 28 | date: Mon, 18 May 2020 13:40:00 GMT 29 | last-modified: Fri, 14 Feb 2020 09:29:22 GMT 30 | etag: "cd86f2b4b40d1e458e1fa8667d855100" 31 | accept-ranges: bytes 32 | server: AmazonS3 33 | x-cache: Miss from cloudfront 34 | via: 1.1 b2721dd2c0bbd4046fd80941e54642eb.cloudfront.net (CloudFront) 35 | x-amz-cf-pop: BUD50-C1 36 | x-amz-cf-id: 6cAXWK0JG8bbBQziSVh89uVAKrwOi1Wujw4kKpIHVZpbM-5Wd0L7iA== 37 | ``` 38 | 39 | As you can see, the `Server: AmazonS3` header gives us the information that `hackbulgaria.com` is hosted on S3. 40 | 41 | That's great. 42 | 43 | How about Python? 44 | 45 | ```python 46 | >>> import requests 47 | >>> r = requests.get("https://hackbulgaria.com") 48 | >>> print(r.headers["Server"]) 49 | AmazonS3 50 | ``` 51 | 52 | Easy as a pie! 53 | 54 | ## Crawling & Information 55 | 56 | Now, if we want to crawl all websites (or almost all websites) that end in .bg, we are going to need a list of them. 57 | 58 | We can a big list, around 10 000 pages, from here - http://register.start.bg/ 59 | 60 | The catch is - there is no API. We will have to crawl our information. **Find all links in the HTML of the page.** 61 | 62 | Our hint here is - use [BeautifulSoup](http://www.crummy.com/software/BeautifulSoup/) in order to parse the HTML of the page into more meaningful structures. 63 | 64 | If you plan on using **regular expressions**, be sure to check 65 | 66 | Check the structure of the website. There are links that look like `link.php?id=64722`. They are the key to solve that problem. Play with them - see what you can do. 67 | 68 | ## Histogram of the results 69 | 70 | While you crawl all the 10 000 pages, create a histogram of the different web servers that run them. 71 | 72 | When you are done, save the result in a database. 73 | 74 | You are going to have results which look like that: 75 | 76 | ``` 77 | ... 78 | Apache/2.4.7 (Unix) PHP/5.4.25: 1 79 | Apache/2.4.7 (Win64) OpenSSL/1.0.1g mod_fcgid/2.3.9: 1 80 | Apache/2.4.9 (Unix) OpenSSL/1.0.1e-fips mod_bwlimited/1.4: 1 81 | Apache/2: 7 82 | Apache: 384 83 | Apache-Coyote/1.1: 1 84 | Apache mod_fcgid/2.3.7 mod_auth_pgsql/2.0.3: 2 85 | Apashi 1.1 mod_fcgid/2.3.5: 1 86 | cloudflare-nginx: 5 87 | Host.bg redirect server based on Apache/1.3.31 (Unix): 2 88 | IBM_HTTP_Server: 2 89 | lighttpd/1.4.22: 1 90 | Microsoft-IIS/5.0: 5 91 | Microsoft-IIS/6.0: 15 92 | Microsoft-IIS/7.0: 3 93 | Microsoft-IIS/7.5: 23 94 | Microsoft-IIS/8.0: 2 95 | Microsoft-IIS/8.5: 3 96 | nginx/0.7.62: 1 97 | nginx/0.7.65: 1 98 | nginx/0.7.67: 1 99 | nginx/0.8.53: 1 100 | nginx/1.0.10: 3 101 | ... 102 | ``` 103 | 104 | ## Consolidate Results & Plot Them 105 | 106 | As you can see, there are a lot of different versions of Apache or nginx or IIS. If we want to make a more general analysis, we are going to consolidate all Apache results into a single "Apache" entity. 107 | 108 | Do so and plot the resulted histogram in a histogram chart that looks something like that: 109 | 110 | ![](histogram.png) 111 | 112 | 113 | You can use [matplotlib](http://matplotlib.org/) for ploting. 114 | 115 | ## Extra Credit 116 | 117 | Think about the design of the problem. Can you split the problem in smaller parts / systems? 118 | 119 | After you are done, make a simple **Flask** HTTP serves, that handles one URL: 120 | 121 | ``` 122 | GET /results 123 | ``` 124 | 125 | This should return the resulting histogram, so far, in a JSON format. 126 | 127 | After this, you can hook the plotter to this server. 128 | 129 | ## Hints 130 | 131 | It is a good idea to create a [spike solution](http://www.extremeprogramming.org/rules/spike.html) first. This will give you more understanding about the problem domain & the libraries that we are going to use. 132 | 133 | ## SQLAlchemy 134 | 135 | Use it. It will make your life easier. 136 | 137 | ### User Agent 138 | 139 | If you send request from python's `requests` it looks like that: 140 | 141 | ``` 142 | GET / HTTP/1.1 143 | Host: localhost:8000 144 | User-Agent: python-requests/2.3.0 CPython/3.4.2 Linux/3.16.0-34-generic 145 | Accept-Encoding: gzip, deflate 146 | Accept: */* 147 | ``` 148 | 149 | As you can see, the `User-Agent` header says python-requests. This is not good. 150 | 151 | If we want to send a request that looks like google chrome, we can check here - http://www.useragentstring.com/pages/Chrome/ 152 | 153 | In order to do this, we will use the `headers=` keyword for `requests.get` 154 | 155 | ```python 156 | our_headers = { 157 | "User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36" 158 | } 159 | 160 | requests.get("http://localhost:8000", headers=our_headers) 161 | ``` 162 | 163 | This will look like a Google Chrome request, which is good when we are crawling. 164 | 165 | ### HEAD instead of GET 166 | 167 | When we make a `GET` request, we download the entire HTML. This can be slow, especially on slow internet connections. 168 | 169 | There is another HTTP verb that we can use. It is called `HEAD`. 170 | 171 | This request asks only for the server headers and not for the content - exactly what we need. 172 | 173 | Read the [requests documentation](http://docs.python-requests.org/en/latest/user/quickstart/) to see how to make a `HEAD` request. 174 | 175 | ### Ploting from dictionary 176 | 177 | When you are ready and you have a dictionary with results, you can plot the histogram like that: (it is ugly!) 178 | 179 | ```python 180 | import matplotlib.pyplot as plt 181 | 182 | h = { .. dict with results } 183 | keys = list(h.keys()) 184 | values = list(h.values()) 185 | 186 | X = list(range(len(keys))) 187 | 188 | plt.bar(X, list(h.values()), align="center") 189 | plt.xticks(X, keys) 190 | 191 | plt.title(".bg servers") 192 | plt.xlabel("Server") 193 | plt.ylabel("Count") 194 | 195 | plt.savefig("histogram.png") 196 | ``` 197 | -------------------------------------------------------------------------------- /week12/README.md: -------------------------------------------------------------------------------- 1 | # Week 12 - HTTP & Django 2 | 3 | Example with `netcat`: 4 | 5 | ``` 6 | nc -l 8080 7 | curl http://localhost:8000 8 | ``` 9 | 10 | Libraries: 11 | 12 | * [requests](https://requests.readthedocs.io/en/master/) 13 | * [BeautifulSoup](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) 14 | -------------------------------------------------------------------------------- /week13/01.Models-and-Admin/README.md: -------------------------------------------------------------------------------- 1 | # Hackademy 2 | 3 | We are going to implement a simple course management system to hold courses & lectures. 4 | 5 | Make a fresh Django app. 6 | 7 | The model specification is as follows: 8 | 9 | ## Courses 10 | 11 | First of all, our system should know about courses. 12 | 13 | We are interested in the following attributes for one course: 14 | 15 | - Name - should be unique 16 | - Description 17 | - Start Date 18 | - End Date - can be nullable 19 | 20 | Here is how the admin page of the `Course` should look like: 21 | 22 | | Name | Description | Start Date | End Date | Duration | 23 | | --------------------------- | ----------- | ---------- | ---------- | -------- | 24 | | Programming 101 with Python | Python! | 01.01.2016 | 01.03.2016 | 3 months | 25 | | Programming 101 with Ruby | Ruby. | 10.01.2016 | 01.03.2016 | 2 months | 26 | 27 | The **Duration** should be an approximation. Figure it out ;) 28 | 29 | ## Lectures 30 | 31 | Having only courses is pointless. So lets add some lectures to the mix! 32 | 33 | Our lecture should have: 34 | 35 | - Unique Identifier 36 | - Name 37 | - Week - like the weeks in HackBulgaria 38 | - course - Foreign key 39 | - URL - where the real lecture or slides are located. 40 | 41 | ## Tasks 42 | 43 | We are interested in the following attributes for one task: 44 | 45 | - Name - should be unique 46 | - Description 47 | - Due date 48 | - Course - FK 49 | - Lecture - optional FK 50 | 51 | ## Solutions 52 | 53 | This is the interesting part of our system. 54 | Our solution should have: 55 | 56 | - Task - FK 57 | - Date 58 | - URL - link with solution in Github. **Validate the GitHub url and don't allow models with wrong urls to be saved!** 59 | 60 | ## Each model should have an admin page! 61 | -------------------------------------------------------------------------------- /week13/02.Views/README.md: -------------------------------------------------------------------------------- 1 | # Course Management System Views 2 | 3 | We'll now create views for the models from the last exercise. 4 | 5 | ## Index view 6 | 7 | - `GET /` There should be an index view with a navigation to all list views. 8 | 9 | ## Courses views 10 | 11 | For our courses, we should have the following HTML views: 12 | 13 | - `GET /courses` should display a nice table with all courses, sorted by their start date. The table should look like this: 14 | 15 | | Course Name | Description | Start Date | End Date | Duration | 16 | | --------------------------- | ----------- | ---------- | ---------- | -------- | 17 | | Programming 101 with Python | Python! | 01.01.2016 | 01.03.2016 | 3 months | 18 | | Programming 101 with Ruby | Ruby. | 10.01.2016 | 01.03.2016 | 2 months | 19 | 20 | The course name should be a link to the course detail page. 21 | 22 | - `GET /courses//` - this is the detailed course page. Add a list of all lectures for the given course with a link to the detail view of the lecture. 23 | - `GET /courses/new/` - this should display a form for creating new course. 24 | - `POST /courses/new/` - this should be handled by Django for creating new courses. 25 | - `GET /courses//edit/` - this should display a form to edit some of the course details. 26 | - `PUT /courses/edit//edit/` - this should be handled by Django for editing existing courses. 27 | 28 | ## Lectures views 29 | 30 | We should have a set of URLs for creating and editing lectures: 31 | 32 | - `GET /lectures` should display a list of all lectures grouped by their course 33 | - `GET /lectures/` should display (in a table) the information for our lecture. Should have a link to the related course. 34 | - `GET /lectures/new` - the form for creating a new lecture 35 | - `POST /lectures/new` - handled by Django for saving the lecture 36 | - `GET /lectures//edit` - the form for editing an existing lecture 37 | - `PUT /lectures//edit` - handled by Django for editing an existing lecture 38 | -------------------------------------------------------------------------------- /week13/hackademy/education/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week13/hackademy/education/__init__.py -------------------------------------------------------------------------------- /week13/hackademy/education/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from education.models import Course 4 | 5 | 6 | @admin.register(Course) 7 | class CourseAdmin(admin.ModelAdmin): 8 | list_display = ('name', 'get_duration', 'start_date', 'end_date') 9 | 10 | def get_duration(self, obj): 11 | if obj.duration: 12 | return f'{obj.duration.days // 7} weeks' 13 | 14 | return 'N/A' 15 | 16 | get_duration.short_description = 'Duration' 17 | -------------------------------------------------------------------------------- /week13/hackademy/education/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class EducationConfig(AppConfig): 5 | name = 'education' 6 | -------------------------------------------------------------------------------- /week13/hackademy/education/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-25 14:58 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Course', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=255, unique=True)), 20 | ('description', models.TextField()), 21 | ('start_date', models.DateTimeField(default=django.utils.timezone.now)), 22 | ('end_date', models.DateTimeField(null=True)), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /week13/hackademy/education/migrations/0002_auto_20200525_1459.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-25 14:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('education', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='course', 15 | name='name', 16 | field=models.CharField(max_length=250, unique=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /week13/hackademy/education/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week13/hackademy/education/migrations/__init__.py -------------------------------------------------------------------------------- /week13/hackademy/education/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | 4 | 5 | class Course(models.Model): 6 | name = models.CharField(max_length=250, unique=True) 7 | description = models.TextField() 8 | start_date = models.DateTimeField(default=timezone.now) 9 | end_date = models.DateTimeField(null=True) 10 | 11 | def __str__(self): 12 | return f'Course "{self.name}"' 13 | 14 | @property 15 | def duration(self): 16 | if self.end_date: 17 | return self.end_date - self.start_date 18 | -------------------------------------------------------------------------------- /week13/hackademy/education/templates/courses/base.html: -------------------------------------------------------------------------------- 1 | Home 2 |

Courses

3 | 4 | {% block content %} {% endblock %} 5 | -------------------------------------------------------------------------------- /week13/hackademy/education/templates/courses/create.html: -------------------------------------------------------------------------------- 1 |

Add new course

2 | 3 |
4 | {% csrf_token %} {{ form.as_p }} 5 | 6 |
7 | -------------------------------------------------------------------------------- /week13/hackademy/education/templates/courses/detail.html: -------------------------------------------------------------------------------- 1 | {% extends "courses/base.html" %} {% block content %} 2 | Edit 3 |

{{ course.name }}

4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /week13/hackademy/education/templates/courses/edit_course.html: -------------------------------------------------------------------------------- 1 | Edit: {{ course }} 2 |
3 | {% csrf_token %} 4 | {{ form.as_p }} 5 | 6 |
-------------------------------------------------------------------------------- /week13/hackademy/education/templates/courses/list.html: -------------------------------------------------------------------------------- 1 | {% extends "courses/base.html" %} {% block content %} 2 | Add new 3 | 4 | {% if courses %} 5 |
    6 | {% for course in courses %} 7 |
  • {{ course.name }} 8 |
    {% csrf_token %}
  • 9 | {% endfor %} 10 |
11 | {% else %} 12 |

No courses yet.

13 | {% endif %} {% endblock %} 14 | -------------------------------------------------------------------------------- /week13/hackademy/education/templates/courses/new_experiment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Heading 1

6 |

Heading 2

7 |

Heading 3

8 | 9 |
10 | {% csrf_token %} 11 | {{ form.as_p }} 12 | 13 |
14 | 15 | 16 |
    17 |
  • Bullet 1
  • 18 |
  • Bullet 2
  • 19 |
  • Bullet 3
  • 20 |
  • Bullet 4
  • 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
MonthSavings
January$100
January$100
January$100
January$100
45 | 46 | 47 | -------------------------------------------------------------------------------- /week13/hackademy/education/templates/courses/new_experiment_success.html: -------------------------------------------------------------------------------- 1 | Bravoo! -------------------------------------------------------------------------------- /week13/hackademy/education/templates/courses/session.html: -------------------------------------------------------------------------------- 1 | {{ request.session.counter }} -------------------------------------------------------------------------------- /week13/hackademy/education/templates/index.html: -------------------------------------------------------------------------------- 1 |

Welcome to Hackademy!

2 | 3 | 6 | -------------------------------------------------------------------------------- /week13/hackademy/education/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /week13/hackademy/education/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | 3 | from education.views import index, courses 4 | 5 | app_name = 'education' 6 | 7 | courses_patterns = [ 8 | path('', courses.list, name='list'), 9 | path('/', courses.detail, name='detail'), 10 | path('new/', courses.CourseCreateView.as_view(), name='create'), 11 | path('new-experiment/', courses.new_experiment, name='new-experiment'), 12 | path('update//', courses.edit_course, name='update'), 13 | path('delete//', courses.delete_course, name='delete'), 14 | path('session/', courses.session, name='session'), 15 | ] 16 | 17 | urlpatterns = [ 18 | path('', index, name='index'), 19 | path('courses/', include((courses_patterns, 'courses'))) 20 | ] 21 | -------------------------------------------------------------------------------- /week13/hackademy/education/views/__init__.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | 4 | def index(request): 5 | return render(request, 'index.html', {}) 6 | 7 | 8 | import education.views.courses 9 | -------------------------------------------------------------------------------- /week13/hackademy/education/views/courses.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404, redirect 2 | from django.views.generic import CreateView 3 | from django.urls import reverse_lazy, reverse 4 | from django.http import HttpResponse 5 | from django import forms 6 | 7 | from education.models import Course 8 | 9 | 10 | def list(request): 11 | return render(request, 'courses/list.html', {'courses': Course.objects.all()}) 12 | 13 | 14 | def detail(request, course_id): 15 | course = get_object_or_404(Course, id=course_id) 16 | return render(request, 'courses/detail.html', {'course': course}) 17 | 18 | 19 | class CrouseForm(forms.ModelForm): 20 | class Meta: 21 | model = Course 22 | fields = ('name', 'description', 'start_date', 'end_date') 23 | 24 | 25 | def session(request): 26 | if not request.session.get('counter'): 27 | request.session['counter'] = 1 28 | else: 29 | request.session['counter'] += 1 30 | 31 | return render(request, 'courses/session.html') 32 | 33 | 34 | def new_experiment(request): 35 | if request.method == "POST": 36 | data = request.POST 37 | form = CrouseForm(data=data) 38 | if form.is_valid(): 39 | new_course = form.save() 40 | return redirect(reverse('education:courses:list')) 41 | else: 42 | return render(request, 'courses/new_experiment.html', {'form': form}) 43 | else: 44 | form = CrouseForm() 45 | return render(request, 'courses/new_experiment.html', {'form': form}) 46 | 47 | 48 | def edit_course(request, course_id): 49 | course = Course.objects.get(id=course_id) 50 | 51 | if request.method == 'POST': 52 | form = CrouseForm(data=request.POST, instance=course) 53 | if form.is_valid(): 54 | form.save() 55 | return redirect(reverse('education:courses:list')) 56 | else: 57 | return render(request, 'courses/edit_course.html', {'course': course, 'form': form}) 58 | else: 59 | form = CrouseForm(instance=course) 60 | return render(request, 'courses/edit_course.html', {'course': course, 'form': form}) 61 | 62 | 63 | def delete_course(request, course_id): 64 | if request.method == 'POST': 65 | Course.objects.get(id=course_id).delete() 66 | return redirect(reverse('education:courses:list')) 67 | 68 | 69 | class CourseCreateView(CreateView): 70 | model = Course 71 | fields = ['name', 'description', 'start_date', 'end_date'] 72 | template_name = 'courses/create.html' 73 | 74 | def get_success_url(self, **kwargs): 75 | return reverse_lazy('education:courses:detail', kwargs={'course_id': self.object.id}) 76 | -------------------------------------------------------------------------------- /week13/hackademy/hackademy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming-101-Python-2020-Spring/443446028df7fe78fcdd6c37dada0b5cd8ed3c93/week13/hackademy/hackademy/__init__.py -------------------------------------------------------------------------------- /week13/hackademy/hackademy/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for hackademy project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hackademy.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /week13/hackademy/hackademy/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for hackademy project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.6. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'y@a!*t%=z_v#0-$fan7f70**d(1+1977uz60%76jf44pg#m' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'education.apps.EducationConfig', 35 | 36 | 'django.contrib.admin', 37 | 'django.contrib.auth', 38 | 'django.contrib.contenttypes', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'hackademy.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'hackademy.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | -------------------------------------------------------------------------------- /week13/hackademy/hackademy/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | urlpatterns = [ 5 | path('education/', include('education.urls')), 6 | path('admin/', admin.site.urls), 7 | ] 8 | -------------------------------------------------------------------------------- /week13/hackademy/hackademy/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for hackademy project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hackademy.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /week13/hackademy/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hackademy.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | --------------------------------------------------------------------------------