Merge pull request #145 from YunoHost/refactoring2

Remove globals variables magic and add explicit import now
This commit is contained in:
Laurent Peuch 2017-08-05 11:48:06 +02:00 committed by GitHub
commit 1efa5c8b6e
17 changed files with 158 additions and 211 deletions

View file

@ -85,7 +85,7 @@ todo_include_todos = True
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
# #
html_theme = 'default' html_theme = 'classic'
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
@ -105,11 +105,11 @@ html_static_path = ['_static']
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = { html_sidebars = {
'**': [ '**': [
'about.html', # 'about.html',
'navigation.html', # 'navigation.html',
'relations.html', # needs 'show_related': True theme option to display # 'relations.html', # needs 'show_related': True theme option to display
'searchbox.html', 'searchbox.html',
'donate.html', # 'donate.html',
] ]
} }

View file

@ -16,6 +16,7 @@ a reference.
:maxdepth: 2 :maxdepth: 2
:caption: Contents: :caption: Contents:
m18n
utils/filesystem utils/filesystem
utils/network utils/network
utils/process utils/process

38
doc/m18n.rst Normal file
View file

@ -0,0 +1,38 @@
Translations using the m18n object
==================================
The moulinette provides a way to do translations and YunoHost uses it. This is
done via the `m18n` object that you can import this way:
::
from moulinette import m18n
The `m18n` object comes with 2 method:
* `m18n.n` to uses for translations within YunoHost
* `m18n.g` to uses for translations within Moulinette itself
Their API is identical.
Here are example of uses:
::
m18n.n('some_translation_key')
m18n.g('some_translation_key')
m18n.n('some_translation_key', string_formating_argument_1=some_variable)
m18n.g('some_translation_key', string_formating_argument_1=some_variable)
The translation key must be present in `locales/en.json` of either YunoHost
(for `.n`) or moulinette (for `.g`).
Docstring
---------
As a reference, here are the docstrings of the m18n class:
.. autoclass:: moulinette.core.Moulinette18n
.. automethod:: moulinette.core.Moulinette18n.n
.. automethod:: moulinette.core.Moulinette18n.g

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from moulinette.core import init_interface, MoulinetteError from moulinette.core import init_interface, MoulinetteError, MoulinetteSignals, Moulinette18n
from moulinette.globals import DATA_DIR, LIB_DIR, LOCALES_DIR, CACHE_DIR
__title__ = 'moulinette' __title__ = 'moulinette'
__version__ = '0.1' __version__ = '0.1'
@ -27,11 +28,17 @@ __credits__ = """
along with this program; if not, see http://www.gnu.org/licenses along with this program; if not, see http://www.gnu.org/licenses
""" """
__all__ = [ __all__ = [
'init', 'api', 'cli', 'init', 'api', 'cli', 'm18n',
'init_interface', 'MoulinetteError', 'init_interface', 'MoulinetteError',
'DATA_DIR', 'LIB_DIR', 'LOCALES_DIR', 'CACHE_DIR',
] ]
msignals = MoulinetteSignals()
msettings = dict()
m18n = Moulinette18n()
# Package functions # Package functions
def init(logging_config=None, **kwargs): def init(logging_config=None, **kwargs):
@ -50,22 +57,12 @@ def init(logging_config=None, **kwargs):
""" """
import sys import sys
import __builtin__
from moulinette.core import (
Package, Moulinette18n, MoulinetteSignals
)
from moulinette.utils.log import configure_logging from moulinette.utils.log import configure_logging
configure_logging(logging_config) configure_logging(logging_config)
# Define and instantiate global objects
__builtin__.__dict__['pkg'] = Package(**kwargs)
__builtin__.__dict__['m18n'] = Moulinette18n(pkg)
__builtin__.__dict__['msignals'] = MoulinetteSignals()
__builtin__.__dict__['msettings'] = dict()
# Add library directory to python path # Add library directory to python path
sys.path.insert(0, pkg.libdir) sys.path.insert(0, LIB_DIR)
# Easy access to interfaces # Easy access to interfaces

View file

@ -9,6 +9,9 @@ import cPickle as pickle
from time import time from time import time
from collections import OrderedDict from collections import OrderedDict
from moulinette import m18n, msignals
from moulinette.cache import open_cachefile
from moulinette.globals import CACHE_DIR, DATA_DIR
from moulinette.core import (MoulinetteError, MoulinetteLock) from moulinette.core import (MoulinetteError, MoulinetteLock)
from moulinette.interfaces import ( from moulinette.interfaces import (
BaseActionsMapParser, GLOBAL_SECTION, TO_RETURN_PROP BaseActionsMapParser, GLOBAL_SECTION, TO_RETURN_PROP
@ -373,10 +376,10 @@ class ActionsMap(object):
for n in namespaces: for n in namespaces:
logger.debug("loading actions map namespace '%s'", n) logger.debug("loading actions map namespace '%s'", n)
actionsmap_yml = '%s/actionsmap/%s.yml' % (pkg.datadir, n) actionsmap_yml = '%s/actionsmap/%s.yml' % (DATA_DIR, n)
actionsmap_yml_stat = os.stat(actionsmap_yml) actionsmap_yml_stat = os.stat(actionsmap_yml)
actionsmap_pkl = '%s/actionsmap/%s-%d-%d.pkl' % ( actionsmap_pkl = '%s/actionsmap/%s-%d-%d.pkl' % (
pkg.cachedir, CACHE_DIR,
n, n,
actionsmap_yml_stat.st_size, actionsmap_yml_stat.st_size,
actionsmap_yml_stat.st_mtime actionsmap_yml_stat.st_mtime
@ -498,7 +501,7 @@ class ActionsMap(object):
""" """
namespaces = [] namespaces = []
for f in os.listdir('%s/actionsmap' % pkg.datadir): for f in os.listdir('%s/actionsmap' % DATA_DIR):
if f.endswith('.yml'): if f.endswith('.yml'):
namespaces.append(f[:-4]) namespaces.append(f[:-4])
return namespaces return namespaces
@ -524,23 +527,23 @@ class ActionsMap(object):
logger.debug("generating cache for actions map namespace '%s'", n) logger.debug("generating cache for actions map namespace '%s'", n)
# Read actions map from yaml file # Read actions map from yaml file
am_file = '%s/actionsmap/%s.yml' % (pkg.datadir, n) am_file = '%s/actionsmap/%s.yml' % (DATA_DIR, n)
with open(am_file, 'r') as f: with open(am_file, 'r') as f:
actionsmaps[n] = ordered_yaml_load(f) actionsmaps[n] = ordered_yaml_load(f)
# at installation, cachedir might not exists # at installation, cachedir might not exists
if os.path.exists('%s/actionsmap/' % pkg.cachedir): if os.path.exists('%s/actionsmap/' % CACHE_DIR):
# clean old cached files # clean old cached files
for i in os.listdir('%s/actionsmap/' % pkg.cachedir): for i in os.listdir('%s/actionsmap/' % CACHE_DIR):
if i.endswith(".pkl"): if i.endswith(".pkl"):
os.remove('%s/actionsmap/%s' % (pkg.cachedir, i)) os.remove('%s/actionsmap/%s' % (CACHE_DIR, i))
# Cache actions map into pickle file # Cache actions map into pickle file
am_file_stat = os.stat(am_file) am_file_stat = os.stat(am_file)
pkl = '%s-%d-%d.pkl' % (n, am_file_stat.st_size, am_file_stat.st_mtime) pkl = '%s-%d-%d.pkl' % (n, am_file_stat.st_size, am_file_stat.st_mtime)
with pkg.open_cachefile(pkl, 'w', subdir='actionsmap') as f: with open_cachefile(pkl, 'w', subdir='actionsmap') as f:
pickle.dump(actionsmaps[n], f) pickle.dump(actionsmaps[n], f)
return actionsmaps return actionsmaps

View file

@ -4,6 +4,8 @@ import errno
import gnupg import gnupg
import logging import logging
from moulinette import m18n
from moulinette.cache import open_cachefile
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
logger = logging.getLogger('moulinette.authenticator') logger = logging.getLogger('moulinette.authenticator')
@ -129,8 +131,8 @@ class BaseAuthenticator(object):
def _open_sessionfile(self, session_id, mode='r'): def _open_sessionfile(self, session_id, mode='r'):
"""Open a session file for this instance in given mode""" """Open a session file for this instance in given mode"""
return pkg.open_cachefile('%s.asc' % session_id, mode, return open_cachefile('%s.asc' % session_id, mode,
subdir='session/%s' % self.name) subdir='session/%s' % self.name)
def _store_session(self, session_id, session_hash, password): def _store_session(self, session_id, session_hash, password):
"""Store a session and its associated password""" """Store a session and its associated password"""

View file

@ -7,6 +7,7 @@ import logging
import ldap import ldap
import ldap.modlist as modlist import ldap.modlist as modlist
from moulinette import m18n
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from moulinette.authenticators import BaseAuthenticator from moulinette.authenticators import BaseAuthenticator
@ -212,5 +213,5 @@ class Authenticator(BaseAuthenticator):
attr, value) attr, value)
raise MoulinetteError(errno.EEXIST, raise MoulinetteError(errno.EEXIST,
m18n.g('ldap_attribute_already_exists', m18n.g('ldap_attribute_already_exists',
attribute=attr, value=value)) attribute=attr, value=value))
return True return True

43
moulinette/cache.py Normal file
View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
import os
from moulinette.globals import CACHE_DIR
def get_cachedir(subdir='', make_dir=True):
"""Get the path to a cache directory
Return the path to the cache directory from an optional
subdirectory and create it if needed.
Keyword arguments:
- subdir -- A cache subdirectory
- make_dir -- False to not make directory if it not exists
"""
path = os.path.join(CACHE_DIR, subdir)
if make_dir and not os.path.isdir(path):
os.makedirs(path)
return path
def open_cachefile(filename, mode='r', **kwargs):
"""Open a cache file and return a stream
Attempt to open in 'mode' the cache file 'filename' from the
default cache directory and in the subdirectory 'subdir' if
given. Directories are created if needed and a stream is
returned if the file can be written.
Keyword arguments:
- filename -- The cache filename
- mode -- The mode in which the file is opened
- **kwargs -- Optional arguments for get_cachedir
"""
# Set make_dir if not given
kwargs['make_dir'] = kwargs.get('make_dir',
True if mode[0] == 'w' else False)
return open('%s/%s' % (get_cachedir(**kwargs), filename), mode)

View file

@ -8,114 +8,14 @@ import logging
from importlib import import_module from importlib import import_module
import moulinette
from moulinette.globals import LOCALES_DIR, LIB_DIR
from moulinette.cache import get_cachedir
logger = logging.getLogger('moulinette.core') logger = logging.getLogger('moulinette.core')
# Package manipulation -------------------------------------------------
class Package(object):
"""Package representation and easy access methods
Initialize directories and variables for the package and give them
easy access.
Keyword arguments:
- _from_source -- Either the package is running from source or
not (only for debugging)
"""
def __init__(self, _from_source=False):
if _from_source:
import sys
logger.debug('initialize Package object running from source')
# Retrieve source's base directory
basedir = os.path.abspath(os.path.dirname(sys.argv[0]) + '/../')
# Set local directories
self._datadir = '%s/data' % basedir
self._libdir = '%s/lib' % basedir
self._localedir = '%s/locales' % basedir
self._cachedir = '%s/cache' % basedir
else:
import package
# Set system directories
self._datadir = package.datadir
self._libdir = package.libdir
self._localedir = package.localedir
self._cachedir = package.cachedir
def __setattr__(self, name, value):
if name[0] == '_' and name in self.__dict__:
# Deny reassignation of package directories
logger.error("cannot reassign Package variable '%s'", name)
return
self.__dict__[name] = value
# Easy access to package directories
@property
def datadir(self):
"""Return the data directory of the package"""
return self._datadir
@property
def libdir(self):
"""Return the lib directory of the package"""
return self._libdir
@property
def localedir(self):
"""Return the locale directory of the package"""
return self._localedir
@property
def cachedir(self):
"""Return the cache directory of the package"""
return self._cachedir
# Additional methods
def get_cachedir(self, subdir='', make_dir=True):
"""Get the path to a cache directory
Return the path to the cache directory from an optional
subdirectory and create it if needed.
Keyword arguments:
- subdir -- A cache subdirectory
- make_dir -- False to not make directory if it not exists
"""
path = os.path.join(self.cachedir, subdir)
if make_dir and not os.path.isdir(path):
os.makedirs(path)
return path
def open_cachefile(self, filename, mode='r', **kwargs):
"""Open a cache file and return a stream
Attempt to open in 'mode' the cache file 'filename' from the
default cache directory and in the subdirectory 'subdir' if
given. Directories are created if needed and a stream is
returned if the file can be written.
Keyword arguments:
- filename -- The cache filename
- mode -- The mode in which the file is opened
- **kwargs -- Optional arguments for get_cachedir
"""
# Set make_dir if not given
kwargs['make_dir'] = kwargs.get('make_dir',
True if mode[0] == 'w' else False)
return open('%s/%s' % (self.get_cachedir(**kwargs), filename), mode)
# Internationalization ------------------------------------------------- # Internationalization -------------------------------------------------
class Translator(object): class Translator(object):
@ -187,20 +87,15 @@ class Translator(object):
- key -- The key to translate - key -- The key to translate
""" """
def _load_key(locale): if key in self._translations.get(self.locale, {}):
value = self._translations[locale][key] return self._translations[self.locale][key].encode('utf-8').format(*args, **kwargs)
return value.encode('utf-8').format(*args, **kwargs)
if self.default_locale != self.locale and key in self._translations.get(self.default_locale, {}):
logger.info("untranslated key '%s' for locale '%s'",
key, self.locale)
return self._translations[self.default_locale][key].encode('utf-8').format(*args, **kwargs)
try:
return _load_key(self.locale)
except (KeyError, IndexError):
if self.default_locale != self.locale:
logger.info("untranslated key '%s' for locale '%s'",
key, self.locale)
try:
return _load_key(self.default_locale)
except:
pass
logger.exception("unable to retrieve key '%s' for default locale '%s'", logger.exception("unable to retrieve key '%s' for default locale '%s'",
key, self.default_locale) key, self.default_locale)
return key return key
@ -244,23 +139,17 @@ class Moulinette18n(object):
""" """
def __init__(self, package, default_locale='en'): def __init__(self, default_locale='en'):
self.default_locale = default_locale self.default_locale = default_locale
self.locale = default_locale self.locale = default_locale
self.pkg = package
# Init global translator # Init global translator
self._global = Translator(self.pkg.localedir, default_locale) self._global = Translator(LOCALES_DIR, default_locale)
# Define namespace related variables # Define namespace related variables
self._namespaces = {} self._namespaces = {}
self._current_namespace = None self._current_namespace = None
@property
def _namespace(self):
"""Return current namespace's Translator object"""
return self._namespaces[self._current_namespace]
def load_namespace(self, namespace): def load_namespace(self, namespace):
"""Load the namespace to use """Load the namespace to use
@ -273,10 +162,10 @@ class Moulinette18n(object):
""" """
if namespace not in self._namespaces: if namespace not in self._namespaces:
# Create new Translator object # Create new Translator object
n = Translator('%s/%s/locales' % (self.pkg.libdir, namespace), translator = Translator('%s/%s/locales' % (LIB_DIR, namespace),
self.default_locale) self.default_locale)
n.set_locale(self.locale) translator.set_locale(self.locale)
self._namespaces[namespace] = n self._namespaces[namespace] = translator
# Set current namespace # Set current namespace
self._current_namespace = namespace self._current_namespace = namespace
@ -312,12 +201,7 @@ class Moulinette18n(object):
- key -- The key to translate - key -- The key to translate
""" """
try: return self._namespaces[self._current_namespace].translate(key, *args, **kwargs)
return self._namespace.translate(key, *args, **kwargs)
except:
logger.exception("cannot translate key '%s' for namespace '%s'",
key, self._current_namespace)
return key
class MoulinetteSignals(object): class MoulinetteSignals(object):
@ -450,7 +334,7 @@ def init_interface(name, kwargs={}, actionsmap={}):
mod = import_module('moulinette.interfaces.%s' % name) mod = import_module('moulinette.interfaces.%s' % name)
except ImportError: except ImportError:
logger.exception("unable to load interface '%s'", name) logger.exception("unable to load interface '%s'", name)
raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) raise MoulinetteError(errno.EINVAL, moulinette.m18n.g('error_see_log'))
else: else:
try: try:
# Retrieve interface classes # Retrieve interface classes
@ -458,7 +342,7 @@ def init_interface(name, kwargs={}, actionsmap={}):
interface = mod.Interface interface = mod.Interface
except AttributeError: except AttributeError:
logger.exception("unable to retrieve classes of interface '%s'", name) logger.exception("unable to retrieve classes of interface '%s'", name)
raise MoulinetteError(errno.EIO, m18n.g('error_see_log')) raise MoulinetteError(errno.EIO, moulinette.m18n.g('error_see_log'))
# Instantiate or retrieve ActionsMap # Instantiate or retrieve ActionsMap
if isinstance(actionsmap, dict): if isinstance(actionsmap, dict):
@ -467,7 +351,7 @@ def init_interface(name, kwargs={}, actionsmap={}):
amap = actionsmap amap = actionsmap
else: else:
logger.error("invalid actionsmap value %r", actionsmap) logger.error("invalid actionsmap value %r", actionsmap)
raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) raise MoulinetteError(errno.EINVAL, moulinette.m18n.g('error_see_log'))
return interface(amap, **kwargs) return interface(amap, **kwargs)
@ -488,7 +372,7 @@ def init_authenticator((vendor, name), kwargs={}):
mod = import_module('moulinette.authenticators.%s' % vendor) mod = import_module('moulinette.authenticators.%s' % vendor)
except ImportError: except ImportError:
logger.exception("unable to load authenticator vendor '%s'", vendor) logger.exception("unable to load authenticator vendor '%s'", vendor)
raise MoulinetteError(errno.EINVAL, m18n.g('error_see_log')) raise MoulinetteError(errno.EINVAL, moulinette.m18n.g('error_see_log'))
else: else:
return mod.Authenticator(name, **kwargs) return mod.Authenticator(name, **kwargs)
@ -504,7 +388,7 @@ def clean_session(session_id, profiles=[]):
- profiles -- A list of profiles to clean - profiles -- A list of profiles to clean
""" """
sessiondir = pkg.get_cachedir('session') sessiondir = get_cachedir('session')
if not profiles: if not profiles:
profiles = os.listdir(sessiondir) profiles = os.listdir(sessiondir)
@ -578,7 +462,7 @@ class MoulinetteLock(object):
if self.timeout is not None and (time.time() - start_time) > self.timeout: if self.timeout is not None and (time.time() - start_time) > self.timeout:
raise MoulinetteError(errno.EBUSY, raise MoulinetteError(errno.EBUSY,
m18n.g('instance_already_running')) moulinette.m18n.g('instance_already_running'))
# Wait before checking again # Wait before checking again
time.sleep(self.interval) time.sleep(self.interval)
@ -605,8 +489,8 @@ class MoulinetteLock(object):
except IOError: except IOError:
raise MoulinetteError( raise MoulinetteError(
errno.EPERM, '%s. %s.'.format( errno.EPERM, '%s. %s.'.format(
m18n.g('permission_denied'), moulinette.m18n.g('permission_denied'),
m18n.g('root_required'))) moulinette.m18n.g('root_required')))
def __enter__(self): def __enter__(self):
if not self._locked: if not self._locked:

4
moulinette/globals.py Normal file
View file

@ -0,0 +1,4 @@
DATA_DIR = '/usr/share/moulinette'
LIB_DIR = '/usr/lib/moulinette'
LOCALES_DIR = '/usr/share/moulinette/locale'
CACHE_DIR = '/var/cache/moulinette'

View file

@ -7,6 +7,7 @@ import logging
import argparse import argparse
from collections import deque from collections import deque
from moulinette import msignals, msettings, m18n
from moulinette.core import (init_authenticator, MoulinetteError) from moulinette.core import (init_authenticator, MoulinetteError)
logger = logging.getLogger('moulinette.interface') logger = logging.getLogger('moulinette.interface')

View file

@ -13,6 +13,7 @@ from geventwebsocket import WebSocketError
from bottle import run, request, response, Bottle, HTTPResponse from bottle import run, request, response, Bottle, HTTPResponse
from moulinette import msignals, m18n, DATA_DIR
from moulinette.core import MoulinetteError, clean_session from moulinette.core import MoulinetteError, clean_session
from moulinette.interfaces import ( from moulinette.interfaces import (
BaseActionsMapParser, BaseInterface, ExtendedArgumentParser, BaseActionsMapParser, BaseInterface, ExtendedArgumentParser,
@ -780,11 +781,11 @@ class Interface(BaseInterface):
""" """
if category is None: if category is None:
with open('%s/../doc/resources.json' % pkg.datadir) as f: with open('%s/../doc/resources.json' % DATA_DIR) as f:
return f.read() return f.read()
try: try:
with open('%s/../doc/%s.json' % (pkg.datadir, category)) as f: with open('%s/../doc/%s.json' % (DATA_DIR, category)) as f:
return f.read() return f.read()
except IOError: except IOError:
return None return None

View file

@ -11,6 +11,7 @@ from collections import OrderedDict
import argcomplete import argcomplete
from moulinette import msignals, m18n
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from moulinette.interfaces import ( from moulinette.interfaces import (
BaseActionsMapParser, BaseInterface, ExtendedArgumentParser, BaseActionsMapParser, BaseInterface, ExtendedArgumentParser,

View file

@ -1,15 +0,0 @@
# -*- coding: utf-8 -*-
# Public constants defined during build
"""Package's data directory (e.g. /usr/share/moulinette)"""
datadir = '%PKGDATADIR%'
"""Package's library directory (e.g. /usr/lib/moulinette)"""
libdir = '%PKGLIBDIR%'
"""Locale directory for the package (e.g. /usr/share/moulinette/locale)"""
localedir = '%PKGLOCALEDIR%'
"""Cache directory for the package (e.g. /var/cache/moulinette)"""
cachedir = '%PKGCACHEDIR%'

View file

@ -5,6 +5,8 @@ import json
import grp import grp
from pwd import getpwnam from pwd import getpwnam
from moulinette import m18n
from moulinette.globals import CACHE_DIR
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
# Files & directories -------------------------------------------------- # Files & directories --------------------------------------------------

View file

@ -2,6 +2,7 @@ import errno
import requests import requests
import json import json
from moulinette import m18n
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError

View file

@ -3,36 +3,19 @@ import os
import sys import sys
from distutils.core import setup from distutils.core import setup
from distutils.dir_util import mkpath
from distutils.sysconfig import PREFIX
# Define package directories from moulinette.globals import LOCALES_DIR
datadir = os.path.join(PREFIX, 'share/moulinette')
libdir = os.path.join(PREFIX, 'lib/moulinette')
localedir = os.path.join(datadir, 'locale')
cachedir = '/var/cache/moulinette'
# Extend installation # Extend installation
locale_files = [] locale_files = []
if "install" in sys.argv: if "install" in sys.argv:
# Evaluate locale files # Evaluate locale files
for f in os.listdir('locales'): for f in os.listdir('locales'):
if f.endswith('.json'): if f.endswith('.json'):
locale_files.append('locales/%s' % f) locale_files.append('locales/%s' % f)
# Generate package.py
package = open('moulinette/package.py.in').read()
package = package.replace('%PKGDATADIR%', datadir) \
.replace('%PKGLIBDIR%', libdir) \
.replace('%PKGLOCALEDIR%', localedir) \
.replace('%PKGCACHEDIR%', cachedir)
with open('moulinette/package.py', 'w') as f:
f.write(package)
# Create needed directories
# mkpath(libdir, mode=0755, verbose=1)
# mkpath(os.path.join(datadir, 'actionsmap'), mode=0755, verbose=1)
setup(name='Moulinette', setup(name='Moulinette',
version='2.0.0', version='2.0.0',
@ -47,5 +30,5 @@ setup(name='Moulinette',
'moulinette.interfaces', 'moulinette.interfaces',
'moulinette.utils', 'moulinette.utils',
], ],
data_files=[(localedir, locale_files)] data_files=[(LOCALES_DIR, locale_files)]
) )