├── .gitignore ├── LICENSE ├── README.md ├── hw_3 ├── Command Line Arguments.ipynb └── hw_3.md ├── lecture_1 ├── 01 Introduction.ipynb ├── 02. Data Types.ipynb ├── 03. Strings.ipynb ├── 04. Iterators.ipynb ├── Python_Lecture1.pptx ├── StringRep.java ├── images │ ├── cpp.png │ ├── hh.png │ ├── java.png │ ├── jupyter.png │ ├── platforms.png │ ├── salary.jpeg │ └── type_hier.png └── string_rep.py ├── lecture_10 ├── ctx.png ├── flask.jpeg ├── hello_flask.py ├── l10.md ├── requirements.txt └── task_tracker │ ├── __init__.py │ ├── tracker_rest │ ├── __init__.py │ ├── api_v1 │ │ ├── __init__.py │ │ ├── models.py │ │ └── schemas.py │ └── resource │ │ ├── api.cfg │ │ └── fill.sql │ └── tracker_web │ ├── __init__.py │ ├── forms.py │ ├── static │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ └── js │ │ ├── bootstrap.bundle.js │ │ ├── bootstrap.bundle.js.map │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ ├── templates │ ├── base.html │ └── tasks_view.html │ └── views.py ├── lecture_11 ├── Patterns.ipynb └── img │ ├── abstract factory.png │ ├── adapter.png │ ├── bridge.png │ ├── bridge1.png │ ├── bridge2.png │ ├── builder.png │ ├── command.png │ ├── command1.png │ ├── composite.png │ ├── decorator.png │ ├── decorator1.png │ ├── decorator2.png │ ├── facade.png │ ├── factory method.png │ ├── flyweight.png │ ├── hierarchy.png │ ├── iterator.png │ ├── mediator.png │ ├── observer.png │ ├── prototype.png │ ├── proxy.png │ ├── state.png │ ├── strategy.png │ ├── visitor.png │ └── why.jpg ├── lecture_12 ├── 01. Asyncio.ipynb ├── 02. Fresh.ipynb ├── 03. Typing.ipynb ├── 04. Setuptools.ipynb ├── 07 │ ├── typing1.py │ ├── typing2.py │ └── typing3.py ├── 08 │ └── thread.svg └── 09 │ └── wheel.png ├── lecture_13 ├── L13.md ├── img │ └── TestingTriangle.png └── matrix │ ├── matrixapp │ ├── __init__.py │ ├── actors.py │ ├── app.py │ └── views.py │ └── tests │ └── actors.py ├── lecture_2 ├── 05. Functions.ipynb ├── 06. Generators.ipynb ├── 07. Libraries.ipynb ├── Lecture 2.ipynb ├── ipynb_content │ ├── 2vars2obj.png │ ├── arena.png │ ├── cbr.jpg │ ├── cbv.jpg │ ├── collections.pdf │ ├── hierarchy.png │ ├── machine.gif │ ├── memlayout.png │ ├── sequence.pdf │ ├── shallow.png │ ├── stackoverflow.jpg │ ├── sticker.png │ └── zones.png └── module.py ├── lecture_3 ├── Classes.ipynb ├── VCS Basics.ipynb └── ipynb_content │ ├── commit.png │ ├── cycle.png │ ├── distr.png │ ├── plumbing.png │ └── shared.png ├── lecture_4 ├── 04-1. Magic Fields.ipynb ├── 04-2. Magic Methods.ipynb ├── 04-3. Descriptors.ipynb ├── 04-4. Attribute Access.ipynb ├── 04-5. Metaclasses.ipynb ├── 05 │ └── slots.jpg └── 06 │ ├── class-creation.png │ ├── factory.png │ ├── instance-creation.png │ ├── interface.png │ ├── metaclass.png │ ├── singleton.png │ └── type_and_object.png ├── lecture_5 ├── 14 │ ├── 0_exception_hierarchy.jpg │ ├── 1_exception_classification.png │ ├── 2_try_except.png │ ├── 3_raise.png │ ├── 4_else.png │ └── 5_finally.png ├── 01. Iterators, Generators and Context Managers.ipynb ├── 02. Exceptions.ipynb ├── 03. Decimal, Logging.ipynb ├── 04 │ └── iterator.png ├── 07 │ ├── typing1.py │ ├── typing2.py │ └── typing3.py └── homework2.md ├── lecture_6 ├── 17 │ ├── 0_assert.png │ ├── 18-00.png │ ├── 18-01.png │ ├── 18-02.png │ ├── 18-03.png │ ├── 18-05.png │ └── 18-06.png ├── 01 Serialization.ipynb ├── 02. Garbage Collection.ipynb ├── 03. Testing.ipynb ├── 03 │ ├── circularref.svg │ ├── image_10.png │ ├── image_12.png │ ├── image_14.png │ ├── image_16.png │ ├── image_4.png │ ├── image_6.png │ └── image_8.png ├── 04. Git.pdf └── README.md ├── lecture_7 ├── busybox.tar.xz ├── images │ ├── agg.png │ ├── arch.png │ ├── arch2.png │ ├── docker-colocation.png │ ├── hyperv.png │ ├── joins.png │ ├── root_tar_xz.png │ ├── unionfs.png │ └── vm-colocation.png └── lecture.md ├── lecture_8 ├── 01. Decorators.ipynb ├── 01 │ └── decorator.png ├── 02. Inspect.ipynb ├── dbc.png ├── df.png ├── hj.png ├── isolation.png ├── lecture8.md ├── osi.png ├── pgmem.png ├── pgproc.png ├── qp.png ├── qp1.png ├── qp2.png ├── qp3.png ├── sel_mvcc.png ├── smj.png └── ts.png ├── lecture_9 ├── .idea │ ├── .gitignore │ ├── inspectionProfiles │ │ ├── Project_Default.xml │ │ └── profiles_settings.xml │ ├── lecture_9.iml │ ├── misc.xml │ ├── modules.xml │ └── vcs.xml ├── dgram.jpg ├── epoll.jpg ├── epollwork.png ├── lecture9.md ├── ptable.png ├── requirements.txt ├── simple_http_server.py ├── simple_tcp_client.py ├── simple_tcp_server.py ├── werkzeug_demo │ └── zeug.py └── workblock.png └── project_the └── project_requirements.md /.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 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KIB Python (spring 2020) 2 | Репозиторий курса "Разработчик Python" 3 | 4 | Slack: https://kibpythoncourse.slack.com/ 5 | 6 | Записи онлайн-лекций: https://yadi.sk/d/rzW-sJxxrflpEg?w=1 7 | -------------------------------------------------------------------------------- /hw_3/Command Line Arguments.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "colab_type": "text", 7 | "id": "m23LDIArjKyj" 8 | }, 9 | "source": [ 10 | "# Передача аргументов командной строки" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": { 16 | "colab_type": "text", 17 | "id": "y7ZbSpt6Q2Fj" 18 | }, 19 | "source": [ 20 | "Параметры запуска, задаваемые через командную строку, чаще всего используют консольные программы, хотя программы с графическим интерфейсом тоже не брезгуют этой возможностью.\n", 21 | "\n", 22 | "Разберем несколько способов разобрать аргументы командной строки." 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": { 28 | "colab_type": "text", 29 | "id": "hq8y-QkrQy1T" 30 | }, 31 | "source": [ 32 | "## Переменная argv\n", 33 | "\n", 34 | "`sys.argv` содержит список параметров, переданных программе через командную строку, причем нулевой элемент списка - это имя скрипта.\n", 35 | "\n", 36 | "Этот способ не очень удобный, поэтому используется редко или только в совсем несложных проектах." 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "metadata": { 43 | "colab": {}, 44 | "colab_type": "code", 45 | "id": "Uu-uheFwQy1U", 46 | "outputId": "2741abf4-4674-41ac-eee5-5d641cbff875" 47 | }, 48 | "outputs": [], 49 | "source": [ 50 | "import sys\n", 51 | "\n", 52 | "for param in sys.argv:\n", 53 | " print (param)" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": { 59 | "colab_type": "text", 60 | "id": "43jwHca3Qy1X" 61 | }, 62 | "source": [ 63 | "## Библиотека argparse\n", 64 | "\n", 65 | "Стандартная библиотека **argparse** предназначена для облегчения разбора командной строки. На нее можно возложить проверку переданных параметров: их количество и обозначения, а уже после того, как эта проверка будет выполнена автоматически, использовать полученные параметры в логике своей программы.\n", 66 | "\n", 67 | "Основа работы с командной строкой в библиотеке argparse является класс **ArgumentParser**. У его конструктора и методов довольно много параметров, все их рассматривать не будем, поэтому в дальнейшем рассмотрим работу этого класса на примерах, попутно обсуждая различные параметры.\n", 68 | "\n", 69 | "Простейший принцип работы с argparse следующий:\n", 70 | "\n", 71 | "- Создаем экземпляр класса ArgumentParser.\n", 72 | "- Добавляем в него информацию об ожидаемых параметрах с помощью метода add_argument (по одному вызову на каждый параметр).\n", 73 | "- Разбираем командную строку помощью метода parse_args, передавая ему полученные параметры командной строки (кроме нулевого элемента списка sys.argv).\n", 74 | "- Начинаем использовать полученные параметры." 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "metadata": { 81 | "colab": {}, 82 | "colab_type": "code", 83 | "id": "6j9s9OOJQy1Y" 84 | }, 85 | "outputs": [], 86 | "source": [ 87 | "# Ожидаемый вызов программы:\n", 88 | "# python coolprogram.py [Имя]\n", 89 | "# где [Имя] является необязательным параметром\n", 90 | "\n", 91 | "import sys\n", 92 | "import argparse\n", 93 | "\n", 94 | "\n", 95 | "def createParser():\n", 96 | " \n", 97 | " # экземпляр класса ArgumentParser с параметрами по умолчанию\n", 98 | " parser = argparse.ArgumentParser()\n", 99 | " \n", 100 | " # Параметр\n", 101 | " parser.add_argument ('name', nargs='?')\n", 102 | " # Такой параметр будет считаться позиционным, \n", 103 | " # т.е. он должен стоять именно на этом месте \n", 104 | " # и у него не будет никаких предварительных обозначений.\n", 105 | " \n", 106 | " # nargs - сколько аргументов ожидаем,\n", 107 | " # может принимать значение '?', '+', '*' или число\n", 108 | " \n", 109 | " return parser\n", 110 | " \n", 111 | "if __name__ == '__main__':\n", 112 | " parser = createParser()\n", 113 | " namespace = parser.parse_args()\n", 114 | "\n", 115 | " # при запуске: python coolprogram.py Вася\n", 116 | " # namespace(name='Вася')\n", 117 | "\n", 118 | " # при запуске: python coolprogram.py\n", 119 | " # nmespace(name=None)\n", 120 | " \n", 121 | " if namespace.name:\n", 122 | " print (\"Привет, {}!\".format (namespace.name) )\n", 123 | " else:\n", 124 | " print (\"Привет, мир!\")" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": { 131 | "colab": {}, 132 | "colab_type": "code", 133 | "id": "RAWcsj6dQy1Z" 134 | }, 135 | "outputs": [], 136 | "source": [ 137 | "# Параметры со значением по умолчанию\n", 138 | "\n", 139 | "import sys\n", 140 | "import argparse\n", 141 | " \n", 142 | "\n", 143 | "def createParser():\n", 144 | " parser = argparse.ArgumentParser()\n", 145 | " # параметр со значением по умолчанию 'мир'\n", 146 | " parser.add_argument ('name', nargs='?', default='мир')\n", 147 | " \n", 148 | " return parser\n", 149 | " \n", 150 | "if __name__ == '__main__': \n", 151 | " parser = createParser()\n", 152 | " namespace = parser.parse_args (sys.argv[1:])\n", 153 | "\n", 154 | " print (\"Привет, {}!\".format (namespace.name) )" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": null, 160 | "metadata": { 161 | "colab": {}, 162 | "colab_type": "code", 163 | "id": "cFqg2VonQy1b" 164 | }, 165 | "outputs": [], 166 | "source": [ 167 | "# Именованные параметры\n", 168 | "\n", 169 | "import sys\n", 170 | "import argparse\n", 171 | " \n", 172 | "\n", 173 | "def createParser():\n", 174 | " parser = argparse.ArgumentParser()\n", 175 | " # именованный параметр\n", 176 | " # должен передаваться после параметра --name или -n.\n", 177 | " parser.add_argument ('-n', '--name', default='мир')\n", 178 | " \n", 179 | " return parser\n", 180 | " \n", 181 | "if __name__ == '__main__':\n", 182 | " parser = createParser()\n", 183 | " namespace = parser.parse_args(sys.argv[1:])\n", 184 | " \n", 185 | " print (\"Привет, {}!\".format (namespace.name) )\n", 186 | "\n", 187 | "# Все именованные параметры считаются необязательными!\n", 188 | "# Чтобы именованный параметр стал обязательным,\n", 189 | "# можно добавить 'required=True'" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": null, 195 | "metadata": { 196 | "colab": {}, 197 | "colab_type": "code", 198 | "id": "10IJ1lsiQy1d" 199 | }, 200 | "outputs": [], 201 | "source": [ 202 | "# Список разрешенных параметров\n", 203 | "\n", 204 | "import sys\n", 205 | "import argparse\n", 206 | " \n", 207 | "def createParser ():\n", 208 | " parser = argparse.ArgumentParser()\n", 209 | " parser.add_argument ('-n', '--name', choices=['Вася', 'Оля', 'Петя'], default='Оля')\n", 210 | " \n", 211 | " return parser\n", 212 | "\n", 213 | "if __name__ == '__main__':\n", 214 | " parser = createParser()\n", 215 | " namespace = parser.parse_args(sys.argv[1:])\n", 216 | " \n", 217 | " print (\"Привет, {}!\".format (namespace.name) )" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": null, 223 | "metadata": { 224 | "colab": {}, 225 | "colab_type": "code", 226 | "id": "5I8i7ZgyQy1e" 227 | }, 228 | "outputs": [], 229 | "source": [ 230 | "# Указание типов параметров\n", 231 | "\n", 232 | "import sys\n", 233 | "import argparse\n", 234 | " \n", 235 | "def createParser ():\n", 236 | " parser = argparse.ArgumentParser()\n", 237 | " parser.add_argument ('-c', '--count', default=1, type=int) \n", 238 | " # в качестве значения параметра type мы передали не строку, \n", 239 | " # а стандартную функцию преобразования в целое число\n", 240 | " \n", 241 | " return parser\n", 242 | " \n", 243 | "if __name__ == '__main__':\n", 244 | " parser = createParser()\n", 245 | " namespace = parser.parse_args(sys.argv[1:])\n", 246 | " \n", 247 | " for _ in range (namespace.count):\n", 248 | " print (\"Привет, мир!\")" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": null, 254 | "metadata": { 255 | "colab": {}, 256 | "colab_type": "code", 257 | "id": "DCZAAF07Qy1g" 258 | }, 259 | "outputs": [], 260 | "source": [ 261 | "# Так как в type передается функция, можно таким способом проверить файл\n", 262 | "\n", 263 | "import sys\n", 264 | "import argparse\n", 265 | " \n", 266 | "def createParser ():\n", 267 | " parser = argparse.ArgumentParser()\n", 268 | " parser.add_argument ('-n', '--name', type=open)\n", 269 | " \n", 270 | " # более изящное решение:\n", 271 | " parser.add_argument ('-n', '--name', type=argparse.FileType())\n", 272 | " # функция argparse.FileType, предназначенной для безопасной попытки открытия файла\n", 273 | " \n", 274 | " return parser\n", 275 | "\n", 276 | "if __name__ == '__main__':\n", 277 | " parser = createParser()\n", 278 | " namespace = parser.parse_args(sys.argv[1:])\n", 279 | " \n", 280 | " text = namespace.name.read()\n", 281 | " \n", 282 | " print (text)" 283 | ] 284 | }, 285 | { 286 | "cell_type": "markdown", 287 | "metadata": { 288 | "colab_type": "text", 289 | "id": "ZcHjkFQSQy1h" 290 | }, 291 | "source": [ 292 | "## Библиотека click\n", 293 | "\n", 294 | "Click решает ту же проблему, что и argparse, но немного иначе. Он использует декораторы, поэтому команды должны быть функциями, которые можно обернуть этими декораторами.\n", 295 | "\n", 296 | "Он принципиально отличается от argparse количеством функционала и подходом к описанию команд и параметров через декораторы, а саму логику предлагается выделять в отдельные функции вместо большого main. Авторы утверждают, что у Click много настроек, но стандартных параметров должно хватить. Среди фич подчёркиваются вложенные команды и их ленивая подгрузка. " 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": null, 302 | "metadata": { 303 | "colab": {}, 304 | "colab_type": "code", 305 | "id": "vLGmm4rTQy1i" 306 | }, 307 | "outputs": [], 308 | "source": [ 309 | "# декоратор @click.command() превращает функцию в команду, \n", 310 | "# которая является главной точкой входа нашего скрипта.\n", 311 | "\n", 312 | "import click\n", 313 | "import datetime\n", 314 | "\n", 315 | "@click.group()\n", 316 | "def cli():\n", 317 | " pass\n", 318 | "\n", 319 | "@click.command()\n", 320 | "# option - опция, необязательный. argument - параметр, обязательный\n", 321 | "@click.option('--date', default='now', help='The date format \"yyyy-mm-dd\"')\n", 322 | "def get_weekday(date):\n", 323 | " if date == 'now':\n", 324 | " date = datetime.datetime.utcnow()\n", 325 | " else:\n", 326 | " date = datetime.datetime.strptime(date, '%Y-%m-%d')\n", 327 | "\n", 328 | " click.echo(date.strftime('%A'))\n", 329 | "\n", 330 | "@click.command()\n", 331 | "@click.option('--date1', help='The date format \"yyyy-mm-dd\"')\n", 332 | "@click.option('--date2', help='The date format \"yyyy-mm-dd\"')\n", 333 | "def delta_day(date1, date2):\n", 334 | " date1 = datetime.datetime.strptime(date1, '%Y-%m-%d')\n", 335 | " date2 = datetime.datetime.strptime(date2, '%Y-%m-%d')\n", 336 | " delta = date1 - date2 if date1 > date2 else date2 - date1\n", 337 | " click.echo(delta.days)\n", 338 | "\n", 339 | "cli.add_command(get_weekday)\n", 340 | "cli.add_command(delta_day)\n", 341 | "\n", 342 | "if __name__ == '__main__':\n", 343 | " cli()" 344 | ] 345 | }, 346 | { 347 | "cell_type": "markdown", 348 | "metadata": { 349 | "colab_type": "text", 350 | "id": "EiyGJBWZQy1j" 351 | }, 352 | "source": [ 353 | "Флаг `--help` выведет автоматически сгенерированную документацию.\n", 354 | "\n", 355 | "Добавив справочный текст в декоратор `@click.option(... help='...')`, мы добавим описание для опций и параметров.\n", 356 | "\n", 357 | "Добавить документацию для всей click-команды можно, добавив строку документации в основную функцию:\n", 358 | "\n", 359 | "```\n", 360 | "def cli():\n", 361 | " '''\n", 362 | " This program make\n", 363 | " cool things\n", 364 | " '''\n", 365 | " pass\n", 366 | "```" 367 | ] 368 | } 369 | ], 370 | "metadata": { 371 | "colab": { 372 | "name": "topic12.ipynb", 373 | "provenance": [] 374 | }, 375 | "kernelspec": { 376 | "display_name": "Python 3", 377 | "language": "python", 378 | "name": "python3" 379 | }, 380 | "language_info": { 381 | "codemirror_mode": { 382 | "name": "ipython", 383 | "version": 3 384 | }, 385 | "file_extension": ".py", 386 | "mimetype": "text/x-python", 387 | "name": "python", 388 | "nbconvert_exporter": "python", 389 | "pygments_lexer": "ipython3", 390 | "version": "3.6.5" 391 | } 392 | }, 393 | "nbformat": 4, 394 | "nbformat_minor": 1 395 | } 396 | -------------------------------------------------------------------------------- /hw_3/hw_3.md: -------------------------------------------------------------------------------- 1 | # Домашка 3 2 | 3 | Все задания - продолжение кода из домашки №2 4 | 5 | 1. В классах Patient и PatientCollection логирование реализовать через декоратор: 6 | ```class Patient: 7 | 8 | @my_logging_decorator 9 | def __init__(self, ....): 10 | .... 11 | 12 | @my_logging_decorator 13 | def save(self): 14 | .... 15 | ``` 16 | 17 | У многих это уже реализовано, так что кому-то будет проще =) 18 | 19 | 2. Развернуть локально Postgres, MySQL или SQLite (последнее - проще всего). Сохранение и считывание данных теперь производить не из csv, а из БД. 20 | 21 | 3. Реализовать следующий интерфейс командной строки (рекомендация - через click): 22 | 23 | ```cd homework``` 24 | - Добавление нового пользователя в БД: ```cli.py create Имя Фамилия --birth-date 1990-01-01 --phone +7-916-000-00-00 --document-type паспорт --document-number 0000 000000``` 25 | - Вывод на экран первых 10 пользователей: ```python cli.py show``` 26 | - Вывод на экран произвольного количества пользователей: ```python cli.py show 8``` 27 | - Вывод на экран количества сохраненных пользователей: ```python cli.py count``` 28 | -------------------------------------------------------------------------------- /lecture_1/02. Data Types.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "colab_type": "text", 7 | "id": "7gou5fqfnRGv" 8 | }, 9 | "source": [ 10 | "# Типы данных в Python. Изменяемые и неизменяемые типы. Хранение переменных в памяти" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": { 16 | "colab_type": "text", 17 | "id": "Px79JIsrnRGz" 18 | }, 19 | "source": [ 20 | "Python – это динамически типизированный язык, тип переменной определяется непосредственно при выполнении программы.\n", 21 | "\n", 22 | "Например, строка:\n", 23 | "\n", 24 | "`a = 5`\n", 25 | "\n", 26 | "объявляет переменную b и присваивает ей значение 5.\n", 27 | "\n", 28 | "Python - язык с сильной типизацией, то есть вы не можете складывать например строки и числа, нужно все приводить к одному типу." 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": null, 34 | "metadata": { 35 | "colab": {}, 36 | "colab_type": "code", 37 | "id": "6N_4uTdznRG1", 38 | "outputId": "22ad0683-5389-4fe2-a6cd-2568c5e2f825" 39 | }, 40 | "outputs": [], 41 | "source": [ 42 | "a = 5\n", 43 | "print(type(a))\n", 44 | "\n", 45 | "a = 5.5\n", 46 | "print(type(a))\n", 47 | "\n", 48 | "a = 'abc'\n", 49 | "print(type(a))\n", 50 | "\n", 51 | "a = [1,2]\n", 52 | "print(type(a))" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": { 58 | "colab_type": "text", 59 | "id": "hGEE9NDznRG_" 60 | }, 61 | "source": [ 62 | "## Типы данных\n", 63 | "К основным встроенным типам данных в Python относятся:\n", 64 | "\n", 65 | "- **None** (неопределенное значение переменной)\n", 66 | "- **Логические переменные** (Boolean Type)\n", 67 | "- **NotImplemented** (используется для указания Python, что специальный метод не поддерживает конкретные аргументы, а Python будет пытаться использовать альтернативы, если они доступны)\n", 68 | "- **Числа** (Numeric Type)\n", 69 | " - *int* – целое число\n", 70 | " - *float* – число с плавающей точкой\n", 71 | " - *complex* – комплексное число\n", 72 | "- **Списки** (Sequence Type)\n", 73 | " - *list* – список\n", 74 | " - *tuple* – кортеж\n", 75 | " - *range* – диапазон\n", 76 | "- **Строки** (Text Sequence Type )\n", 77 | " - *str*\n", 78 | "- **Бинарные списки** (Binary Sequence Types)\n", 79 | " - *bytes* – байты\n", 80 | " - *bytearray* – массивы байт\n", 81 | " - *memoryview* – специальные объекты для доступа к внутренним данным объекта через protocol buffer\n", 82 | "- **Множества** (Set Types)\n", 83 | " - *set* – множество\n", 84 | " - *frozenset* – неизменяемое множество\n", 85 | "- **Словари** (Mapping Types)\n", 86 | " - *dict* – словарь" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": { 92 | "colab_type": "text", 93 | "id": "x1gCErknnRHC" 94 | }, 95 | "source": [ 96 | "## Хранение переменных в памяти\n", 97 | "\n", 98 | "При создании переменной вначале создается **объект**, который имеет **уникальный идентификатор**, **тип** и **значение**, после этого переменная может ссылаться на созданный объект.\n", 99 | "\n", 100 | "![Создание переменной](02/02-00.png)" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 3, 106 | "metadata": {}, 107 | "outputs": [ 108 | { 109 | "data": { 110 | "text/plain": [ 111 | "2384645881968" 112 | ] 113 | }, 114 | "execution_count": 3, 115 | "metadata": {}, 116 | "output_type": "execute_result" 117 | } 118 | ], 119 | "source": [ 120 | "a = 4789\n", 121 | "id(a)" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 4, 127 | "metadata": {}, 128 | "outputs": [ 129 | { 130 | "data": { 131 | "text/plain": [ 132 | "2384645884592" 133 | ] 134 | }, 135 | "execution_count": 4, 136 | "metadata": {}, 137 | "output_type": "execute_result" 138 | } 139 | ], 140 | "source": [ 141 | "b = 4789\n", 142 | "id(b)" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "metadata": { 149 | "colab": { 150 | "base_uri": "https://localhost:8080/", 151 | "height": 68 152 | }, 153 | "colab_type": "code", 154 | "executionInfo": { 155 | "elapsed": 2100, 156 | "status": "ok", 157 | "timestamp": 1575272498278, 158 | "user": { 159 | "displayName": "Надежда Демиденко", 160 | "photoUrl": "https://lh3.googleusercontent.com/a-/AAuE7mA6D7k5OgtG9hzPe8Abs8DfOKAXQoTXaPfn7EY=s64", 161 | "userId": "05224310221243935536" 162 | }, 163 | "user_tz": -180 164 | }, 165 | "id": "tAglMsYQnRHD", 166 | "outputId": "75595566-2e30-4bbb-b6b5-f5c9010c42bc" 167 | }, 168 | "outputs": [], 169 | "source": [ 170 | "a = 19.5\n", 171 | "\n", 172 | "print('идентификатор: ', id(a))\n", 173 | "print('тип: ', type(a))\n", 174 | "print('значение: ', a)" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": { 180 | "colab_type": "text", 181 | "id": "Z3CNEcb5j4DB" 182 | }, 183 | "source": [ 184 | "Операция `*3` при создании объекта копирует ссылки. Это видно на следующем примере:" 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": null, 190 | "metadata": { 191 | "colab": { 192 | "base_uri": "https://localhost:8080/", 193 | "height": 51 194 | }, 195 | "colab_type": "code", 196 | "executionInfo": { 197 | "elapsed": 746, 198 | "status": "ok", 199 | "timestamp": 1575272888037, 200 | "user": { 201 | "displayName": "Надежда Демиденко", 202 | "photoUrl": "https://lh3.googleusercontent.com/a-/AAuE7mA6D7k5OgtG9hzPe8Abs8DfOKAXQoTXaPfn7EY=s64", 203 | "userId": "05224310221243935536" 204 | }, 205 | "user_tz": -180 206 | }, 207 | "id": "j4CHjQZPkEP-", 208 | "outputId": "2e1fad27-5487-47b5-8f3b-b6179565ffc9" 209 | }, 210 | "outputs": [], 211 | "source": [ 212 | "a = [[]] * 3\n", 213 | "print(a)\n", 214 | "\n", 215 | "a[0].append(3)\n", 216 | "print(a)" 217 | ] 218 | }, 219 | { 220 | "cell_type": "markdown", 221 | "metadata": { 222 | "colab_type": "text", 223 | "id": "zBxjS5PBnRHK" 224 | }, 225 | "source": [ 226 | "## Изменяемые и неизменяемые типы данных\n", 227 | "\n", 228 | "В Python существуют изменяемые и неизменяемые типы.\n", 229 | "\n", 230 | "![Типы данных](02/02-03.png)\n", 231 | "\n", 232 | "К **неизменяемым** (*immutable*) типам относятся: \n", 233 | "- целые числа (*int*)\n", 234 | "- числа с плавающей точкой (*float*)\n", 235 | "- комплексные числа (*complex*)\n", 236 | "- логические переменные (*bool*)\n", 237 | "- кортежи (*tuple*)\n", 238 | "- строки (*str*)\n", 239 | "- неизменяемые множества (*frozen set*).\n", 240 | "\n", 241 | "К **изменяемым** (mutable) типам относятся:\n", 242 | "- списки (*list*)\n", 243 | "- множества (*set*)\n", 244 | "- словари (*dict*)" 245 | ] 246 | }, 247 | { 248 | "cell_type": "markdown", 249 | "metadata": { 250 | "colab_type": "text", 251 | "id": "rMEig-G9nRHM" 252 | }, 253 | "source": [ 254 | "### Неизменяемые типы\n", 255 | "\n", 256 | "Неизменяемость типа данных означает, что созданный объект больше не изменяется.\n", 257 | "При изменении значения происходит создание нового объекта, на который теперь будет ссылаться переменная." 258 | ] 259 | }, 260 | { 261 | "cell_type": "code", 262 | "execution_count": null, 263 | "metadata": { 264 | "colab": {}, 265 | "colab_type": "code", 266 | "id": "2fNDSEDPnRHO", 267 | "outputId": "36cd1687-3702-44cf-e7df-f087b85ac6aa" 268 | }, 269 | "outputs": [], 270 | "source": [ 271 | "a = 2\n", 272 | "print(id(a))\n", 273 | "\n", 274 | "a = 4\n", 275 | "print(id(a))" 276 | ] 277 | }, 278 | { 279 | "cell_type": "markdown", 280 | "metadata": { 281 | "colab_type": "text", 282 | "id": "j46-f4ibnRHV" 283 | }, 284 | "source": [ 285 | "Если выполнить операцию присваивания\n", 286 | "\n", 287 | "`a = b`\n", 288 | "\n", 289 | "то переменная `a` будет ссылаться на тот же объект, что и переменная `b`.\n", 290 | "\n", 291 | "Узнать, ссылаются ли переменные на один и тот же объект, можно при помощи операторов тождественности:\n", 292 | "\n", 293 | "* `is`\n", 294 | "* `is not`\n", 295 | "\n", 296 | "![Пример адресации переменных неизменяемых типов](02/02-01.png)" 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": null, 302 | "metadata": { 303 | "colab": {}, 304 | "colab_type": "code", 305 | "id": "M0XLS-XcnRHX", 306 | "outputId": "431b3e62-481a-499b-fde6-b88d25339bb3" 307 | }, 308 | "outputs": [], 309 | "source": [ 310 | "a = 16\n", 311 | "print('id(a) = ', id(a))\n", 312 | "\n", 313 | "b = 2.2\n", 314 | "print('id(b) = ', id(b))\n", 315 | "\n", 316 | "a = b\n", 317 | "\n", 318 | "print('id(a) = ', id(a))\n", 319 | "print(a is b)" 320 | ] 321 | }, 322 | { 323 | "cell_type": "markdown", 324 | "metadata": { 325 | "colab_type": "text", 326 | "id": "UWHsKNrp89xX" 327 | }, 328 | "source": [ 329 | "Так как строки - также неизменяемые объекты, при попытке изменить какой-нибудь символ в строке произойдет ошибка:" 330 | ] 331 | }, 332 | { 333 | "cell_type": "code", 334 | "execution_count": null, 335 | "metadata": { 336 | "colab": { 337 | "base_uri": "https://localhost:8080/", 338 | "height": 180 339 | }, 340 | "colab_type": "code", 341 | "executionInfo": { 342 | "elapsed": 1819, 343 | "status": "error", 344 | "timestamp": 1575530835506, 345 | "user": { 346 | "displayName": "Надежда Демиденко", 347 | "photoUrl": "https://lh3.googleusercontent.com/a-/AAuE7mA6D7k5OgtG9hzPe8Abs8DfOKAXQoTXaPfn7EY=s64", 348 | "userId": "05224310221243935536" 349 | }, 350 | "user_tz": -180 351 | }, 352 | "id": "lvbc7Hsx9IXH", 353 | "outputId": "ce421abf-21a7-498b-efbf-2cfd666f440c" 354 | }, 355 | "outputs": [], 356 | "source": [ 357 | "s = \"абракадабра\"\n", 358 | "s[3] = 'ы'" 359 | ] 360 | }, 361 | { 362 | "cell_type": "markdown", 363 | "metadata": { 364 | "colab_type": "text", 365 | "id": "Fp8gCBqvnRHc" 366 | }, 367 | "source": [ 368 | "### Изменяемые типы\n", 369 | "\n", 370 | "Изменяемыми объектами являются списки, множества и словари, и их можно менять:\n", 371 | "\n", 372 | "- Менять элементы\n", 373 | "- Добавлять/удалять элементы\n", 374 | "\n", 375 | "В примере ниже создан список, а потом изменен второй элемент.\n", 376 | "\n", 377 | "В качестве данных списка выступают не объекты, а отношения между объектами. Т.е. в переменной a хранятся ссылки на объекты содержащие числа 1 и 3, а не непосредственно сами эти числа.\n", 378 | "\n", 379 | "![Пример адресации переменных изменяемых типов](02/02-02.png)" 380 | ] 381 | }, 382 | { 383 | "cell_type": "code", 384 | "execution_count": null, 385 | "metadata": { 386 | "colab": {}, 387 | "colab_type": "code", 388 | "id": "qMz0niUSnRHe", 389 | "outputId": "8b8390f8-cbe7-4b67-ee70-7815b33cfa5b" 390 | }, 391 | "outputs": [], 392 | "source": [ 393 | "# Создаем список\n", 394 | "a = [1, 2]\n", 395 | "\n", 396 | "print(a)\n", 397 | "print('a: ', id(a))\n", 398 | "print('a[1]: ', id(a[1]))\n", 399 | "print()\n", 400 | "\n", 401 | "# Изменяем один элемент\n", 402 | "a[1] = 3\n", 403 | "\n", 404 | "print(a)\n", 405 | "print('a: ', id(a))\n", 406 | "print('a[1]: ', id(a[1]))\n", 407 | "print()\n", 408 | "\n", 409 | "# Добавляем еще один элемент в список\n", 410 | "a.append(4)\n", 411 | "\n", 412 | "print(a)\n", 413 | "print('a: ', id(a))\n" 414 | ] 415 | } 416 | ], 417 | "metadata": { 418 | "colab": { 419 | "name": "topic02.ipynb", 420 | "provenance": [] 421 | }, 422 | "kernelspec": { 423 | "display_name": "Python 3", 424 | "language": "python", 425 | "name": "python3" 426 | }, 427 | "language_info": { 428 | "codemirror_mode": { 429 | "name": "ipython", 430 | "version": 3 431 | }, 432 | "file_extension": ".py", 433 | "mimetype": "text/x-python", 434 | "name": "python", 435 | "nbconvert_exporter": "python", 436 | "pygments_lexer": "ipython3", 437 | "version": "3.7.4" 438 | } 439 | }, 440 | "nbformat": 4, 441 | "nbformat_minor": 1 442 | } 443 | -------------------------------------------------------------------------------- /lecture_1/Python_Lecture1.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_1/Python_Lecture1.pptx -------------------------------------------------------------------------------- /lecture_1/StringRep.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | public class StringRep { 3 | public static void main(String[] args) { 4 | String str1 = "PHP"; 5 | System.out.println("Original string: "+str1); 6 | String resultV1 = repeat_str(str1, 7); 7 | System.out.println("\nAfter repeating 7 times: "+resultV1); 8 | } 9 | public static String repeat_str(String str1, int n) { 10 | if (str1 == null || str1.isEmpty()) { 11 | return ""; 12 | } 13 | if (n <= 0) { 14 | return str1; 15 | } 16 | StringBuilder x = new StringBuilder(str1.length() * n); 17 | for (int i = 1; i <= n; i++) { 18 | x.append(str1); 19 | } 20 | return x.toString(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lecture_1/images/cpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_1/images/cpp.png -------------------------------------------------------------------------------- /lecture_1/images/hh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_1/images/hh.png -------------------------------------------------------------------------------- /lecture_1/images/java.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_1/images/java.png -------------------------------------------------------------------------------- /lecture_1/images/jupyter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_1/images/jupyter.png -------------------------------------------------------------------------------- /lecture_1/images/platforms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_1/images/platforms.png -------------------------------------------------------------------------------- /lecture_1/images/salary.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_1/images/salary.jpeg -------------------------------------------------------------------------------- /lecture_1/images/type_hier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_1/images/type_hier.png -------------------------------------------------------------------------------- /lecture_1/string_rep.py: -------------------------------------------------------------------------------- 1 | def repeat_string(data, times=7): 2 | if data and times > 0: 3 | print(data*times) 4 | else: 5 | print("Incorrect data") 6 | 7 | repeat_string('REP', 3) 8 | 9 | -------------------------------------------------------------------------------- /lecture_10/ctx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_10/ctx.png -------------------------------------------------------------------------------- /lecture_10/flask.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_10/flask.jpeg -------------------------------------------------------------------------------- /lecture_10/hello_flask.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from werkzeug.serving import run_simple 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/') 7 | def index(): 8 | return 'Hello World!' 9 | 10 | if __name__ == '__main__': 11 | run_simple('localhost', 5000, app) 12 | -------------------------------------------------------------------------------- /lecture_10/l10.md: -------------------------------------------------------------------------------- 1 | # Lecture 10 - Flask, REST 2 | ![ffuuu](flask.jpeg) 3 | *Flask* - микрофреймворк для веба. 4 | ### Hello, Flask! 5 | hello_flask.py 6 | ```python 7 | from flask import Flask 8 | from werkzeug.serving import run_simple 9 | 10 | app = Flask(__name__) 11 | 12 | @app.route('/') 13 | def index(): 14 | return 'Hello World!' 15 | 16 | if __name__ == '__main__': 17 | run_simple('localhost', 5000, app) 18 | ``` 19 | 20 | Что произошло: 21 | * Создали инстанс класса Flask 22 | class Flask - точка входа обработки полученного сервером http-запроса 23 | Реализует контракт `WSGI callable` 24 | * Настроили url-маршрутизацию 25 | Если URL-путь совпадает с одним из заданных шаблонов маршрутизацию, будет вызвана привязанная к нему функция 26 | Такие функции называются `view` 27 | * Отправляем наш WSGI callable на исполнение в сервер 28 | Сам по себе Flask не будет сервить себя 29 | 30 | Формат view-функции: 31 | ```python 32 | @app.route('/music/') 33 | def view_one(): 34 | return , , 35 | ``` 36 | 37 | ### Прокси и контексты 38 | 39 | Вся машинерия фласка вертится внутри цикла "запрос-ответ". 40 | Приложение может обслуживаться 1..N потоками - каждый запрос приходит на свой поток. 41 | Состояние каждого цикла можно держать в thread-local глобальных переменных. 42 | Передавать request в каждую функцию (django-way) или сделать некую глобальную проксю (flask). 43 | Flask-way warning - циклические зависимости при импорте: 44 | ```python 45 | # snake.py 46 | import tail 47 | 48 | # tail.py 49 | import snake 50 | ``` 51 | Решение: 52 | * Завести глобальные "thread-local" стеки 53 | * Трактовать их для обработки запросов как стекфреймы у python-функций 54 | * Расширения и внутренняя машинерия управляет состояниями этих стеков 55 | * Оба cтека каждый раз создается заново 56 | * Сделать указатели на эти стеки 57 | * Состояние стеков волатильно во время обработки запроса 58 | 59 | *Контексты - самое противоречивое решение во фласке*. 60 | Прекрасная идея, но описано крайне плохо. 61 | 62 | Контексты Flask и доступные из них объекты 63 | * current_app - ApplicationContext - активное приложение 64 | * g - ApplicationContext - дескриптор, доступный во время обработки запроса 65 | * request - RequestContext - объект активного запроса в обработке 66 | * session - RequestContext - "состояние" клиента (из cookies), сохраняющееся между запросами 67 | 68 | Последствия: 69 | * Во время инициализации app не изменять ApplicationContext 70 | * Во время обработки запроса не изменять ApplicationContext 71 | * Хранить глобальные объекты в ApplicationContext нет смысла - все равно потеряешь 72 | * Если нужно что-то прикрепить к текущему app на время обработки запроса - юзаем g, а не current_app 73 | * `app` напрямую используется только при инициализации всех расширений 74 | * Можно держать на одном интерпретаторе несколько app и пайплайнить запросы 75 | 76 | ![req_ctx](ctx.png) 77 | 78 | ```python 79 | from flask import Flask, request 80 | app = Flask(__name__) 81 | @app.route("/order") 82 | def order_form(): 83 | if request.method == 'POST': 84 | # update_smth(...) 85 | else: 86 | pass 87 | ``` 88 | Смысл трюка с прокси-объектами 89 | https://pynash.org/2013/02/12/proxy-objects/ 90 | ```python 91 | thing = "hi" 92 | 93 | class ThingProxy(object): 94 | def __getattribute__(self, name): 95 | return getattr(thing, name) 96 | 97 | proxy = ThingProxy() 98 | ``` 99 | Инициализация контекста (через contextmanager/push) 100 | ```python 101 | class Flask: 102 | def wsgi_app(self, environ, start_response): 103 | ctx = self.request_context(environ) 104 | error = None 105 | ctx.push() 106 | response = self.full_dispatch_request() 107 | ... 108 | ``` 109 | https://www.peterspython.com/en/blog/the-mysterious-flask-application-context-my-questions-and-answers 110 | 111 | ### Flask-плагины 112 | Для интеграции с app, Flask-плагин может сделать следующие шаги: 113 | * Привязать app, на котором расширение будет активно 114 | * Завести себе глобальное состояние - через словарь app.extensions 115 | * Произвести настройку окружения (sqla пример), завести конфиги в словарь app.config 116 | * Зарегистрировать callback-вызовы - например, закрыть ресурс при завершении запроса 117 | 118 | ### Маршрутизация 119 | Построена на werkzeug.routing 120 | Мы задаем набор правил, которые применяются на URL-путь 121 | Также возможно определять динамические компоненты в правиле - `` 122 | Доступные конвертеры (werkzeug.routing.___Converter): 123 | * string (по умолчанию) 124 | * int (>=0) 125 | * float (>=0.0) 126 | * path 127 | * uuid 128 | 129 | Каноничный URL - `/about_me` превратится в `/about_me/` 130 | 131 | ```python 132 | from werkzeug.routing import Map, Rule, NotFound, RequestRedirect 133 | 134 | url_map = Map([ 135 | Rule('/', endpoint='blog/index'), 136 | Rule('//', endpoint='blog/archive'), 137 | Rule('///', endpoint='blog/archive'), 138 | Rule('////', endpoint='blog/archive'), 139 | Rule('////', 140 | endpoint='blog/show_post'), 141 | Rule('/about', endpoint='blog/about_me'), 142 | Rule('/feeds/', endpoint='blog/feeds'), 143 | Rule('/feeds/.rss', endpoint='blog/show_feed') 144 | ]) 145 | 146 | def application(environ, start_response): 147 | urls = url_map.bind_to_environ(environ) 148 | try: 149 | endpoint, args = urls.match() 150 | except HTTPException as e: 151 | return e(environ, start_response) 152 | start_response('200 OK', [('Content-Type', 'text/plain')]) 153 | return ['Rule points to %r with arguments %r' % (endpoint, args)] 154 | ``` 155 | 156 | ### Некоторые паттерны Flask-приложений 157 | #### blueprint 158 | С ростом кодобазы, держать все вьюхи в одном модуле станет тяжко. 159 | Blueprint - пространство вьюх, которые могут быть позднее подключены к приложению. 160 | Это дает возможность: 161 | * подключать его на несколько url-пространств/поддоменов 162 | * иметь свои шаблоны/статику/функции шаблонизатора 163 | 164 | #### application_factory 165 | 166 | 167 | ## Кейс на реализацию 168 | ``` 169 | Task Tracker 170 | 171 | /api/tasklist?order={desc} 172 | GET - получить все TODO-списки 173 | POST - создать новый TODO-список 174 | 175 | /api/tasklist/?order={desc} 176 | GET - получить список по $id 177 | PATCH - обновить список по $id 178 | DELETE - удалить список по $id 179 | 180 | /api/tasklist// 181 | GET - получить задачу $pos из списка $id 182 | PATCH - обновить задачу $pos из списка $id 183 | DELETE - удалить задачу $pos из списка $id 184 | ``` 185 | Модель данных: 186 | ``` 187 | TaskList 188 | list_id: int, 189 | list_name: str, 190 | created_dttm: datetime, 191 | description: str 192 | 193 | Task 194 | task_id: int 195 | list_id: int (fk) 196 | task_value: str 197 | created_dttm: datetime 198 | ``` 199 | 200 | 201 | ## REST principles 202 | REST-принципы: 203 | * клиент-серверность 204 | * stateless - сервер не хранит состояния для клиента 205 | * кэшируемость 206 | * единообразный интерфейс 207 | * распределенность - нет привязки к одному серваку 208 | 209 | *Ресурс* - ключевая абстракция (контент/сервис/...) 210 | Имеет идентификатор и методы. 211 | Методы обычно соотносят с методами HTTP - GET/POST/... 212 | 213 | *REST != HTTP* 214 | Ничего не мешает сделать REST поверх TCP, это всего лишь архитектурный паттерн 215 | 216 | Для чего REST - robot-friendly интерфейс систем, для интеграций, для тонких клиентов: 217 | * интеграционные сервисы - связать две разных системы (пример: фронт-офис и кредитная фабрика) 218 | * backend мобильных приложений 219 | * rich web apps (react/vue/...) 220 | 221 | https://restfulapi.net/rest-architectural-constraints 222 | 223 | ### SQLAlchemy basics 224 | 225 | ### Flask CLI 226 | 227 | ## Шаблонизация - Jinja 228 | Шаблоном может быть любой plain text 229 | Чаще всего применяется для динамизации HTML 230 | 231 | В Jinja есть два понятия: 232 | * контект - контейнер функций и объектов 233 | * filter - f(x) 234 | * variable - некое значение (объект) 235 | 236 | Инстансы Flask содержат в себе контекст, дополнить фильтрами через декоратор `app.template_filter` 237 | 238 | https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ii-templates 239 | 240 | ## Формы 241 | 242 | ## Авторизация & cессия 243 | 244 | 245 | -------------------------------------------------------------------------------- /lecture_10/requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | flask-restplus 3 | flask-sqlalchemy 4 | flask-marshmallow 5 | marshmallow -------------------------------------------------------------------------------- /lecture_10/task_tracker/__init__.py: -------------------------------------------------------------------------------- 1 | from werkzeug.middleware.dispatcher import DispatcherMiddleware 2 | from task_tracker.tracker_rest import create_app as get_restapp 3 | from task_tracker.tracker_web import create_app as get_webapp 4 | 5 | 6 | app = DispatcherMiddleware( 7 | get_restapp(), {'/web': get_webapp()} 8 | ) 9 | 10 | 11 | if __name__ == '__main__': 12 | from werkzeug.serving import run_simple 13 | run_simple('localhost', 5000, app) 14 | -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_rest/__init__.py: -------------------------------------------------------------------------------- 1 | import click 2 | from flask import Flask 3 | from flask.cli import with_appcontext 4 | 5 | 6 | def create_app(): 7 | app = Flask(__name__) 8 | app.config.from_pyfile('resource/api.cfg') 9 | 10 | # bind db helper and marshmallow 11 | from .api_v1.models import db 12 | db.init_app(app) 13 | from .api_v1.schemas import ma 14 | ma.init_app(app) 15 | 16 | # init blueprints 17 | from task_tracker.tracker_rest import api_v1 18 | app.register_blueprint(api_v1.bp) 19 | 20 | # register cli funcs 21 | app.cli.add_command(init_db_command) 22 | 23 | return app 24 | 25 | 26 | def init_db(): 27 | from .api_v1.models import db 28 | db.drop_all() 29 | db.create_all() 30 | 31 | 32 | @click.command("init-db") 33 | @with_appcontext 34 | def init_db_command(): 35 | init_db() 36 | click.echo("Initialized the database.") 37 | 38 | 39 | if __name__ == '__main__': 40 | app = create_app() 41 | app.run(debug=True) 42 | -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_rest/api_v1/__init__.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import and_ 2 | from sqlalchemy.orm import aliased 3 | from flask import Blueprint, request 4 | from flask_restplus import Api, Resource, fields 5 | 6 | import task_tracker.tracker_rest.api_v1.schemas as marshal 7 | from task_tracker.tracker_rest.api_v1.models import db, TaskList as TasklistModel, Task as TaskModel 8 | 9 | 10 | bp = Blueprint('api_v1', __name__, url_prefix="/api/v1") 11 | api = Api(bp, 12 | version='v1', 13 | title='Reminder APIv1', 14 | description='A simple API v1') 15 | 16 | 17 | @api.route('/tasklist', 18 | '/tasklist/') 19 | class TaskListEndpoint(Resource): 20 | # /tasklist?contains_task={id|substring} 21 | def get(self, list_id=None): 22 | singleton = False 23 | 24 | t = aliased(TaskModel) 25 | tm = aliased(TasklistModel) 26 | # data = db.session.query(tm, t).join(t, t.list_id==tm.list_id) 27 | 28 | if list_id: 29 | singleton = True 30 | data = db.session.query(tm, t).join(t, t.list_id == tm.list_id).filter(tm.list_id == list_id) 31 | elif list_id is None and 'contains_task' in request.args: 32 | task_cond_value = request.args['contains_task'] 33 | if marshal.int_regexp.match(task_cond_value): 34 | condition = (t.task_id == int(task_cond_value)) 35 | else: 36 | condition = t.value.ilike(f'%{task_cond_value}%') 37 | data = db.session.query(tm).join(t, t.list_id==tm.list_id).filter(condition) 38 | else: 39 | data = db.session.query(tm) 40 | 41 | res = data.all() 42 | if len(res) == 0: 43 | return 'No tasklists' 44 | 45 | if singleton: # output hateoas 46 | out = marshal.TaskListHATEOASOutput.dump(res) 47 | else: 48 | out = marshal.TasksListOutput.dump(res) 49 | return out 50 | 51 | def post(self, *args, **kwargs): 52 | # создать новый список задач 53 | new_list = marshal.TasklistInput.load(request.get_json()) 54 | if new_list.errors: 55 | return new_list.errors, 500 56 | list_row = new_list.data 57 | db.session.add(list_row) 58 | db.session.commit() 59 | 60 | def put(self, list_id): 61 | # обновить список задач 62 | old_row = db.session.query(TasklistModel).filter(TasklistModel.list_id == list_id).first() 63 | new_data = marshal.TasklistInput.load(request.get_json()) 64 | if new_data.errors: 65 | return new_data.errors, 500 66 | new_row = new_data.data 67 | if old_row: 68 | old_row.description = new_row.description 69 | old_row.created_dttm = new_row.created_dttm 70 | db.session.add(old_row) 71 | db.session.commit() 72 | else: 73 | return 'NotExists', 404 74 | 75 | def delete(self, list_id): 76 | # удалить список задач 77 | db.session.query(TasklistModel).filter(TasklistModel.list_id == list_id).delete() 78 | db.session.commit() 79 | 80 | 81 | @api.route('/task/', 82 | '/task//') 83 | class TaskEndpoint(Resource): 84 | """ 85 | /api/task// 86 | GET - получить задачу $pos из списка $id 87 | PATCH - обновить задачу $pos из списка $id 88 | DELETE - удалить задачу $pos из списка $id 89 | """ 90 | def get(self, list_id, task_id=None): 91 | if task_id is None: 92 | # return all by list_id 93 | data = db.session.query(TaskModel).filter(TaskModel.task_id == task_id) 94 | else: 95 | # get specific 96 | data = db.session.query(TaskModel) 97 | res = data.all() 98 | out = marshal.TaskOutput.dump(res) 99 | return out 100 | 101 | def post(self, list_id, *args, **kwargs): 102 | # create new task by list_id, return task_id 103 | new_data = marshal.TaskInput.load(request.get_json()) 104 | if new_data.errors: 105 | return new_data.errors, 500 106 | new_row = marshal.TaskInput.make_task(new_data.data, list_id=list_id) 107 | db.session.add(new_row) 108 | db.session.commit() 109 | 110 | def delete(self, list_id=None, task_id=None): 111 | if list_id and task_id is None: 112 | # delete all by list_id 113 | delete_pred = db.session.query(TasklistModel).filter(TaskModel.list_id == list_id) 114 | else: 115 | # delete specific 116 | delete_pred = db.session.query(TasklistModel).filter(and_(TaskModel.list_id == list_id, 117 | TaskModel.task_id == task_id)) 118 | delete_pred.delete() 119 | db.session.commit() 120 | 121 | def put(self, list_id, task_id): 122 | # update specific 123 | old_row = db.session.query(TaskModel).filter(and_(TaskModel.list_id == list_id, 124 | TaskModel.task_id == task_id)).first() 125 | # only if content-type == 'application/json' 126 | new_data = marshal.TaskInput.load(request.get_json()) 127 | if new_data.errors: 128 | return new_data.errors, 500 129 | new_row = marshal.TaskInput.make_task(new_data.data, list_id=list_id) 130 | if old_row: 131 | old_row.value = new_row.value 132 | old_row.created_dttm = new_row.created_dttm 133 | db.session.add(old_row) 134 | db.session.commit() 135 | else: 136 | return 'NotExists', 404 137 | -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_rest/api_v1/models.py: -------------------------------------------------------------------------------- 1 | # https://docs.sqlalchemy.org/en/13/orm/basic_relationships.html 2 | # https://docs.sqlalchemy.org/en/13/orm/tutorial.html#building-a-relationship 3 | # https://flask-sqlalchemy.palletsprojects.com/en/2.x/models 4 | 5 | from sqlalchemy import Column, Integer, String, DateTime, ForeignKey 6 | from sqlalchemy.orm import relationship 7 | from flask_sqlalchemy import SQLAlchemy 8 | 9 | 10 | db = SQLAlchemy() 11 | # https://docs.sqlalchemy.org/en/13/orm/cascades.html#cascades 12 | 13 | class TaskList(db.Model): 14 | __tablename__ = 'tasklists' 15 | fields = {'list_id', 'name', 'description', 'created_dttm'} 16 | 17 | list_id = Column(Integer, primary_key=True, autoincrement=True) 18 | name = Column(String) 19 | description = Column(String) 20 | created_dttm = Column(DateTime) 21 | task = relationship("Task", back_populates="tasklist") 22 | 23 | def __init__(self, name=None, description=None, created_dttm=None): 24 | self.name = name 25 | self.description = description 26 | self.created_dttm = created_dttm 27 | 28 | 29 | class Task(db.Model): 30 | __task__ = 'tasks' 31 | task_id = Column(Integer, primary_key=True, autoincrement=True) 32 | list_id = Column(Integer, ForeignKey('tasklists.list_id', ondelete='CASCADE')) 33 | value = Column(String) 34 | created_dttm = Column(DateTime) 35 | tasklist = relationship("TaskList", back_populates="task") 36 | 37 | def __init__(self, list_id=None, value=None, created_dttm=None): 38 | self.list_id = list_id 39 | self.value = value 40 | self.created_dttm = created_dttm 41 | 42 | 43 | class ProxyTasklistHATEOAS: 44 | def __init__(self, tasks=None, **kwargs): 45 | self.tasks = tasks 46 | for k,v in kwargs.items(): 47 | setattr(self, k, v) 48 | -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_rest/api_v1/schemas.py: -------------------------------------------------------------------------------- 1 | import re 2 | import inspect 3 | from datetime import datetime 4 | from marshmallow import fields, ValidationError, pre_dump, post_load, validates_schema 5 | from flask_marshmallow import Marshmallow 6 | from flask.helpers import url_for 7 | from task_tracker.tracker_rest.api_v1 import models 8 | 9 | 10 | ma = Marshmallow() 11 | 12 | int_regexp = re.compile('^\d{1,10}$') 13 | name_regexp = re.compile('^[a-z_]+$') 14 | 15 | 16 | # ma.Schema = Schema + jsonify 17 | def extract_cls_attrs(obj, keys): 18 | mems = inspect.getmembers(obj, lambda f: not inspect.isroutine(f)) 19 | return {a[0]: a[1] for a in mems if a[0] in keys} 20 | 21 | 22 | # class LoadMixin(): 23 | # def dump_object(self, obj): 24 | # msg, dm = None, None 25 | # try: 26 | # dm = self.load(obj) 27 | # except ValidationError as err: 28 | # msg = json.dumps(list(err.messages.values())) 29 | # finally: 30 | # return dm, msg 31 | 32 | get_time = lambda: datetime.now().strftime('%Y-%m-%d %H:%M:%S') 33 | 34 | 35 | class TaskListInputSchema(ma.Schema): 36 | name = fields.String(required=True, allow_none=False) 37 | description = fields.String(required=True, allow_none=False) 38 | 39 | @validates_schema 40 | def validate_name(self, data, **kwargs): 41 | if name_regexp.match(data.get('name', '123')) is None: 42 | raise ValidationError("Name must be latin-alphabetical + '_'") 43 | 44 | @post_load 45 | def make_tasklist(self, data, **kwargs): 46 | return models.TaskList(created_dttm=get_time(), **data) 47 | 48 | 49 | class TaskInputSchema(ma.Schema): 50 | list_id = fields.Integer(required=True, allow_none=False) 51 | value = fields.String(required=True, allow_none=False) 52 | 53 | @staticmethod 54 | def make_task(self, data, list_id=None, **kwargs): 55 | return models.Task(created_dttm=get_time(), list_id=list_id, **data) 56 | 57 | 58 | TaskInput = TaskInputSchema() 59 | TasklistInput = TaskListInputSchema() 60 | 61 | # -------------------------------------------------------------- 62 | 63 | class TaskListOutputSchemaHATEOAS(ma.Schema): 64 | list_id = fields.Integer(required=True, allow_none=False) 65 | name = fields.String(required=True, allow_none=False) 66 | description = fields.String(required=True, allow_none=False) 67 | created_dttm = fields.DateTime(format='%Y-%m-%d %H:%M:%S') 68 | tasks = fields.List( 69 | fields.URL(relative=True) 70 | ) 71 | 72 | @staticmethod 73 | def make_tasklist(self, data, list_id=None, **kwargs): 74 | return models.TaskList(created_dttm=get_time(), **data) 75 | 76 | @pre_dump 77 | def preprocess(self, data, **kwargs): 78 | list_i: dict = extract_cls_attrs(data[0][0], models.TaskList.fields) 79 | tasks_i: list = [url_for('api_v1.task_endpoint', 80 | list_id=list_i['list_id'], 81 | task_id=getattr(row[1], 'task_id')) 82 | for row in data] 83 | d = models.ProxyTasklistHATEOAS( 84 | tasks=tasks_i, 85 | **list_i 86 | ) 87 | return d 88 | 89 | 90 | class TaskListOutputSchema(ma.SQLAlchemySchema): 91 | class Meta: 92 | model = models.TaskList 93 | list_id = fields.Integer(required=True, allow_none=False) 94 | name = fields.String(required=True, allow_none=False) 95 | description = fields.String(required=True, allow_none=False) 96 | created_dttm = fields.DateTime(format='%Y-%m-%d %H:%M:%S') 97 | 98 | 99 | class TaskOutputSchema(ma.Schema): 100 | task_id = fields.Integer(required=True, allow_none=False) 101 | list_id = fields.Integer(required=True, allow_none=False) 102 | value = fields.String(required=True, allow_none=False) 103 | created_dttm = fields.DateTime(format='%Y-%m-%d %H:%M:%S') 104 | # is_done - Boolean 105 | 106 | 107 | TaskListOutput = TaskListOutputSchema() 108 | TaskListHATEOASOutput = TaskListOutputSchemaHATEOAS() 109 | TasksListOutput = TaskListOutputSchema(many=True) 110 | 111 | TaskOutput = TaskOutputSchema() 112 | -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_rest/resource/api.cfg: -------------------------------------------------------------------------------- 1 | SECRET_KEY='Serious protection, really' 2 | SQLALCHEMY_DATABASE_URI='postgresql+psycopg2://apiuser:megapass99@localhost:5433/l10' 3 | # SQLALCHEMY_DATABASE_URI='sqlite:///test.db' 4 | -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_rest/resource/fill.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO public.tasklists (list_id, name, description, created_dttm) VALUES (1, 'work_daily', 'Рабочий список дел', '2020-05-08 09:19:00.000000'); 2 | INSERT INTO public.tasklists (list_id, name, description, created_dttm) VALUES (3, 'rest_outside', 'Базовый список при загороных поездках', '2020-05-08 09:20:05.000000'); 3 | INSERT INTO public.tasklists (list_id, name, description, created_dttm) VALUES (4, 'daily_evening', 'Simple evening routine', '2020-05-08 12:21:36.000000'); 4 | INSERT INTO public.tasklists (list_id, name, description, created_dttm) VALUES (2, 'home_evening', 'Complex evening routine', '2020-05-08 12:24:08.000000'); 5 | 6 | 7 | INSERT INTO public.task (task_id, list_id, value, created_dttm) VALUES (1, 1, 'Выпить чашку кофе', '2020-05-08 09:20:59.000000'); 8 | INSERT INTO public.task (task_id, list_id, value, created_dttm) VALUES (2, 2, 'Почистить зубы', '2020-05-08 09:21:13.000000'); 9 | INSERT INTO public.task (task_id, list_id, value, created_dttm) VALUES (3, 3, 'Вынести мусор', '2020-05-08 09:21:24.000000'); 10 | INSERT INTO public.task (task_id, list_id, value, created_dttm) VALUES (4, 3, 'Помыть посуду', '2020-05-08 10:07:04.000000'); 11 | INSERT INTO public.task (task_id, list_id, value, created_dttm) VALUES (5, 2, 'Сделать уборку', '2020-05-08 10:07:26.000000'); -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_web/__init__.py: -------------------------------------------------------------------------------- 1 | import click 2 | from flask import Flask 3 | from flask_bootstrap import Bootstrap 4 | 5 | bs = Bootstrap() 6 | 7 | 8 | def create_app(): 9 | app = Flask(__name__, 10 | static_folder='static', 11 | template_folder='templates' 12 | ) 13 | bs.init_app(app) 14 | from .views import bp 15 | app.register_blueprint(bp) 16 | return app 17 | -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_web/forms.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_10/task_tracker/tracker_web/forms.py -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_web/static/css/bootstrap-reboot.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.4.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | *, 9 | *::before, 10 | *::after { 11 | box-sizing: border-box; 12 | } 13 | 14 | html { 15 | font-family: sans-serif; 16 | line-height: 1.15; 17 | -webkit-text-size-adjust: 100%; 18 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 19 | } 20 | 21 | article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { 22 | display: block; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 28 | font-size: 1rem; 29 | font-weight: 400; 30 | line-height: 1.5; 31 | color: #212529; 32 | text-align: left; 33 | background-color: #fff; 34 | } 35 | 36 | [tabindex="-1"]:focus:not(:focus-visible) { 37 | outline: 0 !important; 38 | } 39 | 40 | hr { 41 | box-sizing: content-box; 42 | height: 0; 43 | overflow: visible; 44 | } 45 | 46 | h1, h2, h3, h4, h5, h6 { 47 | margin-top: 0; 48 | margin-bottom: 0.5rem; 49 | } 50 | 51 | p { 52 | margin-top: 0; 53 | margin-bottom: 1rem; 54 | } 55 | 56 | abbr[title], 57 | abbr[data-original-title] { 58 | text-decoration: underline; 59 | -webkit-text-decoration: underline dotted; 60 | text-decoration: underline dotted; 61 | cursor: help; 62 | border-bottom: 0; 63 | -webkit-text-decoration-skip-ink: none; 64 | text-decoration-skip-ink: none; 65 | } 66 | 67 | address { 68 | margin-bottom: 1rem; 69 | font-style: normal; 70 | line-height: inherit; 71 | } 72 | 73 | ol, 74 | ul, 75 | dl { 76 | margin-top: 0; 77 | margin-bottom: 1rem; 78 | } 79 | 80 | ol ol, 81 | ul ul, 82 | ol ul, 83 | ul ol { 84 | margin-bottom: 0; 85 | } 86 | 87 | dt { 88 | font-weight: 700; 89 | } 90 | 91 | dd { 92 | margin-bottom: .5rem; 93 | margin-left: 0; 94 | } 95 | 96 | blockquote { 97 | margin: 0 0 1rem; 98 | } 99 | 100 | b, 101 | strong { 102 | font-weight: bolder; 103 | } 104 | 105 | small { 106 | font-size: 80%; 107 | } 108 | 109 | sub, 110 | sup { 111 | position: relative; 112 | font-size: 75%; 113 | line-height: 0; 114 | vertical-align: baseline; 115 | } 116 | 117 | sub { 118 | bottom: -.25em; 119 | } 120 | 121 | sup { 122 | top: -.5em; 123 | } 124 | 125 | a { 126 | color: #007bff; 127 | text-decoration: none; 128 | background-color: transparent; 129 | } 130 | 131 | a:hover { 132 | color: #0056b3; 133 | text-decoration: underline; 134 | } 135 | 136 | a:not([href]) { 137 | color: inherit; 138 | text-decoration: none; 139 | } 140 | 141 | a:not([href]):hover { 142 | color: inherit; 143 | text-decoration: none; 144 | } 145 | 146 | pre, 147 | code, 148 | kbd, 149 | samp { 150 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 151 | font-size: 1em; 152 | } 153 | 154 | pre { 155 | margin-top: 0; 156 | margin-bottom: 1rem; 157 | overflow: auto; 158 | } 159 | 160 | figure { 161 | margin: 0 0 1rem; 162 | } 163 | 164 | img { 165 | vertical-align: middle; 166 | border-style: none; 167 | } 168 | 169 | svg { 170 | overflow: hidden; 171 | vertical-align: middle; 172 | } 173 | 174 | table { 175 | border-collapse: collapse; 176 | } 177 | 178 | caption { 179 | padding-top: 0.75rem; 180 | padding-bottom: 0.75rem; 181 | color: #6c757d; 182 | text-align: left; 183 | caption-side: bottom; 184 | } 185 | 186 | th { 187 | text-align: inherit; 188 | } 189 | 190 | label { 191 | display: inline-block; 192 | margin-bottom: 0.5rem; 193 | } 194 | 195 | button { 196 | border-radius: 0; 197 | } 198 | 199 | button:focus { 200 | outline: 1px dotted; 201 | outline: 5px auto -webkit-focus-ring-color; 202 | } 203 | 204 | input, 205 | button, 206 | select, 207 | optgroup, 208 | textarea { 209 | margin: 0; 210 | font-family: inherit; 211 | font-size: inherit; 212 | line-height: inherit; 213 | } 214 | 215 | button, 216 | input { 217 | overflow: visible; 218 | } 219 | 220 | button, 221 | select { 222 | text-transform: none; 223 | } 224 | 225 | select { 226 | word-wrap: normal; 227 | } 228 | 229 | button, 230 | [type="button"], 231 | [type="reset"], 232 | [type="submit"] { 233 | -webkit-appearance: button; 234 | } 235 | 236 | button:not(:disabled), 237 | [type="button"]:not(:disabled), 238 | [type="reset"]:not(:disabled), 239 | [type="submit"]:not(:disabled) { 240 | cursor: pointer; 241 | } 242 | 243 | button::-moz-focus-inner, 244 | [type="button"]::-moz-focus-inner, 245 | [type="reset"]::-moz-focus-inner, 246 | [type="submit"]::-moz-focus-inner { 247 | padding: 0; 248 | border-style: none; 249 | } 250 | 251 | input[type="radio"], 252 | input[type="checkbox"] { 253 | box-sizing: border-box; 254 | padding: 0; 255 | } 256 | 257 | input[type="date"], 258 | input[type="time"], 259 | input[type="datetime-local"], 260 | input[type="month"] { 261 | -webkit-appearance: listbox; 262 | } 263 | 264 | textarea { 265 | overflow: auto; 266 | resize: vertical; 267 | } 268 | 269 | fieldset { 270 | min-width: 0; 271 | padding: 0; 272 | margin: 0; 273 | border: 0; 274 | } 275 | 276 | legend { 277 | display: block; 278 | width: 100%; 279 | max-width: 100%; 280 | padding: 0; 281 | margin-bottom: .5rem; 282 | font-size: 1.5rem; 283 | line-height: inherit; 284 | color: inherit; 285 | white-space: normal; 286 | } 287 | 288 | progress { 289 | vertical-align: baseline; 290 | } 291 | 292 | [type="number"]::-webkit-inner-spin-button, 293 | [type="number"]::-webkit-outer-spin-button { 294 | height: auto; 295 | } 296 | 297 | [type="search"] { 298 | outline-offset: -2px; 299 | -webkit-appearance: none; 300 | } 301 | 302 | [type="search"]::-webkit-search-decoration { 303 | -webkit-appearance: none; 304 | } 305 | 306 | ::-webkit-file-upload-button { 307 | font: inherit; 308 | -webkit-appearance: button; 309 | } 310 | 311 | output { 312 | display: inline-block; 313 | } 314 | 315 | summary { 316 | display: list-item; 317 | cursor: pointer; 318 | } 319 | 320 | template { 321 | display: none; 322 | } 323 | 324 | [hidden] { 325 | display: none !important; 326 | } 327 | /*# sourceMappingURL=bootstrap-reboot.css.map */ -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_web/static/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.4.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_web/templates/base.html: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap/base.html" %} 2 | {% block title %} TaskTracker {% endblock %} 3 | 4 | -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_web/templates/tasks_view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bender Sandbox Monitoring 6 | 7 | 8 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {% for container in containers %} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 50 | 51 | {% endfor %} 52 |
Branch_IDUser_IDImage_IDImage_CreatedContainer_IDContainer_CreatedContainer_StatusLinkClick to delete
{{ container.get("branch_id") }}{{ container.get("user_id") }}{{ container.get("image_id") }}{{ container.get("image_dttm") }}{{ container.get("container_id") }}{{ container.get("container_dttm") }}{{ container.get("container_status") }}{{ container.get('link') }} 46 | 49 |
53 | 54 | -------------------------------------------------------------------------------- /lecture_10/task_tracker/tracker_web/views.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request, render_template 2 | 3 | 4 | bp = Blueprint('web', __name__, 5 | static_folder="static", 6 | template_folder="templates" 7 | ) 8 | 9 | 10 | @bp.route('/home') 11 | def homepage(): 12 | return render_template('base.html') 13 | -------------------------------------------------------------------------------- /lecture_11/img/abstract factory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/abstract factory.png -------------------------------------------------------------------------------- /lecture_11/img/adapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/adapter.png -------------------------------------------------------------------------------- /lecture_11/img/bridge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/bridge.png -------------------------------------------------------------------------------- /lecture_11/img/bridge1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/bridge1.png -------------------------------------------------------------------------------- /lecture_11/img/bridge2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/bridge2.png -------------------------------------------------------------------------------- /lecture_11/img/builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/builder.png -------------------------------------------------------------------------------- /lecture_11/img/command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/command.png -------------------------------------------------------------------------------- /lecture_11/img/command1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/command1.png -------------------------------------------------------------------------------- /lecture_11/img/composite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/composite.png -------------------------------------------------------------------------------- /lecture_11/img/decorator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/decorator.png -------------------------------------------------------------------------------- /lecture_11/img/decorator1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/decorator1.png -------------------------------------------------------------------------------- /lecture_11/img/decorator2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/decorator2.png -------------------------------------------------------------------------------- /lecture_11/img/facade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/facade.png -------------------------------------------------------------------------------- /lecture_11/img/factory method.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/factory method.png -------------------------------------------------------------------------------- /lecture_11/img/flyweight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/flyweight.png -------------------------------------------------------------------------------- /lecture_11/img/hierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/hierarchy.png -------------------------------------------------------------------------------- /lecture_11/img/iterator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/iterator.png -------------------------------------------------------------------------------- /lecture_11/img/mediator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/mediator.png -------------------------------------------------------------------------------- /lecture_11/img/observer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/observer.png -------------------------------------------------------------------------------- /lecture_11/img/prototype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/prototype.png -------------------------------------------------------------------------------- /lecture_11/img/proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/proxy.png -------------------------------------------------------------------------------- /lecture_11/img/state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/state.png -------------------------------------------------------------------------------- /lecture_11/img/strategy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/strategy.png -------------------------------------------------------------------------------- /lecture_11/img/visitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/visitor.png -------------------------------------------------------------------------------- /lecture_11/img/why.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_11/img/why.jpg -------------------------------------------------------------------------------- /lecture_12/03. Typing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Модуль typing\n", 8 | "\n", 9 | "https://habr.com/ru/company/lamoda/blog/432656/\n", 10 | "\n", 11 | "Создан для того, чтобы отслеживать правильность типов передаваемых аргументов в функции. Аннотации функций появились в Python 3.6, модуль typing - в 3.7.\n", 12 | "\n", 13 | "Аннотации типов призваны помочь программисту быстрее понять сигнатуру функции, но не предотвращают ошибок в передаваемых данных. Также они поддерживаются PyCharm'ом и линтерами." 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": { 20 | "scrolled": true 21 | }, 22 | "outputs": [], 23 | "source": [ 24 | "def upper(s: str) -> str:\n", 25 | " print(\"entered\")\n", 26 | " return s.upper()\n", 27 | "\n", 28 | "upper(12342)" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "Установим популярный статический анализатор mypy:" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "metadata": {}, 42 | "outputs": [], 43 | "source": [ 44 | "!pip install mypy" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "Код из предыдущей ячейки записан в файле 07/typing1.py. Попробуем прогнать его через mypy:" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": null, 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "!mypy 07/typing1.py" 61 | ] 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "metadata": {}, 66 | "source": [ 67 | "### Алиасы типов\n", 68 | "\n", 69 | "Благодаря возможностям библиотеки typing, можно часто используемые составные типы записывать в переменные:" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "from typing import List\n", 79 | "Vector = List[float]\n", 80 | "\n", 81 | "def scale(scalar: float, vector: Vector) -> Vector:\n", 82 | " return [scalar * num for num in vector]\n", 83 | "\n", 84 | "\n", 85 | "scale(2, [1, -4.2, 5.2])" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": {}, 91 | "source": [ 92 | "## Структуры типов модуля typing\n", 93 | "\n", 94 | "- `Any` - произвольный тип\n", 95 | "- `List[int]` - список, который содержит только один тип данных int\n", 96 | "- `Tuple[int, str]` - кортеж, который может содержать несколько типов данных\n", 97 | "- `Union[str, bytes]` - можно подавать либо строку, либо bytes\n", 98 | "- `Callable[[int, int], float]` - вызываемый объект, который на вход принимает два аргумента int, а возвращает float\n", 99 | "- `Iterable[T]` - Iterable со значениями типа T\n", 100 | "- `Mapping[K, V]`, `Dict[K, V]` - словарь с ключами типа K и значениями типа V\n", 101 | "- `Awaitable[T_co]`\n", 102 | "- `Type[T]` - тип/класс\n", 103 | "\n", 104 | "\n", 105 | "- `Optional[T]` - показывает, что переменная может быть None\n", 106 | "\n", 107 | "### Optional\n", 108 | "\n", 109 | "По умолчанию аннотированный тип не может быть None:" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "from typing import Optional\n", 119 | "\n", 120 | "amount: int\n", 121 | "amount = None # Incompatible types in assignment (expression has type \"None\", variable has type \"int\")\n", 122 | "\n", 123 | "price: Optional[int]\n", 124 | "price = None" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [ 133 | "!mypy -c \"amount: int = None\"" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "metadata": {}, 140 | "outputs": [], 141 | "source": [ 142 | "!mypy -c \"from typing import Optional; amount: Optional[int] = None\"" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "### Any\n", 150 | "\n", 151 | "Если мы берем на себя ручную обработку типов, можем аннотировать переменную классом Any, тогда она сможет принимать любые значения" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": null, 157 | "metadata": {}, 158 | "outputs": [], 159 | "source": [ 160 | "from typing import Any\n", 161 | "\n", 162 | "amount: Any\n", 163 | "amount = 1\n", 164 | "amount = \"Some\"\n", 165 | "amount = None" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "metadata": {}, 172 | "outputs": [], 173 | "source": [ 174 | "!mypy -c \"from typing import Any; amount: Any; amount = 1; amount = 'Some'; amount = None\"" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": {}, 180 | "source": [ 181 | "### Union\n", 182 | "\n", 183 | "Предназначен для случаев, когда можно использовать только некоторые типы" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "from typing import Union, Dict\n", 193 | "\n", 194 | "def some_func(arg: Union[Dict[str, str], str]) -> int:\n", 195 | " return len(arg)\n", 196 | "\n", 197 | "\n", 198 | "some_func({\"a\": \"b\"})\n", 199 | "some_func(\"abc\")\n", 200 | "some_func({\"a\": 1})" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": null, 206 | "metadata": {}, 207 | "outputs": [], 208 | "source": [ 209 | "!mypy 07/typing2.py" 210 | ] 211 | }, 212 | { 213 | "cell_type": "markdown", 214 | "metadata": {}, 215 | "source": [ 216 | "## Generic-типы\n", 217 | "\n", 218 | "Иногда нужно просто указать, что данные должны быть однотипными, без жесткой фиксации типов. Для этого используется TypeVar:" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": null, 224 | "metadata": {}, 225 | "outputs": [], 226 | "source": [ 227 | "from typing import TypeVar, Generic\n", 228 | "\n", 229 | "T = TypeVar(\"T\")\n", 230 | "\n", 231 | "class LinkedList(Generic[T]):\n", 232 | " data: T\n", 233 | " next: \"LinkedList[T]\"\n", 234 | "\n", 235 | " def __init__(self, data: T):\n", 236 | " self.data = data\n", 237 | "\n", 238 | "head_int: LinkedList[int] = LinkedList(1)\n", 239 | "head_int.next = LinkedList(2)\n", 240 | "head_int.next = 2 # error: Incompatible types in assignment (expression has type \"int\", variable has type \"LinkedList[int]\")\n", 241 | "head_int.data += 1\n", 242 | "head_int.data.replace(\"0\", \"1\") # error: \"int\" has no attribute \"replace\"\n", 243 | "\n", 244 | "head_str: LinkedList[str] = LinkedList(\"1\")\n", 245 | "head_str.data.replace(\"0\", \"1\")\n", 246 | "\n", 247 | "head_str = LinkedList[str](1) # error: Argument 1 to \"LinkedList\" has incompatible type \"int\"; expected \"str\"" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": null, 253 | "metadata": {}, 254 | "outputs": [], 255 | "source": [ 256 | "!mypy 07/typing3.py" 257 | ] 258 | }, 259 | { 260 | "cell_type": "markdown", 261 | "metadata": {}, 262 | "source": [ 263 | "Еще один пример - пара объектов для любых конкретных типов:" 264 | ] 265 | }, 266 | { 267 | "cell_type": "code", 268 | "execution_count": null, 269 | "metadata": {}, 270 | "outputs": [], 271 | "source": [ 272 | "from typing import TypeVar, Generic\n", 273 | "\n", 274 | "K = TypeVar('K')\n", 275 | "V = TypeVar('V')\n", 276 | "\n", 277 | "class Pair(Generic[K, V]):\n", 278 | " def __init__(self, key: K, value: V):\n", 279 | " self._key = key\n", 280 | " self._value = value\n", 281 | "\n", 282 | " @property\n", 283 | " def key(self) -> K:\n", 284 | " return self._key\n", 285 | "\n", 286 | " @property\n", 287 | " def value(self) -> V:\n", 288 | " return self._value\n", 289 | "\n", 290 | "\n", 291 | "class IntPair(Pair[int, int]):\n", 292 | " pass\n", 293 | "\n", 294 | "p = IntPair(\"1\", \"2\")" 295 | ] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "metadata": {}, 300 | "source": [ 301 | "## Cast\n", 302 | "\n", 303 | "Иногда статический анализатор не может однозначно определить тип переменной. Чтобы показать анализатору, что возвращается действительно заявленный тип, можно в коде использовать функцию cast." 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": null, 309 | "metadata": {}, 310 | "outputs": [], 311 | "source": [ 312 | "from typing import List, cast\n", 313 | "\n", 314 | "def find_first_str(a: List[object]) -> str:\n", 315 | " index = next(i for i, x in enumerate(a) if isinstance(x, str))\n", 316 | " return cast(str, a[index])" 317 | ] 318 | }, 319 | { 320 | "cell_type": "markdown", 321 | "metadata": {}, 322 | "source": [ 323 | "Это полезно для декораторов, поскольку анализатору может быть непонятно, что представляет собой обобщенный wrapper:" 324 | ] 325 | }, 326 | { 327 | "cell_type": "code", 328 | "execution_count": null, 329 | "metadata": {}, 330 | "outputs": [], 331 | "source": [ 332 | "MyCallable = TypeVar(\"MyCallable\", bound=Callable)\n", 333 | "\n", 334 | "def logged(func: MyCallable) -> MyCallable:\n", 335 | " @wraps(func)\n", 336 | " def wrapper(*args, **kwargs):\n", 337 | " print(func.__name__, args, kwargs)\n", 338 | " return func(*args, **kwargs)\n", 339 | "\n", 340 | " return cast(MyCallable, wrapper)\n", 341 | "\n", 342 | "@logged\n", 343 | "def mysum(a: int, b: int) -> int:\n", 344 | " return a + b\n", 345 | "\n", 346 | "mysum(a=1) # error: Missing positional argument \"b\" in call to \"mysum\"" 347 | ] 348 | } 349 | ], 350 | "metadata": { 351 | "kernelspec": { 352 | "display_name": "Python 3", 353 | "language": "python", 354 | "name": "python3" 355 | }, 356 | "language_info": { 357 | "codemirror_mode": { 358 | "name": "ipython", 359 | "version": 3 360 | }, 361 | "file_extension": ".py", 362 | "mimetype": "text/x-python", 363 | "name": "python", 364 | "nbconvert_exporter": "python", 365 | "pygments_lexer": "ipython3", 366 | "version": "3.6.5" 367 | } 368 | }, 369 | "nbformat": 4, 370 | "nbformat_minor": 2 371 | } 372 | -------------------------------------------------------------------------------- /lecture_12/07/typing1.py: -------------------------------------------------------------------------------- 1 | def upper(s: str) -> str: 2 | print("entered") 3 | return s.upper() 4 | 5 | upper(12342) -------------------------------------------------------------------------------- /lecture_12/07/typing2.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Dict 2 | 3 | def some_func(arg: Union[Dict[str, str], str]) -> int: 4 | return len(arg) 5 | 6 | 7 | some_func({"a": "b"}) 8 | some_func("abc") 9 | some_func({"a": 1}) -------------------------------------------------------------------------------- /lecture_12/07/typing3.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic 2 | 3 | T = TypeVar("T") 4 | 5 | class LinkedList(Generic[T]): 6 | data: T 7 | next: "LinkedList[T]" 8 | 9 | def __init__(self, data: T): 10 | self.data = data 11 | 12 | head_int: LinkedList[int] = LinkedList(1) 13 | head_int.next = LinkedList(2) 14 | head_int.next = 2 # error: Incompatible types in assignment (expression has type "int", variable has type "LinkedList[int]") 15 | head_int.data += 1 16 | head_int.data.replace("0", "1") # error: "int" has no attribute "replace" 17 | 18 | head_str: LinkedList[str] = LinkedList("1") 19 | head_str.data.replace("0", "1") 20 | 21 | head_str = LinkedList[str](1) # error: Argument 1 to "LinkedList" has incompatible type "int"; expected "str" -------------------------------------------------------------------------------- /lecture_12/08/thread.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_12/08/thread.svg -------------------------------------------------------------------------------- /lecture_12/09/wheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_12/09/wheel.png -------------------------------------------------------------------------------- /lecture_13/L13.md: -------------------------------------------------------------------------------- 1 | # Lecture 13 - continuous integration & delivery, devops 2 | 3 | ## Continuous Integration (CI) 4 | Переводят как `непрерывная интеграция`, и зачастую вредно воспринимать перевод буквально. 5 | В произвольный момент времени мы всегда имеем актуальный код, в котором косяки были минимализированы 6 | 7 | Концепцию популяризовал Фаулер (2006), но зародилась она в 90х (Kent Beck's XP, Grady Booch): 8 | https://martinfowler.com/articles/continuousIntegration.html 9 | ``` 10 | Continuous Integration (CI) is a development practice that requires developers to integrate code 11 | into a shared repository several times a day. Each check-in is then verified by an automated build, 12 | allowing teams to detect problems early. 13 | ``` 14 | 15 | *Just to recap - пирамида тестов* 16 | дороговизна, окупаемость, число тестов. 17 | [Q] - как оно бьется на картинке? 18 | ![pyramid](img/TestingTriangle.png) 19 | 20 | Unit тесты - тестируем работу класса без взаимодействия с другими компонентами системы 21 | 22 | Integration тесты - проверяем корректность совместной работы нескольких модулей 23 | 24 | Acceptance Tests (AT) - приемочные тесты, описывающие требования и ожидания 25 | * Спецификации - cucumber и прочие BDD-инструменты 26 | * UI тесты (Selenium) 27 | 28 | 29 | ### Принципы CI 30 | *Maintain a single source repository* 31 | Сейчас это норма жизни, есть бесплатные github, gitlab, bitbucket, .... 32 | Также доступно и для on-premise - gitlab например. 33 | 34 | *Automate the build* 35 | Больше актуально для древних компилируемых языков. 36 | Сделать make-скрипт, который как надо компилирует. 37 | Либо настроить систему сборки (CMake, Maven, ...) 38 | 39 | *Make your build self-testing* 40 | Иметь под рукой набор тестов - юнит, интеграционных, ... 41 | Хоть что-нибудь, что позволило бы предотвращать появление нерабочего кода, банальных косяков, наступление на старые ошибки и тд 42 | 43 | *Every commit should build on an integration machine* 44 | Делайте коммиты чаще, избегайте мега-коммитов. 45 | Так будет проще отслеживать историю изменений, без потерь разрешать конфликты. 46 | 47 | *Keep the build fast* 48 | Обычно всегда соблюдается, пока в вашем проекте условно не станет > ~1M строк кода. 49 | Например, 30 мин сборки android яндекс-браузера - https://m.habr.com/en/company/yandex/blog/343278/ 50 | Решается кластерной сборочной системой и сборочным DAG 51 | 52 | *Test in a clone of the production environment* 53 | Звучит очевидно. Всегда стараться поддерживать однообразие среды. 54 | Docker, Kubernetes & Openshift 55 | 56 | *Make it easy for anyone to get the latest executable version* 57 | Используем хранилище сборочных артефактов - nexus, gitlab, artifactory, ... 58 | 59 | *Everyone can see what’s happening* 60 | Следствие предыдущих пунктов. Есть SCM, сборочная система, артефактори - все прозрачно и доступно 61 | 62 | *Automate deployment* 63 | Если нужно производить acceptance-тесты в разных средах, неплохо бы сделать развертывание этих сред воспроизводимым. 64 | 65 | ### Внедрение CI в работу 66 | Без 100% принятия среди команды, CI сможет принести не только плоды, но и дополнительные проблемки. 67 | * жирные коммиты тяжело ревьюить 68 | * если некто не прогнал/отключил тесты перед пушем в репу 69 | * если деплой упал, а на часах 17:58 (я осьминог, пришел в 9 и мне пора домой) 70 | 71 | Решается через командные "ритуалы", перфоманс ревью, и тд. 72 | 73 | Отличный инструмент *Git hooks*, и примеры использования 74 | * commit-msg - валидация комментария коммита 75 | * pre-commit - выполнить все подготовительные действия (линтеры, ...) и проверки - перед совершением коммита 76 | https://pre-commit.com/ 77 | * post-checkout - проверка branch naming 78 | 79 | ## Continuous delivery, continuous deployment 80 | Воспроизводимый процесс доставки новых версий проекта (по кнопке). 81 | Частый паттерн - как только все тестирования и ревью были проведены, изменения принимаются в main-ветвь. 82 | В зависимости от объема изменений в новой версии, эти термины различаются: 83 | * delivery - развертывание автоматизировано и доступен по кнопке (релиз) 84 | * deployment - развертывание происходит сразу же после одобрения коммита 85 | требует практически идеального QA и ведения документации. 86 | 87 | ## CI/CD + SCM 88 | Рассмотрим Gitlab CI 89 | https://docs.gitlab.com/ee/development/architecture.html 90 | 91 | Концепты 92 | https://docs.gitlab.com/ee/ci/pipelines/pipeline_architectures.html 93 | 94 | Описание файла конфигурации 95 | https://docs.gitlab.com/ee/ci/yaml/README.html#configuration-parameters 96 | 97 | 98 | ## Менеджер артефактов - Nexus 99 | https://www.sonatype.com/product-nexus-repository 100 | https://hub.docker.com/r/sonatype/nexus3/ 101 | 102 | ## systemd 103 | https://www.freedesktop.org/software/systemd/man/systemd.unit.html 104 | 105 | ## Real case study 106 | https://gitlab.com/lancerx/python_lesson_13 107 | -------------------------------------------------------------------------------- /lecture_13/img/TestingTriangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_13/img/TestingTriangle.png -------------------------------------------------------------------------------- /lecture_13/matrix/matrixapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_13/matrix/matrixapp/__init__.py -------------------------------------------------------------------------------- /lecture_13/matrix/matrixapp/actors.py: -------------------------------------------------------------------------------- 1 | class Actor: 2 | msg_id = None 3 | fallback = 'not talking with strangers' 4 | 5 | def interact(self, *args, **kwargs): 6 | raise NotImplementedError 7 | 8 | 9 | class WhiteRabbit(Actor): 10 | msg_id = 'white_rabbit' 11 | 12 | def disable(self): 13 | self.enabled = not self.enabled 14 | 15 | def __str__(self): 16 | return f'WhiteRabbit girl: (enabled={self.enabled})' 17 | 18 | def __init__(self): 19 | self.enabled = False 20 | self.name = 'White Rabbit' 21 | self.job = 'party girl' 22 | self.is_awaken = False 23 | self.people_to_search = None 24 | 25 | def interact(self, actor: Actor): 26 | if actor.msg_id == Neo.msg_id: 27 | return 'Definitely' 28 | else: 29 | return self.fallback 30 | 31 | 32 | class Neo(Actor): 33 | msg_id = 'neo' 34 | 35 | def disable(self): 36 | self.enabled = not self.enabled 37 | 38 | def __str__(self): 39 | return f'Neo: (enabled={self.enabled})' 40 | 41 | def __init__(self): 42 | self.enabled = False 43 | self.name = 'Thomas Anderson' 44 | self.job = 'program writer' 45 | self.is_awaken = False 46 | self.people_to_search = ['Morpheus'] 47 | 48 | def sleep(self): 49 | if not self.is_awaken: 50 | return 'Hardly' 51 | else: 52 | return 'Good' 53 | 54 | def fly(self): 55 | if self.job != 'program writer': 56 | return True 57 | else: 58 | return False 59 | 60 | def interact(self, actor: Actor): 61 | if actor.msg_id == WhiteRabbit.msg_id: 62 | return 'I have to work tomorrow' 63 | elif actor.msg_id == Morpheus.msg_id: 64 | return 'Thought it wasnt real' 65 | else: 66 | return self.fallback 67 | 68 | 69 | class Morpheus(Actor): 70 | msg_id = 'morpheus' 71 | 72 | def __str__(self): 73 | return f'Morpheus: (enabled={self.enabled})' 74 | 75 | def disable(self): 76 | self.enabled = not self.enabled 77 | 78 | def __init__(self): 79 | self.enabled = False 80 | self.name = 'Morpheus' 81 | self.job = 'terrorist' 82 | self.is_awaken = True 83 | self.people_to_search = ['Neo'] 84 | 85 | def interact(self, actor: Actor): 86 | if actor.msg_id == Neo.msg_id: 87 | return 'Your mind makes it real' 88 | else: 89 | return self.fallback 90 | 91 | 92 | -------------------------------------------------------------------------------- /lecture_13/matrix/matrixapp/app.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from flask import Flask 3 | from werkzeug.serving import run_simple 4 | 5 | 6 | def create_app(): 7 | app = Flask(__name__) 8 | from .views import bp 9 | 10 | app.register_blueprint(bp) 11 | return app 12 | 13 | 14 | if __name__ == '__main__': 15 | run_simple('0.0.0.0', 5000, create_app()) 16 | -------------------------------------------------------------------------------- /lecture_13/matrix/matrixapp/views.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import inspect 3 | from flask import Blueprint 4 | 5 | import matrixapp.actors as actors 6 | 7 | bp = Blueprint('matrix', __name__, url_prefix='/') 8 | 9 | class_map = {cls.msg_id: cls() 10 | for cls_name, cls in inspect.getmembers(actors, inspect.isclass) 11 | if cls.msg_id 12 | } 13 | 14 | 15 | @bp.route('/') 16 | @bp.route('//meets/') 17 | def main_view(actor_id, companion=None): 18 | if actor_id in class_map: 19 | if not companion: 20 | return str(class_map[actor_id]) 21 | else: 22 | return class_map[actor_id].interact(class_map[companion]) 23 | else: 24 | return 404, 'Actor not found' 25 | -------------------------------------------------------------------------------- /lecture_13/matrix/tests/actors.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from lecture_13.matrix.matrixapp.actors import Neo, Morpheus, WhiteRabbit 3 | 4 | 5 | class TestActors(unittest.TestCase): 6 | def setUp(self) -> None: 7 | self.neo = Neo() 8 | self.morpheus = Morpheus() 9 | self.white_rabbit = WhiteRabbit() 10 | 11 | def test_neo_morpheus(self): 12 | """ 13 | Replica of Neo with Morpheus 14 | """ 15 | self.assertIsNotNone(self.neo.interact(self.morpheus)) 16 | 17 | def test_morpheus_wr(self): 18 | """ 19 | Replica of Morpheus with WhiteRabbit 20 | """ 21 | self.assertRegex(self.morpheus.interact(self.white_rabbit), 'strangers') 22 | 23 | def test_wr_neo(self): 24 | """ 25 | Replica of WhiteRabbit with Neo 26 | """ 27 | self.assertIsNotNone(self.white_rabbit.interact(self.neo)) 28 | 29 | def tearDown(self) -> None: 30 | del self.neo 31 | del self.morpheus 32 | del self.white_rabbit 33 | -------------------------------------------------------------------------------- /lecture_2/06. Generators.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "colab_type": "text", 7 | "id": "NaZJVtw1iyEU" 8 | }, 9 | "source": [ 10 | "# Генераторы и ленивые вычисления" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": { 16 | "colab_type": "text", 17 | "id": "URnSv8ibiyEW" 18 | }, 19 | "source": [ 20 | "В Python просто **генераторы** (*generator*) и **генераторы списков** (*list comprehension*) - разные вещи. Рассмотрим и то, и другое." 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": { 26 | "colab_type": "text", 27 | "id": "rgiQ3Or1iyEW" 28 | }, 29 | "source": [ 30 | "## Генераторы списков\n", 31 | "\n", 32 | "В Python генераторы списков позволяют создавать и быстро заполнять списки.\n", 33 | "\n", 34 | "Синтаксическая конструкция генератора списка предполагает наличие итерируемого объекта или итератора, на базе которого будет создаваться новый список, а также выражение, которое будет что-то делать с извлеченными из последовательности элементами перед тем как добавить их в формируемый список. " 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": { 41 | "colab": {}, 42 | "colab_type": "code", 43 | "id": "CvKmmPBSiyEX", 44 | "outputId": "2cd4db93-40f2-4608-8cab-157acb5cbc15" 45 | }, 46 | "outputs": [], 47 | "source": [ 48 | "a = [1, 2, 3]\n", 49 | "b = [i+10 for i in a]\n", 50 | "\n", 51 | "print(a)\n", 52 | "print(b)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": { 58 | "colab_type": "text", 59 | "id": "shNzUj_6iyEa" 60 | }, 61 | "source": [ 62 | "Здесь генератором списка является выражение `[i+10 for i in a]`.\n", 63 | "\n", 64 | "`a` - итерируемый объект. В данном случае это другой список.\\\n", 65 | "Из него извлекается каждый элемент в цикле for.\\\n", 66 | "Перед for описывается действие, которое выполняется над элементом перед его добавлением в новый список.\n", 67 | "\n", 68 | "В генератор списка можно добавить условие:" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": null, 74 | "metadata": { 75 | "colab": {}, 76 | "colab_type": "code", 77 | "id": "NcnulnksiyEa", 78 | "outputId": "a9986a0d-4aef-46ec-8e66-1f2859e0ee03" 79 | }, 80 | "outputs": [], 81 | "source": [ 82 | "from random import randint\n", 83 | "\n", 84 | "nums = [randint(10, 20) for i in range(10)]\n", 85 | "print(nums)\n", 86 | "\n", 87 | "nums = [i for i in nums if i%2 == 0]\n", 88 | "print(nums)" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": { 94 | "colab_type": "text", 95 | "id": "X1XwV-J7iyEc" 96 | }, 97 | "source": [ 98 | "Генераторы списков могут содержать вложенные циклы:" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": null, 104 | "metadata": { 105 | "colab": {}, 106 | "colab_type": "code", 107 | "id": "HzA3ylUwiyEc", 108 | "outputId": "a4965705-89c2-4d50-f955-99f6ffdf779c" 109 | }, 110 | "outputs": [], 111 | "source": [ 112 | "a = \"12\"\n", 113 | "b = \"3\"\n", 114 | "c = \"456\"\n", 115 | "\n", 116 | "comb = [i+j+k for i in a for j in b for k in c]\n", 117 | "print(comb)" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": { 123 | "colab_type": "text", 124 | "id": "3Zr4QTCaiyEe" 125 | }, 126 | "source": [ 127 | "### Генераторы словарей и множеств\n", 128 | "\n", 129 | "Если в выражении генератора списка заменить квадратные скобки на фигурные, то можно получить не список, а словарь.\n", 130 | "\n", 131 | "При этом синтаксис выражения до `for` должен быть соответствующий словарю, то есть включать ключ и через двоеточие значение. Если этого нет, будет сгенерировано множество." 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": { 138 | "colab": {}, 139 | "colab_type": "code", 140 | "id": "oWBWe1xHiyEf", 141 | "outputId": "8c81f542-9f5b-44aa-f093-e0d80dcaeea6" 142 | }, 143 | "outputs": [], 144 | "source": [ 145 | "a = {i:i**2 for i in range(11,15)}\n", 146 | "print(a)\n", 147 | "\n", 148 | "a = {i for i in range(11,15)}\n", 149 | "print(a)\n", 150 | "b = {1, 2, 3}\n", 151 | "print(b)" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "metadata": { 157 | "colab_type": "text", 158 | "id": "Y2a4aMApozEg" 159 | }, 160 | "source": [ 161 | "Примечание:\n", 162 | "\n", 163 | "В Python3 в словарях при использовании метода `.keys()`, `.values()` и `.items()` для доступа к ключам и значениям создаётся представление соответствующего элемента. По сути это представление является генератором. Копия данных не создаётся.\n", 164 | "\n", 165 | "Тип этих данных - dict_keys, dict_values, dict_items." 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "metadata": { 172 | "colab": {}, 173 | "colab_type": "code", 174 | "id": "rfH857mspGyQ" 175 | }, 176 | "outputs": [], 177 | "source": [ 178 | "my_dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}\n", 179 | "print(my_dict.keys())\n", 180 | "print(my_dict.values())\n", 181 | "print(my_dict.items())" 182 | ] 183 | }, 184 | { 185 | "cell_type": "markdown", 186 | "metadata": { 187 | "colab_type": "text", 188 | "id": "yx5_ThfQiyEg" 189 | }, 190 | "source": [ 191 | "## Генераторы\n", 192 | "\n", 193 | "Ленивые вычисления - стратегия вычисления, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат. Для ленивых вычислений нам потребуются генераторы.\n", 194 | "\n", 195 | "Выражения, создающие объекты-генераторы, похожи на выражения, генерирующие списки. Чтобы создать генераторный объект, надо использовать круглые скобки." 196 | ] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": null, 201 | "metadata": { 202 | "colab": { 203 | "base_uri": "https://localhost:8080/", 204 | "height": 136 205 | }, 206 | "colab_type": "code", 207 | "executionInfo": { 208 | "elapsed": 1438, 209 | "status": "ok", 210 | "timestamp": 1574937028745, 211 | "user": { 212 | "displayName": "Надежда Демиденко", 213 | "photoUrl": "https://lh3.googleusercontent.com/a-/AAuE7mA6D7k5OgtG9hzPe8Abs8DfOKAXQoTXaPfn7EY=s64", 214 | "userId": "05224310221243935536" 215 | }, 216 | "user_tz": -180 217 | }, 218 | "id": "fromjY0ajnqX", 219 | "outputId": "4007a616-d710-4dc1-d3e5-1fecafbe3eb2" 220 | }, 221 | "outputs": [], 222 | "source": [ 223 | "a = (i for i in range(2, 8))\n", 224 | "print(a)\n", 225 | "\n", 226 | "for i in a:\n", 227 | " print(i)" 228 | ] 229 | }, 230 | { 231 | "cell_type": "markdown", 232 | "metadata": { 233 | "colab_type": "text", 234 | "id": "XWMxc70Cknuh" 235 | }, 236 | "source": [ 237 | "Второй раз перебрать генератор в цикле for не получится, так как объект-генератор уже сгенерировал все значения по заложенной в него \"формуле\". Поэтому генераторы обычно используются, когда надо единожды пройтись по итерируемому объекту.\n", 238 | "\n", 239 | "Кроме того, генераторы экономят память, так как в ней хранятся не все значения, скажем, большого списка, а только предыдущий элемент, предел и формула, по которой вычисляется следующий элемент. \n", 240 | "\n", 241 | "Выражение, создающее генератор, это сокращенная запись следующего:\n", 242 | "\n" 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": null, 248 | "metadata": { 249 | "colab": { 250 | "base_uri": "https://localhost:8080/", 251 | "height": 85 252 | }, 253 | "colab_type": "code", 254 | "executionInfo": { 255 | "elapsed": 1136, 256 | "status": "ok", 257 | "timestamp": 1574937260584, 258 | "user": { 259 | "displayName": "Надежда Демиденко", 260 | "photoUrl": "https://lh3.googleusercontent.com/a-/AAuE7mA6D7k5OgtG9hzPe8Abs8DfOKAXQoTXaPfn7EY=s64", 261 | "userId": "05224310221243935536" 262 | }, 263 | "user_tz": -180 264 | }, 265 | "id": "49M-KQKckqS0", 266 | "outputId": "776c2c3a-ddfc-4e8e-b0f7-7f3bebb64784" 267 | }, 268 | "outputs": [], 269 | "source": [ 270 | "def func(start, finish):\n", 271 | " while start < finish:\n", 272 | " yield start * 0.33\n", 273 | " start += 1\n", 274 | "\n", 275 | "a = func(1, 4)\n", 276 | "print(a)\n", 277 | "\n", 278 | "for i in a:\n", 279 | " print(i)" 280 | ] 281 | }, 282 | { 283 | "cell_type": "markdown", 284 | "metadata": { 285 | "colab_type": "text", 286 | "id": "XWinvS2JlGnf" 287 | }, 288 | "source": [ 289 | "Функция, содержащая `yield`, возвращает объект-генератор, а не выполняет свой код сразу. Тело функции исполняется при каждом вызове метода `__next__()`. В цикле for это делается автоматически.\n", 290 | "\n", 291 | "При этом после выдачи результата командой yield состояние генератора, все его внутренние переменные сохраняются. При следующей попытке выдернуть элемент из генератора работа начнётся со строчки, следующей за yield." 292 | ] 293 | }, 294 | { 295 | "cell_type": "markdown", 296 | "metadata": { 297 | "colab_type": "text", 298 | "id": "EIQZJ2xxld2T" 299 | }, 300 | "source": [ 301 | "Пример генератора чисел Фибоначчи:" 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": null, 307 | "metadata": { 308 | "colab": { 309 | "base_uri": "https://localhost:8080/", 310 | "height": 85 311 | }, 312 | "colab_type": "code", 313 | "executionInfo": { 314 | "elapsed": 1351, 315 | "status": "ok", 316 | "timestamp": 1574937601981, 317 | "user": { 318 | "displayName": "Надежда Демиденко", 319 | "photoUrl": "https://lh3.googleusercontent.com/a-/AAuE7mA6D7k5OgtG9hzPe8Abs8DfOKAXQoTXaPfn7EY=s64", 320 | "userId": "05224310221243935536" 321 | }, 322 | "user_tz": -180 323 | }, 324 | "id": "oMYZrqnwlghq", 325 | "outputId": "e2c7a252-b05d-48be-a517-1af853e8a6ac" 326 | }, 327 | "outputs": [], 328 | "source": [ 329 | "def fibonacci(n):\n", 330 | " fib1, fib2 = 0, 1\n", 331 | " for i in range(n):\n", 332 | " fib1, fib2 = fib2, fib1 + fib2\n", 333 | " yield fib1\n", 334 | "\n", 335 | "for fib in fibonacci(20):\n", 336 | " print(fib, end=' ')\n", 337 | "\n", 338 | "print('Сумма первых 100 чисел Фибоначчи равна', sum(fibonacci(100)))\n", 339 | "print(list(fibonacci(16)))\n", 340 | "print([x*x for x in fibonacci(14)])" 341 | ] 342 | } 343 | ], 344 | "metadata": { 345 | "colab": { 346 | "name": "topic05.ipynb", 347 | "provenance": [] 348 | }, 349 | "kernelspec": { 350 | "display_name": "Python 3", 351 | "language": "python", 352 | "name": "python3" 353 | }, 354 | "language_info": { 355 | "codemirror_mode": { 356 | "name": "ipython", 357 | "version": 3 358 | }, 359 | "file_extension": ".py", 360 | "mimetype": "text/x-python", 361 | "name": "python", 362 | "nbconvert_exporter": "python", 363 | "pygments_lexer": "ipython3", 364 | "version": "3.6.5" 365 | } 366 | }, 367 | "nbformat": 4, 368 | "nbformat_minor": 1 369 | } 370 | -------------------------------------------------------------------------------- /lecture_2/ipynb_content/2vars2obj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/2vars2obj.png -------------------------------------------------------------------------------- /lecture_2/ipynb_content/arena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/arena.png -------------------------------------------------------------------------------- /lecture_2/ipynb_content/cbr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/cbr.jpg -------------------------------------------------------------------------------- /lecture_2/ipynb_content/cbv.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/cbv.jpg -------------------------------------------------------------------------------- /lecture_2/ipynb_content/collections.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/collections.pdf -------------------------------------------------------------------------------- /lecture_2/ipynb_content/hierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/hierarchy.png -------------------------------------------------------------------------------- /lecture_2/ipynb_content/machine.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/machine.gif -------------------------------------------------------------------------------- /lecture_2/ipynb_content/memlayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/memlayout.png -------------------------------------------------------------------------------- /lecture_2/ipynb_content/sequence.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/sequence.pdf -------------------------------------------------------------------------------- /lecture_2/ipynb_content/shallow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/shallow.png -------------------------------------------------------------------------------- /lecture_2/ipynb_content/stackoverflow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/stackoverflow.jpg -------------------------------------------------------------------------------- /lecture_2/ipynb_content/sticker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/sticker.png -------------------------------------------------------------------------------- /lecture_2/ipynb_content/zones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_2/ipynb_content/zones.png -------------------------------------------------------------------------------- /lecture_2/module.py: -------------------------------------------------------------------------------- 1 | def hello(): 2 | print('Hello, world!') 3 | 4 | def fib(n): 5 | a = b = 1 6 | for i in range(n - 2): 7 | a, b = b, a + b 8 | return b 9 | 10 | def ex(): 11 | if __name__ == "__main__": 12 | hello() 13 | for i in range(10): 14 | print(fib(i)) 15 | else: 16 | print("not in __main__") 17 | print(__name__) 18 | 19 | 20 | print("all run") -------------------------------------------------------------------------------- /lecture_3/ipynb_content/commit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_3/ipynb_content/commit.png -------------------------------------------------------------------------------- /lecture_3/ipynb_content/cycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_3/ipynb_content/cycle.png -------------------------------------------------------------------------------- /lecture_3/ipynb_content/distr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_3/ipynb_content/distr.png -------------------------------------------------------------------------------- /lecture_3/ipynb_content/plumbing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_3/ipynb_content/plumbing.png -------------------------------------------------------------------------------- /lecture_3/ipynb_content/shared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_3/ipynb_content/shared.png -------------------------------------------------------------------------------- /lecture_4/05/slots.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_4/05/slots.jpg -------------------------------------------------------------------------------- /lecture_4/06/class-creation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_4/06/class-creation.png -------------------------------------------------------------------------------- /lecture_4/06/factory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_4/06/factory.png -------------------------------------------------------------------------------- /lecture_4/06/instance-creation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_4/06/instance-creation.png -------------------------------------------------------------------------------- /lecture_4/06/interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_4/06/interface.png -------------------------------------------------------------------------------- /lecture_4/06/metaclass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_4/06/metaclass.png -------------------------------------------------------------------------------- /lecture_4/06/singleton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_4/06/singleton.png -------------------------------------------------------------------------------- /lecture_4/06/type_and_object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_4/06/type_and_object.png -------------------------------------------------------------------------------- /lecture_5/04/iterator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_5/04/iterator.png -------------------------------------------------------------------------------- /lecture_5/07/typing1.py: -------------------------------------------------------------------------------- 1 | def upper(s: str) -> str: 2 | print("entered") 3 | return s.upper() 4 | 5 | upper(12342) -------------------------------------------------------------------------------- /lecture_5/07/typing2.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Dict 2 | 3 | def some_func(arg: Union[Dict[str, str], str]) -> int: 4 | return len(arg) 5 | 6 | 7 | some_func({"a": "b"}) 8 | some_func("abc") 9 | some_func({"a": 1}) -------------------------------------------------------------------------------- /lecture_5/07/typing3.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic 2 | 3 | T = TypeVar("T") 4 | 5 | class LinkedList(Generic[T]): 6 | data: T 7 | next: "LinkedList[T]" 8 | 9 | def __init__(self, data: T): 10 | self.data = data 11 | 12 | head_int: LinkedList[int] = LinkedList(1) 13 | head_int.next = LinkedList(2) 14 | head_int.next = 2 # error: Incompatible types in assignment (expression has type "int", variable has type "LinkedList[int]") 15 | head_int.data += 1 16 | head_int.data.replace("0", "1") # error: "int" has no attribute "replace" 17 | 18 | head_str: LinkedList[str] = LinkedList("1") 19 | head_str.data.replace("0", "1") 20 | 21 | head_str = LinkedList[str](1) # error: Argument 1 to "LinkedList" has incompatible type "int"; expected "str" -------------------------------------------------------------------------------- /lecture_5/14/0_exception_hierarchy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_5/14/0_exception_hierarchy.jpg -------------------------------------------------------------------------------- /lecture_5/14/1_exception_classification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_5/14/1_exception_classification.png -------------------------------------------------------------------------------- /lecture_5/14/2_try_except.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_5/14/2_try_except.png -------------------------------------------------------------------------------- /lecture_5/14/3_raise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_5/14/3_raise.png -------------------------------------------------------------------------------- /lecture_5/14/4_else.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_5/14/4_else.png -------------------------------------------------------------------------------- /lecture_5/14/5_finally.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_5/14/5_finally.png -------------------------------------------------------------------------------- /lecture_5/homework2.md: -------------------------------------------------------------------------------- 1 | # # Домашка 2 2 | 3 | Вам нужно реализовать модуль, который будет собирать информацию по заболевшим коронавирусом =) 4 | 5 | ## Объект Patient 6 | 7 | Нужно хранить следующую информацию: 8 | 9 | - имя 10 | - фамилия 11 | - дата рождения 12 | - номер телефона 13 | - тип документа, удостоверяющего личность (паспорт, загран либо водительские права) 14 | - номер документа, удостоверяющего личность 15 | 16 | Эта информация должна быть доступна соответственно из полей: 17 | 18 | - first_name 19 | - last_name 20 | - birth_date 21 | - phone 22 | - document_type 23 | - document_id 24 | 25 | ### Создание объекта 26 | 27 | Объект больного должен поддерживать два способа создания: 28 | - через конструктор: `Patient("Кондрат", "Коловрат", "1978-01-31", "+7-916-000-00-00", "паспорт", "4514 000000")` 29 | - через метод create: `Patient.create("Кондрат", "Коловрат", "1978-01-31", "+7-916-000-00-00", "паспорт", "4514 000000")` 30 | 31 | Для каждого поля должна проводиться проверка правильности значения. При этом некоторые значения могут подаваться в разных вариантах, и их все нужно привести к единому виду. Например, телефон можно записать так: "89160000000", а можно "+7 (916) 000-00-00". Нужно учесть несколько вариантов. 32 | 33 | Создание нового пользователя должно быть залогировано в файл. Если на вход поданы некорректные по формату данные, вызывается соответствующее исключение. Любое исключение должно быть залогировано в отдельный лог с ошибками. 34 | 35 | ### Изменение объекта 36 | 37 | После создания объекта попытки изменить имя и фамилию должны вызывать ошибку (с логированием). Изменение остальных полей должно происходить с теми же проверками, что и в конструкторе. Логировать изменение поля также нужно (можно сделать общий лог для назначения поля извне и из `__init__`) 38 | 39 | ### Хранение объектов 40 | 41 | Хранить полученные записи нужно в csv-файле. Можно работать с ним средствами питона, а можно через бибилотеку pandas. У объекта Patient должен быть метод `save`, при вызове которого он дозаписывается в файл. Сохранение объекта должно логироваться в файл с успешными логами. Ошибки - в лог с ошибками. 42 | 43 | ## Объект PatientCollection 44 | 45 | Должен поддерживать следующий синтаксис: 46 | 47 | 48 | 49 | ```python 50 | collection = PatientCollection(path_to_file) 51 | for patient in collection: 52 | print(patient) 53 | ``` 54 | 55 | При этом patient должен быть объектом класса Patient. 56 | 57 | Также должен быть метод `limit`, который будет возвращать итератор/генератор первых n записей: 58 | 59 | ```python 60 | collection = PatientCollection(path_to_file) 61 | for patient in collection.limit(8): 62 | print(patient) 63 | ``` 64 | 65 | При ручном изменении файла во время исполнения этого кода на следующей итерации нужно выдавать уже новые значения. 66 | 67 | ## Проверка задания 68 | 69 | Я напилю скрипт, который проверяет основные моменты и выложу его на гитхаб. После того, как скрипт выдаст, что всё ок, можно делать пулл-реквест. 70 | -------------------------------------------------------------------------------- /lecture_6/01 Serialization.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "colab_type": "text", 7 | "id": "3saG1xD4-JfF" 8 | }, 9 | "source": [ 10 | "# Сериализация\n", 11 | "\n", 12 | "## Обработка конфигурационных файлов" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": { 18 | "colab_type": "text", 19 | "id": "M417rJFT-fG3" 20 | }, 21 | "source": [ 22 | "### json\n", 23 | "\n", 24 | "JSON (JavaScript Object Notation) - простой формат обмена данными, основанный на подмножестве синтаксиса JavaScript. Модуль json позволяет кодировать и декодировать данные в удобном формате.\n", 25 | "\n", 26 | "Некоторые возможности библиотеки **json**\n", 27 | "\n", 28 | "**json.dump**`(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)` - сериализует obj как форматированный JSON поток в fp.\n", 29 | "\n", 30 | "**json.dumps**`(obj, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)` - сериализует obj в строку JSON-формата.\n", 31 | "\n", 32 | "**json.load**`(fp, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)` - десериализует JSON из fp.\n", 33 | "\n", 34 | "**json.loads**`(s, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)` - десериализует s (экземпляр str, содержащий документ JSON) в объект Python." 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": { 41 | "colab": { 42 | "base_uri": "https://localhost:8080/", 43 | "height": 170 44 | }, 45 | "colab_type": "code", 46 | "executionInfo": { 47 | "elapsed": 976, 48 | "status": "ok", 49 | "timestamp": 1575465221468, 50 | "user": { 51 | "displayName": "Надежда Демиденко", 52 | "photoUrl": "https://lh3.googleusercontent.com/a-/AAuE7mA6D7k5OgtG9hzPe8Abs8DfOKAXQoTXaPfn7EY=s64", 53 | "userId": "05224310221243935536" 54 | }, 55 | "user_tz": -180 56 | }, 57 | "id": "gvxfsMJoi5C3", 58 | "outputId": "cf696a08-e536-4c05-b091-36e5947ea4f5" 59 | }, 60 | "outputs": [], 61 | "source": [ 62 | "import json\n", 63 | "\n", 64 | "# Кодирование основных объектов Python\n", 65 | "print(json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]))\n", 66 | "print(json.dumps({\"c\": 0, \"b\": 0, \"a\": 0}, sort_keys=True))\n", 67 | "\n", 68 | "# Компактное кодирование\n", 69 | "print(json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',', ':')))\n", 70 | "\n", 71 | "# Красивый вывод\n", 72 | "print(json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4))\n", 73 | "\n", 74 | "# Декодирование (парсинг) JSON\n", 75 | "print(json.loads('[\"foo\", {\"bar\":[\"baz\", null, 1.0, 2]}]'))\n", 76 | "print(json.loads('\"\\\\\"foo\\\\bar\"'))" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": { 82 | "colab_type": "text", 83 | "id": "ai6CKu-_A16t" 84 | }, 85 | "source": [ 86 | "### yaml\n", 87 | "\n", 88 | "YAML (YAML Ain’t Markup Language) - еще один текстовый формат для записи данных.\n", 89 | "\n", 90 | "YAML более приятен для восприятия человеком, чем JSON, поэтому его часто используют для описания сценариев в ПО. Например, в Ansible.\n", 91 | "\n", 92 | "Для работы с YAML в Python используется модуль **pyyaml**. Он не входит в стандартную библиотеку модулей, поэтому его нужно установить:\n", 93 | "\n", 94 | "`pip install pyyaml`" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "metadata": { 101 | "colab": {}, 102 | "colab_type": "code", 103 | "id": "F8gvhIQ8l5ZN", 104 | "outputId": "f10539be-c56f-4a90-ac6a-772c211cf20a" 105 | }, 106 | "outputs": [], 107 | "source": [ 108 | "# Чтение из YAML (файл info.yaml)\n", 109 | "\n", 110 | "import yaml\n", 111 | "from pprint import pprint # Модуль pprint позволяет красиво отображать объекты Python\n", 112 | "\n", 113 | "with open('info.yaml') as f:\n", 114 | " templates = yaml.safe_load(f)\n", 115 | "\n", 116 | "pprint(templates) # Использование функции модуля pprint для вывода" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "metadata": { 123 | "colab": {}, 124 | "colab_type": "code", 125 | "id": "zrzjXmXhl5ZP", 126 | "outputId": "3b65519e-c3df-44af-b262-cc6fa58a4db3" 127 | }, 128 | "outputs": [], 129 | "source": [ 130 | "# Запись в YAML\n", 131 | "\n", 132 | "trunk_template = [\n", 133 | " 'switchport trunk encapsulation dot1q', 'switchport mode trunk',\n", 134 | " 'switchport trunk native vlan 999', 'switchport trunk allowed vlan'\n", 135 | "]\n", 136 | "\n", 137 | "access_template = [\n", 138 | " 'switchport mode access', 'switchport access vlan',\n", 139 | " 'switchport nonegotiate', 'spanning-tree portfast',\n", 140 | " 'spanning-tree bpduguard enable'\n", 141 | "]\n", 142 | "\n", 143 | "to_yaml = {'trunk': trunk_template, 'access': access_template}\n", 144 | "\n", 145 | "with open('sw_templates.yaml', 'w') as f:\n", 146 | " yaml.dump(to_yaml, f)\n", 147 | "\n", 148 | "with open('sw_templates.yaml') as f:\n", 149 | " print(f.read())" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "metadata": { 155 | "colab_type": "text", 156 | "id": "hro87YF3CKLN" 157 | }, 158 | "source": [ 159 | "### ini\n", 160 | "\n", 161 | "Как правило, ini-файлы используют для хранения настроек приложения или операционной системы. Библиотека в ядре Python включает в себя модуль, под названием **configparser**, который вы можете использовать для создания и работы с файлами конфигурации." 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "metadata": { 168 | "colab": { 169 | "base_uri": "https://localhost:8080/", 170 | "height": 238 171 | }, 172 | "colab_type": "code", 173 | "executionInfo": { 174 | "elapsed": 980, 175 | "status": "ok", 176 | "timestamp": 1575465679176, 177 | "user": { 178 | "displayName": "Надежда Демиденко", 179 | "photoUrl": "https://lh3.googleusercontent.com/a-/AAuE7mA6D7k5OgtG9hzPe8Abs8DfOKAXQoTXaPfn7EY=s64", 180 | "userId": "05224310221243935536" 181 | }, 182 | "user_tz": -180 183 | }, 184 | "id": "PB1plBV8CYE-", 185 | "outputId": "3f709612-cbf4-4de2-f434-47202994d2ad" 186 | }, 187 | "outputs": [], 188 | "source": [ 189 | "import configparser\n", 190 | "\n", 191 | "# Создание конфигурационного файла\n", 192 | "config = configparser.ConfigParser()\n", 193 | "config.add_section(\"Settings\")\n", 194 | "config.set(\"Settings\", \"font\", \"Courier\")\n", 195 | "config.set(\"Settings\", \"font_size\", \"10\")\n", 196 | "config.set(\"Settings\", \"font_style\", \"Normal\")\n", 197 | "config.set(\"Settings\", \"font_info\",\n", 198 | " \"You are using %(font)s at %(font_size)s pt\")\n", 199 | " \n", 200 | "with open('my_settings.ini', 'w') as config_file:\n", 201 | " config.write(config_file)\n", 202 | "\n", 203 | "# ===Выведем содержимое файла===\n", 204 | "with open('my_settings.ini', 'r') as config_file:\n", 205 | " print(config_file.read())\n", 206 | "\n", 207 | "\n", 208 | "\n", 209 | "# Чтение конфигурационного файла\n", 210 | "config = configparser.ConfigParser()\n", 211 | "config.read('my_settings.ini')\n", 212 | " \n", 213 | "# Читаем некоторые значения из конфиг. файла.\n", 214 | "font = config.get(\"Settings\", \"font\")\n", 215 | "font_size = config.get(\"Settings\", \"font_size\")\n", 216 | "\n", 217 | "# Меняем значения из конфиг. файла.\n", 218 | "config.set(\"Settings\", \"font_size\", \"12\")\n", 219 | "\n", 220 | "# Удаляем значение из конфиг. файла.\n", 221 | "config.remove_option(\"Settings\", \"font_style\")\n", 222 | " \n", 223 | "# Вносим изменения в конфиг. файл.\n", 224 | "with open('my_settings.ini', \"w\") as config_file:\n", 225 | " config.write(config_file)\n", 226 | "\n", 227 | "# ===Выведем содержимое файла===\n", 228 | "with open('my_settings.ini', 'r') as config_file:\n", 229 | " print(config_file.read())" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": { 235 | "colab_type": "text", 236 | "id": "7HYnq7_0fMez" 237 | }, 238 | "source": [ 239 | "## Консервация объектов\n", 240 | "\n", 241 | "Модуль `pickle` (англ. pickle - консервировать) реализует мощный алгоритм сериализации и десериализации объектов Python. \"Pickling\" - процесс преобразования объекта Python в поток байтов, а \"unpickling\" - обратная операция, в результате которой поток байтов преобразуется обратно в Python-объект. Так как поток байтов легко можно записать в файл, модуль `pickle` широко применяется для сохранения и загрузки сложных объектов в Python.\n", 242 | "\n", 243 | "Модуль pickle предоставляет следующие функции для удобства сохранения/загрузки объектов:\n", 244 | "\n", 245 | "- `pickle.dump(obj, file, protocol=None, *, fix_imports=True)`\\\n", 246 | "Записывает сериализованный объект в файл. Дополнительный аргумент protocol указывает используемый протокол. По умолчанию равен 3 и именно он рекомендован для использования в Python 3 (несмотря на то, что в Python 3.4 добавили протокол версии 4 с некоторыми оптимизациями). В любом случае, записывать и загружать надо с одним и тем же протоколом.\n", 247 | "\n", 248 | "- `pickle.dumps(obj, protocol=None, *, fix_imports=True)`\\\n", 249 | "Возвращает сериализованный объект. Впоследствии вы его можете использовать как угодно.\n", 250 | "\n", 251 | "- `pickle.load(file, *, fix_imports=True, encoding=\"ASCII\", errors=\"strict\")`\\\n", 252 | "Загружает объект из файла.\n", 253 | "\n", 254 | "- `pickle.loads(bytes_object, *, fix_imports=True, encoding=\"ASCII\", errors=\"strict\")`\\\n", 255 | "Загружает объект из потока байт.\n", 256 | "\n", 257 | "Модуль `pickle` также определяет несколько исключений:\n", 258 | "\n", 259 | "`pickle.PickleError`\n", 260 | "- `pickle.PicklingError` - случились проблемы с сериализацией объекта.\n", 261 | "- `pickle.UnpicklingError` - случились проблемы с десериализацией объекта.\n", 262 | "\n", 263 | "Этих функций вполне достаточно для сохранения и загрузки встроенных типов данных.\n", 264 | "\n" 265 | ] 266 | }, 267 | { 268 | "cell_type": "code", 269 | "execution_count": null, 270 | "metadata": { 271 | "colab": { 272 | "base_uri": "https://localhost:8080/", 273 | "height": 34 274 | }, 275 | "colab_type": "code", 276 | "executionInfo": { 277 | "elapsed": 1532, 278 | "status": "ok", 279 | "timestamp": 1575808672530, 280 | "user": { 281 | "displayName": "Надежда Демиденко", 282 | "photoUrl": "https://lh3.googleusercontent.com/a-/AAuE7mA6D7k5OgtG9hzPe8Abs8DfOKAXQoTXaPfn7EY=s64", 283 | "userId": "05224310221243935536" 284 | }, 285 | "user_tz": -180 286 | }, 287 | "id": "tZdGPehqgtNn", 288 | "outputId": "f3037b95-629e-46b1-ec5b-d2f7eaa8685d" 289 | }, 290 | "outputs": [], 291 | "source": [ 292 | "import pickle\n", 293 | "data = {\n", 294 | " 'a': [1, 2.0, 3, 4+6j],\n", 295 | " 'b': (\"character string\", b\"byte string\"),\n", 296 | " 'c': {None, True, False}\n", 297 | "}\n", 298 | "\n", 299 | "with open('data.pickle', 'wb') as f:\n", 300 | " pickle.dump(data, f)\n", 301 | "\n", 302 | "with open('data.pickle', 'rb') as f:\n", 303 | " data_new = pickle.load(f)\n", 304 | "\n", 305 | "print(data_new)" 306 | ] 307 | } 308 | ], 309 | "metadata": { 310 | "colab": { 311 | "name": "topic11.ipynb", 312 | "provenance": [] 313 | }, 314 | "kernelspec": { 315 | "display_name": "Python 3", 316 | "language": "python", 317 | "name": "python3" 318 | }, 319 | "language_info": { 320 | "codemirror_mode": { 321 | "name": "ipython", 322 | "version": 3 323 | }, 324 | "file_extension": ".py", 325 | "mimetype": "text/x-python", 326 | "name": "python", 327 | "nbconvert_exporter": "python", 328 | "pygments_lexer": "ipython3", 329 | "version": "3.6.5" 330 | } 331 | }, 332 | "nbformat": 4, 333 | "nbformat_minor": 1 334 | } 335 | -------------------------------------------------------------------------------- /lecture_6/03. Testing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "colab_type": "text", 7 | "id": "HDxLZmF5LnUq" 8 | }, 9 | "source": [ 10 | "# Тестирование приложений. Инструкция assert. Библиотеки unittest, pytest" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": { 16 | "colab_type": "text", 17 | "id": "Quv01HVLXUqz" 18 | }, 19 | "source": [ 20 | "## Unit – тестирование\n", 21 | "\n", 22 | "Как должно быть:\n", 23 | "\n", 24 | "![Как должно быть](17/18-02.png)\n", 25 | "\n", 26 | "Принципы автоматизации тестов\n", 27 | "\n", 28 | "- Атомарность\n", 29 | "- Независимость\n", 30 | "- Изолированность/герметичность" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 0, 36 | "metadata": { 37 | "colab": {}, 38 | "colab_type": "code", 39 | "id": "X_iIPNi3LZUC" 40 | }, 41 | "outputs": [], 42 | "source": [ 43 | "# Атомарность\n", 44 | "# Тесты должны быть атомарными, каждый из них должен проверять \n", 45 | "# ровно один тестовый случай. \n", 46 | "# Громоздкие и сложные тесты необходимо разбивать на несколько более мелких.\n", 47 | "\n", 48 | "def test_add_smth():\n", 49 | " user = create_new_user(email='some@ema.il')\t\n", 50 | " # user.register() # лишнее\n", 51 | " # user.auth() # лишнее\n", 52 | " smth = user.create_smth()\n", 53 | " smth.add()\n", 54 | " # assert user.is_authorized() # лишнее\n", 55 | " assert smth.is_added()\n", 56 | " # assert user.email == 'some@ema.il' # лишнее" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": { 62 | "colab_type": "text", 63 | "id": "WDtxpV_haOws" 64 | }, 65 | "source": [ 66 | "### Независимость\n", 67 | "\n", 68 | "```\n", 69 | "test1 -> test2 -> test3\n", 70 | "test2 -> test1 -> test3\n", 71 | "test3 -> test2 -> test1\n", 72 | "test1 -> test3 -> test2\n", 73 | "test3 -> test1 -> test2\n", 74 | "test2 -> test3 -> test1\n", 75 | "```" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "metadata": { 81 | "colab_type": "text", 82 | "id": "fzvVKBvofHVD" 83 | }, 84 | "source": [ 85 | "### Изолированность/герметичность\n", 86 | "\n", 87 | "![Изолированность](17/18-00.png)\n", 88 | "\n", 89 | "![Изолированность](17/18-01.png)" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": { 95 | "colab_type": "text", 96 | "id": "bd0hXIXfgj7-" 97 | }, 98 | "source": [ 99 | "## Как придумать тесты?\n", 100 | "\n", 101 | "- Разбор тестируемоего метода\n", 102 | "- Определение граничных значении\n", 103 | "- Определение классов эквивалентности\n", 104 | "\n", 105 | "> Какие тесты сделать для функции a / b?" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": { 111 | "colab_type": "text", 112 | "id": "G93AkfuQg6KI" 113 | }, 114 | "source": [ 115 | "## Инструменты для тестирования в Python\n", 116 | "\n", 117 | "- Unittest\n", 118 | "- Pytest" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": { 124 | "colab_type": "text", 125 | "id": "JdzVIFumA7rl" 126 | }, 127 | "source": [ 128 | "## Unittest" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 2, 134 | "metadata": { 135 | "colab": {}, 136 | "colab_type": "code", 137 | "id": "V3iGDfajA_3N" 138 | }, 139 | "outputs": [], 140 | "source": [ 141 | "import unittest\n", 142 | "\n", 143 | "class TestStringMethods(unittest.TestCase):\n", 144 | " def test_upper(self):\n", 145 | " self.assertEqual('foo'.upper(), 'FOO')\n", 146 | "\n", 147 | " def test_isupper(self):\n", 148 | " self.assertTrue('FOO'.isupper())\n", 149 | " self.assertFalse('Foo'.isupper())\n", 150 | "\n", 151 | " def test_split(self):\n", 152 | " s = 'hello world'\n", 153 | " self.assertEqual(s.split(), ['hello', 'world'])\n", 154 | " # check that s.split fails when the separator is not a string\n", 155 | " with self.assertRaises(TypeError):\n", 156 | " s.split(2)\n", 157 | " \n", 158 | " def something(self):\n", 159 | " 1 / 0" 160 | ] 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "metadata": { 165 | "colab_type": "text", 166 | "id": "ZfrBKRYHCX4V" 167 | }, 168 | "source": [ 169 | "Подготовка состояния (фикстуры)\n", 170 | "\n", 171 | "- setUp()\n", 172 | "- tearDown()\n", 173 | "- setUpClass()\n", 174 | "- tearDownClass()\n", 175 | "- setUpModule()\n", 176 | "- tearDownModule()\n", 177 | "\n", 178 | "Проверка результата\n", 179 | "\n", 180 | "|Метод|Проверяет|Версия|\n", 181 | "|:---|:---|:---:|\n", 182 | "|assertEqual(a,b)|`a == b`||\n", 183 | "|assertNotEqual(a,b)|`a != b`||\n", 184 | "|assertTrue(x)|`bool(x) is True`||\n", 185 | "|assertFalse(x)|`bool(x) is False`||\n", 186 | "|assertIs(a, b)|`a is b`|3.1|\n", 187 | "|assertIsNot(a, b)|`a is not b`|3.1|\n", 188 | "|assertIsNone(x)|`x is None`|3.1|\n", 189 | "|assertIsNotNone(x)|`x is not None`|3.1|\n", 190 | "|assertIn(a, b)|`a in b`|3.1|\n", 191 | "|asertNotIn(a, b)|`a not in b`|3.1|\n", 192 | "|assertIsInstance(a, b)|`instance(a, b)`|3.2|\n", 193 | "|assertNotIsInstance(a, b)|`not instance(a, b)`|3.2|" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": { 199 | "colab_type": "text", 200 | "id": "y3gRb4MjFfB1" 201 | }, 202 | "source": [ 203 | "### Запуск unittest\n", 204 | "\n", 205 | "```\n", 206 | ">>> python -m unittest unit/test_simple.py\n", 207 | "```" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": 10, 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [ 216 | "import unittest\n", 217 | "\n", 218 | "\n", 219 | "class TestStringMethods(unittest.TestCase):\n", 220 | " def test_upper(self):\n", 221 | " self.assertEqual('foo'.upper(), 'FOO')\n", 222 | "\n", 223 | " def test_isupper(self):\n", 224 | " self.assertTrue('FOO'.isupper())\n", 225 | " self.assertFalse('Foo'.isupper())\n", 226 | "\n", 227 | " def test_split(self):\n", 228 | " s = 'hello world'\n", 229 | " self.assertEqual(s.split(), ['hello', 'world'])\n", 230 | " # check that s.split fails when the separator is not a string\n", 231 | " with self.assertRaises(TypeError):\n", 232 | " s.split(2)" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "metadata": { 238 | "colab_type": "text", 239 | "id": "B4167KX-GIJK" 240 | }, 241 | "source": [ 242 | "Почему unittest не очень удобен:\n", 243 | "\n", 244 | "- 1 класс – 1 тест\n", 245 | "- Fixtures в формате setUp tearDown\n", 246 | "- Скоуп фикстуры\n", 247 | "- Приходится наследоваться если у тестов\n", 248 | "одинаковый setUp\n", 249 | "- Сложные assert\n", 250 | "\n", 251 | "![скрин](17/18-03.png)" 252 | ] 253 | }, 254 | { 255 | "cell_type": "markdown", 256 | "metadata": { 257 | "colab_type": "text", 258 | "id": "PA1qP6e_GziQ" 259 | }, 260 | "source": [ 261 | "## Pytest" 262 | ] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "execution_count": 3, 267 | "metadata": { 268 | "colab": {}, 269 | "colab_type": "code", 270 | "id": "6nx-QuAWG3E5" 271 | }, 272 | "outputs": [], 273 | "source": [ 274 | "def test_upper():\n", 275 | " assert 'foo'.upper() == 'FOO'\n", 276 | "\n", 277 | "def test_isupper():\n", 278 | " assert 'FOO'.isupper()\n", 279 | " assert not 'Foo'.isupper()\n", 280 | "\n", 281 | "def test_split():\n", 282 | " s = 'hello world'\n", 283 | " assert s.split() == ['hello', 'world']\n", 284 | "\n", 285 | " try:\n", 286 | " s.split(1)\n", 287 | " assert False\n", 288 | " except TypeError:\n", 289 | " assert True" 290 | ] 291 | }, 292 | { 293 | "cell_type": "markdown", 294 | "metadata": { 295 | "colab_type": "text", 296 | "id": "-CKF8_-SHj3g" 297 | }, 298 | "source": [ 299 | "Подготовка состояния (фикстуры)" 300 | ] 301 | }, 302 | { 303 | "cell_type": "code", 304 | "execution_count": 4, 305 | "metadata": { 306 | "colab": {}, 307 | "colab_type": "code", 308 | "id": "MF6E1mQARrpO" 309 | }, 310 | "outputs": [], 311 | "source": [ 312 | "import pytest\n", 313 | "\n", 314 | "@pytest.fixture(scope='function', autouse=False)\n", 315 | "def f():\n", 316 | " print(1)\n", 317 | " yield\n", 318 | " print(2)" 319 | ] 320 | }, 321 | { 322 | "cell_type": "markdown", 323 | "metadata": { 324 | "colab_type": "text", 325 | "id": "dJv-iKeDSDz9" 326 | }, 327 | "source": [ 328 | "Проверка результата" 329 | ] 330 | }, 331 | { 332 | "cell_type": "markdown", 333 | "metadata": { 334 | "colab": {}, 335 | "colab_type": "code", 336 | "id": "FTtpfA1HSGHg" 337 | }, 338 | "source": [ 339 | "\n", 340 | " assert res is None\n", 341 | " assert res is False\n", 342 | " assert res == {}\n", 343 | " assert res == []\n", 344 | " assert isinstance(res, list)\n", 345 | "" 346 | ] 347 | }, 348 | { 349 | "cell_type": "markdown", 350 | "metadata": { 351 | "colab_type": "text", 352 | "id": "vDmvIquuTF8p" 353 | }, 354 | "source": [ 355 | "```\n", 356 | ">>> python -m pytest test_simple.py\n", 357 | "```" 358 | ] 359 | }, 360 | { 361 | "cell_type": "code", 362 | "execution_count": 17, 363 | "metadata": {}, 364 | "outputs": [], 365 | "source": [ 366 | "import pytest\n", 367 | "\n", 368 | "\n", 369 | "@pytest.fixture()\n", 370 | "def f():\n", 371 | " print(1)\n", 372 | "\n", 373 | "\n", 374 | "@pytest.mark.usefixtures('f')\n", 375 | "def test_success():\n", 376 | " assert True\n", 377 | "\n", 378 | "\n", 379 | "@pytest.mark.parametrize('a,b,res', [\n", 380 | " (5, 2, 2.5),\n", 381 | " (0, 2, 0),\n", 382 | " (4, 2, 2),\n", 383 | " (-4, 2, -2)\n", 384 | "])\n", 385 | "def test_div(a, b, res):\n", 386 | " assert a / b == res" 387 | ] 388 | }, 389 | { 390 | "cell_type": "markdown", 391 | "metadata": { 392 | "colab_type": "text", 393 | "id": "eatjhF0uT8bg" 394 | }, 395 | "source": [ 396 | "## Антипаттерны\n", 397 | "\n", 398 | "- Liar\n", 399 | "- Giant\n", 400 | "- Secret Catcher\n", 401 | "- Enumerator\n", 402 | "- Slowpoke " 403 | ] 404 | }, 405 | { 406 | "cell_type": "markdown", 407 | "metadata": { 408 | "colab_type": "text", 409 | "id": "aec2QIRJUJs4" 410 | }, 411 | "source": [ 412 | "### Liar" 413 | ] 414 | }, 415 | { 416 | "cell_type": "code", 417 | "execution_count": 7, 418 | "metadata": { 419 | "colab": {}, 420 | "colab_type": "code", 421 | "id": "PvB2PzICUMkn" 422 | }, 423 | "outputs": [], 424 | "source": [ 425 | "def test_some_smth():\n", 426 | " do_smth()\n", 427 | " assert True" 428 | ] 429 | }, 430 | { 431 | "cell_type": "markdown", 432 | "metadata": { 433 | "colab_type": "text", 434 | "id": "0sjK8zRhUPyK" 435 | }, 436 | "source": [ 437 | "### Giant\n", 438 | "\n", 439 | "![Giant](17/18-05.png)" 440 | ] 441 | }, 442 | { 443 | "cell_type": "markdown", 444 | "metadata": { 445 | "colab_type": "text", 446 | "id": "XU3kfKmtUjCC" 447 | }, 448 | "source": [ 449 | "### The Secret Catcher" 450 | ] 451 | }, 452 | { 453 | "cell_type": "code", 454 | "execution_count": 0, 455 | "metadata": { 456 | "colab": {}, 457 | "colab_type": "code", 458 | "id": "-v5fPDvlUkB6" 459 | }, 460 | "outputs": [], 461 | "source": [ 462 | "def test_smth_one_two_three():\n", 463 | " do_smth1()\n", 464 | " do_smth2()\n", 465 | " do_smth3()" 466 | ] 467 | }, 468 | { 469 | "cell_type": "markdown", 470 | "metadata": { 471 | "colab_type": "text", 472 | "id": "tqeuj7X1UtDk" 473 | }, 474 | "source": [ 475 | "### Enumerator" 476 | ] 477 | }, 478 | { 479 | "cell_type": "code", 480 | "execution_count": 18, 481 | "metadata": { 482 | "colab": {}, 483 | "colab_type": "code", 484 | "id": "eIaAqT1rUu7Y" 485 | }, 486 | "outputs": [], 487 | "source": [ 488 | "def test1():\n", 489 | " pass\n", 490 | "def test2():\n", 491 | " pass\n", 492 | "def test3():\n", 493 | " pass" 494 | ] 495 | }, 496 | { 497 | "cell_type": "markdown", 498 | "metadata": { 499 | "colab_type": "text", 500 | "id": "8MSXp8g9UyyG" 501 | }, 502 | "source": [ 503 | "### Slowpoke" 504 | ] 505 | }, 506 | { 507 | "cell_type": "code", 508 | "execution_count": 19, 509 | "metadata": { 510 | "colab": {}, 511 | "colab_type": "code", 512 | "id": "_Zs8LXEuVRZm" 513 | }, 514 | "outputs": [], 515 | "source": [ 516 | "def test_pokemon():\n", 517 | " do_smth1()\n", 518 | " time.sleep(N)\n", 519 | " assert True" 520 | ] 521 | }, 522 | { 523 | "cell_type": "markdown", 524 | "metadata": { 525 | "colab_type": "text", 526 | "id": "pvcQRL6EVX9l" 527 | }, 528 | "source": [ 529 | "## Тесты на тесты\n", 530 | "\n", 531 | "Анализ покрытия:\n", 532 | "\n", 533 | "- По файлам\n", 534 | "- По классам\n", 535 | "- По методам\n", 536 | "- По строкам\n", 537 | "- По ветвям\n", 538 | "\n", 539 | "### Mutation testing\n", 540 | "\n", 541 | "Метод тестирования основанный на внесении\n", 542 | "небольших измененийв код программы." 543 | ] 544 | }, 545 | { 546 | "cell_type": "markdown", 547 | "metadata": { 548 | "colab_type": "text", 549 | "id": "ZJdJQzk-V8rt" 550 | }, 551 | "source": [ 552 | "## Зачем писать тесты?\n", 553 | "\n", 554 | "- Улучшение качества\n", 555 | "- Облегчение внесения изменений\n", 556 | "- Документация на продукт\n", 557 | "\n", 558 | "![зачем писать тесты](17/18-06.png)" 559 | ] 560 | }, 561 | { 562 | "cell_type": "markdown", 563 | "metadata": { 564 | "colab_type": "text", 565 | "id": "0qVUbEMnWicE" 566 | }, 567 | "source": [ 568 | "###### Блокнот подготовлен по материалам Опрышко Александра" 569 | ] 570 | } 571 | ], 572 | "metadata": { 573 | "colab": { 574 | "name": "topic18.ipynb", 575 | "provenance": [] 576 | }, 577 | "kernelspec": { 578 | "display_name": "Python 3", 579 | "language": "python", 580 | "name": "python3" 581 | }, 582 | "language_info": { 583 | "codemirror_mode": { 584 | "name": "ipython", 585 | "version": 3 586 | }, 587 | "file_extension": ".py", 588 | "mimetype": "text/x-python", 589 | "name": "python", 590 | "nbconvert_exporter": "python", 591 | "pygments_lexer": "ipython3", 592 | "version": "3.6.5" 593 | } 594 | }, 595 | "nbformat": 4, 596 | "nbformat_minor": 1 597 | } 598 | -------------------------------------------------------------------------------- /lecture_6/03/circularref.svg: -------------------------------------------------------------------------------- 1 | 2 |
Object 1
[Not supported by viewer]
Object 2
[Not supported by viewer]
lst
[Not supported by viewer]
-------------------------------------------------------------------------------- /lecture_6/03/image_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/03/image_10.png -------------------------------------------------------------------------------- /lecture_6/03/image_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/03/image_12.png -------------------------------------------------------------------------------- /lecture_6/03/image_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/03/image_14.png -------------------------------------------------------------------------------- /lecture_6/03/image_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/03/image_16.png -------------------------------------------------------------------------------- /lecture_6/03/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/03/image_4.png -------------------------------------------------------------------------------- /lecture_6/03/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/03/image_6.png -------------------------------------------------------------------------------- /lecture_6/03/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/03/image_8.png -------------------------------------------------------------------------------- /lecture_6/04. Git.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/04. Git.pdf -------------------------------------------------------------------------------- /lecture_6/17/0_assert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/17/0_assert.png -------------------------------------------------------------------------------- /lecture_6/17/18-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/17/18-00.png -------------------------------------------------------------------------------- /lecture_6/17/18-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/17/18-01.png -------------------------------------------------------------------------------- /lecture_6/17/18-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/17/18-02.png -------------------------------------------------------------------------------- /lecture_6/17/18-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/17/18-03.png -------------------------------------------------------------------------------- /lecture_6/17/18-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/17/18-05.png -------------------------------------------------------------------------------- /lecture_6/17/18-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_6/17/18-06.png -------------------------------------------------------------------------------- /lecture_6/README.md: -------------------------------------------------------------------------------- 1 | Презентация по Git взята из курса Data Mining in Action -------------------------------------------------------------------------------- /lecture_7/busybox.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_7/busybox.tar.xz -------------------------------------------------------------------------------- /lecture_7/images/agg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_7/images/agg.png -------------------------------------------------------------------------------- /lecture_7/images/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_7/images/arch.png -------------------------------------------------------------------------------- /lecture_7/images/arch2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_7/images/arch2.png -------------------------------------------------------------------------------- /lecture_7/images/docker-colocation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_7/images/docker-colocation.png -------------------------------------------------------------------------------- /lecture_7/images/hyperv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_7/images/hyperv.png -------------------------------------------------------------------------------- /lecture_7/images/joins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_7/images/joins.png -------------------------------------------------------------------------------- /lecture_7/images/root_tar_xz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_7/images/root_tar_xz.png -------------------------------------------------------------------------------- /lecture_7/images/unionfs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_7/images/unionfs.png -------------------------------------------------------------------------------- /lecture_7/images/vm-colocation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_7/images/vm-colocation.png -------------------------------------------------------------------------------- /lecture_8/01/decorator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/01/decorator.png -------------------------------------------------------------------------------- /lecture_8/dbc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/dbc.png -------------------------------------------------------------------------------- /lecture_8/df.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/df.png -------------------------------------------------------------------------------- /lecture_8/hj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/hj.png -------------------------------------------------------------------------------- /lecture_8/isolation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/isolation.png -------------------------------------------------------------------------------- /lecture_8/lecture8.md: -------------------------------------------------------------------------------- 1 | # Lecture 8 - RDBMS Architecture, SQL queries under-the-hood 2 | 3 | ## At-a-glance - СУБД и клиент-серверная модель 4 | *Сервер* - это сама RDBMS (aka СУБД, база, БД, базёнка, базюка ...) 5 | // RDBMS - relational-database management system, СУБД - система управления базами данных 6 | 7 | ![postgres database cluster](dbc.png) 8 | /* легко путать, но не нужно путать с базой данных - область на файловой системе, в которой находятся объекты, хранящиеся в виде файлов 9 | // почти всегда база СУБД организуется как структура подкаталогов в хранилище СУБД 10 | /* схема - некоторое выделенное пространство внутри базы данных 11 | Термин *database cluster* - набор баз данных, управляемых одним сервером СУБД 12 | 13 | Базовый набор обязанностей сервера СУБД: 14 | * управляет внутренними файлами базами данных 15 | * принимает соединения от клиентов 16 | * выполняет действия внутри СУБД от имени пользователей (выборка, удаление, вставка данных, ...) 17 | 18 | *Клиент* - некое приложение, желающее выполнить некоторые операции на СУБД. 19 | Клиентом может быть любое приложение (настольное, консольное, веб-сервис, специальная IDE - Jetbrains DataGrip к примеру) 20 | 21 | Клиент и сервер общаются между собой посредством application-layer протокола. 22 | ![osi-tcp](osi.png) 23 | Этот протокол - прикладного уровня, и в качестве транспорта используется сетевой протокол TCP. 24 | Примеры других прикладных протоколов - HTTP, Thrift, Oracle TNS, ... 25 | 26 | Многие протоколы СУБД проприентарны. 27 | Пример устройства протокола PostgreSQL: 28 | https://www.postgresql.org/docs/current/protocol.html 29 | 30 | ## Физическая архитектура СУБД (на примере Postgres) 31 | Process / Memory architecture отвечают на вопрос - что за что отвечает, как и где хранить 32 | ``` 33 | polkitd 21564 0.0 0.1 287444 17664 ? Ss Apr01 0:02 | \_ postgres 34 | polkitd 21776 0.0 0.0 287444 2040 ? Ss Apr01 0:00 | \_ postgres: checkpointer process 35 | polkitd 21777 0.0 0.0 287444 3088 ? Ss Apr01 0:20 | \_ postgres: writer process 36 | polkitd 21778 0.0 0.0 287444 2048 ? Ss Apr01 0:21 | \_ postgres: wal writer process 37 | polkitd 21779 0.0 0.0 287856 2772 ? Ss Apr01 0:08 | \_ postgres: autovacuum launcher process 38 | polkitd 21780 0.0 0.0 142444 1996 ? Ss Apr01 0:06 | \_ postgres: stats collector process 39 | ``` 40 | // Как осознать - process, memory, then back to process 41 | ![postgres process architecture](pgproc.png) 42 | * server postgres (`postgres`, в ранних версиях `postmaster`) - самый главный процесс. 43 | Он запускает необходимые фоновые процессы, создает зоны памяти, поднимает backend-процесс при клиентских соединениях 44 | * backend process - клиентская сессия, выполняющая все запрошенные действия от клиента (запросы) 45 | Сессия может работать в пределах одной базы. 46 | Число сессий - не бесконечно, обычно бывают лимиты 47 | ### Фоновые процессы (самые важные) 48 | * Background writer 49 | Запись измененных данных в постоянное хранение 50 | * Autovacuum Launcher 51 | Из-за особенностей, в процессе работы порождается мусор (блоки данных), и его надо очищать (автоматически или руками) 52 | * WAL writer 53 | Сброс содержимого Write-Ahead Log в постоянное хранилище 54 | * Statistics 55 | Сборка статистике по каждой таблице (чисто строк, уникальные значения, ...) 56 | ![postgres memory architecture](pgmem.png) 57 | 58 | ### Хранение на файловой системе 59 | Postgres имеет свой домашний каталог - переменная среды PGDATA 60 | Некоторые важные подкаталоги PGDATA (для pg 9.6): 61 | * base - каталоги баз данных 62 | * global - служебные таблицы 63 | * pg_clog - данные состояний транзакций 64 | * pg_stat - собранные данные по статистике таблиц 65 | * pg_xlog - файлы Write-ahead Log 66 | 67 | 68 | *Tablespace* - по своему разложить таблицы на диске 69 | ![postgres table space](ts.png) 70 | 71 | `CREATE TABLESPACE fastspace LOCATION '/pgstore/data1';` 72 | 73 | ``` 74 | root@43bef05b003f:/pgstore/data1# ls -al 75 | drwx------. 2 postgres postgres 6 Apr 22 14:50 PG_9.6_201608131 76 | ``` 77 | 78 | ``` 79 | root@43bef05b003f:/var/lib/postgresql/data/pgdata# ls -al pg_tblspc/ 80 | total 4 81 | lrwxrwxrwx. 1 postgres postgres 14 Apr 22 14:50 24577 -> /pgstore/data1 82 | ``` 83 | 84 | По умолчанию: 85 | * pg_global 86 | * pg_default 87 | 88 | *Datafile* физически можно рассматривать можно как heap, разделенный на блоки(страницы) по 8КБ. 89 | ![postgres datafile](df.png) 90 | Таблицы, индекс - хранятся в одном или более датафайлах 91 | Датафайлы хранятся tablespace-ах 92 | 93 | ### Общая память (shared), размещенная в RAM 94 | * shared buffer pool 95 | Минимизация чтения из файловой системы. По сути кэш из блоков тяжелых/часто используемых объектов. 96 | Если блок из этого пула изменится, он становится "грязным". 97 | 98 | * Write-ahead Log 99 | Буфер изменений, перед записью на файловую систему, для буферизации изменений (x3 write/modify) 100 | * commit log 101 | Хранит сведения обо всех транзакциях 102 | 103 | 104 | ### Локальная память (local) - RAM 105 | * work_mem 106 | Операционная память для сортировок и джойнов 107 | * maintenance_work_mem 108 | Память для служеных операций (сборка мусора, сборка индекса) 109 | * temp_buffers 110 | Память для временных таблиц 111 | 112 | ## Обработка запроса 113 | Шаги обработки запроса: 114 | ![postgres query processing](qp.png) 115 | 116 | Подопытный запрос: 117 | ``` 118 | select id, data 119 | from tbl_a 120 | where id < 300 121 | order by data 122 | ``` 123 | 124 | * Parser - преобразование SQL-запроса в parse tree 125 | На этой фазе происходит только проверка синтаксиса 126 | `Синтаксис определяет как упорядочить, скоординировать и соединить слова в некий текст` 127 | ![postgres query processing](qp1.png) 128 | * Analyzer - генерация query tree на основе parse tree (https://www.postgresql.org/docs/current/querytree.html) 129 | Здесь происходит проверка семантики - смысловой нагрузки элементов parse tree 130 | ![postgres query processing](qp2.png) 131 | Происходит полная подстановка значений 132 | * например, `select * from tab_a` превратится в `select ` 133 | * используемые таблицы - подставятся конкретный идентификаторы (object id), и соответствующие им предикаты 134 | * Rewriter - перобразование query tree эвристиками 135 | Достаточно сложная система. 136 | Пример - используется view в запросе, то поле RangeTableEntry.subquery будет ссылаться на query tree подзапроса 137 | * Planner - генерация из query tree нескольких plan tree и выбор наилучшего. 138 | Самая сложная из подсистем! Во всех базах данных, и даже в BigData-системах! 139 | Cost-based optimization 140 | ![postgres query processing](qp3.png) 141 | * Executor - выполнение query tree - хождение по таблицам и индексам в предписанном порядке 142 | 143 | ## Выполнение запроса - подробней 144 | Каждый элемент plan tree имеет свой реализованный функционал 145 | https://github.com/postgres/postgres/tree/master/src/backend/executor 146 | Получить наилучший с точки зрения базы plan tree можно через команду `EXPLAIN`: 147 | ``` 148 | Limit (cost=98070.51..98070.71 rows=10 width=28) (actual time=5503.859..5546.036 rows=10 loops=1) 149 | -> GroupAggregate (cost=98070.51..105041.81 rows=348565 width=28) (actual time=5503.856..5546.030 rows=10 loops=1) 150 | Group Key: t.passenger_name, t.passenger_id" 151 | Filter: (count(DISTINCT f.departure_airport) > 4) 152 | Rows Removed by Filter: 9274 153 | -> Sort (cost=98070.51..98941.92 rows=348565 width=32) (actual time=5494.363..5527.503 rows=26530 loops=1) 154 | Sort Key: t.passenger_name, t.passenger_id" 155 | Sort Method: external merge Disk: 43072kB 156 | -> Hash Join (cost=18960.59..57642.74 rows=348565 width=32) (actual time=385.127..1627.805 rows=1045726 loops=1) 157 | Hash Cond: (tf.ticket_no = t.ticket_no) 158 | -> Hash Join (cost=1341.10..27920.48 rows=348565 width=18) (actual time=47.817..599.844 rows=1045726 loops=1) 159 | Hash Cond: (tf.flight_id = f.flight_id) 160 | -> Seq Scan on ticket_flights tf (cost=0.00..19172.26 rows=1045726 width=18) (actual time=0.033..282.050 rows=1045726 loops=1) 161 | -> Hash (cost=1203.10..1203.10 rows=11040 width=8) (actual time=47.661..47.661 rows=33121 loops=1) 162 | Buckets: 65536 (originally 16384) Batches: 1 (originally 1) Memory Usage: 1806kB 163 | -> Hash Join (cost=10.68..1203.10 rows=11040 width=8) (actual time=0.164..40.570 rows=33121 loops=1) 164 | Hash Cond: (f.arrival_airport = ml_1.airport_code) 165 | -> Hash Join (cost=5.34..1045.95 rows=11040 width=12) (actual time=0.086..32.132 rows=33121 loops=1) 166 | Hash Cond: (f.departure_airport = ml.airport_code) 167 | -> Seq Scan on flights f (cost=0.00..888.82 rows=11040 width=12) (actual time=0.015..21.930 rows=33121 loops=1) 168 | Filter: ((scheduled_departure)::date > '2010-09-14'::date) 169 | -> Hash (cost=4.04..4.04 rows=104 width=4) (actual time=0.055..0.055 rows=104 loops=1) 170 | Buckets: 1024 Batches: 1 Memory Usage: 12kB 171 | -> Seq Scan on airports_data ml (cost=0.00..4.04 rows=104 width=4) (actual time=0.002..0.033 rows=104 loops=1) 172 | -> Hash (cost=4.04..4.04 rows=104 width=4) (actual time=0.061..0.062 rows=104 loops=1) 173 | Buckets: 1024 Batches: 1 Memory Usage: 12kB 174 | -> Seq Scan on airports_data ml_1 (cost=0.00..4.04 rows=104 width=4) (actual time=0.005..0.039 rows=104 loops=1) 175 | -> Hash (cost=9811.33..9811.33 rows=366733 width=42) (actual time=335.791..335.791 rows=366733 loops=1) 176 | Buckets: 65536 Batches: 16 Memory Usage: 2164kB 177 | -> Seq Scan on tickets t (cost=0.00..9811.33 rows=366733 width=42) (actual time=0.017..224.859 rows=366733 loops=1) 178 | Planning time: 3.230 ms 179 | Execution time: 5556.284 ms 180 | ``` 181 | ### Join-операции 182 | * Nestep-Loop Join (NLJ) 183 | По сути, вложеннный цикл 184 | ```python 185 | predicate = 'where t1.name = t2.imya' 186 | result_set = [] 187 | for row_left in table_a: 188 | for row_right in table_b: 189 | if row_left.matches(row_right, predicate): 190 | result_set.append(row_left, row_right) 191 | # чего-то не хватает! 192 | ``` 193 | Вопросы: 194 | * какая сложность? 195 | * когда такой джойн будет работать ооочень долго? 196 | * можно ли убыстрить? (INLJ) 197 | Минусы: 198 | * Ограничение на предикат (только по равенству) 199 | * (Sort) Merge Join (SMJ) 200 | Вдохновлено сортировкой слиянием (СС) - кто помнит? По сути это финальная фаза СС. 201 | Сортируем обе таблицы и вычисляем результат джойна 202 | ![postgres query processing](smj.png) 203 | Минусы: 204 | * Нужно сортировать таблицы (но иногда они уже заранее отсортированы) 205 | * Много места может занять 206 | * Ограничение на предикат (только по равенству) 207 | * Hash Join (HJ) 208 | Строится хэш-таблица (2^N бакетов) - хранится в памяти. 209 | Строки "внутренней" таблицы раскидываются по бакетам хэш-таблицы. 210 | При работе с хэш-таблицей считаем хэш по полям заданного предиката. 211 | Затем пробегаем по "внешней" таблице, обращаясь к хэш-таблице и проверяем после по предикату. 212 | ![postgres query processing](hj.png) 213 | Вопросы: 214 | * Ничего не напоминает? 215 | 216 | *Оптимизация запросов* - на 70% состоит из того, как оптимальней сджойнить таблицы! 217 | 218 | 219 | ## Транзакции 220 | Транзакция - набор изменений, распостраняемых в БД. 221 | Нельзя не выполнить транзакцию! 222 | Состояния транзакций: 223 | * IN_PROGRESS 224 | * ABORTED 225 | * COMMITED 226 | * 227 | 228 | *Вопрос* - можем ли мы изменять один и тот же файл несколькими процессами? 229 | 230 | *ACID* - требования к многопользовательской СУБД: 231 | * Atomicity - фиксируем изменение, либо не применяем 232 | * Consistency - не нарушаем корректность БД (не будет семантических нарушений бизнес-модели данных) 233 | * Isolation - работающие транзакции не мешают друг другу 234 | * Durability - если зафиксировали изменения, то ничего не приведет к их потере 235 | 236 | Чтобы обеспечить эти требования, в современных СУБД реализован MVCC - multi-version concurrency control. 237 | Рассмотрим реализацию в Postgres. 238 | * У каждой транзакции есть монотонный идентификатор (TID) 239 | * Внутри одной транзакции может быть несколько команд - они пронумерованы с нуля 240 | * Есть глобальный реестр транзакций (pg_clog) - кто еще работает, кто откатился. 241 | * Для каждой строки таблицы есть: 242 | * Xmin, Xmax - TID создавшей, TID удалившей 243 | * Cmin, Cmax - Номер создавшего/удалившего выражения внутри транзакции 244 | 245 | Как MVCC трактует DML-действия (реализация в Postgres): 246 | ### select 247 | * select - выборка подходящих записей (существовавших до появления транзакции) 248 | c_tid > Xmin && (c_tid < Xmax && Xmax != 0) && Xmin not in clog.cancelled 249 | ![postgres select](sel_mvcc.png) 250 | * insert - добавляем запись в свободное место таблицы. 251 | проставляем Xmin 252 | * delete - запись в таблице помечается как устаревшая - но физически она не удаляется 253 | проставляем Xmax 254 | * update - старая запись помечается как устаревшая, затем добавляется новая 255 | *Вопрос* - давайте подумаем что будет? 256 | 257 | ### vacuum 258 | Подчищает неактуальные строки 259 | 260 | ## Уровни изоляции 261 | ``` 262 | dirty read 263 | A transaction reads data written by a concurrent uncommitted transaction. 264 | 265 | nonrepeatable read 266 | A transaction re-reads data it has previously read and finds that data has been modified by another transaction (that committed since the initial read). 267 | 268 | phantom read 269 | A transaction re-executes a query returning a set of rows that satisfy a search condition and finds that the set of rows satisfying the condition has changed due to another recently-committed transaction. 270 | 271 | serialization anomaly 272 | The result of successfully committing a group of transactions is inconsistent with all possible orderings of running those transactions one at a time. 273 | ``` 274 | 275 | ![postgres isolation level](isolation.png) 276 | * READ COMMITTED 277 | * REPEATABLE READ 278 | * SERIALIZABLE 279 | 280 | 281 | 282 | # Further read 283 | * Полный свод терминов PostgreSQL - https://en.wikibooks.org/wiki/PostgreSQL/Terms 284 | * Query processing in Postgres - http://www.interdb.jp/pg/pgsql03.html 285 | * Системные столбцы строк Postgres - https://postgrespro.ru/docs/postgrespro/9.5/ddl-system-columns -------------------------------------------------------------------------------- /lecture_8/osi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/osi.png -------------------------------------------------------------------------------- /lecture_8/pgmem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/pgmem.png -------------------------------------------------------------------------------- /lecture_8/pgproc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/pgproc.png -------------------------------------------------------------------------------- /lecture_8/qp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/qp.png -------------------------------------------------------------------------------- /lecture_8/qp1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/qp1.png -------------------------------------------------------------------------------- /lecture_8/qp2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/qp2.png -------------------------------------------------------------------------------- /lecture_8/qp3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/qp3.png -------------------------------------------------------------------------------- /lecture_8/sel_mvcc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/sel_mvcc.png -------------------------------------------------------------------------------- /lecture_8/smj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/smj.png -------------------------------------------------------------------------------- /lecture_8/ts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_8/ts.png -------------------------------------------------------------------------------- /lecture_9/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /lecture_9/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35 | -------------------------------------------------------------------------------- /lecture_9/.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /lecture_9/.idea/lecture_9.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lecture_9/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /lecture_9/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lecture_9/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /lecture_9/dgram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_9/dgram.jpg -------------------------------------------------------------------------------- /lecture_9/epoll.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_9/epoll.jpg -------------------------------------------------------------------------------- /lecture_9/epollwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_9/epollwork.png -------------------------------------------------------------------------------- /lecture_9/lecture9.md: -------------------------------------------------------------------------------- 1 | # Lecture 9 - networking, http 2 | 3 | # Networking 4 | ## HTTP Overview, Terms 5 | 6 | HTTP - своего рода "медиа"-курьер в Интернете. 7 | Мы что-то хотим - и можем получаем ответ - клиент-серверная модель. 8 | Мы не думаем как нам это сделать - вся низкоуровневая работа уже реализована предыдущими слоями (TCP, IP, ....). 9 | 10 | *Что мы запрашиваем - URL* 11 | Полная URL-схема: 12 | `<схема>:[//[<логин>[:<пароль>]@]<хост>[:<порт>]][/][?<параметры>][#<якорь>]` 13 | 14 | Пример: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#URLs_and_URNs 15 | * use HTTP(s) protocol 16 | * on server en.wikipedia.org 17 | * grab resource /wiki/Uniform_Resource_Identifier 18 | 19 | 20 | *Тип данных - MIME* 21 | https://en.wikipedia.org/wiki/Media_type 22 | Пример: 23 | * text/html 24 | * application/zip 25 | 26 | *Метод запроса* - какое действие совершить серверу 27 | * GET - send resource from server to client 28 | * PUT - store data from client into named server resource 29 | * POST - send client data to a server 30 | * DELETE - delete named resource from server 31 | 32 | *Код ответа запроса* - всегда трехзначный 33 | * 200 - OK 34 | * 404 - not found 35 | * 500 - internal error 36 | При создании своих HTTP-серверов надо придерживаться заданного смысла кодов. 37 | 38 | *Версии HTTP* 39 | Сейчас доминируют v1.1 и v2. 40 | Основные отличия: 41 | * v2 был продавлен google 42 | * v2 - бинарный протокол 43 | * v2 позволяет отдавать клиенту дополнительные ресурсы за один запрос (актуально для богатых контентом страниц) 44 | 45 | *HTTP-сообщения* 46 | В клиент-сервере всегда обмениваются сообщениями. 47 | * Start Line - что сделать/что было сделано - метод, адрес ресурса, версия HTTP, код ответа 48 | * Заголовок - key-value, определяющие атрибуты 49 | * Тело запроса - информация (текст/бинарь) 50 | 51 | ``` 52 | (base) EnlightAir:lecture_9 lancer$ curl -i 2ip.ru 53 | HTTP/1.1 200 OK 54 | 55 | Server: nginx 56 | Date: Wed, 29 Apr 2020 07:11:36 GMT 57 | Content-Type: text/plain 58 | Content-Length: 14 59 | Connection: keep-alive 60 | 61 | 37.190.23.107 62 | ``` 63 | 64 | *Транспорт * 65 | HTTP подразумевает надежную и последовательную доставку данных неограниченного размера. 66 | Сначала нужно установить TCP-соединение - у нас уже есть адрес сервера и порт, либо мы узнаем адрес по имени сервера. 67 | Последнее происходит через DNS - Domain Name Service 68 | 69 | Порты - если неуказан порт, по умолчанию - 80 http, 443 https 70 | https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers 71 | К примеру - GET neverssl.com 72 | ``` 73 | 1 0.000000 192.168.1.2 8.8.8.8 DNS 89 Standard query 0x8e28 A kfdlbcnsmrthvxwz.neverssl.com 74 | 2 0.074903 8.8.8.8 192.168.1.2 DNS 153 Standard query response 0x8e28 A kfdlbcnsmrthvxwz.neverssl.com A 13.33.47.133 A 13.33.47.80 A 13.33.47.26 A 13.33.47.241 75 | 6 0.107518 192.168.1.2 13.33.47.133 HTTP 478 GET /online HTTP/1.1 76 | 10 0.137736 13.33.47.133 192.168.1.2 HTTP 71 HTTP/1.1 200 OK (text/html) 77 | ``` 78 | 79 | 80 | ## Sockets & Ports 81 | Открывая файл, мы вызываем системный вызов `open(2)` и получаем файловый дескриптор. 82 | Каждый процесс имеет таблицу открытых дескрипторов 83 | Ядро ОС поддерживает таблицу всех открытых файлов 84 | ![ptable](ptable.png) 85 | 86 | `Everything in UNIX is a file` 87 | Практически все в nix сводится к файлу. 88 | `ls` может показать тип файла: 89 | * `-` - regular file 90 | * `d` - directory 91 | * `l` - symbolic link - ссылка на файл/каталог 92 | * `c` - character device file (терминалы/периферийные устройства) 93 | * `b` - block device file (память) 94 | * `s` - socket file 95 | * `p` - named pipe 96 | 97 | *Сокет - "разъем"* 98 | Любой транспорт (TCP, UDP) всегда устанавливает передачу через два сокета - на передачу и прием. 99 | Сокеты бывают сетевыми и IPC. 100 | Сетевые сокеты: 101 | * Datagram - просто отправляем пакет, который индивидуально маршрутизируется и доставлется 102 | Несколько процессов могут слать данные через один сокет 103 | Порядок доставки - не опрееделен 104 | ![dgram](dgram.jpg) 105 | * Stream - устанавливаем соединение и надежно передаем последовательный поток данных 106 | 107 | Реализация сокетов лежит на протоколе. 108 | Posix Socket API (нечто похожее есть и у винды): 109 | https://en.wikipedia.org/wiki/Berkeley_sockets#Socket_API_functions 110 | * socket(domain, type, protocol) - создать сокет 111 | domain: {AF_INET, AF_UNIX} 112 | type: {SOCK_STREAM, SOCK_DGRAM} 113 | protocol: {IPPROTO_TCP, IPPROTO_UDP} 114 | * bind() - привязывает сокет к конкретному адресу и порту - `{ip_addr, port_number}` (сервер) 115 | Сетевой порт - целое число - абстрация. Во всех транспортах получатель/отправитель - адрес+порт. 116 | Cокет не может работать с данными пока сокет не ассоциирован с портом 117 | * listen() - перевод сокета в состояние прослушки (сервер) 118 | * connect() - присваивает сокету свободный порт (клиент) 119 | * accept() - принять входящее соединение и создать новый сокет 120 | * send/recv() - отправить/получить данные 121 | * close() - закрыть соединение и сокет 122 | 123 | Давайте сделаем свой простенький TCP-сервис: 124 | ``` 125 | simple_tcp_server.py 126 | simple_tcp_client.py 127 | ``` 128 | 129 | Запустим две сессии клиента и посмотрим lsof: 130 | lsof - list open files and processes using them 131 | `lsof -iTCP | grep python` 132 | 133 | 134 | ## Simple HTTP server 135 | https://docs.python.org/3/library/http.server.html 136 | Давайте сделаем свой простенький HTTP-сервачок: 137 | ``` 138 | simple_http_server.py 139 | ``` 140 | Посмотрим на то, что он делает 141 | 142 | *Polling* 143 | Ожидание наступления события (прилетели данные в socket) - позволим нас лишь уведомить. 144 | Аналогия из жизни - уход за младенцем - можем вечно стоять над ним, либо поставить рацию и приходить только на крики. 145 | ![ptable](epoll.jpg) 146 | https://docs.python.org/3/library/selectors.html 147 | Polling считается ключевым компонентом асинхронного IO, но само по себе это не "асинхронщина". 148 | 149 | Прежде всего - мультиплексирование IO. 150 | Считаем и запишем - ждем одно и ждем другое: 151 | ```python 152 | while True: 153 | n = read(read_tty, buf) 154 | if n > 0: 155 | if write(write_tty, buf, n) != n: 156 | log.critical("IO error") 157 | else: 158 | log.info("Success") 159 | ``` 160 | Если файл - то норм, а если это интерактивный ввод/вывод? 161 | Файловые дескрипторы можно выставить в неблокирующий режим. 162 | ```python 163 | fd_read, fd_write = open_fd(read_tty, write_tty) 164 | 165 | def ready_func(sock): 166 | conn, addr = sock.accept() 167 | selector.register(conn, EVENT_READ, write_func) 168 | 169 | def write_func(sock): 170 | conn, addr = sock.accept() 171 | data = conn.recv(10000) 172 | if data: 173 | fd_write.write(data) 174 | conn.close() 175 | 176 | selector.register(fd_read, EVENT_READ, callback=ready_func) 177 | while True: 178 | events = selector.select() 179 | for e in events: 180 | e.callback() 181 | ``` 182 | 183 | read() vs epoll(): 184 | * для read мы ждем только один дескриптор 185 | * epoll позволяет нам ожидать события по нескольким дескрипторам 186 | * не тратим вхолостую CPU - read постоянно проверяет готовность буфера 187 | (системные вызовы почти никогда не работают напрямую с IO, ну кроме вещей типа fsync) 188 | 189 | ## Nginx 190 | (c) Гроссмейстер соединений 191 | https://habr.com/ru/post/260065/ 192 | 193 | С давних времен был главным Apache. 194 | Проблема его - 10к соединений (на каждый коннект - отдельный поток, пока не коннект не завершился - поток блокирован) 195 | https://en.wikipedia.org/wiki/C10k_problem 196 | ![block](workblock.png) 197 | 198 | Затем пришел Nginx и всех уделал. 199 | ![block](epollwork.png) 200 | Активный сокет распределен по всем воркерам 201 | Новое соединения порождают новый сокеты 202 | Далее воркер мониторит (polling) сокеты соединений и если пришли данные - возвращается мгновенный ответ 203 | Но воркеры в целом не резиновые и исчерпать лимит открытых дескрипторов можно, придется тюнить 204 | https://medium.com/@cubxi/nginx-too-many-open-files-error-solution-for-ubuntu-b2adaf155dc5 205 | 206 | *Nginx обязателен при работе в проме!* - чтобы снизить нагрузку и уберечься от ботов. 207 | Выставить питоновский сервер в интернет - для сервера это как уронить мыло в тюрьме... 208 | 209 | ## CGI 210 | Ранний интернет был статичным. 211 | Набросал страничку, сервак ее раздал. 212 | 213 | Со временем появилась потребность в динамической генерации контента. 214 | Сервера начали учиться запускать скрипты при обработке http-запроса. 215 | Так появился CGI. 216 | 217 | Сервер и запущенный скрипт общались через стандартные стримы и переменные среды. 218 | Пример с википедии: 219 | ```perl 220 | #!/usr/bin/perl 221 | print "Content-Type: text/plain\n\n"; 222 | for my $var ( sort keys %ENV ) { 223 | printf "%s = \"%s\"\n", $var, $ENV{$var}; 224 | } 225 | > curl http://example.com/cgi-bin/printenv.pl/foo/bar?var1=value1&var2=with%20percent%20encoding 226 | Response: 227 | DOCUMENT_ROOT="C:/Program Files (x86)/Apache Software Foundation/Apache2.4/htdocs" 228 | GATEWAY_INTERFACE="CGI/1.1" 229 | HTTP_ACCEPT="text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" 230 | HTTP_ACCEPT_CHARSET="ISO-8859-1,utf-8;q=0.7,*;q=0.7" 231 | HTTP_ACCEPT_ENCODING="gzip, deflate, br" 232 | HTTP_ACCEPT_LANGUAGE="en-us,en;q=0.5" 233 | HTTP_CONNECTION="keep-alive" 234 | HTTP_HOST="example.com" 235 | HTTP_USER_AGENT="Mozilla/5.0 (Windows NT 6.1; WOW64; rv:67.0) Gecko/20100101 Firefox/67.0" 236 | SERVER_ADDR="127.0.0.1" 237 | SERVER_ADMIN="(server admin's email address)" 238 | SERVER_NAME="127.0.0.1" 239 | SERVER_PORT="80" 240 | SERVER_PROTOCOL="HTTP/1.1" 241 | SERVER_SIGNATURE="" 242 | SERVER_SOFTWARE="Apache/2.4.39 (Win32) PHP/7.3.7" 243 | ``` 244 | https://en.wikipedia.org/wiki/Common_Gateway_Interface 245 | 246 | Проблемы: 247 | * Большой оверхед - каждый запрос поднимал новый CGI-процесс 248 | * Небезопасно - неэкранированные данные приводили к произвольному исполнению кода 249 | 250 | Одно из удачных решений - FastCGI. 251 | Суть - поднять пул воркеров. 252 | 253 | ## WSGI 254 | Приложения Python для веба деплоились по разному - через cgi/fastcgi/mod_python/.... 255 | Решили сделать единый интерфейс веб-сервера - PEP 333 256 | https://www.python.org/dev/peps/pep-0333/ 257 | 258 | Роли остались прежними: 259 | * Server/Gateway - специальный веб-сервер(nginx/apache) или некий application server 260 | * Application/Framework - python callable 261 | 262 | WSGI определяет: 263 | * какие параметры должен принять callable и что должно вернуть серверу 264 | * пространство переменных среды передаваемых для обработки 265 | * спецификацию потокового ввода, буферизация 266 | * обработку заголовков и других фичей HTTP 267 | 268 | WSGI не определяет: 269 | * как именно запускать интепретатор 270 | * как инициализировать приложение 271 | 272 | *Пайплайнинг обработки запроса* 273 | Некоторый callable могут использоваться для дополнительной обработки запроса - фильтрация/валидация/авторизация/... 274 | WSGI определяет это как middleware 275 | 276 | ## Werkzeug 277 | Самая функциональная библиотека для работы с WSGI. 278 | Что должна уметь WSGI-библиотека? 279 | * обработка заголовков 280 | * отправка и прием cookies 281 | * обеспечить доступ к данным HTML-форм 282 | * ... 283 | 284 | Werkzeug - не сервер. Но есть модуль-сервер. 285 | Состряпаем свой сервачок на Werkzeug. 286 | ``` 287 | werkzeug_demo/zeug.py 288 | ``` 289 | Для тестов сервера возьмем requests. 290 | ``` 291 | import requests 292 | r = requests.post('http://localhost:5001/bigdata', data={'name': 'евпатий'}) 293 | ``` 294 | 295 | ## Gunicorn 296 | WSGI HTTP Server 297 | ``` 298 | pip install gunicorn 299 | cd werkzeug_demo 300 | gunicorn -w 2 zeug:create_app 301 | ``` 302 | -------------------------------------------------------------------------------- /lecture_9/ptable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_9/ptable.png -------------------------------------------------------------------------------- /lecture_9/requirements.txt: -------------------------------------------------------------------------------- 1 | werkzeug -------------------------------------------------------------------------------- /lecture_9/simple_http_server.py: -------------------------------------------------------------------------------- 1 | from http.server import HTTPServer, SimpleHTTPRequestHandler 2 | 3 | #1 https://docs.python.org/3/library/http.server.html 4 | #2 https://github.com/python/cpython/blob/3.8/Lib/http/server.py 5 | #3 https://github.com/python/cpython/blob/cc011b5190b63f0be561ddec38fc4cd9e60cbf6a/Lib/socketserver.py#L390 6 | 7 | # From #3.L262 8 | # The distinction between handling, getting, processing and finishing a 9 | # request is fairly arbitrary. Remember: 10 | # 11 | # - handle_request() is the top-level call. It calls selector.select(), 12 | # get_request(), verify_request() and process_request() 13 | # - get_request() is different for stream or datagram sockets 14 | # - process_request() is the place that may fork a new process or create a 15 | # new thread to finish the request 16 | # - finish_request() instantiates the request handler class; this 17 | # constructor will handle the request all by itself 18 | 19 | PORT = 8080 20 | ADDR = 'localhost' 21 | 22 | 23 | # SimpleHTTPRequestHandler отнаследован от StreamRequestHandler 24 | class PimpHandler(SimpleHTTPRequestHandler): 25 | HTML = "

Yo dawg %s

" 26 | 27 | def set_header(self): 28 | self.send_response(200) 29 | self.send_header("Content-Type", "text/html") 30 | self.end_headers() 31 | 32 | def do_GET(self) -> None: 33 | self.set_header() 34 | self.wfile.write( 35 | (self.HTML % self.path.strip().replace('/', '')).encode() 36 | ) 37 | 38 | 39 | httpd = HTTPServer((ADDR, PORT), PimpHandler) 40 | httpd.serve_forever() 41 | -------------------------------------------------------------------------------- /lecture_9/simple_tcp_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | import socket 3 | from click import prompt 4 | 5 | # Create a TCP/IP socket 6 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 7 | 8 | # Connect the socket to the port on the server given by the caller 9 | server_address = ('localhost', 7777) 10 | print('PID=%d, connecting to %s port %s' % (os.getpid(), *server_address)) 11 | sock.connect(server_address) 12 | 13 | while True: 14 | message = ('"'+prompt("Enter message, to stop - STOP/Ctrl-C", type=str)+'"').encode() 15 | if message == 'STOP': 16 | break 17 | sock.sendall(message) 18 | receiver = b"" 19 | amount_received = 0 20 | amount_expected = len(message) 21 | while amount_received < amount_expected: 22 | # блокирующий вызов 23 | data = sock.recv(8) 24 | receiver += data 25 | amount_received += len(data) 26 | print(' ~~~ received %s' % data) 27 | print("Final answer: %s" % receiver.decode()) 28 | 29 | sock.close() 30 | -------------------------------------------------------------------------------- /lecture_9/simple_tcp_server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import socket 3 | 4 | # Create a TCP/IP socket 5 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 6 | 7 | # Bind the socket to the port 8 | server_address = ('localhost', 7777) 9 | print('PID=%d, running at %s:%s' % (os.getpid(), *server_address)) 10 | sock.bind(server_address) 11 | sock.listen(1) 12 | 13 | while True: 14 | # blocking call 15 | connection, client_address = sock.accept() 16 | print('Incoming transmission: ', client_address) 17 | data: bytes = b"" 18 | while True: 19 | # blocking call 20 | data_i: bytes = connection.recv(8) 21 | if len(data_i) == 0: 22 | # Skipping empty sendings - form slow clients 23 | continue 24 | print(' ~~~ received batch "%s"' % data_i) 25 | data += data_i 26 | if data.count(b'"') == 2: 27 | break 28 | else: 29 | continue 30 | data_repr = data.decode() 31 | sending = data_repr[::-1] 32 | connection.sendall(sending.encode()) 33 | print('Got its kicks (%s), %s' % (client_address, sending)) 34 | connection.close() -------------------------------------------------------------------------------- /lecture_9/werkzeug_demo/zeug.py: -------------------------------------------------------------------------------- 1 | import os 2 | from werkzeug.wrappers import Request, Response 3 | from werkzeug.routing import Map, Rule 4 | 5 | # werkzeug.serving.BaseWSGIServer 6 | # TODO - сделать подходящим для Gunicorn 7 | 8 | class ProxyAudit(object): 9 | """Combine multiple applications as a single WSGI application. 10 | Requests are dispatched to an application based on the path it is 11 | mounted under. 12 | 13 | :param app: The WSGI application to dispatch to if the request 14 | doesn't match a mounted path. 15 | :param mounts: Maps path prefixes to applications for dispatching. 16 | """ 17 | 18 | def __init__(self, app, *args, **kwargs): 19 | self.app = app 20 | 21 | def __call__(self, environ, start_response): 22 | print("Incoming transmission :: accessing '%s'" % 23 | environ.get('REQUEST_URI') 24 | ) 25 | return self.app(environ, start_response) 26 | 27 | 28 | class Briefly(object): 29 | def __init__(self, *args, **kwargs): 30 | self.glob_value = 'Dawg' 31 | 32 | def dispatch_request(self, request): 33 | if request.method == 'GET': 34 | return Response('Yo dawg [%s], I heard you like...' % self.glob_value, status=200) 35 | elif request.method == 'POST': 36 | self.glob_value = request.form['name'] 37 | return Response(self.glob_value[::-1], status=200) 38 | 39 | def wsgi_app(self, environ, start_response): 40 | request = Request(environ) 41 | response = self.dispatch_request(request) 42 | return response(environ, start_response) 43 | 44 | def __call__(self, environ, start_response): 45 | return self.wsgi_app(environ, start_response) 46 | 47 | 48 | def create_app(*args, **kwargs): 49 | app = Briefly() 50 | n = ProxyAudit(app) 51 | return n 52 | 53 | 54 | if __name__ == '__main__': 55 | from werkzeug.serving import run_simple 56 | app = create_app() 57 | run_simple('127.0.0.1', 5001, app, use_debugger=True, use_reloader=True) 58 | -------------------------------------------------------------------------------- /lecture_9/workblock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kib-courses/python_developer/7141c9e4d0deb5f2943d70eaf189316631942e56/lecture_9/workblock.png -------------------------------------------------------------------------------- /project_the/project_requirements.md: -------------------------------------------------------------------------------- 1 | # Требования к семестровому проекту 2 | 3 | ## Сущность проекта 4 | 5 | - Проект представляет собой серверное многопользовательское приложение, реализованное на Python 3.6. Тема проекта - любая, 6 | но особенно приветствуются темы, связанные с Data Science и инфобезом. 7 | 8 | ## Организационные вопросы 9 | 10 | 1. Состав команд: 3 человека (по отдельному согласованию с преподавателями допускается 2 или 4 человека) 11 | 2. Создаете репозиторий на гитхабе, добавляете туда участников своей команды, а также @romvano и @nmatkheev 12 | 3. Регистрируетесь в таблице: https://docs.google.com/spreadsheets/d/1s_WJ66cojB8z-EmbiLnAFlRtLFPQVo50SucwmA6M9l8/edit?usp=sharing 13 | 4. Мы в таблице проставляем каждому проекту ментора 14 | 5. Создаете в slack чат своей команды с ментором 15 | 6. Дедлайн по написанию кода - **20 июня**, дальше для защиты технической части задания - созвон командой с не своим ментором 16 | 7. **24 июня** - публичная защита проектов (скорее всего, в zoom, но если всё будет хорошо - то очно) 17 | 8. Оценки общие для команды, но парадигма "один паровоз и два пассажира" не сработает =) 18 | 19 | ## Техническая реализация 20 | ### Нюансы по коду Python 21 | 22 | - Реализация веб-сервиса на одном из фреймворков - Flask, Django, Dash (остальное по согласованию) 23 | - Использование хотя бы трех паттернов ООП 24 | - Единый стиль кода. PEP8 + Аннотации. Документация функций и классов в коде 25 | - Классы и функции должны быть атомарными. Не нужно всё лепить в кучу 26 | - Взаимодействие с БД - крайне желательно через sqlalchemy ORM (либо Django ORM) 27 | 28 | ### Структура проекта 29 | Проект должен содержать: 30 | 31 | - сам код 32 | - тесты 33 | - cli 34 | - requirements.txt 35 | - [опционально] setup.py 36 | 37 | ### Требования к технологиям 38 | - Должно быть реализовано хранение данных в Postgres. Использование нереляционных БД - дополнительно и по согласованию 39 | - Проект должен быть развернут на связке [выбранный фреймворк] + gunicorn + nginx 40 | 41 | ### CI & CD: 42 | Автоматически должно происходить следующее: 43 | 44 | - при мерже в мастер - обязательный прогон тестов 45 | - автоматический деплой проекта после мержа в мастер 46 | - [опционально]: автоматическая сборка whl и залив на приватный pypi-репозиторий после мержа в мастер 47 | 48 | Можно использовать любые инструменты CI/CD 49 | 50 | ### Использование готового проекта 51 | 52 | Проект должен поставляться во всех следующих вариантах: 53 | 54 | - приложение, развернутое на сервере (heroku или любой другой) 55 | - docker-контейнер с развернутым проектом + docker-compose-файл 56 | - [опционально]: pip install your_project; your_project run - внутри docker-compose-файла 57 | --------------------------------------------------------------------------------