<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Python | Jacob Aloysious</title><link>https://jacobaloysious.in/tag/python/</link><atom:link href="https://jacobaloysious.in/tag/python/index.xml" rel="self" type="application/rss+xml"/><description>Python</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><lastBuildDate>Fri, 02 Oct 2020 00:00:00 +0000</lastBuildDate><image><url>https://jacobaloysious.in/images/icon_hu4591c05f594249c11c1e99a3a8f1f246_3759739_512x512_fill_lanczos_center_2.png</url><title>Python</title><link>https://jacobaloysious.in/tag/python/</link></image><item><title>Apache Airflow and Regression Monitoring</title><link>https://jacobaloysious.in/post/tech_airflow_reg_monitor/</link><pubDate>Fri, 02 Oct 2020 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_airflow_reg_monitor/</guid><description>&lt;h2 id="introduction">Introduction:&lt;/h2>
&lt;p>Airflow is a platform to programmatically author, schedule and monitor workflows or data pipelines. It was originally developed and open sourced by Airbnb, later joined Apache Software foundation’s incubation program in 2016. Workflow is a sequence of tasks defined around Directed Acyclic Graph(DAGs) – which could be started on a schedule or triggered by an event or using Command line interface. Airflow pipelines are configuration as code (Python), allowing for dynamic pipeline generation. This allows for writing code that instantiate pipelines dynamically.&lt;/p>
&lt;h2 id="components">Components:&lt;/h2>
&lt;p>&lt;img src="airflow_component.jpg" alt="alt Components" title="Airflow Components">&lt;/p>
&lt;h4 id="metadata-db">Metadata DB:&lt;/h4>
&lt;p>Stores information&amp;rsquo;s like job status and task instance status.&lt;/p>
&lt;h4 id="scheduler">Scheduler:&lt;/h4>
&lt;p>Airflow scheduler executes your tasks on an array of workers while following the specified dependencies. The scheduler is the brains behind setting up the workflows in airflow. The execution time begins at DAG start date and repeat every schedule interval.&lt;/p>
&lt;h4 id="web-interface-ui">Web Interface (UI):&lt;/h4>
&lt;p>Airflow ships with a Flask app that tracks all the defined workflows and lets you easily change, start or stop them. The rich user interface makes it easy to visualize pipelines running in production, monitor progress and troubleshoot issues.&lt;/p>
&lt;h4 id="cli">CLI:&lt;/h4>
&lt;p>Airflow has a very rich command line interface that allows to test, run, backfill, describe and clear parts of your DAGs&lt;/p>
&lt;h2 id="concepts">Concepts:&lt;/h2>
&lt;h4 id="dag">DAG:&lt;/h4>
&lt;p>A DAG is the container that is used to organize tasks in a way that reflects their relationship, dependencies and set their execution context and order.&lt;/p>
&lt;h4 id="operators">Operators:&lt;/h4>
&lt;p>Operators are the worker that run the tasks. Workflows are defined by creating a DAG of operators. They are broadly classified into three – Sensors, Operators and Transfers. Airflow provides many prebuild operators for many common tasks and new operators can be created by inheriting BaseOperator class.&lt;/p>
&lt;h4 id="tasks">Tasks:&lt;/h4>
&lt;p>Once an operator is instantiated, its is referred to as a “task”. Each task is user defined and responsible for performing a specific operation in the workflow. Instantiating a task requires providing a unique task_id and DAG container. Task can be python function or external scripts that could be invoked.&lt;/p>
&lt;h2 id="example">Example:&lt;/h2>
&lt;p>&lt;img src="example.jpg" alt="alt Example" title="Example">&lt;/p>
&lt;p>In the example, we show case - how Airflow could be used to express a workflow that can be used to generate the statistics/ report as part of end-to-end regression test suit; which involves multiple systems to work together. A traditional approach would use something very basic like bunch of batch scripts w/o CRON. But the challenge is - it would very easily get tangled and developer would spend a lot of time to figure out where the log files are or what failed and why/who owns what. Airflow helps solves this problem by helping in orchestrating your processes, managing the logs and really good dashboard with visualization of what failed and much more information.&lt;/p>
&lt;p>&lt;img src="code_snippet.jpg" alt="alt CodeSnippet" title="Code Snippt">&lt;/p>
&lt;h2 id="references">References:&lt;/h2>
&lt;ul>
&lt;li>Airflow : &lt;a href="https://airflow.apache.org">https://airflow.apache.org&lt;/a>&lt;/li>
&lt;li>Luigi: &lt;a href="https://luigi.readthedocs.io/en/stable/index.html">https://luigi.readthedocs.io/en/stable/index.html&lt;/a>&lt;/li>
&lt;li>Blog: &lt;a href="https://medium.com/airbnb-engineering/airflow-a-workflow-management-platform-46318b977fd8">https://medium.com/airbnb-engineering/airflow-a-workflow-management-platform-46318b977fd8&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Python Plugins with Topics</title><link>https://jacobaloysious.in/post/tech_python_plugins/</link><pubDate>Sun, 13 Sep 2020 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_python_plugins/</guid><description>&lt;p>Source Code:
&lt;a href="https://github.com/jacobaloysious/pyplugins" target="_blank" rel="noopener">PyPlugins&lt;/a>&lt;/p>
&lt;p>Any infrastructure should always have the capability to extend itself. It would be better if the functionality is added by a contributor - who is not part of the core team. And it doesn&amp;rsquo;t get in the way of core components - development, compilation and deployment.&lt;/p>
&lt;p>The concept of plugin has been around there for quite a while - Visual Studio/VS Code all has plugins (aka extensions). Basic idea here is to add new functionalities - by just deploying a new dll, jar or py modules.&lt;/p>
&lt;p>In this proposal using python, we have a root folder named &lt;strong>Plugins&lt;/strong> - the infra would enumerate the Plugins directory to add functions. The functionalities register themselves with a &lt;strong>Key&lt;/strong> - let’s call them &lt;strong>TOPICS&lt;/strong>.&lt;/p>
&lt;p>Well why &lt;strong>Topics&lt;/strong>? I am borrowing this idea from Messaging Queue infra like
&lt;a href="https://zeromq.org/" target="_blank" rel="noopener">ZMQ&lt;/a> and
&lt;a href="https://kafka.apache.org/" target="_blank" rel="noopener">Kafka&lt;/a>&amp;hellip; So, that we could create a Topic to Function mapping - and we would be able to map caller to a MQ subscriber.&lt;/p>
&lt;p>Let me walk through each component:&lt;/p>
&lt;h4 id="base-class">Base class&lt;/h4>
&lt;p>Base class that each plugin must inherit from; this class exposes couple of items 1) List of Topics 2) Execute Method - which your plugin should implement.&lt;/p>
&lt;pre>&lt;code>class IPlugin(object):
def __init__(self):
self.description = 'UNKNOWN'
self.topics = []
def execute(self, topic, argument):
&amp;quot;&amp;quot;&amp;quot;The method that we expect all plugins to implement. This is the
method that our framework will call
&amp;quot;&amp;quot;&amp;quot;
raise NotImplementedError
&lt;/code>&lt;/pre>
&lt;h4 id="example-plugin--calculate">Example Plugin : Calculate&lt;/h4>
&lt;p>Calculate Plugin - exposes two functionalities: &lt;em>add&lt;/em> and &lt;em>subtract&lt;/em>. For the client its exposed as two topics. The execute function takes in topic and the argument. Based on the topic the respective plugin could dispatch it to sub-functions within the plugin.&lt;/p>
&lt;pre>&lt;code>class CalculatorPlugin(IPlugin):
def __init__(self):
self.description = 'Calculator'
self.topics = ['Add', 'Subtract']
def execute(self, topic, args):
if topic == &amp;quot;Add&amp;quot;:
return self.add(args)
raise Exception (f'Topic: {topic} has no mapping function')
def add(self, args):
count = 0
for index in range(0, len(args)):
count += args[index]
return count
&lt;/code>&lt;/pre>
&lt;h4 id="service-discovery">Service Discovery:&lt;/h4>
&lt;p>The infrastructure would enumerate the plugin base_directory and try find sub class of &lt;strong>IPlugin&lt;/strong>.
Create instance of the sub_class and register to the store: &lt;code>MAP&amp;lt;topic, instance&amp;gt;&lt;/code>. Infra should be able to directly call the &lt;strong>execute&lt;/strong> API, on the instance.&lt;/p>
&lt;pre>&lt;code>class ServiceDiscovery(object):
def __init__(self, plugin_package_dir='plugins'):
self.plugin_package_base_dir = plugin_package_dir
self.plugin_topic_instance_map = {}
self.enumerate_packages()
def enumerate_packages(self, package):
&amp;quot;&amp;quot;&amp;quot;Recursively walk the supplied package to retrieve all plugins
&amp;quot;&amp;quot;&amp;quot;
imported_package = __import__(package, fromlist=['foo'])
for _, pluginname, ispkg in pkgutil.iter_modules(imported_package.__path__, imported_package.__name__ + '.'):
if not ispkg:
plugin_module = __import__(pluginname, fromlist=['foo'])
clsmembers = inspect.getmembers(plugin_module, inspect.isclass)
for (_, c) in clsmembers:
# Only add classes that are a sub class of Plugin, but NOT Plugin itself
if issubclass(c, IPlugin) &amp;amp; (c is not IPlugin):
print(f' Found plugin class: {c.__module__}.{c.__name__}')
cls_instance = c()
for topic in cls_instance.topics:
print(f' Registering Topics: {topic}')
self.plugin_topic_instance_map[topic] = cls_instance
&lt;/code>&lt;/pre>
&lt;h4 id="execution">Execution&lt;/h4>
&lt;p>Now that we have a &lt;code>Map&amp;lt;Topic,instance&amp;gt;&lt;/code>. When a call comes in - it would have a topic and the args. Using the Map, we could get the corresponding instance and call by passing in both the topic and args. This is similar to delegate (&lt;strong>C#&lt;/strong>) or function pointers(in &lt;strong>C&lt;/strong>).&lt;/p>
&lt;pre>&lt;code>class ServiceDiscovery(object):
def __init__(self, plugin_package_dir='plugins'):
...
self.plugin_topic_instance_map = {}
def execute(self, topic, argument):
if topic not in self.plugin_topic_instance_map:
raise Exception (f'Topic: {topic} is not registered')
return self.plugin_topic_instance_map[topic].execute(topic, argument)
&lt;/code>&lt;/pre>
&lt;h4 id="unit-test">Unit Test:&lt;/h4>
&lt;p>Writing unit test is not optional. Well, I am a fan of TDD 😉&lt;/p>
&lt;pre>&lt;code>def test_cal_plugin_add_func(self):
# Arrange
ser_dis = ServiceDiscovery()
# Action
result = ser_dis.execute(&amp;quot;Add&amp;quot;, [1, 2])
# Assert
self.assertEqual(result, 3)
&lt;/code>&lt;/pre>
&lt;h4 id="deployment">Deployment:&lt;/h4>
&lt;p>Adding a new plugin should be as simple as&lt;/p>
&lt;ul>
&lt;li>Copy and paste a new directory under the Plugin base directory&lt;/li>
&lt;li>Directory should have a class which implements &lt;code>IPlugin&lt;/code>&lt;/li>
&lt;/ul>
&lt;h4 id="conclusion">Conclusion:&lt;/h4>
&lt;p>Building a comprehensive plugin infrastructure is non-trivial; look at Visual Studio - you could override pretty much anything, starting from adding intellisese to a new compiler tool chain. Here, we are just look at a small tip - to get started - on having a python based plugin. Always starting off any infra project with the idea of extension in mind - is good to ensure cleaner responsibility separation.&lt;/p>
&lt;p>From the &lt;strong>SOLID&lt;/strong> principle : &lt;strong>O&lt;/strong> -&amp;gt; our software should be Opened for extension but closed for modifications 😍&lt;/p></description></item></channel></rss>