Ural penguins
28 августа 2017
04 октября 2014
22 сентября 2014
Документация python проекта на практике
Документация в python проектах пишется при помощи sphinx, он умеет используя расширение automodule читать докстринги и формировать документацию из кода.
Создать проект можно ответив на вопросы через sphinx-quickstart или использовать уже подготовленный шаблон который генерит дополнительно API пакета:
Автогенератор API обычно создает много лишнего и далеко не идеально генерит названия, поэтому удалим API тестов и попереимеуем все остальное.
Для сборки доков нужно в папке docs выполнить make html. Ниже структура получившихся файлов:
В директории build/html находится наша готовая документация в формате html. Добавим описание проекта на основную страницу. Для этого создаем readme.rst в директории docs и включаем его в index.rst, а в дальнейшем этот же readme.rst будет использоваться в README.rst в корне проекта для github и PyPi.
readme.rst
Здесь вроде все понятно, но есть нюансы, если например в директиве .. code-block не указать язык или не дописывать "=" в заголовках то документация будет нормально генериться, но PyPi её не поймет и выведет что-то типа этого:
Нужно быть внимательным и проверять как PyPi подхватил README.rst. Дальше в наш index.rst добавим readme.rst что бы он был по презентабельнее.
index.rst
В принципе уже хорошо, добавим это описание ещё для github и PyPi. Они по умолчанию берут файл README.rst из корня проекта и ничего про папку docs не знают. Можно было бы заинклудить readme.rst в корневой README.rst(..include:: docs/readme.rst), но github и PyPi инклуды не понимают, поэтому придется писать свой велосипед для копирования. Либо тупо делать это вручную. Я написал на коленке простой скрипт который это делает и заменяет директивы include содержанием их файлов.
make_README.py
Теперь если запустить из папки docs команду python make_README.py, то в корне проекта появится или перезапишется файл README.rst
Что бы это делалось автоматически при каждой сборке документации добавим в Makefile:
При вызове "make readme_html" у нас будет собираться документация html и копипаститься readme.
Конфиг для sphinx лежит в папке документации с названием conf.py. В нем можно настраивать документацию как угодно, к примеру оформим тему как у проекта Pyramid.
Добавим в conf.py
Этот код я скопировал из проекта Pyramid, он просто выкачивает их тему с github в директорию _themes. Далее укажем в конфиге название темы и где их искать.
После сборки проект будет выглядеть так:
Вообщем конфиг умеет много чего, есть еще много разных расширений, можно писать свои, либо переопределять/добавлять директивы итд. Вот например как вставлять ссылки на сторонние проекты:
Теперь можно писать так:
И наверно последний этап это добавление документации на readthedocs. Сложностей там особо нету, регаешься, добавляешь гитхаб профиль, синхронизируешь репы и выбираешь нужные. Хук на коммиты в таком случае вешается автоматически, единственное что нужно отметить что если в документации используется automodule нужно ставить галку virtualenv(в настройках readthedocs) иначе он просто будет пустым.
Готовый пример можно посмотреть здесь http://sacrud-deform.readthedocs.org/en/develop/
Создать проект можно ответив на вопросы через sphinx-quickstart или использовать уже подготовленный шаблон который генерит дополнительно API пакета:
sphinx-apidoc -F -o docs sacrud Creating file docs/sacrud_deform.rst. Creating file docs/sacrud_deform.tests.rst. Creating file docs/conf.py. Creating file docs/index.rst. Creating file docs/Makefile. Creating file docs/make.bat.
Автогенератор API обычно создает много лишнего и далеко не идеально генерит названия, поэтому удалим API тестов и попереимеуем все остальное.
Для сборки доков нужно в папке docs выполнить make html. Ниже структура получившихся файлов:
. ├── _build │ ├── doctrees │ │ ├── environment.pickle │ │ ├── index.doctree │ │ ├── readme.doctree │ │ ├── sacrud_deform.doctree │ │ └── sacrud_deform.tests.doctree │ └── html │ ├── genindex.html │ ├── index.html │ ├── _modules │ │ ├── index.html │ │ ├── sacrud_deform │ │ │ ├── tests │ │ │ │ └── test_form.html │ │ │ └── widgets.html │ │ └── sacrud_deform.html │ ├── objects.inv │ ├── py-modindex.html │ ├── readme.html │ ├── sacrud_deform.html │ ├── sacrud_deform.tests.html │ ├── search.html │ ├── searchindex.js │ ├── _sources │ │ ├── index.txt │ │ ├── readme.txt │ │ ├── sacrud_deform.tests.txt │ │ └── sacrud_deform.txt │ └── _static │ ├── ajax-loader.gif │ ├── basic.css │ ├── comment-bright.png │ ├── comment-close.png │ ├── comment.png │ ├── default.css │ ├── doctools.js │ ├── down.png │ ├── down-pressed.png │ ├── file.png │ ├── jquery.js │ ├── minus.png │ ├── plus.png │ ├── pygments.css │ ├── searchtools.js │ ├── sidebar.js │ ├── underscore.js │ ├── up.png │ ├── up-pressed.png │ └── websupport.js ├── conf.py ├── index.rst ├── make.bat ├── Makefile ├── readme.rst ├── sacrud_deform.rst ├── _static └── _templates
В директории build/html находится наша готовая документация в формате html. Добавим описание проекта на основную страницу. Для этого создаем readme.rst в директории docs и включаем его в index.rst, а в дальнейшем этот же readme.rst будет использоваться в README.rst в корне проекта для github и PyPi.
readme.rst
|Build Status| |Coverage Status| |Stories in Progress| |PyPI| .. |Build Status| image:: https://travis-ci.org/ITCase/sacrud_deform.svg?branch=master :target: https://travis-ci.org/ITCase/sacrud_deform .. |Coverage Status| image:: https://coveralls.io/repos/ITCase/sacrud_deform/badge.png?branch=master :target: https://coveralls.io/r/ITCase/sacrud_deform?branch=master .. |Stories in Progress| image:: https://badge.waffle.io/ITCase/sacrud_deform.png?label=in%20progress&title=In%20Progress :target: http://waffle.io/ITCase/sacrud_defrom .. |PyPI| image:: http://img.shields.io/pypi/dm/sacrud_deform.svg :target: https://pypi.python.org/pypi/sacrud_deform/ sacrud_deform ============== Form generotor for SQLAlchemy models. Install ======= develop version from source .. code-block:: bash pip install git+git://github.com/ITCase/sacrud_deform@develop from pypi .. code-block:: bash pip install sacrud_deform Use === .. code-block:: python data = form_generator(dbsession=DBSession, obj=obj_of_model, table=MyModel, columns=columns_of_model) form, js_list = data.render()
Здесь вроде все понятно, но есть нюансы, если например в директиве .. code-block не указать язык или не дописывать "=" в заголовках то документация будет нормально генериться, но PyPi её не поймет и выведет что-то типа этого:
Нужно быть внимательным и проверять как PyPi подхватил README.rst. Дальше в наш index.rst добавим readme.rst что бы он был по презентабельнее.
index.rst
Welcome to sacrud_deform's documentation! ========================================= .. include:: readme.rst Contents: .. toctree:: :maxdepth: 4 sacrud_deform Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search`Теперь главная страница документации выглядит так:
В принципе уже хорошо, добавим это описание ещё для github и PyPi. Они по умолчанию берут файл README.rst из корня проекта и ничего про папку docs не знают. Можно было бы заинклудить readme.rst в корневой README.rst(..include:: docs/readme.rst), но github и PyPi инклуды не понимают, поэтому придется писать свой велосипед для копирования. Либо тупо делать это вручную. Я написал на коленке простой скрипт который это делает и заменяет директивы include содержанием их файлов.
make_README.py
import fileinput import os from shutil import copyfile src = "readme.rst" src_path = os.path.dirname(os.path.realpath(src)) dst = "../README.rst" copyfile(src, dst) def read_file(path): with open(path, 'r') as f: return f.read() for line in fileinput.input(dst, inplace=1): splitted = line.rstrip().split('.. include:: ') if len(splitted) == 2: line = read_file(os.path.join(src_path, splitted[1])) print line else: print line.rstrip()
Теперь если запустить из папки docs команду python make_README.py, то в корне проекта появится или перезапишется файл README.rst
Что бы это делалось автоматически при каждой сборке документации добавим в Makefile:
... readme: python make_README.py readme_html: html readme
При вызове "make readme_html" у нас будет собираться документация html и копипаститься readme.
Конфиг для sphinx лежит в папке документации с названием conf.py. В нем можно настраивать документацию как угодно, к примеру оформим тему как у проекта Pyramid.
Добавим в conf.py
import sys import os # Add and use Pylons theme if 'sphinx-build' in ' '.join(sys.argv): # protect against dumb importers from subprocess import call, Popen, PIPE p = Popen('which git', shell=True, stdout=PIPE) git = p.stdout.read().strip() cwd = os.getcwd() _themes = os.path.join(cwd, '_themes') if not os.path.isdir(_themes): call([git, 'clone', 'git://github.com/Pylons/pylons_sphinx_theme.git', '_themes']) else: os.chdir(_themes) call([git, 'checkout', 'master']) call([git, 'pull']) os.chdir(cwd) sys.path.append(os.path.abspath('_themes')) parent = os.path.dirname(os.path.dirname(__file__)) sys.path.append(os.path.abspath(parent)) wd = os.getcwd() os.chdir(parent) os.chdir(wd) for item in os.listdir(parent): if item.endswith('.egg'): sys.path.append(os.path.join(parent, item))
Этот код я скопировал из проекта Pyramid, он просто выкачивает их тему с github в директорию _themes. Далее укажем в конфиге название темы и где их искать.
# -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'pyramid' # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_themes']
После сборки проект будет выглядеть так:
Вообщем конфиг умеет много чего, есть еще много разных расширений, можно писать свои, либо переопределять/добавлять директивы итд. Вот например как вставлять ссылки на сторонние проекты:
# Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions += [ 'sphinx.ext.intersphinx' ] intersphinx_mapping = { 'sacrud': ('http://sacrud.readthedocs.org/en/latest/', None), }
Теперь можно писать так:
Use :py:class:`sacrud.common.TableProperty` decorator.
И наверно последний этап это добавление документации на readthedocs. Сложностей там особо нету, регаешься, добавляешь гитхаб профиль, синхронизируешь репы и выбираешь нужные. Хук на коммиты в таком случае вешается автоматически, единственное что нужно отметить что если в документации используется automodule нужно ставить галку virtualenv(в настройках readthedocs) иначе он просто будет пустым.
Готовый пример можно посмотреть здесь http://sacrud-deform.readthedocs.org/en/develop/
05 сентября 2014
REST API для Pyramid при помощи Cornice и SACRUD
Mozilla использует в своих проектах Pyramid и у них есть отличный модуль для создания REST API https://cornice.readthedocs.org/en/latest/
REST API обычно меняет, создает и удаляет записи, которые хранятся в БД. Что бы не писать много кода на SQLAlchemy я использую заготовленные функции из sacrud
Итак поехали, представим сервис REST API для платежных карт с моделью типа:
Создадим отдельную папку в pyramid проекте с названием rest, где будут храниться функции api, и пропишем это в основном конфиге.
В pyramid'е есть такая штука как config.scan() она смотрит все файлы в проекте с расширением *.py (от текущей директории) и ищет вьюхи обернутые декоратором @view_config. Я предпочитаю включать в основной конфиг папочки через инклуд как в примере выше, а локально в каждой папке уже вызывать config.scan()
Структура папки rest
Файл __init__.py
В card.py сама реализация REST API
GET
Декоратор card_api превращает функцию get_card во вьюху, ожидающею HTTP метод GET, и config.scan() её автоматически подхватит. Внутри можно реализовывать все что угодно. Про метод validated ниже.
POST
Метод POST меняет параметры карты или создает новую если такой нету. Карта создается при помощи мега модуля sacrud. Подробнее о создании записей через sacrud здесь http://sacrud.readthedocs.org/en/latest/plain_usage.html#create-action
DELETE
Удаление происходит при вызове HTTP метода DELETE. sacrud опять же несколько упрощает эту операцию http://sacrud.readthedocs.org/en/latest/plain_usage.html#delete-action
В файле validators.py хранятся всякие исключения и сами проверки. Если в декораторе card_api указан валидатор, то валидные данные нужно будет выбирать не через request.matchdict а через request.validated
Пример файла validators.py
Таким образом можно довольно просто создать API для вашего проекта на пирамиде. Cornice делает много за вас, создает вьюхи, пути, валидацию, может генерить автоматически Sphinx документацию, а SACRUD упрощает работу с БД.
REST API обычно меняет, создает и удаляет записи, которые хранятся в БД. Что бы не писать много кода на SQLAlchemy я использую заготовленные функции из sacrud
Итак поехали, представим сервис REST API для платежных карт с моделью типа:
class Card(Base): __tablename__ = 'card' id = Column(Integer, primary_key=True) number = Column(BigInteger, nullable=False, unique=True) uid = Column(BYTEA, nullable=False, unique=True) balance = Column(Numeric(10, 2), nullable=False, default=0) preference = Column(GUID()) def __json__(self): return {'id': self.id, 'number': self.number, 'uid': str(self.uid), 'balance': str(self.balance) }
Создадим отдельную папку в pyramid проекте с названием rest, где будут храниться функции api, и пропишем это в основном конфиге.
# REST API config.include("cornice") config.include("myapp.rest", route_prefix="/api")
В pyramid'е есть такая штука как config.scan() она смотрит все файлы в проекте с расширением *.py (от текущей директории) и ищет вьюхи обернутые декоратором @view_config. Я предпочитаю включать в основной конфиг папочки через инклуд как в примере выше, а локально в каждой папке уже вызывать config.scan()
Структура папки rest
rest/ ├── card.py ├── __init__.py └── validators.py
Файл __init__.py
def includeme(config): config.scan()
В card.py сама реализация REST API
GET
import json from cornice import Service from myapp.models import DBSession from myapp.models import Card from myapp.rest.validators import _400, _404, valid_hex from sacrud.action import CRUD from sqlalchemy.exc import DataError from sqlalchemy.orm.exc import NoResultFound card_api = Service(name='card', path='/card/{UID}', description="REST API for card") @card_api.get(validators=valid_hex) def get_card(request): """``GET``: возвращает данные о карте. .. code-block:: bash $ curl -D - http://0.0.0.0:6543/api/card/07a8e29d HTTP/1.1 200 OK Content-Length: 69 Content-Type: application/json; charset=UTF-8 Date: Sat, 02 Aug 2014 11:22:52 GMT Server: waitress {"uid": "07a8e29d", "balance": 507.05, "id": 3, "number": 8224548674} """ key = request.validated['UID'] card = DBSession.query(Card) if key: try: card = card.filter_by(uid=key).one() except NoResultFound: raise _404() except DataError: raise _400() return card.__json__()
Декоратор card_api превращает функцию get_card во вьюху, ожидающею HTTP метод GET, и config.scan() её автоматически подхватит. Внутри можно реализовывать все что угодно. Про метод validated ниже.
POST
@card_api.post(validators=valid_hex) def set_card(request): """``POST``: передает параметры для редактирования карты. .. code-block:: bash $ curl -H 'Accept: application/json'\\ -H 'Content-Type: application/json'\\ http://0.0.0.0:6543/api/card/07a8e29d\\ -d '{"number": "8224548674", "balance": "507.05"}' {"uid": "07a8e29d", "balance": 507.05, "id": 3, "number": 8224548674} Если карты нету, то создается новая .. code-block:: bash $ curl -H 'Accept: application/json'\\ -H 'Content-Type: application/json'\\ http://0.0.0.0:6543/api/card/550e8400\\ -d '{"balance": "100.11", "preference": "956bfc40"}' {"uid": "550e8400", "balance": 100.11, "id": 7, "number": 91328937986} """ key = request.validated['UID'] data = {'request': json.loads(request.body)} card = DBSession.query(Card).filter_by(uid=key).first() if card: data['pk'] = {'id': card.id} else: data['request']['uid'] = key try: CRUD(DBSession, Card, **data).add() except Exception as e: raise _400(msg=str(e.message)) card = DBSession.query(Card).filter_by(uid=key).one() return card.__json__()
Метод POST меняет параметры карты или создает новую если такой нету. Карта создается при помощи мега модуля sacrud. Подробнее о создании записей через sacrud здесь http://sacrud.readthedocs.org/en/latest/plain_usage.html#create-action
DELETE
@card_api.delete(validators=valid_hex) def del_card(request): """``DELETE``: удаляет карту по UID .. code-block:: bash $ curl -H 'Accept: application/json'\\ -H 'Content-Type: application/json'\\ http://0.0.0.0:6543/api/card/550e8400 -X DELETE {"Goodbye": "550e8400"} """ key = request.validated['UID'] card = DBSession.query(Card).filter_by(uid=key).first() if not card: raise _404() try: CRUD(DBSession, Card, pk={'id': card.id}).delete() except DataError as e: raise _400(msg=str(e.message)) return {'Goodbye': key}
Удаление происходит при вызове HTTP метода DELETE. sacrud опять же несколько упрощает эту операцию http://sacrud.readthedocs.org/en/latest/plain_usage.html#delete-action
В файле validators.py хранятся всякие исключения и сами проверки. Если в декораторе card_api указан валидатор, то валидные данные нужно будет выбирать не через request.matchdict а через request.validated
Пример файла validators.py
import json import string from webob import exc, Response class _404(exc.HTTPError): def __init__(self, msg='Not Found'): body = {'status': 404, 'message': msg} Response.__init__(self, json.dumps(body)) self.status = 404 self.content_type = 'application/json' class _400(exc.HTTPError): def __init__(self, msg='Bad Request'): body = {'status': 400, 'message': msg} Response.__init__(self, json.dumps(body)) self.status = 400 self.content_type = 'application/json' def valid_hex(request): key = request.matchdict['UID'] if not all(c in string.hexdigits for c in key): raise _400(msg="Not valid UID '%s'" % key) request.validated['UID'] = str(key)
Таким образом можно довольно просто создать API для вашего проекта на пирамиде. Cornice делает много за вас, создает вьюхи, пути, валидацию, может генерить автоматически Sphinx документацию, а SACRUD упрощает работу с БД.
19 августа 2014
Chameleon, deform и маленькая хитрость
Deform - это такая штука которая генерит формы, а шаблоны для виджетов в нем написаны в формате Chameleon шаблонизатора.
Простой пример формы(http://deformdemo.xo7.de/nonrequiredfields/):
К полям можно добавлять описание, но если вставить html то он заэскапируется(https://github.com/Pylons/deform/blob/bb4fc86913884deafa9350de86d87fb5232263fa/deform/templates/form.pt#L42). Можно воспользоваться хитрой возможностью Chamelon'а что бы вывести html.
Простой пример формы(http://deformdemo.xo7.de/nonrequiredfields/):
class Schema(colander.Schema): required = colander.SchemaNode( colander.String(), description='Required Field' ) notrequired = colander.SchemaNode( colander.String(), missing=unicode(''), description='Unrequired Field') schema = Schema() form = deform.Form(schema, buttons=('submit',)) return self.render_form(form)
К полям можно добавлять описание, но если вставить html то он заэскапируется(https://github.com/Pylons/deform/blob/bb4fc86913884deafa9350de86d87fb5232263fa/deform/templates/form.pt#L42). Можно воспользоваться хитрой возможностью Chamelon'а что бы вывести html.
class HTMLText(object): def __init__(self, text): self.text = text def __html__(self): return unicode(self.text) notrequired = colander.SchemaNode( colander.String(), missing=unicode(''), description=HTMLText('Hello <hr color="red" /> World!!! <hr />'))Chameleon по умолчанию ищет метод __html__ у объектов и если он есть то выводит его результат в чистом виде. Вот.
26 июня 2014
sqlalchemy_mptt v0.0.5
В новом релизе:
- устранены некоторые баги
- написана документация http://sqlalchemy-mptt.readthedocs.org/
Подписаться на:
Сообщения (Atom)