[Перевод] Точки входа в Python

Многие думают, что точки входа это такие инструкции в setup.py, которые позволяют сделать пакет доступным для запуска из командной строки. Это, в целом, верно, но возможности точек входа не ограничиваются этим.

Ниже я покажу как можно реализовать систему плагинов для пакета, чтобы другие люди могли с ним взаимодействовать или, например, расширять его функциональность динамически.

d88d31319863acdaadce022ef52175eb.png

Осторожно: специфический юмор далее по тексту.

ООО «Змейка»


Поздравляю! Вас только что назначили главой ООО «Змейка». Это очень ответственная должность, ударить в грязь лицом нельзя, а значит, нужно как можно скорее дать указание отделу разработки приступить к созданию прототипа продукта. И вот, лучшие умы компании начинают работу над snek.py:

ascii_snek = """\
    --..,_                     _,.--.
       `'.'.                .'`__ o  `;__.
          '.'.            .'.'`  '---'`  `
            '.`'--....--'`.'
              `'--....--'`
"""

def main():
    print(ascii_snek)
    
if __name__ == '__main__':
    main()


Чуть позже, на собрании акционеров вы с гордостью демонстрируете первые результаты!

$ python snek.py
    --..,_                     _,.--.
       `'.'.                .'`__ o  `;__.
          '.'.            .'.'`  '---'`  `
            '.`'--....--'`.'
              `'--....--'`


Змейка как сервис


К сожалению, рядовой потребитель пока ещё не освоил Python и хочет запускать программу из консоли, не думая об интерпретаторе или местоположении snek.py. Что ж, наши лучшие специалисты на то и лучшие, что смогли запаковать скрипт так, чтобы он автоматически создавал консольную команду при установке.

Для создания распространяемого пакета нам нужен файл setup.py, где содержится информация о зависимостях, лицензии и т.д. Помимо этого, в нём можно указать точки входа:

from setuptools import setup

setup(
    name='snek',
    entry_points={
        'console_scripts': [
            'snek = snek:main',
        ],
    }
)


console_scripts, как пояснили специалисты, специальная точка входа. setuptools читает её элементы как »<консольный скрипт> = <путь к питоновскому объекту>», создавая для каждого элемента консольную утилиту при установке пакета.

Пока что давайте установим скрипт из исходников:

$ python setup.py develop

running develop
running egg_info
writing snek.egg-info\PKG-INFO
writing dependency_links to snek.egg-info\dependency_links.txt
writing entry points to snek.egg-info\entry_points.txt
writing top-level names to snek.egg-info\top_level.txt
reading manifest file 'snek.egg-info\SOURCES.txt'
writing manifest file 'snek.egg-info\SOURCES.txt'
running build_ext
Creating c:\program files (x86)\py36-32\lib\site-packages\snek.egg-link (link to .)
snek 0.0.0 is already the active version in easy-install.pth
Installing snek-script.py script to C:\Program Files (x86)\Py36-32\Scripts
Installing snek.exe script to C:\Program Files (x86)\Py36-32\Scripts
Installing snek.exe.manifest script to C:\Program Files (x86)\Py36-32\Scripts

Installed c:\users\rachum\notebooks
Processing dependencies for snek==0.0.0
Finished processing dependencies for snek==0.0.0


На конференции, посвящённой итогам года, вы выступаете, демонстрируя новейшую разработку:

$ snek
    --..,_                     _,.--.
       `'.'.                .'`__ o  `;__.
          '.'.            .'.'`  '---'`  `
            '.`'--....--'`.'
              `'--....--'`


Змейка в каждый дом


Змейка завоёвывает мир. Компания провела IPO и была оценена в рекордные 60 миллиардов долларов. Хипстеры требуют новую, стильную, модную и молодёжную змейку. А раз есть спрос, будет и предложение:

"""Печатает ASCII змейку.

Использование:
    snek [--type=TYPE]
    
"""
import docopt

normal_snek = """\
    --..,_                     _,.--.
       `'.'.                .'`__ o  `;__.
          '.'.            .'.'`  '---'`  `
            '.`'--....--'`.'
              `'--....--'`
"""

fancy_snek = """\
                          _,..,,,_
                     '``````^~"-,_`"-,_
       .-~c~-.                    `~:. ^-.
   `~~~-.c    ;                      `:.  `-,     _.-~~^^~:.
         `.   ;      _,--~~~~-._       `:.   ~. .~          `.
          .` ;'   .:`           `:       `:.   `    _.:-,.    `.
        .' .:   :'    _.-~^~-.    `.       `..'   .:      `.    '
       :  .' _:'   .-'        `.    :.     .:   .'`.        :    ;
       :  `-'   .:'             `.    `^~~^`   .:.  `.      ;    ;
        `-.__,-~                  ~-.        ,' ':    '.__.`    :'
                                     ~--..--'     ':.         .:'
                                                     ':..___.:'
"""

def get_sneks():
    return {
        'normal': normal_snek,
        'fancy': fancy_snek,
    }


def main():
    args = docopt.docopt(__doc__)
    snek_type = args['--type'] or 'normal'
    print(get_sneks()[snek_type])
    
if __name__ == '__main__':
    main()


Хипстеры в восторге:

$ snek
    --..,_                     _,.--.
       `'.'.                .'`__ o  `;__.
          '.'.            .'.'`  '---'`  `
            '.`'--....--'`.'
              `'--....--'`

$ snek --type fancy
                          _,..,,,_
                     '``````^~"-,_`"-,_
       .-~c~-.                    `~:. ^-.
   `~~~-.c    ;                      `:.  `-,     _.-~~^^~:.
         `.   ;      _,--~~~~-._       `:.   ~. .~          `.
          .` ;'   .:`           `:       `:.   `    _.:-,.    `.
        .' .:   :'    _.-~^~-.    `.       `..'   .:      `.    '
       :  .' _:'   .-'        `.    :.     .:   .'`.        :    ;
       :  `-'   .:'             `.    `^~~^`   .:.  `.      ;    ;
        `-.__,-~                  ~-.        ,' ':    '.__.`    :'
                                     ~--..--'     ':.         .:'
                                                     ':..___.:'


Транснациональная Змейка


Миллионы человек не мыслят свой день без змейки. Даже после поглощения Гугла ресурсов ООО «Змейка» не хватает, чтобы удовлетворять потребности пользователей по всему миру. Похоже настало время дать людям возможность создавать собственных змеек на базе нашей инфраструктуры.

"""Печатает ASCII змейку.

Использование:
    snek [--type=TYPE]
    
"""
import docopt
import pkg_resources

normal_snek = """\
    --..,_                     _,.--.
       `'.'.                .'`__ o  `;__.
          '.'.            .'.'`  '---'`  `
            '.`'--....--'`.'
              `'--....--'`
"""

fancy_snek = """\
                          _,..,,,_
                     '``````^~"-,_`"-,_
       .-~c~-.                    `~:. ^-.
   `~~~-.c    ;                      `:.  `-,     _.-~~^^~:.
         `.   ;      _,--~~~~-._       `:.   ~. .~          `.
          .` ;'   .:`           `:       `:.   `    _.:-,.    `.
        .' .:   :'    _.-~^~-.    `.       `..'   .:      `.    '
       :  .' _:'   .-'        `.    :.     .:   .'`.        :    ;
       :  `-'   .:'             `.    `^~~^`   .:.  `.      ;    ;
        `-.__,-~                  ~-.        ,' ':    '.__.`    :'
                                     ~--..--'     ':.         .:'
                                                     ':..___.:'
"""

def get_sneks():
    sneks = {
        'normal': normal_snek,
        'fancy': fancy_snek,
    }
    for entry_point in pkg_resources.iter_entry_points('snek_types'):
        sneks[entry_point.name] = entry_point.load()
    return sneks


def main():
    args = docopt.docopt(__doc__)
    snek_type = args['--type'] or 'normal'
    print(get_sneks()[snek_type])
    
if __name__ == '__main__':
    main()


Теперь, каждый раз когда snek запущена, она ищет других зарегистрированных в системе змеек, используя точку входа snek_types. Каждая такая змейка зарегистрирована под названием её типа, что позволяет выбирать нужную змейку в зависимости от параметров консоли.

Всё самое главное происходит внутри get_sneks. Вызов pkg_resources.iter_entry_points ('snek_types') позволяет пройтись по всем точкам входа, зарегистрированным под именем «snek_types» где-либо. Таким образом, любой сторонний пакет сможет в своём setup.py создать точку входа «snek_types», чтобы быть загруженным нашим скриптом.

Мы рассказали о «snek_types» нашим коллегам из ООО «Змеиные решения», и они тут же начали создавать змейку своей мечты. Вот каким получился их пакет cute_snek.py:

cute_snek = r"""
                    /^\/^\
                  _|__|  O|
         \/     /~     \_/ \
          \____|__________/  \
                 \_______      \
                         `\     \                 \
                           |     |                  \
                          /      /                    \
                         /     /                       \
                       /      /                         \ \
                      /     /                            \  \
                    /     /             _----_            \   \
                   /     /           _-~      ~-_         |   |
                  (      (        _-~    _--_    ~-_     _/   |
                   \      ~-____-~    _-~    ~-_    ~-_-~    /
                     ~-_           _-~          ~-_       _-~ 
                        ~--______-~                ~-___-~
"""


А вот как они реализовали свой setup.py, чтобы наша snek могла загрузить их змейку:

from setuptools import setup

setup(
    name='cute_snek',
    entry_points={
        'snek_types': [
            'cute = cute_snek:cute_snek',
        ],
    }
)


Они зарегистрировали переменную cute_snek в модуле cute_snek под именем cute. Далее они устанавливают пакеты snek и cute_snek:

$ cd cute_snek && python setup.py develop
running develop
running egg_info
writing cute_snek.egg-info\PKG-INFO
writing dependency_links to cute_snek.egg-info\dependency_links.txt
writing entry points to cute_snek.egg-info\entry_points.txt
writing top-level names to cute_snek.egg-info\top_level.txt
reading manifest file 'cute_snek.egg-info\SOURCES.txt'
writing manifest file 'cute_snek.egg-info\SOURCES.txt'
running build_ext
Creating c:\program files (x86)\py36-32\lib\site-packages\cute-snek.egg-link (link to .)
cute-snek 0.0.0 is already the active version in easy-install.pth

Installed c:\users\rachum\cute_snek
Processing dependencies for cute-snek==0.0.0
Finished processing dependencies for cute-snek==0.0.0


Теперь, запустив snek, они могут вывести свою змейку из пакета cute_snek за счёт её динамической загрузки по точке входа:

$ snek --type cute
                    /^\/^\
                  _|__|  O|
         \/     /~     \_/ \
          \____|__________/  \
                 \_______      \
                         `\     \                 \
                           |     |                  \
                          /      /                    \
                         /     /                       \
                       /      /                         \ \
                      /     /                            \  \
                    /     /             _----_            \   \
                   /     /           _-~      ~-_         |   |
                  (      (        _-~    _--_    ~-_     _/   |
                   \      ~-____-~    _-~    ~-_    ~-_-~    /
                     ~-_           _-~          ~-_       _-~ 
                        ~--______-~                ~-___-~


Змейка 2.0


Пока всё внимание высшего руководства уходит на разборки с налоговой инспекцией и антимонопольной службой, отдел разработки наконец-то может выкроить немного времени на рефакторинг кода.

Главный системный архитектор понял, что если сторонние змейки могут быть загружены как плагины, значит, так же могут быть загружены и встроенные змейки.

Уберём особую обработку встроенных змеек:

--- a/snek.py
+++ b/snek.py
@@ -31,10 +31,7 @@ fancy_snek = """\
 """

 def get_sneks():
-    sneks = {
-        'normal': normal_snek,
-        'fancy': fancy_snek,
-    }
+    sneks = {}
     for entry_point in pkg_resources.iter_entry_points('snek_types'):
         sneks[entry_point.name] = entry_point.load()
     return sneks


И взамен зарегистрируем для них универсальные точки входа:

--- a/setup.py
+++ b/setup.py
@@ -6,5 +6,9 @@ setup(
         'console_scripts': [
             'snek = snek:main',
        ],
+       'snek_types': [
+           'normal = snek:normal_snek',
+           'fancy = snek:fancy_snek',
+       ],
     },
 )


Переустановим изменённую змейку:

$ python setup.py develop
running develop
running egg_info
writing snek.egg-info\PKG-INFO
writing dependency_links to snek.egg-info\dependency_links.txt
writing entry points to snek.egg-info\entry_points.txt
writing top-level names to snek.egg-info\top_level.txt
reading manifest file 'snek.egg-info\SOURCES.txt'
writing manifest file 'snek.egg-info\SOURCES.txt'
running build_ext
Creating c:\program files (x86)\py36-32\lib\site-packages\snek.egg-link (link to .)
snek 0.0.0 is already the active version in easy-install.pth
Installing snek-script.py script to C:\Program Files (x86)\Py36-32\Scripts
Installing snek.exe script to C:\Program Files (x86)\Py36-32\Scripts
Installing snek.exe.manifest script to C:\Program Files (x86)\Py36-32\Scripts

Installed c:\users\rachum\notebooks
Processing dependencies for snek==0.0.0
Finished processing dependencies for snek==0.0.0


Проверим результат:

$ snek
    --..,_                     _,.--.
       `'.'.                .'`__ o  `;__.
          '.'.            .'.'`  '---'`  `
            '.`'--....--'`.'
              `'--....--'`

$ snek --type fancy
                          _,..,,,_
                     '``````^~"-,_`"-,_
       .-~c~-.                    `~:. ^-.
   `~~~-.c    ;                      `:.  `-,     _.-~~^^~:.
         `.   ;      _,--~~~~-._       `:.   ~. .~          `.
          .` ;'   .:`           `:       `:.   `    _.:-,.    `.
        .' .:   :'    _.-~^~-.    `.       `..'   .:      `.    '
       :  .' _:'   .-'        `.    :.     .:   .'`.        :    ;
       :  `-'   .:'             `.    `^~~^`   .:.  `.      ;    ;
        `-.__,-~                  ~-.        ,' ':    '.__.`    :'
                                     ~--..--'     ':.         .:'
                                                     ':..___.:'

$ snek --type cute
                    /^\/^\
                  _|__|  O|
         \/     /~     \_/ \
          \____|__________/  \
                 \_______      \
                         `\     \                 \
                           |     |                  \
                          /      /                    \
                         /     /                       \
                       /      /                         \ \
                      /     /                            \  \
                    /     /             _----_            \   \
                   /     /           _-~      ~-_         |   |
                  (      (        _-~    _--_    ~-_     _/   |
                   \      ~-____-~    _-~    ~-_    ~-_-~    /
                     ~-_           _-~          ~-_       _-~ 
                        ~--______-~                ~-___-~



На этом всё. Теперь вы знаете, как использовать точки входа в Python!

© Habrahabr.ru