summaryrefslogtreecommitdiff
path: root/core/package.py
blob: f1275217380c4506dd60a64ecc27827d42acb709 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
import settings
from yamlcrap import FennObject, load, yaml
import re, os, sys
from copy import copy, deepcopy
from string import join

def check_unix_name(name):
    '''returns True if name (string) is a valid unix_name'''
    assert name, "check_unix_name: name is empty (name=\"%s\")" % (name)
    check = re.match('^[a-zA-Z0-9_\-]*$', name)
    assert check, 'allowed characters in a name are: a-z, 0-9, "-", and "_". Instead, got: "'+str(name)+'"'
    if check: return True
    else: return False

def package_file(package_name, filename, mode='r'):
    '''construct a dummy package and return a filehandler for filename.
    needed for packages to find their own files (can't be used as a method of Package)'''
    package_path = os.path.join(settings.paths["package_dir"], package_name)
    filepath = os.path.join(package_path, filename)
    try: #fail early
        assert os.access(filepath, os.F_OK)
        return open(filepath, mode)
    except AssertionError: 
        raise IOError, 'error in package "'+package_name+'": could not read file "'+filename+'"'

def open_package(path):
    '''just a synonym for load_package'''
    return load_package(path)

def __lame_asserts__():
    assert hasattr(settings,"paths")
    assert settings.paths.has_key("package_dir")

def load_metadata(name):
    '''returns a package loaded from the filesystem
    input should be something like "f-16" or "human-exoskeleton-1.0"
    this only loads the metadata and is safe if you haven't actually downloaded the entire package'''
    if not check_unix_name(name):
        return None
    __lame_asserts__()
    assert os.access(settings.paths['package_dir'], os.F_OK), str(package_path)+": skdb package not found or unreadable"
    #must have the required files
    assert package_file(name, "metadata.yaml")
    loaded_package = load(package_file(name, "metadata.yaml"))
    return loaded_package

def load_package(name):
    '''returns a package loaded from the filesystem
    input should be something like "f-16" or "human-exoskeleton-1.0"
    see settings.paths['package_dir']'''
    if not check_unix_name(name): #fail even if asserts are turned off
        return None
    __lame_asserts__()
    package_path = os.path.join(settings.paths["package_dir"],name)
    assert os.access(settings.paths['package_dir'], os.F_OK), str(package_path)+": skdb package not found or unreadable"
    #must have the required files
    required_files = ["metadata.yaml", "data.yaml"]
    for file in required_files:
        assert package_file(name, file) #just check if present
    loaded_package = load(package_file(name, 'metadata.yaml'))
    loaded_package.package_path = package_path
    loaded_package.import_package_classes()
    #import_package_classes(loaded_package, package_path)
    return loaded_package

def import_package_classes(loaded_package, package_path):
    '''assigns classes to the Package's namespace; for example:
    package = load_package('lego')
    mybrick = package.Brick()'''
    for module_name in loaded_package.classes.keys():
        try: 
            module = __import__(module_name)
        except ImportError:
            sys.path.append(package_path)
            module = __import__(module_name)
        for class_name in loaded_package.classes[module_name]:
            cls = getattr(module, class_name)
            setattr(loaded_package, class_name, cls )
            setattr(cls, "package", loaded_package)

class Package(FennObject): #should this be a FennObject? ideally it should spit out metadata.yaml, data.yaml, etc.
    yaml_tag='!package'
    data_loaded=False
    def __init__(self, name=None, data=True, create=True):
        '''name is name of the package
        data is True or False for whether or not to load the source data'''
        if not hasattr(self, "name") and name is None: return #not like we can do much of anything
        if hasattr(self, "name") and name is None: self.name = name
        if not hasattr(self, "name") and name is not None: self.name = name
        if self.name is None: return #not like we can do much of anything
        #check if the package already exists
        filepath = self.path()
        pkg_exists = os.access(filepath, os.F_OK)
        if pkg_exists:
            #load it from a file
            pkg = Package.__load_package__(name, data=data)
            #put the values in the local dictionary
            for key in pkg.__dict__.keys():
                value = pkg.__dict__[key]
                self.__dict__[key] = deepcopy(value)
            #call post_init_hook with data=(not data) because __load_package__ was called with data=data
            self.post_init_hook(data=(not data), first_time=False) #this isn't yaml so we have to call the hook on our own
        elif create==False: raise ValueError, "no package by that name"
    def post_init_hook(self, data=True, first_time=True):
        '''FennObject.from_yaml calls this 'after' loading a package'''
        if first_time:
            check_unix_name(self.name)
            if hasattr(self, "source data"):
                self.source_data = self.__getattribute__("source data")
                delattr(self, "source data") #bah who needs data anyway
                new_source_data = []
                #load up the files
                for each in self.source_data:
                    new_source_data.append(File(self.file_path(each)))
                self.source_data = new_source_data
                self.import_package_classes() #otherwise self.parts will be made up of skdb.core.yamlcrap.Dummy objects
                if data: self.load_data()
    @staticmethod
    def __load_package__(package_name, data=True):
        '''loads a package. call the constructor instead.'''
        __lame_asserts__()
        package_path = os.path.join(settings.paths["package_dir"],package_name)
        assert os.access(settings.paths['package_dir'], os.F_OK), str(package_path)+": skdb package not found or unreadable"
        #must have the required files
        required_files = ["metadata.yaml"]
        if data: required_files.append("data.yaml")
        for file in required_files:
            assert os.access(os.path.join(settings.package_path(package_name), file), os.F_OK) #check if present
        loaded_package = load(open(os.path.join(settings.package_path(package_name), "metadata.yaml"),"r"))
        loaded_package.package_path = package_path
        loaded_package.import_package_classes()
        if data: loaded_package.load_data()
        return loaded_package
    def import_package_classes(self):
        '''assigns classes to the Package's namespace; for example:
        package = Package("lego")
        mybrick = package.Lego()'''
        package_path = self.path()
        if not hasattr(self, "classes"): return
        if not hasattr(self.classes, "keys"): return
        for module_name in self.classes.keys():
            try: 
                module = __import__(module_name)
            except ImportError:
                sys.path.append(package_path)
                module = __import__(module_name)
            for class_name in self.classes[module_name]:
                cls = getattr(module, class_name)
                setattr(self, class_name, cls )
                setattr(cls, "package", self)
    def path(self,package_name=None):
        '''returns the absolute path on the file system to the package folder'''
        if not hasattr(self, "name"): self.name = package_name
        check_unix_name(self.name)
        return settings.package_path(self.name)
    def file_path(self, filename):
        '''returns the absolute path of a file in the package'''
        return os.path.join(self.path(), filename)
    #def load_metdata(self, file=None):
    #'''loads metadata based off of the package name from the skdb package directory on localhost'''
    def load_data(self, file=None):
        '''loads all the files listed in "source_data:" from metadata.yaml'''
        if self.data_loaded == True: return #don't do it again
        if not file:
            catalog = getattr(self, 'source_data')
        else: catalog = [file]
        for x in catalog:
            if isinstance(x, File):
                self.overlay(x.load())
            else: self.overlay(load(open(os.path.join(self.path(), x)))) #merge data from entire catalog into package
        self.data_loaded = True
    def dump(self):
        '''returns this object in yaml'''
        return yaml.dump(self)
    def dump_metadata(self):
        '''dump only the content for or from metadata.yaml'''
        raise NotImplementedError
    def dump_template(self):
        '''dump only the content for or from template.yaml'''
        raise NotImplementedError
    def dump_data(self):
        '''dump only the content for or from data.yaml'''
        raise NotImplementedError
    def makes_sense(self): #FIXME this is really lame
        '''checks for whether or not the package data makes sense'''
        assert self.name is not None, "package name"
        assert self.functionality is not None, "functionality"
        assert self.created is not None, "created"
        assert self.version is not None, "version"
        assert self.description is not None, "description"
        assert self.classes is not None, "classes"
        assert getattr(self, 'source_data') is not None, "source data"
        assert self.dependencies is not None, "dependencies" #unless you really know what you're doing?
        return True
    def has_file(self, filename, extensions=True):
        if filename in self.all_files(extensions=extensions): return True
        return False
    def get_file(self, filename, extensions=True):
        all_the_files = self.all_files()

        #first check to see if it's in there
        if filename in all_the_files:
            full_path = os.path.join(self.path(), all_the_files[all_the_files.index(filename)])
            return File(full_path)

        with_no_extensions = self.all_files(extensions=False)
        #now check to see if it's in there without extensions
        if filename in with_no_extensions:
            full_path = os.path.join(self.path(), (all_the_files[with_no_extensions.index(filename)]))
            return File(full_path)

        raise IOError, "Package.get_file: unable to find file"
    def all_files(self, extensions=True):
        '''returns a list of all files that this package provides
        set extensions to False if you don't want extensions in the filenames'''
        pkg_path = self.path()
        the_list = os.listdir(pkg_path)
        for filename in self.source_data:
            filename = os.path.basename(filename._name)
            if not (filename in the_list): the_list.append(filename)
        if not extensions:
            new_filenames = []
            for filename in the_list:
                a_list = os.path.basename(filename).split(".")
                a_list.pop()
                new_filenames.append(join(a_list, "."))
            the_list = new_filenames
        return the_list

class File(FennObject, file):
    yaml_tag = "!file"
    def __init__(self, name, abspath_portion=None):
        '''name = the file name,
        abspath_portion = the absolute path to where the filename is talking about'''
        if abspath_portion is None:
            file.__init__(self, name)
        else: file.__init__(self, os.path.join(abspath_portion, name))
        self._name = name
        self.abspath = abspath_portion
    def yaml_repr(self):
        return self._name
    def __repr__(self):
        return self._name
    def status(self):
        if not self.name == self._name: return self.name
    def read(self, size=None):
        try:
            if type(size)==int: return file.read(self, size)
            else:
                self.reopen()
                return_contents = file.read(self)
                file.close(self)
        except ValueError:
            self.reopen()
            return_contents = file.read(self)
            file.close(self)
        return return_contents
    def load(self):
        if self.closed: self.reopen()
        results = yaml.load(self)
        self.close()
        return results
    def reopen(self):
        file.__init__(self, self._name)