Last active
May 23, 2021 16:50
-
-
Save pshirali/b7f9ec7276303a1cf98ac6a7b5e95a56 to your computer and use it in GitHub Desktop.
data_access.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# Example for middleware constructs to handle query and data exchange | |
# https://rtindru.medium.com/python-middleware-api-client-design-patterns-7c4261ecff88 | |
class Store: | |
'''Base class. Stores metadata with which it was instantiated. | |
This class could be extended to perform validation, serialization etc''' | |
def __init__(self, *args, **kwargs): | |
self.args = args | |
self.kwargs = kwargs | |
def __repr__(self): | |
ra = ",".join([str(a) for a in self.args]) | |
rk = ",".join(["{}={}".format(k, self.kwargs[k]) for k in self.kwargs]) | |
return "{}({})".format(self.__class__.__name__, ra + rk) | |
class ORClass: | |
'''OR is a singleton to indicate an infix OR operation. This class exists | |
solely to define a repr''' | |
def __repr__(self): | |
return "OR" | |
OR = ORClass() # singleton 'OR' to be used | |
def build(class_list, parent_class): | |
'''Helper to quickly build classes with different identities for this | |
demo. Populates the global namespace with them.''' | |
for c in class_list: | |
globals()[c] = type(c, (parent_class,), {}) | |
# high level constructs: | |
# AND condition is assumed as default. OR and NOT are explicit. | |
# OR is meant to be used an infix [OR between LHS and RHS of 'OR' object] | |
# However, it could also be implemented prefix: OR(func1, func2, func3 ...) | |
# Filter - encapsulates the entire set of rules to filter with. | |
# Field - represents each field/column | |
# Matcher - generic matcher to be used in combination with a field | |
# | |
build(["Filter", "Field", "Matcher"], Store) | |
build(["Movie", "Year", "Director", "Genre"], Field) | |
build(["startswith", "endswith", "has", "gt", "ge", "lt", "le", "eq"], Matcher) | |
# ^^ Possibilities: | |
# Matchers could semantically validate values passed to it | |
# Fields of homogenous types could restrict Matchers for it | |
# Ex: A field 'Movie' will allow 'startswith', 'endswith', 'has', 'eq' | |
# but will error if 'gt', 'lt', etc are passed | |
# ----------------------------------------------------------------------------- | |
# | |
# The example below defines a 'filter' encompassing all rules with which | |
# movies should be shortlisted. A data-accessor (adapter which provides a | |
# segregated interface for I/O) will consume this filter and use the info | |
# encapsulated in it while filtering data | |
# | |
FAVOURITE = Filter( | |
Movie( | |
startswith("star") | |
), | |
Director( | |
has("lucas"), OR, has("abrams") | |
), | |
Year(gt(1970)) | |
) | |
class MovieDataAccessor: | |
'''The data accessor (wrapper) which provides the interface(s) through | |
which information can be requested and retrieved''' | |
def fetch(self, fields=None, filter=None) -> list: | |
'''Returns list of tuples, contianing requested fields filtered | |
as per the supplied 'Filter' object''' | |
pass | |
print(FAVOURITE) | |
# Prints: Filter(Movie(startswith(star)),Director(has(lucas),OR,has(abrams)),Year(gt(1970))) | |
# This is useful for reading. Serialisation will require something more complex. | |
mda = MovieDataAccessor() | |
mda.fetch(fields=(Movie, Director, Year), filter=FAVOURITE) | |
# returns: [ | |
# (<movie>, <director>, <year>), | |
# ... | |
# ] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment