Пишем кросс-платформенную библиотеку в Python
Привет, Хабр! Сегодня немного поговорим про кроссплатформенную разработку, а именно — на языке Python.Язык Python сам по себе считается кросс-платформенным, но до тех пор, пока дело не доходит до взаимодействия python-скрипта и внешних платформозависимых компонентов. Например, механизм подключения сетевой папки в Windows и Linux кардинально отличается. И если Вы пишите кросс-платформенный скрипт или даже библиотеку, то без организации кросс-платформенного кода на самом высоком уровне абстракции вам не обойтись.Черпать идеи мы будем из самой кросс-платформенной библиотеки среди всех библиотек языка Python — os.py. В ней импортируются низкоуровневые модули в зависимости от состава стандартной библиотеки. Для разных платформ набор стандартных модулей интерпретатора Python разный. Вы можете в этом убедиться, посмотрев код модуля os.
_names = sys.builtin_module_names
# Note: more names are added to __all__ later. __all__ = [«altsep», «curdir», «pardir», «sep», «extsep», «pathsep», «linesep», «defpath», «name», «path», «devnull», «SEEK_SET», «SEEK_CUR», «SEEK_END»]
def _get_exports_list (module): try: return list (module.__all__) except AttributeError: return [n for n in dir (module) if n[0] != '_']
if 'posix' in _names: name = 'posix' linesep = '\n' from posix import * try: from posix import _exit except ImportError: pass import posixpath as path
import posix __all__.extend (_get_exports_list (posix)) del posix Как видите, кортеж names содержит имена всех доступных модулей. Переменная модуля __all__ отвечает, какие объекты будут доступны при импорте c помощью инструкции from os import *. Если доступен модуль «posix», то выполняем специфичный для posix платформ код, а также прописываем ссылки на его доступные объекты в os.__all__. Затем удаляем объект posix, чтобы он был не доступен по вызову os.posix, так как он нам больше не нужен.Таким образом, все объекты модуля posix доступны для вызова с помощью os.объект_модуля_posix. Аналогично и для Windows, только вместо posix — nt.
Следует отметить, что в Windows версии интерпретатора доступен только модуль nt. Posix, solaris и другие модули платформ не входят в состав поставки. Аналогично и для других платформ. Тот факт, что интерпретатор скомпилирован под определенную платформу, не вписывается в идею кроссплатформенности. Поэтому дистрибутив нашей библиотеки содержит модули для всех платформ.
Приступим к написанию собственной кросс-платформенной библиотеки. Для примера, напишем библиотеку, которая будет подключать сетевую папку независимо от платформы. Давайте посмотрим на команды, с помощью которых происходит подключение сетевой папки в разных операционных системах:
В Linux:
mount //server/share /mount_point -o user=user, pass=password В Mac OSX: mount_smbfs //user: password@server/share /mount_point В windows: net use z: \\server\share /user: user password Не такие уж и разные команды. Как видите, каждая команда имеет в своем синтаксисе server, share, user и password. Все, что нам нужно, это определить платформу и выполнить функцию с входными параметрами server, share, user, password, mount_point (для win это будет буква диска).Структура библиотеки Структура файлов для нашей библиотеки будет следующей: __init__.py Этот файл выполняет 2 функции: Определяет, что папка mounter является пакетом Импортирует в область видимости пакета mounter, объекты в зависимости от платформы if sys.platform == «win32»: from mounter.win import * if sys.platform == «darwin»: from mounter.osx import * if sys.platform == «linux2»: from mounter.linux import * base.py Содержит общие для всех платформ объекты и прототипы классов, на основе которых будут построены платформозависмые классы. import os from subprocess import Popen, PIPE
class MounterBase (): mount_cmd = None umount_cmd = None mount_point = None network_folder = None
def _prepare (self): if not os.path.exists (self.mount_point): print 'creating %s folder for mounting' % self.mount_point os.makedirs (self.mount_point)
def mount (self): self._prepare () Popen (self.mount_cmd, stdout=PIPE, stderr=PIPE, shell=True)
def umount (self): Popen (self.umount_cmd, stdout=PIPE, stderr=PIPE, shell=True) Здесь класс MounterBase является прототипом будущих платформозависимых классов из файлов win.py, osx.py, linux.py и пока ничего полезного не умеет, так как команды для монтирования и размонтирования не определенны. А теперь рассмотрим один из платформозависымых модулей.linux.py import mounter
class Mounter (mounter.MounterBase): def __init__(self, network_folder, mount_point, user, password): mount_cmd = «mount {network_folder} {mount_point} -o user={user}, pass={password}» self.mount_cmd = mount_cmd.format (network_folder=network_folder, mount_point=mount_point, user=user, password=password) self.umount_cmd = «umount {mount_point}».format (mount_point=mount_point) В этом классе мы описываем специфичные параметры для платформы Linux, а именно команды монтирования и размонтирования. Сделаем аналогично для Мака и Windows.osx.py class Mounter (mounter.MounterBase): def __init__(self, network_folder, mount_point, user, password): mount_cmd = «mount_smbfs //{user}:{password}@{network_folder} {mount_point}» self.mount_cmd = mount_cmd.format (network_folder=network_folder, mount_point=mount_point, user=user, password=password) self.umount_cmd = «umount {mount_point}».format (mount_point=mount_point) win.py class Mounter (mounter.MounterBase): def __init__(self, network_folder, mount_point, user, password): network_folder =network_folder.replace (»/»,»\\») mount_cmd = «net use {mount_point} \\{network_folder} /user:{user} {password}» self.mount_cmd = mount_cmd.format (network_folder=network_folder, mount_point=mount_point, user=user, password=password) self.umount_cmd = «net use {mount_point} /delete /y».format (mount_point=mount_point) В итоге, чтобы подключить сетевую папку, нам достаточно вызвать метод mount () экземпляра класса mounter.Mounter (): import mounter
… mount_point = »/mnt/mount_moint» share = mounter.Mounter («server/share», mount_point, «guest», «secret_password») share.mount () copy_requared_files (source=mount_point, dest=»/tmp») share.umount () Теперь не нужно помнить синтаксис команд для всех платформ. Конечно, мы реализовали самый примитивный механизм подключения сетевой папки. В этом механизме не учитывается, что сетевая папка может быть без пароля, а также другие особенности. Реализацию этих особенностей я оставлю вам.Подобную архитектуру мы используем в автоматическом тестировании продуктов Acronis True Image for Windows и Acronis True Image for Mac. Например, чтобы выполнить процедуру бекапа, достаточно вызвать функцию TrueImage.backup (), и в зависимости от платформы, на которой был запущен скрипт, будет выполнятся соответствующий платформозависимый код.
Предыдущие посты у нас в блоге: — Мультиплатформенная разработка True Image– 50 оттенков синего, или сказ о том, как мы делали дизайн True Image 2015– Acronis Snap Deploy 5: Массовый деплоймент быстро просто и надёжно– «Ни единого разрыва!» или зачем клиенту воевать с техподдержкой– Стадии рождения новой функциональности в программном продукте– Золотое правило бэкапа