<?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/category/python/</link><atom:link href="https://jacobaloysious.in/category/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>Sun, 31 Oct 2021 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/category/python/</link></image><item><title>Access Control - Casbin</title><link>https://jacobaloysious.in/post/tech_casbin/</link><pubDate>Sun, 31 Oct 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_casbin/</guid><description>&lt;p>I was trying to read about RBAB (Role-Based Access Control) and ABAC(Attribute-Based Access Control); after some reading - I realized, as a software engineer - it&amp;rsquo;s way more easier when you get to read code - So, I started to search for open source project which implements access control infrastructure, and stumbled upon
&lt;a href="https://casbin.org/" target="_blank" rel="noopener">Casbin&lt;/a>.&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>Snip from Readme:&lt;/em> In Casbin, an access control model is abstracted into a CONF file based on the &lt;strong>PERM metamodel (Policy, Effect, Request, Matchers)&lt;/strong>. So switching or upgrading the authorization mechanism for a project is just as simple as modifying a configuration. You can customize your own access control model by combining the available models. For example, you can get RBAC roles and ABAC attributes together inside one model and share one set of policy rules.&lt;/p>
&lt;/blockquote>
&lt;p>The interesting part here is how Casbin - has designed the whole system behind a simple yet flexible configuration file and grammars to describe predicates. Intent of this blog is - just to get you curious about Casbin. Casbin has reasonally good documentation on their site.&lt;/p>
&lt;p>&lt;em>ProTip:&lt;/em> Best way to understand an open source is to start with the test cases, look at each test case - review the context and assert statements.&lt;/p>
&lt;p>Lets look at a simple
&lt;a href="https://github.com/jacobaloysious/pycasbin/blob/master/examples/rbac_model.conf" target="_blank" rel="noopener">RBAC Model Config&lt;/a>&lt;/p>
&lt;pre>&lt;code>[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) &amp;amp;&amp;amp; r.obj == p.obj &amp;amp;&amp;amp; r.act == p.act
&lt;/code>&lt;/pre>
&lt;p>
&lt;a href="https://github.com/jacobaloysious/pycasbin/blob/master/examples/rbac_policy.csv" target="_blank" rel="noopener">RBAC_Policy.CSV&lt;/a>&lt;/p>
&lt;pre>&lt;code>p, alice, data1, read
p, bob, data2, write
p, data2_admin, data2, read
p, data2_admin, data2, write
g, alice, data2_admin
&lt;/code>&lt;/pre>
&lt;p>Basically there are four parts: Request, Policy, Matcher and Effect.
&lt;a href="https://casbin.org/docs/en/how-it-works" target="_blank" rel="noopener">Ref&lt;/a>&lt;/p>
&lt;p>&lt;strong>Request:&lt;/strong> Defines the parameter &lt;em>name&lt;/em> and &lt;em>order&lt;/em> which we should provide for access control matching function. This enables the code/model to be setup when the data is read from the source.&lt;/p>
&lt;p>&lt;strong>Policy:&lt;/strong> It&amp;rsquo;s the Access stratergy. This is how our internal system is designed. It defines the name and order of the fields in the &lt;em>Policy rule document&lt;/em>.&lt;/p>
&lt;p>&lt;strong>Matchers:&lt;/strong>: This is similar to a predicate function - which shall describe if the request allowed. Rules which help us to match the request and the policy.&lt;/p>
&lt;p>&lt;strong>Effect:&lt;/strong> It&amp;rsquo;s useful, when there are multiple patterns which would match and you would need to make a decision. There are defaults, here you get to override both allow and deny defaults. Refer:
&lt;a href="https://casbin.org/docs/en/syntax-for-models#policy-effect" target="_blank" rel="noopener">PolicyEffect&lt;/a>.&lt;/p>
&lt;h4 id="inheritance">Inheritance:&lt;/h4>
&lt;ul>
&lt;li>If A has role B, B has role C, then A has role C. This transitivity is infinite for now&lt;/li>
&lt;li>Casbin doesn&amp;rsquo;t distinguish role from user in its RBAC. They are all treated as strings.&lt;/li>
&lt;/ul>
&lt;pre>&lt;code>p, data2_admin, data2, read
g, alice, data2_admin
&lt;/code>&lt;/pre>
&lt;p>In the above example,&lt;/p>
&lt;ul>
&lt;li>data2_admin has read access to data2&lt;/li>
&lt;li>alice inherits/is a member of role data2_admin. alice here can be a user, resource or a role.&lt;/li>
&lt;/ul>
&lt;p>
&lt;a href="https://github.com/jacobaloysious/pycasbin/blob/master/tests/rbac/test_role_manager.py" target="_blank" rel="noopener">test_role_manager.py&lt;/a>&lt;/p>
&lt;pre>&lt;code> rm = get_role_manager()
#1
rm.add_link(&amp;quot;u1&amp;quot;, &amp;quot;g1&amp;quot;)
rm.add_link(&amp;quot;u3&amp;quot;, &amp;quot;g2&amp;quot;)
self.assertTrue(rm.has_link(&amp;quot;u1&amp;quot;, &amp;quot;g1&amp;quot;))
self.assertFalse(rm.has_link(&amp;quot;u1&amp;quot;, &amp;quot;g2&amp;quot;))
self.assertCountEqual(rm.get_roles(&amp;quot;u1&amp;quot;), [&amp;quot;g1&amp;quot;])
self.assertCountEqual(rm.get_roles(&amp;quot;u2&amp;quot;), [&amp;quot;g1&amp;quot;])
#2
rm.add_link(&amp;quot;u1&amp;quot;, &amp;quot;g1&amp;quot;, &amp;quot;domain1&amp;quot;)
self.assertTrue(rm.has_link(&amp;quot;u1&amp;quot;, &amp;quot;g1&amp;quot;, &amp;quot;domain1&amp;quot;))
self.assertFalse(rm.has_link(&amp;quot;u1&amp;quot;, &amp;quot;g1&amp;quot;, &amp;quot;domain2&amp;quot;))
&lt;/code>&lt;/pre>
&lt;h4 id="what-casbin-does-not-do">What Casbin does NOT do:&lt;/h4>
&lt;ul>
&lt;li>Authentication (aka verify username and password when a user logs in)&lt;/li>
&lt;li>manage the list of users or roles.&lt;/li>
&lt;/ul>
&lt;p>Reference:
&lt;a href="https://github.com/jacobaloysious/pycasbin" target="_blank" rel="noopener">Casbin&lt;/a>&lt;/p></description></item><item><title>Bitmap Indexed Storage</title><link>https://jacobaloysious.in/post/tech_bitmaped_indexed_storage/</link><pubDate>Sat, 10 Jul 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_bitmaped_indexed_storage/</guid><description>&lt;p>Always been facinated by the ways - data structure&amp;rsquo;s can enable better storage and faster queries.&lt;/p>
&lt;p>BitMapped Indexed Storage - is useful while using &lt;strong>Columnar Databases&lt;/strong> - where per column data is stored together.&lt;/p>
&lt;p>Often the number of &lt;strong>distinct&lt;/strong> values in a column is small compared to the total number of rows. In the below example: column &lt;em>sex&lt;/em> there are only two distinct value &lt;em>Female or Male&lt;/em> in the given table - the cardinality is really low.&lt;/p>
&lt;p>In such cases: We can now take a column with &lt;em>n&lt;/em> distinct values and turn it into &lt;em>n&lt;/em> separate bitmaps: one bitmap for each distinct value, with one bit for each row. The bit is 1 if the row has that value, and 0 if not.&lt;/p>
&lt;p>Picture is worth a thousand words.. here we you go..&lt;/p>
&lt;p>&lt;img src="dbimage.jpg" alt="alt Components" title="Work">&lt;/p>
&lt;p>Fantastic Query performance&amp;hellip; As the amount of data read from disk on to the memory is less - improved Disk Throughput.
As always, solution are for a specific problem. If you wanna do a range query, then this solution might not the best.&lt;/p>
&lt;p>If &lt;em>n&lt;/em> is very small, those bitmaps can be stored with one bit per row. But if &lt;em>n&lt;/em> is bigger, there will be a lot of zeros in most of the bitmaps (&lt;em>sparse&lt;/em>). In that case, the bitmaps can additionally be run-length encoded or might not be recommened.&lt;/p>
&lt;p>Reference:
&lt;a href="https://www.goodreads.com/book/show/23463279-designing-data-intensive-applications" target="_blank" rel="noopener">Designing Data-Intensive Applications, by Martin Kleppmann &lt;/a>&lt;/p></description></item><item><title>Feature Toggle - Nightmares</title><link>https://jacobaloysious.in/post/tech_feature_toggle/</link><pubDate>Sat, 01 May 2021 00:00:00 +0000</pubDate><guid>https://jacobaloysious.in/post/tech_feature_toggle/</guid><description>&lt;p>We were working on a custom algorithm to optimally grab images given a list of area. Every image grabbed is written into an object store with a generated hashkey. The haskkey can be used to query the recorded data - when we would like to play them back.&lt;/p>
&lt;p>Software was a monolith and the algorithm was shipped with it. So, any new fixes would be shipped with the new software versions and user would be able to playback the same recorded data.&lt;/p>
&lt;pre>&lt;code>def customAlgorithm():
locations = []
# actual algo...
return locations;
&lt;/code>&lt;/pre>
&lt;p>A day came when we - rolled out a bug fix in the algorithm. But, we broke the backward compatiblity 😟. Users had lots of existing recorded data which they wanted to playback. Since the algorithm had changed - the generated &lt;strong>hashkeys&lt;/strong> didn&amp;rsquo;t match the ones in the object store.&lt;/p>
&lt;p>We thought its proabaly a one off scenario: so we added a feature toggle - user will have to &lt;strong>manually&lt;/strong> (pain) figure out - which version the data was recorded on and update the toggle config (xml) and rerun.&lt;/p>
&lt;pre>&lt;code>def customAlgorithm():
locs = []
if version == '1.0':
locs = oldAlgo()
else:
locs = newAlgo()
return locs
&lt;/code>&lt;/pre>
&lt;p>And - Well there was another bug fix:&lt;/p>
&lt;pre>&lt;code>def customAlgorithm():
locs = []
if version == '1.0':
locs = Algo_1_0()
else if version == ''2.0':
locs = Algo_2_0()
else:
locs = latestAlgo()
return locs
&lt;/code>&lt;/pre>
&lt;p>And yeah the story went on for one more iteration and we had to STOP!!!! 😓 It was already becoming a nightmare to mantain the code.&lt;/p>
&lt;p>It wasn&amp;rsquo;t just manually figuring out the toggle which was creating pain - but to enable code reuse, the algorthm had to be refactored multiple times - so that parts of the algorithm can be reused across versions. In addition to that the number of test suites we had to manage had gone up significantly, as there were multiple users each one using different version - and oh yeah there were version specific fixes released as patches 😟&lt;/p>
&lt;p>Finally we decided to write all the &lt;strong>metadata&lt;/strong> - related to recording - into an embedded database. And on playback we get the information from the database and not by re-running the code/algorithm.&lt;/p>
&lt;p>On hindsight: the database feels like it an obvious solution, right? well the devil is in the details - in our case it about effort(read, time). The playback infrastructure based on database took ~8 weeks to build -first version. It had to handle numerous (10s) use cases. Previously, we only had one flow to be maintained i.e the flow which ran the use cases + algo shipped. But now we have to manage &lt;strong>TWO&lt;/strong> different flows - one for recording and one for playback (x) No_Of_Use_Cases.&lt;/p>
&lt;p>Its almost 3yrs since we rolled out this solution to production. When we look back it was a very good decision to build two different workflows 😍. Code is more structured and maintainable.&lt;/p>
&lt;p>At times - short term goal/fix takes priority. But, its always good to step back and look at the pain points and have an item in your backlog to find a better solution. The priority should be driven based on feedback: remember the 80/20 rule&lt;/p>
&lt;blockquote>
&lt;p>80 percent of customers only use 20 percent of the features in the software they’ve bought.&lt;/p>
&lt;/blockquote>
&lt;p>BTW: the database schema has changed a lot - with the addition of multiple use cases. Which also needs version management 😉 And evey new use case discussion now has two parts to be discussed - some problems are good to have (read, tradeoffs).&lt;/p>
&lt;p>Ref:
&lt;a href="https://martinfowler.com/articles/feature-toggles.html" target="_blank" rel="noopener">Feature Toggle&lt;/a>&lt;/p></description></item><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>