# -*- python -*-
#
# OpenAlea.OALab: Multi-Paradigm GUI
#
# Copyright 2014 INRIA - CIRAD - INRA
#
# File author(s): Julien Coste <julien.coste@inria.fr>
#
# File contributor(s):
#
# Distributed under the Cecill-C License.
# See accompanying file LICENSE.txt or copy at
# http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
#
# OpenAlea WebSite : http://openalea.gforge.inria.fr
#
###############################################################################
"""
The Project is a structure which permit to manage different objects.
It store **metadata** (name, author, description, version, license, ...) and **data** (src, models, images, ...).
You have here the default architecture of the project named "project_name",
stored in your computer.
/project_name
oaproject.cfg (Configuration file)
/src (Files sources, Script Python, LPy...)
/control (Control, like color map or curve)
/scene (scene, scene 3D)
/cache (Intermediary saved objects)
/data (Data files like images, .dat, ...)
/startup (Preprocessing scripts)
*.py (Preprocessing scripts)
*import*.py (Libs and packages to import in preprocessing)
:use:
.. code-block:: python
project1 = Project(project_name="mynewproj", project_path="/path/to/proj")
project1.start()
project1.add(category="src", name"hello.py", value="print 'Hello World'")
project1.author = "John Doe"
project1.description = "This project is used to said hello to everyone"
project1.save()
"""
import os
import warnings
from openalea.core.path import path as path_
from openalea.vpltk.project.configobj import ConfigObj
from openalea.vpltk.project.loader import get_loader
from openalea.vpltk.project.saver import get_saver
[docs]class Project(object):
def __init__(self, project_name, project_path, icon="", author="OpenAlea Consortium", author_email="",
description="", long_description="", citation="", url="", dependencies=[], license="CeCILL-C",
version="0.1"):
"""
:param project_name: name of the project to create or load
:param project_path: path of the project to create or load
"""
# Metadata
self.path = path_(project_path)
self.metadata = {
"name": str(project_name),
"icon": path_(icon),
"author": str(author),
"author_email": str(author_email),
"description": str(description),
"long_description": str(long_description),
"citation": str(citation),
"url": str(url),
"dependencies": dependencies,
"license": str(license),
"version": str(version),
}
self.config_file = "oaproject.cfg"
# Data
self.files = {
"src": dict(),
"control": dict(),
"cache": dict(),
"data": dict(),
"scene": dict(),
"startup": dict(),
"doc": dict()
}
# Others
self._ns = dict()
self._shell = None
self._set_ipython()
#----------------------------------------
# Public API
#----------------------------------------
[docs] def create(self):
"""
Do the same thing that import method.
.. seealso:: :func:`start` :func:`load` :func:`load_manifest`
"""
self.start()
[docs] def start(self):
"""
1. :func:`load` objects into project.
2. Import startup.
3. Run preprocessing.
.. seealso:: :func:`load` :func:`load_manifest`
"""
# Load in object
self.load()
# Load in shell
self._startup_import()
self._startup_run()
[docs] def load(self):
"""
Realize a total loading of project (contrary to :func:`load_manifest`).
1. Load manifest :func:`load_manifest`
2. Read data files listed in manifest and fill project object.
.. seealso:: :func:`start` :func:`load_manifest`
"""
self.load_manifest()
for category in self.files.keys():
obj = self._load(str(category))
setattr(self, category, obj)
[docs] def save(self):
"""
Save project on disk.
1. Save **data** files.
2. Save **metadata** and **list on previously saved data** into a manifest file (*oaproject.cfg*).
.. seealso:: :func:`save_manifest`
"""
for category in self.files.keys():
self._save(str(category))
self.save_manifest()
[docs] def get(self, category, name):
"""
Search an object inside project and return it.
:param category: category of object to get
:param name: name of object to get
:return: object named *name* in the category *category* if it exists. Else, None.
:use: >>> get(category="src", name="myscript.py")
.. seealso:: :func:`add`
"""
if hasattr(self, category):
cat = getattr(self, category)
if hasattr(cat, name):
return cat[name]
elif hasattr(cat, "has_key"):
# if category is a dict
if cat.has_key(name):
return cat[name]
return None
[docs] def add(self, category, name, value):
"""
Add an object in the project
:param category: *type* of object to add ("src", "control", "scene", ...)
:param name: filename of the object to add (path or str)
:param value: to add (string)
.. seealso:: :func:`get` :func:`remove`
"""
if not hasattr(self, category):
setattr(self, category, dict())
cat = getattr(self, category)
cat[name] = value
[docs] def remove(self, category, name):
"""
Remove an object in the project
Remove nothing on disk.
:param category: category of object to remove ("src", "control", "scene", ...) (str)
:param name: filename of the src to remove (path or str)
"""
category = str(category)
filename = path_(name)
if hasattr(self, category):
cat = getattr(self, category)
if cat.has_key(filename):
del cat[filename]
[docs] def rename(self, category, old_name, new_name):
"""
Rename a src, a scene or a control in the project.
If category is project, rename the entire project.
:param category: Can be "src", "control", "scene" or "project" (str)
:param old_name: current name of thing to rename (str)
:param new_name: future name of thing to rename (str)
"""
if (category == "project"):
self.name = new_name
self.save()
if (self.path / old_name).exists():
try:
(self.path / old_name).removedirs()
except IOError:
pass
else:
if hasattr(self, category):
cat = getattr(self, category)
# Rename in project
cat[str(new_name)] = cat[str(old_name)]
# Remove inside project
self.remove(category, old_name)
# TODO: Remove on disk
# temp_path = self.path / self.name / category / old_name
# if temp_path.exists():
# try:
# path_(temp_path).removedirs()
# except IOError:
# pass
#----------------------------------------
# Manifest
#----------------------------------------
[docs] def save_manifest(self):
"""
Save a manifest file on disk. His name is "*oaproject.cfg*".
It contains **list of files** that are inside project (*manifest*) and **metadata** (author, version, ...).
.. seealso:: :func:`load_manifest`
"""
config = ConfigObj()
config.filename = self.path / self.name / self.config_file
config['metadata'] = dict()
config['manifest'] = dict()
config['metadata'] = self.metadata
for files in self.files.keys():
filenames = getattr(self, files)
if filenames.keys():
config['manifest'][files] = filenames.keys()
config.write()
[docs] def load_manifest(self):
"""
*Partially load* a project from a manifest file.
1. Read manifest file (oaproject.cfg).
2. Load metadata inside project from manifest.
3. Load **filenames** of data files inside project from manifest.
4. **Not** load data ! If you want to load data, please use :func:`load`.
:warning: load metadata and list of filenames but does not load files
.. seealso:: :func:`save_manifest` :func:`load`
"""
config = ConfigObj(self.path / self.name / self.config_file)
if config.has_key('metadata'):
for info in config["metadata"].keys():
setattr(self, info, config['metadata'][info])
if config.has_key('manifest'):
# Load file names in good place (dict.keys()) but don't load entire object:
# ie. load keys but not values
for files in config["manifest"].keys():
filedict = dict()
for f in config['manifest'][files]:
filedict[f] = ""
setattr(self, files, filedict)
#----------------------------------------
# src
#----------------------------------------
[docs] def add_script(self, name, script):
"""
Add a src in the project
:deprecated: replace by :func:`add` method
:param name: filename of the src to add (path or str)
:param script: to add (string)
.. seealso:: :func:`add`
"""
warnings.warn(
"project.add_script(name, script) is deprecated. Please use project.add('src', name, script) instead.")
self.add("src", name, script)
[docs] def remove_script(self, name):
"""
Add a src in the project
Remove nothing on disk.
:deprecated: replace by :func:`remove` method
:param name: filename of the src to remove (path or str)
.. seealso:: :func:`remove`
"""
warnings.warn("project.remove_script(name) is deprecated. Please use project.remove('src', name) instead.")
self.remove("src", name)
[docs] def run_src(self, name):
"""
Try to run the source file named *name* into current shell
"""
src = self.get("src", name)
exec (src, self._ns)
#----------------------------------------
# Protected
#----------------------------------------
def _load(self, object_type):
"""
Load files listed in self.object_type.keys()
"""
object_type = str(object_type)
return_object = dict()
if hasattr(self, object_type):
temp_path = self.path / self.name / object_type
if not temp_path.exists():
return return_object
files = getattr(self, object_type)
files = files.keys()
for filename in files:
filename = path_(filename)
pathname = self.path / self.name / object_type / filename
if filename.isabs():
# Load files that are outside project
pathname = filename
Loader = get_loader("GenericLoader")
if object_type == "control":
Loader = get_loader("CPickleLoader")
if object_type == "scene":
Loader = get_loader("BGEOMLoader")
loader = Loader()
result = loader.load(pathname)
return_object[filename] = result
# hack to add cache in namespace
if object_type == "cache":
for cache_name in return_object:
self._ns[cache_name] = eval(str(return_object[cache_name]), self._ns)
return return_object
def _save(self, object_type):
object_type = str(object_type)
object_ = getattr(self, object_type)
if object_:
temp_path = self.path / self.name / object_type
# Make default directories if necessary
if not (self.path / self.name).exists():
os.mkdir(self.path / self.name)
if not temp_path.exists():
os.mkdir(temp_path)
for sub_object in object_:
filename = temp_path / sub_object
sub_object = path_(sub_object)
Saver = get_saver()
if sub_object.isabs():
# Permit to save object outside project
filename = sub_object
if object_type == "scene":
# Save PlantGL objects
Saver = get_saver("BGEOMSaver")
elif object_type == "control":
Saver = get_saver("CPickleSaver")
saver = Saver()
saver.save(object_[sub_object], filename)
def _save_scripts(self):
warnings.warn("project._save_scripts is deprecated. Please use project._save('src') instead.")
self._save("src")
def _startup_import(self):
use_ip = self.use_ipython()
for s in self.startup:
if s.find('import') != -1:
exec (self.startup[s], self._ns)
if use_ip:
self._shell.runcode(self.startup[s])
def _startup_run(self):
use_ip = self.use_ipython()
for s in self.startup:
if s.find('import') == -1:
exec (self.startup[s], self._ns)
if use_ip:
self._shell.runcode(self.startup[s])
def __repr__(self):
txt = "Project named " + str(self.name) + " in path " + str(self.path) + """.
"""
for metada in self.metadata:
if self.metadata[metada]:
txt = txt + metada + " : " + str(self.metadata[metada]) + ". "
txt = txt + """
"""
for file_ in self.files:
if self.files[file_]:
txt = txt + file_ + " : " + str(self.files[file_])
return txt
def _set_ipython(self, shell=None):
if not self.use_ipython():
try:
# Try to get automatically current IPython shell
shell = get_ipython()
except NameError:
shell = None
self._shell = shell
[docs] def use_ipython(self):
"""
:return: True if project is instaciated with a shell IPython. Else, return False.
"""
if self._shell == None:
return False
else:
return True
[docs] def get_scene(self):
"""
:return: self.scene (dict)
"""
return self.scene
[docs] def is_project(self):
"""
:return: True
"""
return True
[docs] def is_script(self):
"""
:return: False
"""
return False
@property
[docs] def ns(self):
return self._ns
# Metadata
@property
def name(self):
return self.metadata["name"]
@name.setter
[docs] def name(self, value):
self.metadata["name"] = value
@property
def icon(self):
return self.metadata["icon"]
@icon.setter
[docs] def icon(self, value):
self.metadata["icon"] = value
@property
def author(self):
return self.metadata["author"]
@author.setter
[docs] def author(self, value):
self.metadata["author"] = value
@property
def author_email(self):
return self.metadata["author_email"]
@author_email.setter
[docs] def author_email(self, value):
self.metadata["author_email"] = value
@property
def description(self):
return self.metadata["description"]
@description.setter
[docs] def description(self, value):
self.metadata["description"] = value
@property
def long_description(self):
return self.metadata["long_description"]
@long_description.setter
[docs] def long_description(self, value):
self.metadata["long_description"] = value
@property
def citation(self):
return self.metadata["citation"]
@citation.setter
[docs] def citation(self, value):
self.metadata["citation"] = value
@property
def url(self):
return self.metadata["url"]
@url.setter
[docs] def url(self, value):
self.metadata["url"] = value
@property
def dependencies(self):
return self.metadata["dependencies"]
@dependencies.setter
[docs] def dependencies(self, value):
self.metadata["dependencies"] = value
@property
def license(self):
return self.metadata["license"]
@license.setter
[docs] def license(self, value):
self.metadata["license"] = value
@property
def version(self):
return self.metadata["version"]
@version.setter
[docs] def version(self, value):
self.metadata["version"] = value
@property
def src(self):
return self.files["src"]
@src.setter
[docs] def src(self, value):
self.files["src"] = value
@property
def control(self):
return self.files["control"]
@control.setter
[docs] def control(self, value):
self.files["control"] = value
@property
def cache(self):
return self.files["cache"]
@cache.setter
[docs] def cache(self, value):
self.files["cache"] = value
@property
def data(self):
return self.files["data"]
@data.setter
[docs] def data(self, value):
self.files["data"] = value
@property
def scene(self):
return self.files["scene"]
@scene.setter
[docs] def scene(self, value):
self.files["scene"] = value
@property
def startup(self):
return self.files["startup"]
@startup.setter
[docs] def startup(self, value):
self.files["startup"] = value
@property
def doc(self):
return self.files["doc"]
@doc.setter
[docs] def doc(self, value):
self.files["doc"] = value