Skip to content

Instantly share code, notes, and snippets.

@andreasgrv
Created May 13, 2016 18:42
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save andreasgrv/4c1c0b0b7c7f210ebfe0afb1814c330b to your computer and use it in GitHub Desktop.
Python metaclass experimentation (python 2.7) applied on creating paths
""" experiment on whether we can make a more intuitive interface
for paths by (ab)using metaclasses :D
result :
s = DotPath.myfolder
versus:
s = os.path.join('home','user','myfolder')"""
import os
import codecs
def userpath(function, *args):
"""Decorator that can be used to wrap functions returning paths
so that they return user paths instead. Calling this function
on a path already user-expanded should have no effect."""
def expand(*args):
joined_path = function(*args)
return os.path.expanduser(joined_path)
return expand
class DynamicStatic(type):
"""Classes which inherit this type get their constructor called with
any attempted attribute access on the class.
(on the class not the class instance - ex - Path.dog versus Path().dog)"""
def __getattr__(self, name):
""" return instance of class and pass name to its constructor"""
# uses DynamicStatic __call__ procedure
# since it is called with an instance of DynamicStatic
return self(name)
def __repr__(self):
""" do whatever the sub __repr__ func will do """
# TODO check these exist..
return repr(self())
def __str__(self):
""" do whatever the sub __str__ func will do """
# TODO check these exist..
return str(self())
def __call__(self, *args):
# We can't call our constructor since we have overrided call
# so we need to use __new__ and __init__
cl = self.__new__(self)
cl.__init__(*args)
return cl
class DotPath(object):
__metaclass__ = DynamicStatic
READ = 'r'
WRITE = 'w'
PERMISSIONS = (READ, WRITE)
"""Docstring for DotPath. """
def __init__(self, *items):
self.file_permission = DotPath.READ
self.path_tokens = ['~']
self.path_tokens.extend(items)
def __getattr__(self, name):
""" Whenever we access something that doesnt exist
assume add it to the path we are creating.. """
self.path_tokens.append(name)
return self
def __call__(self, permission, encoding='utf-8'):
if permission in DotPath.PERMISSIONS:
self.file_permission = permission
self.encoding = encoding
return self
else:
raise TypeError('permission must be in %s' % DotPath.PERMISSIONS)
def __enter__(self):
self.opened_file = codecs.open(str(self),
self.file_permission,
encoding=self.encoding)
return self.opened_file
def __exit__(self, exc_type, exc_val, exc_tb):
self.opened_file.close()
@userpath
def __repr__(self):
"""get string representation of the dotpath on the callers os"""
return os.path.join(*self.path_tokens)
@userpath
def __str__(self):
"""get string representation of the dotpath on the callers os"""
return os.path.join(*self.path_tokens)
if __name__ == "__main__":
# ok, so suppose we want to create a path to the file shrubbery
path = DotPath.Documents.shrubbery
# path is an instance of DotPath with a list of the path_tokens
# which grew each time . (getattr) was used on DotPath
print(path)
# well, since we got this far, why not make the class usable
# using the with - as statement?
# problem is - we need a way to pass arguments!
# but we can use override the __call__ function for that ^_^
with path('w') as out:
msg = 'it works!'
print('writing message "%s" to %s\n' % (msg, path))
out.write(msg)
with path('r') as out:
print('reading from %s' % path)
print(out.read())
/home/grv/Documents/shrubbery
writing message "it works!" to /home/grv/Documents/shrubbery
reading from /home/grv/Documents/shrubbery
it works!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment