Created
August 8, 2012 20:28
-
-
Save psobot/3298353 to your computer and use it in GitHub Desktop.
Real-time YAML object access in Python
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
""" | |
liveyamlfile.py | |
Live Pythonic attribute access to properties in a yaml file. | |
by Peter Sobot ([email protected]), August 8, 2012 | |
""" | |
import os | |
import time | |
import yaml | |
import logging | |
class LiveYamlFile(object): | |
""" | |
Magical class that allows for real-time access of variables in a yaml | |
file via Pythonic attribute-like access. | |
Any functions, objects or properties on the class (or its subclasses) | |
will not be looked up in the yaml file. Every access may result in at | |
worst, a YAML load and at best, an os.stat call at most every __timeout | |
seconds. | |
Simple example usage: | |
> my_object = yaml_file("file.yml") | |
> my_object.some_changing_property | |
5 | |
> # (now here, file.yml has changed outside of python) | |
> my_object.some_changing_property | |
6 | |
> my_object.blah # blah is not defined in the yaml file | |
AttributeError: object my_object has no attribute 'blah' | |
""" | |
__last_updated = 0 | |
__timeout = 5 # seconds | |
__exclude = [] | |
def __init__(self, filename): | |
self.__file = filename | |
# Any subclass's properties will be ignored here | |
supers = LiveYamlFile.__dict__ | |
subs = self.__class__.__dict__ | |
self.__exclude = [k for k, _ in | |
dict(supers.items() + subs.items()).iteritems() | |
if not k.startswith("_")] | |
def update(self): | |
""" | |
Update the object's attributes from the YAML file. | |
""" | |
target_file = open(self.__file) | |
for attr in dir(self): | |
if not attr.startswith("_") and attr not in self.__exclude: | |
delattr(self, attr) | |
for key, val in yaml.load(target_file).iteritems(): | |
if not key.startswith("_") and key not in self.__exclude: | |
setattr(self, key, val) | |
target_file.close() | |
if hasattr(self, 'log_config_file_changes')\ | |
and self.log_config_file_changes: | |
logging.getLogger(__name__).info("Config file has updated.") | |
def __getattribute__(self, name): | |
""" | |
When trying to access an attribute, check if the underlying file | |
has changed first. Best case: reads from Python cache. Worst case: | |
performs an os.stat and a YAML load every __timeout seconds. | |
""" | |
if name.startswith("_") or name in self.__exclude: | |
return object.__getattribute__(self, name) | |
else: | |
last_updated = self.__last_updated | |
# Only check once every __timeout seconds | |
if (time.time() - last_updated) > self.__timeout: | |
fmod_time = os.stat(self.__file)[9] | |
if last_updated < fmod_time: | |
self.__last_updated = fmod_time | |
self.update() | |
return object.__getattribute__(self, name) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment