Skip to content

Commit 15b9129

Browse files
committed
lazymixin system now supports per-attribute baking, it is up to the class whether it bakes more. This also leads to more efficient use of memory as values are only cached and set when required - the baking system does not require an own tracking variable anymore, and values are only to be cached once - then python will natively find the cache without involving any additional overhead. This works by using __getattr__ instead of __get_attribute__ which would always be called
1 parent 7a7eedd commit 15b9129

File tree

7 files changed

+114
-147
lines changed

7 files changed

+114
-147
lines changed

lib/git/base.py

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,38 @@
77

88
class LazyMixin(object):
99
lazy_properties = []
10+
__slots__ = tuple()
1011

11-
__slots__ = "__baked__"
12-
13-
def __init__(self):
14-
self.__baked__ = False
15-
16-
def __getattribute__(self, attr):
17-
val = object.__getattribute__(self, attr)
18-
if val is not None:
19-
return val
20-
else:
21-
self.__prebake__()
22-
return object.__getattribute__(self, attr)
23-
24-
def __bake__(self):
25-
""" This method should be overridden in the derived class. """
26-
raise NotImplementedError(" '__bake__' method has not been implemented.")
27-
28-
def __prebake__(self):
29-
if self.__baked__:
30-
return
31-
self.__bake__()
32-
self.__baked__ = True
12+
def __getattr__(self, attr):
13+
"""
14+
Whenever an attribute is requested that we do not know, we allow it
15+
to be created and set. Next time the same attribute is reqeusted, it is simply
16+
returned from our dict/slots.
17+
"""
18+
self._set_cache_(attr)
19+
# will raise in case the cache was not created
20+
return object.__getattribute__(self, attr)
3321

34-
def __bake_it__(self):
35-
self.__baked__ = True
22+
def _set_cache_(self, attr):
23+
""" This method should be overridden in the derived class.
24+
It should check whether the attribute named by attr can be created
25+
and cached. Do nothing if you do not know the attribute or call your subclass
3626
27+
The derived class may create as many additional attributes as it deems
28+
necessary in case a git command returns more information than represented
29+
in the single attribute."""
30+
pass
31+
3732

3833
class Object(LazyMixin):
3934
"""
4035
Implements an Object which may be Blobs, Trees, Commits and Tags
4136
"""
4237
TYPES = ("blob", "tree", "commit", "tag")
43-
__slots__ = ("repo", "id", "size", "_data_cached" )
38+
__slots__ = ("repo", "id", "size", "data" )
4439
type = None # to be set by subclass
4540

46-
def __init__(self, repo, id, size=None):
41+
def __init__(self, repo, id):
4742
"""
4843
Initialize an object by identifying it by its id. All keyword arguments
4944
will be set on demand if None.
@@ -53,21 +48,32 @@ def __init__(self, repo, id, size=None):
5348
5449
``id``
5550
SHA1 or ref suitable for git-rev-parse
56-
57-
``size``
58-
Size of the object's data in bytes
5951
"""
6052
super(Object,self).__init__()
6153
self.repo = repo
6254
self.id = id
63-
self.size = size
64-
self._data_cached = type(None)
6555

66-
def __bake__(self):
56+
def _set_self_from_args_(self, args_dict):
57+
"""
58+
Initialize attributes on self from the given dict that was retrieved
59+
from locals() in the calling method.
60+
61+
Will only set an attribute on self if the corresponding value in args_dict
62+
is not None
63+
"""
64+
for attr, val in args_dict.items():
65+
if attr != "self" and val is not None:
66+
setattr( self, attr, val )
67+
# END set all non-None attributes
68+
69+
def _set_cache_(self, attr):
6770
"""
6871
Retrieve object information
6972
"""
70-
self.size = int(self.repo.git.cat_file(self.id, s=True).rstrip())
73+
if attr == "size":
74+
self.size = int(self.repo.git.cat_file(self.id, s=True).rstrip())
75+
elif attr == "data":
76+
self.data = self.repo.git.cat_file(self.id, p=True, with_raw_output=True)
7177

7278
def __eq__(self, other):
7379
"""
@@ -105,18 +111,12 @@ def __repr__(self):
105111
return '<git.%s "%s">' % (self.__class__.__name__, self.id)
106112

107113
@property
108-
def data(self):
114+
def id_abbrev(self):
109115
"""
110-
The binary contents of this object.
111-
112116
Returns
113-
str
114-
115-
NOTE
116-
The data will be cached after the first access.
117+
First 7 bytes of the commit's sha id as an abbreviation of the full string.
117118
"""
118-
self._data_cached = ( self._data_cached is not type(None) and self._data_cached ) or self.repo.git.cat_file(self.id, p=True, with_raw_output=True)
119-
return self._data_cached
119+
return self.id[0:7]
120120

121121
@classmethod
122122
def get_type_by_name(cls, object_type_name):
@@ -154,7 +154,7 @@ class IndexObject(Object):
154154
"""
155155
__slots__ = ("path", "mode")
156156

157-
def __init__(self, repo, id, mode=None, path=None, size = None):
157+
def __init__(self, repo, id, mode=None, path=None):
158158
"""
159159
Initialize a newly instanced IndexObject
160160
``repo``
@@ -169,14 +169,11 @@ def __init__(self, repo, id, mode=None, path=None, size = None):
169169
``path`` : str
170170
is the path to the file in the file system, relative to the git repository root, i.e.
171171
file.ext or folder/other.ext
172-
173-
``size`` : int
174-
size of the object data in bytes
175172
"""
176-
super(IndexObject, self).__init__(repo, id, size)
173+
super(IndexObject, self).__init__(repo, id)
177174
self.mode = mode
178175
self.path = path
179-
176+
180177
@property
181178
def basename(self):
182179
"""
@@ -304,5 +301,6 @@ def from_string(cls, repo, line):
304301
git.Head
305302
"""
306303
full_path, hexsha, type_name, object_size = line.split("\x00")
307-
obj = Object.get_type_by_name(type_name)(repo, hexsha, object_size)
304+
obj = Object.get_type_by_name(type_name)(repo, hexsha)
305+
obj.size = object_size
308306
return cls(full_path, obj)

lib/git/commit.py

Lines changed: 28 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class Commit(base.Object):
2525

2626
# object configuration
2727
type = "commit"
28+
__slots__ = ("tree", "author", "authored_date", "committer", "committed_date",
29+
"message", "parents")
2830

2931
def __init__(self, repo, id, tree=None, author=None, authored_date=None,
3032
committer=None, committed_date=None, message=None, parents=None):
@@ -38,7 +40,7 @@ def __init__(self, repo, id, tree=None, author=None, authored_date=None,
3840
is the sha id of the commit
3941
4042
``parents`` : list( Commit, ... )
41-
is a list of commit ids
43+
is a list of commit ids or actual Commits
4244
4345
``tree`` : Tree
4446
is the corresponding tree id
@@ -61,49 +63,34 @@ def __init__(self, repo, id, tree=None, author=None, authored_date=None,
6163
Returns
6264
git.Commit
6365
"""
64-
super(Commit,self).__init__(repo, id, "commit")
65-
self.parents = None
66-
self.tree = None
67-
self.author = author
68-
self.authored_date = authored_date
69-
self.committer = committer
70-
self.committed_date = committed_date
71-
self.message = message
72-
73-
if self.id:
74-
if parents is not None:
75-
self.parents = [Commit(repo, p) for p in parents]
76-
if tree is not None:
77-
self.tree = Tree(repo, id=tree)
78-
79-
def __eq__(self, other):
80-
return self.id == other.id
81-
82-
def __ne__(self, other):
83-
return self.id != other.id
84-
85-
def __bake__(self):
86-
"""
87-
Called by LazyMixin superclass when the first uninitialized member needs
88-
to be set as it is queried.
89-
"""
90-
super(Commit, self).__bake__()
91-
temp = Commit.find_all(self.repo, self.id, max_count=1)[0]
92-
self.parents = temp.parents
93-
self.tree = temp.tree
94-
self.author = temp.author
95-
self.authored_date = temp.authored_date
96-
self.committer = temp.committer
97-
self.committed_date = temp.committed_date
98-
self.message = temp.message
66+
super(Commit,self).__init__(repo, id)
67+
self._set_self_from_args_(locals())
9968

100-
@property
101-
def id_abbrev(self):
69+
if parents is not None:
70+
self.parents = tuple( self.__class__(repo, p) for p in parents )
71+
# END for each parent to convert
72+
73+
if self.id and tree is not None:
74+
self.tree = Tree(repo, id=tree)
75+
# END id to tree conversion
76+
77+
def _set_cache_(self, attr):
10278
"""
103-
Returns
104-
First 7 bytes of the commit's sha id as an abbreviation of the full string.
79+
Called by LazyMixin superclass when the given uninitialized member needs
80+
to be set.
81+
We set all values at once.
10582
"""
106-
return self.id[0:7]
83+
if attr in self.__slots__:
84+
temp = Commit.find_all(self.repo, self.id, max_count=1)[0]
85+
self.parents = temp.parents
86+
self.tree = temp.tree
87+
self.author = temp.author
88+
self.authored_date = temp.authored_date
89+
self.committer = temp.committer
90+
self.committed_date = temp.committed_date
91+
self.message = temp.message
92+
else:
93+
super(Commit, self)._set_cache_(attr)
10794

10895
@property
10996
def summary(self):

lib/git/head.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,6 @@ class Head(base.Ref):
2727
'1c09f116cbc2cb4100fb6935bb162daa4723f455'
2828
"""
2929

30-
def __init__(self, path, commit):
31-
"""
32-
Initialize a newly instanced Head
33-
34-
``path``
35-
is the path to the head ref, relative to the .git directory, i.e.
36-
refs/heads/master
37-
38-
`commit`
39-
is the Commit object that the head points to
40-
"""
41-
super(Head, self).__init__(name, commit)
42-
43-
4430
@property
4531
def commit(self):
4632
"""

lib/git/tag.py

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class TagObject(base.Object):
7474
type = "tag"
7575
__slots__ = ( "object", "tag", "tagger", "tagged_date", "message" )
7676

77-
def __init__(self, repo, id, size=None, object=None, tag=None,
77+
def __init__(self, repo, id, object=None, tag=None,
7878
tagger=None, tagged_date=None, message=None):
7979
"""
8080
Initialize a tag object with additional data
@@ -85,9 +85,6 @@ def __init__(self, repo, id, size=None, object=None, tag=None,
8585
``id``
8686
SHA1 or ref suitable for git-rev-parse
8787
88-
``size``
89-
Size of the object's data in bytes
90-
9188
``object``
9289
Object instance of object we are pointing to
9390
@@ -100,29 +97,30 @@ def __init__(self, repo, id, size=None, object=None, tag=None,
10097
``tagged_date`` : (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst)
10198
is the DateTime of the tag creation
10299
"""
103-
super(TagObject, self).__init__(repo, id , size)
104-
self.object = object
105-
self.tag = tag
106-
self.tagger = tagger
107-
self.tagged_date = tagged_date
108-
self.message = message
109-
110-
def __bake__(self):
111-
super(TagObject, self).__bake__()
112-
113-
output = self.repo.git.cat_file(self.type,self.id)
114-
lines = output.split("\n")
115-
116-
obj, hexsha = lines[0].split(" ") # object <hexsha>
117-
type_token, type_name = lines[1].split(" ") # type <type_name>
118-
self.object = base.Object.get_type_by_name(type_name)(self.repo, hexsha)
119-
120-
self.tag = lines[2][4:] # tag <tag name>
121-
122-
tagger_info = lines[3][7:]# tagger <actor> <date>
123-
self.tagger, self.tagged_date = commit.Commit._actor(tagger_info)
100+
super(TagObject, self).__init__(repo, id )
101+
self._set_self_from_args_(locals())
124102

125-
# line 4 empty - check git source to figure out purpose
126-
self.message = "\n".join(lines[5:])
103+
def _set_cache_(self, attr):
104+
"""
105+
Cache all our attributes at once
106+
"""
107+
if attr in self.__slots__:
108+
output = self.repo.git.cat_file(self.type,self.id)
109+
lines = output.split("\n")
110+
111+
obj, hexsha = lines[0].split(" ") # object <hexsha>
112+
type_token, type_name = lines[1].split(" ") # type <type_name>
113+
self.object = base.Object.get_type_by_name(type_name)(self.repo, hexsha)
114+
115+
self.tag = lines[2][4:] # tag <tag name>
116+
117+
tagger_info = lines[3][7:]# tagger <actor> <date>
118+
self.tagger, self.tagged_date = commit.Commit._actor(tagger_info)
119+
120+
# line 4 empty - check git source to figure out purpose
121+
self.message = "\n".join(lines[5:])
122+
# END check our attributes
123+
else:
124+
super(TagObject, self)._set_cache_(attr)
127125

128126

lib/git/tree.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,19 @@ class Tree(base.IndexObject):
1313
type = "tree"
1414
__slots__ = "_contents"
1515

16-
def __init__(self, repo, id, mode=None, path=None, size=None):
17-
super(Tree, self).__init__(repo, id, mode, path, size)
18-
self._contents = None
19-
20-
def __bake__(self):
21-
# Read the tree contents.
22-
super(Tree, self).__bake__()
23-
self._contents = {}
24-
for line in self.repo.git.ls_tree(self.id).splitlines():
25-
obj = self.content_from_string(self.repo, line)
26-
if obj is not None:
27-
self._contents[obj.path] = obj
16+
def __init__(self, repo, id, mode=None, path=None):
17+
super(Tree, self).__init__(repo, id, mode, path)
18+
19+
def _set_cache_(self, attr):
20+
if attr == "_contents":
21+
# Read the tree contents.
22+
self._contents = {}
23+
for line in self.repo.git.ls_tree(self.id).splitlines():
24+
obj = self.content_from_string(self.repo, line)
25+
if obj is not None:
26+
self._contents[obj.path] = obj
27+
else:
28+
super(Tree, self)._set_cache_(attr)
2829

2930
@staticmethod
3031
def content_from_string(repo, text):

0 commit comments

Comments
 (0)