Commit df87d84e authored by christian.foerster's avatar christian.foerster
Browse files

restructering tutorial, and adding better description

parent a5c79194
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Observer Pattern"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [],
"source": [
"from __future__ import annotations\n",
"from abc import ABC, abstractmethod\n",
"from random import randrange\n",
"from typing import List\n",
"\n",
"\n",
"class Subject(ABC):\n",
" \"\"\"\n",
" The Subject interface declares a set of methods for managing subscribers.\n",
" \"\"\"\n",
"\n",
" @abstractmethod\n",
" def attach(self, observer: Observer) -> None:\n",
" \"\"\"\n",
" Attach an observer to the subject.\n",
" \"\"\"\n",
" pass\n",
"\n",
" @abstractmethod\n",
" def detach(self, observer: Observer) -> None:\n",
" \"\"\"\n",
" Detach an observer from the subject.\n",
" \"\"\"\n",
" pass\n",
"\n",
" @abstractmethod\n",
" def notify(self) -> None:\n",
" \"\"\"\n",
" Notify all observers about an event.\n",
" \"\"\"\n",
" pass\n",
"\n",
"\n",
"class ConcreteSubject(Subject):\n",
" \"\"\"\n",
" The Subject owns some important state and notifies observers when the state\n",
" changes.\n",
" \"\"\"\n",
"\n",
" _state: int = None\n",
" \"\"\"\n",
" For the sake of simplicity, the Subject's state, essential to all\n",
" subscribers, is stored in this variable.\n",
" \"\"\"\n",
"\n",
" _observers: List[Observer] = []\n",
" \"\"\"\n",
" List of subscribers. In real life, the list of subscribers can be stored\n",
" more comprehensively (categorized by event type, etc.).\n",
" \"\"\"\n",
"\n",
" def attach(self, observer: Observer) -> None:\n",
" print(\"Subject: Attached an observer.\")\n",
" self._observers.append(observer)\n",
"\n",
" def detach(self, observer: Observer) -> None:\n",
" self._observers.remove(observer)\n",
"\n",
" \"\"\"\n",
" The subscription management methods.\n",
" \"\"\"\n",
"\n",
" def notify(self) -> None:\n",
" \"\"\"\n",
" Trigger an update in each subscriber.\n",
" \"\"\"\n",
"\n",
" print(\"Subject: Notifying observers...\")\n",
" for observer in self._observers:\n",
" observer.update(self)\n",
"\n",
" def some_business_logic(self) -> None:\n",
" \"\"\"\n",
" Usually, the subscription logic is only a fraction of what a Subject can\n",
" really do. Subjects commonly hold some important business logic, that\n",
" triggers a notification method whenever something important is about to\n",
" happen (or after it).\n",
" \"\"\"\n",
"\n",
" print(\"\\nSubject: I'm doing something important.\")\n",
" self._state = randrange(0, 10)\n",
"\n",
" print(f\"Subject: My state has just changed to: {self._state}\")\n",
" self.notify()\n",
"\n",
"\n",
"class Observer(ABC):\n",
" \"\"\"\n",
" The Observer interface declares the update method, used by subjects.\n",
" \"\"\"\n",
"\n",
" @abstractmethod\n",
" def update(self, subject: Subject) -> None:\n",
" \"\"\"\n",
" Receive update from subject.\n",
" \"\"\"\n",
" pass\n",
"\n",
"\n",
"\"\"\"\n",
"Concrete Observers react to the updates issued by the Subject they had been\n",
"attached to.\n",
"\"\"\"\n",
"\n",
"\n",
"class ConcreteObserverA(Observer):\n",
" def update(self, subject: Subject) -> None:\n",
" if subject._state < 3:\n",
" print(\"ConcreteObserverA: Reacted to the event\")\n",
"\n",
"\n",
"class ConcreteObserverB(Observer):\n",
" def update(self, subject: Subject) -> None:\n",
" if subject._state == 0 or subject._state >= 2:\n",
" print(\"ConcreteObserverB: Reacted to the event\")\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Subject: Attached an observer.\n",
"Subject: Attached an observer.\n",
"\n",
"Subject: I'm doing something important.\n",
"Subject: My state has just changed to: 7\n",
"Subject: Notifying observers...\n",
"ConcreteObserverB: Reacted to the event\n",
"\n",
"Subject: I'm doing something important.\n",
"Subject: My state has just changed to: 8\n",
"Subject: Notifying observers...\n",
"ConcreteObserverB: Reacted to the event\n",
"\n",
"Subject: I'm doing something important.\n",
"Subject: My state has just changed to: 9\n",
"Subject: Notifying observers...\n",
"ConcreteObserverB: Reacted to the event\n"
]
}
],
"source": [
"subject = ConcreteSubject()\n",
"\n",
"observer_a = ConcreteObserverA()\n",
"subject.attach(observer_a)\n",
"\n",
"observer_b = ConcreteObserverB()\n",
"subject.attach(observer_b)\n",
"\n",
"subject.some_business_logic()\n",
"subject.some_business_logic()\n",
"\n",
"subject.detach(observer_a)\n",
"\n",
"subject.some_business_logic()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.1"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
......@@ -44,7 +44,7 @@
"source": [
"## Tank Example\n",
"\n",
"The goal here is to show you how to aget from an idea (a bit of quick and dirty code) to a fully setup and documented python package. We'll be stepping through this entire process not necessarily in the right order! But for demonstration purposes this way is better!"
"The goal here is to show you how to get from an idea (a bit of quick and dirty code) to a fully setup and documented python package. We'll be stepping through this entire process not necessarily in the right order! But for demonstration purposes this way is better!"
]
},
{
......@@ -58,7 +58,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 2,
"metadata": {},
"outputs": [
{
......@@ -98,9 +98,6 @@
"# check the model\n",
"print(tank_4[\"level\"])\n",
"\n",
"\n",
"\n",
"\n",
"# Thoughts:\n",
"# What about if the setup changes and we want more tanks?\n",
"# Will other people understand my code?\n",
......@@ -119,6 +116,44 @@
"also understands whats goin on."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<type Tank>\n",
"tank_id: tank_1\n",
"level: 50\n",
"rate: 0.21"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# creating a tank class and defining its representation\n",
"class Tank:\n",
" # run when initating an instance\n",
" def __init__(self, tank_id, level, rate):\n",
" self.tank_id = tank_id\n",
" self.level = level\n",
" self.rate = rate\n",
" self.__this_is_hidden = \"abc\"\n",
" # our joice of representation for a tank!\n",
" # so that you and the user knows whats happening\n",
" # accessable with print(tank_instance) or repr(tank_instance)\n",
" def __repr__(self):\n",
" return \"<type Tank>\\ntank_id: {}\\nlevel: {}\\nrate: {}\".format(self.tank_id,self.level,self.rate)\n",
" \n",
"tank_1 = Tank(\"tank_1\", 50, 0.21)\n",
"tank_1"
]
},
{
"cell_type": "code",
"execution_count": 2,
......@@ -163,6 +198,13 @@
" tank_3.level += Q_23\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
......@@ -173,6 +215,59 @@
"Or in other words each tank has its own discharge so let's code it like that."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"# adding discharge as property of a tank so we should add this to our Tank class\n",
"class Tank:\n",
" # run when initating an instance\n",
" def __init__(self, tank_id, level, rate):\n",
" self.tank_id = tank_id\n",
" self.level = level\n",
" self.rate = rate\n",
" \n",
" # the method Q calculates the flow of a given tank every time it's called\n",
" @property # allows use of the method without brackets \n",
" def Q(self):\n",
" return self.level * self.rate\n",
" \n",
" # our joice of representation for a tank!\n",
" # so that the user knows whats happening\n",
" def __repr__(self):\n",
" return \"<type Tank>\\ntank_id: {}\\nlevel: {}\\nrate: {}\".format(self.tank_id,self.level,self.rate)\n",
"tank_1 = Tank(\"tank_1\", 50, 0.21)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"10.5"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tank_1.Q"
]
},
{
"cell_type": "code",
"execution_count": 12,
......@@ -289,6 +384,50 @@
"- What about the levels? We want to know what is happending to the tank levels so lets create a function to retrieve this info..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def initiate_tanks(network_structure, attributes):\n",
" # init all tanks without upstream tanks\n",
" tanks = {}\n",
" for tank_id in attributes:\n",
" level = attributes[tank_id][0]\n",
" rate = attributes[tank_id][1]\n",
" tanks[tank_id] = Tank(tank_id, level, rate, upstream_tanks=None)\n",
" # add upstream tanks\n",
" for tank_id in network_structure:\n",
"\n",
" tanks[tank_id].upstream_tanks = [tanks[tank_id_up] for tank_id_up in network_structure[tank_id]]\n",
" return tanks"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def initiate_tanks(network_structure, attributes):\n",
" # init all tanks without upstream tanks\n",
" tanks = {}\n",
" for tank_id in attributes:\n",
" level = attributes[tank_id][0]\n",
" rate = attributes[tank_id][1]\n",
" tanks[tank_id] = Tank(tank_id, level, rate, upstream_tanks=None)\n",
" # add upstream tanks\n",
" for tank_id in network_structure:\n",
" \n",
" up_tanks = []\n",
" for tank_id_up in network_structure[tank_id]:\n",
" up_tanks.append(tanks[tank_id_up])\n",
" \n",
" tanks[tank_id].upstream_tanks = up_tanks\n",
" return tanks"
]
},
{
"cell_type": "code",
"execution_count": 34,
......@@ -340,6 +479,7 @@
" tanks[tank_id] = Tank(tank_id, level, rate, upstream_tanks=None)\n",
" # add upstream tanks\n",
" for tank_id in network_structure:\n",
" \n",
" tanks[tank_id].upstream_tanks = [tanks[tank_id_up] for tank_id_up in network_structure[tank_id]]\n",
" return tanks\n",
"\n",
......@@ -388,6 +528,52 @@
"Document to use [sphinx](http://www.sphinx-doc.org/en/master/) later! Different documentation styles are available we'll be using the [numpy style](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_numpy.html)."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"def maximum(a,b):\n",
" \"\"\"\n",
" My documentation\n",
" \n",
" Parameter\n",
" ---------\n",
" a : int\n",
" \n",
" b : int\n",
" \"\"\"\n",
" if a>b:\n",
" return a\n",
" else:\n",
" return b"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Help on function maximum in module __main__:\n",
"\n",
"maximum(a, b)\n",
" My documentation\n",
" \n",
" a : int\n",
" b : int\n",
"\n"
]
}
],
"source": [
"help(maximum)"
]
},
{
"cell_type": "code",
"execution_count": 35,
......@@ -589,7 +775,7 @@
},
{
"cell_type": "code",
"execution_count": 36,
"execution_count": 27,
"metadata": {},
"outputs": [
{
......@@ -794,7 +980,7 @@
},
{
"cell_type": "code",
"execution_count": 38,
"execution_count": 28,
"metadata": {},
"outputs": [
{
......@@ -846,338 +1032,6 @@
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Packaging\n",
"\n",
"Jupyter Notebook is not ideal for packaging.\n",
"We'll be negelecting the setup of a virtual environment not, as it might only confuse you and it's setup if different from ide to ide. \n",
"\n",
"Setting up virtual environments:\n",
" - Anaconda (https://uoa-eresearch.github.io/eresearch-cookbook/recipe/2014/11/20/conda/)\n",
" - virtualenv python module (https://realpython.com/python-virtual-environments-a-primer/)\n",
" - Pycharm simply does it for you! (also uses virtualenv)\n",
"\n",
"---\n",
"### Set up Folders and Files\n",
"We're gonna go through all the steps now. The low level basic way!\n",
"- create a folder called after your package (tanks)\n",
"- go into that folder\n",
"- create \n",
" - readme.md\n",
" - setup.py\n",
" - license.txt\n",
" - package folder\n",
" - doc folder\n",
" - test folder\n",
"\n",
"**Terminal commands make our lives easier!**\n",
"\n",
"Linux and Mac commands for setup\n",
"```shell\n",
"mkdir tanks\n",
"cd tanks\n",
"mkdir tanks\n",
"mkdir doc\n",
"mkdir test\n",
"touch readme.md\n",
"touch setup.py\n",
"touch license.txt\n",
"```\n",
"\n",
"Windows commands for setup (not sure whetherthes commands work on all windows versions!)\n",
"```shell\n",
"mkdir tanks\n",
"cd tanks\n",
"mkdir tanks\n",
"mkdir doc\n",
"mkdir test\n",
"type NUL > readme.md\n",
"type NUL > setup.py\n",
"type NUL > license.txt\n",
"```\n",
"---\n",
"### Fill Folders and Files\n",
"The folder structure has been set up. Let's fill files and folders:\n",
"\n",
"- write your readme.md\n",
"- add licence eg. copy paste (https://opensource.org/licenses/MIT)\n",
"- enter the test folder and add test.py with our tests (the following snipped needs to be added)\n",
"\n",
"```python\n",
"import os\n",
"import sys\n",
"sys.path.append(os.path.abspath(\".\"))\n",
"\n",
"from tanks.tanks import Tank\n",
"```\n",
"\n",
"- enter the package folder and create two files \n",
" - \\__init__.py (2 underscores front and back)\n",
" - tanks.py (add our code)\n",
"- fill in the setup.py with the following\n",
"\n",
"```python\n",
"from setuptools import setup, find_packages\n",
"\n",
"setup(name='tanks',\n",
" version='1.0',\n",
" description='A tool for simulating water tanks.',\n",
" long_description='Our very long explanation on what our package does',\n",
" classifiers=[\n",
" 'Development Status :: early stage',\n",
" 'Programming Language :: Python :: 3',\n",
" ],\n",
" install_requires=[], # this will normally be containg depencies from packages\n",
" packages=find_packages(exclude=(\"doc\",\".git\",\".idea\",\"venv\", \"test\")),\n",
" keywords='Water Management',\n",
" author='Christian Foerster',\n",
" author_email='Christian.Foerster@eawag.ch',\n",
" license='MIT')\n",
"```\n",
"\n",
"---\n",
"### Generate Documentation\n",
"\n",
"First we'll install Sphinx.\n",
"```sh\n",
"pip3 install -U sphinx sphinx_rtd_theme\n",
"```\n",
"\n",
"Now we want to open a terminal in the doc folder we created before and run (defaults will do):\n",
"\n",
"```sh\n",
"sphinx-quickstart\n",
"```\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Tweak Sphinx Settings\n",
"\n",
"\n",
"The doctrings in our code are written in the NumPy style. Other styles are available, if you want to check some out ( ([stackflow overview](https://stackoverflow.com/questions/3898572/what-is-the-standard-python-docstring-format), [sphinx overview](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html)) )\n",
"\n",
"In order for spinx to handle the NumPy style we must include the `sphinx.ext.napoleon` extension and add a few other settings to the conf.py. \n",
"\n",
"```python\n",
"# Configuration file for the Sphinx documentation builder.\n",
"#\n",
"# This file only contains a selection of the most common options. For a full\n",
"# list see the documentation:\n",
"# http://www.sphinx-doc.org/en/master/config\n",
"\n",
"# -- Path setup --------------------------------------------------------------\n",
"\n",
"# If extensions (or modules to document with autodoc) are in another directory,\n",