<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8645505283412677599</id><updated>2026-05-07T10:00:36.766+03:00</updated><category term="tips and tricks"/><category term="django"/><category term="reusable apps"/><category term="flask"/><category term="tests"/><category term="custom model fields"/><category term="django trunk"/><category term="libraries"/><category term="widgets"/><category term="django orm"/><category term="fast python"/><category term="newforms"/><category term="pip"/><category term="python"/><category term="virtualenv"/><category term="новости"/><category term="argparse"/><category term="celery"/><category term="decimal"/><category term="django forms"/><category term="django template"/><category term="django weblog"/><category term="documentation"/><category term="formatting"/><category term="gunicorn"/><category term="jquery"/><category term="kikola"/><category term="nosetests"/><category term="tddspry"/><category term="администрирование django"/><category term="о блоге"/><category term="aiohttp.web"/><category term="autocomplete"/><category term="bootstrap"/><category term="configparser"/><category term="coverage"/><category term="development environment"/><category term="django 1.0"/><category term="django 1.2"/><category term="django forum"/><category term="django todo"/><category term="dropbox"/><category term="facebook"/><category term="file uploads"/><category term="flickr"/><category term="flickrapi"/><category term="forms"/><category term="github"/><category term="gsoc"/><category term="i18n"/><category term="javascript"/><category term="learn python"/><category term="lettuce"/><category term="mediafiles"/><category term="my projects"/><category term="newforms-admin"/><category term="optimization"/><category term="pbs"/><category term="profiling"/><category term="pypi"/><category term="python string methods"/><category term="queryset-refactor"/><category term="rabbitmq"/><category term="redis"/><category term="rororo"/><category term="search"/><category term="security fix"/><category term="sessions"/><category term="signals"/><category term="six"/><category term="sphinx"/><category term="sql"/><category term="sublime text"/><category term="task manager"/><category term="testing"/><category term="timedelta"/><category term="unicode"/><category term="validators"/><category term="wtforms"/><category term="xmpp"/><category term="архитектура проектов"/><title type='text'>О Python с Игорем Давыденко</title><subtitle type='html'>Всякое разное о Python, раньше было про Django/Flask, сейчас больше про Fast Python и asyncio.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://djangonaut.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>75</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-5392296642326319096</id><published>2020-01-08T20:22:00.000+02:00</published><updated>2020-01-08T20:23:24.592+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="aiohttp.web"/><category scheme="http://www.blogger.com/atom/ns#" term="rororo"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Базовая настройка aiohttp.web приложений</title><content type='html'>&lt;p&gt;Словил себя на мысли, что несмотря на то, что большинство моих веб-приложений работают на &lt;a href=&quot;https://aiohttp.rtfd.io&quot;&gt;aiohttp.web&lt;/a&gt;, их настройка происходит в лучших &lt;a href=&quot;https://www.djangoproject.com/&quot;&gt;Django&lt;/a&gt; традициях,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Определение настроек в &lt;code&gt;settings.py&lt;/code&gt; модуле, как &lt;code&gt;DEBUG = to_bool(os.getenv(&quot;DEBUG&quot;) or False)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Импорт целого модуля и сохранение его в &lt;code&gt;aiohttp.web&lt;/code&gt; приложении, как &lt;code&gt;types.MappingTypeProxy&lt;/code&gt; структура&lt;/li&gt;
&lt;li&gt;Использование сохраненного маппинга во вью-функциях, как, например, &lt;code&gt;request.config_dict[&quot;settings&quot;][&quot;DEBUG&quot;]&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;И если этот подход работал like a magic 5 лет назад, не говорит о том, что он идеально вписывается в Python в 2020 году. Главные проблемы с тем, как это было сделано ранее, это:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Инстинктивно хочется сделать &lt;code&gt;from app import settings&lt;/code&gt; и потом просто &lt;code&gt;settings.DEBUG&lt;/code&gt;, вместо того чтоб идти за настройками в &lt;code&gt;request.config_dict&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Работать с настройками, которые хранятся как &lt;code&gt;Mapping[str, Any]&lt;/code&gt; &amp;mdash; это выстрел себе в обе ноги. Никакой &lt;code&gt;mypy&lt;/code&gt; не подскажет тебе, есть ли у тебя в настройках ключ &lt;code&gt;VERY_IMPORTANT_KEY&lt;/code&gt; и не ошибься ли ты с тем, что считаешь, что &lt;code&gt;PRICE_MULTIPLICATOR&lt;/code&gt; это &lt;code&gt;decimal.Decimal&lt;/code&gt;, а не &lt;code&gt;float&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Что же делать? Как обычно есть два пути, взять что-то готовое или написать свое. Второй путь, куда более заманчивый, поэтому план такой,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Нужно хранить настройке в какой-то структуре данных, а что может быть лучше &lt;a href=&quot;https://www.attrs.org/&quot;&gt;&lt;code&gt;@attr.dataclass&lt;/code&gt;&lt;/a&gt; для этого?&lt;/li&gt;
&lt;li&gt;Нужно упростить получение настроек из окружения для &lt;code&gt;attr.ib&lt;/code&gt; (аттрибутов) структуры данных&lt;/li&gt;
&lt;li&gt;Ну и куда же без &lt;code&gt;mypy&lt;/code&gt;, так что нужно убедиться, что структура данных с настройками будет поддерживать любые типы данных и работать без магических &lt;code&gt;# type: ignore&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;План рабочий, поэтому на его реализацию много времени не понадобилось. Начал с &lt;code&gt;env_factory&lt;/code&gt;:&lt;/p&gt;

&lt;pre class=&quot;py&quot;&gt;&lt;code&gt;import os
from typing import Optional, overload, TypeVar

import attr
from rororo.settings import to_bool


T = TypeVar(&quot;T&quot;)


@overload
def env_factory(name: str) -&gt; Optional[str]:
    ...


@overload
def env_factory(name: str, default: T) -&gt; T:
    ...


def env_factory(name: str, default: T = None) -&gt; Union[Optional[str], T]:
    def getenv() -&gt; Union[Optional[str], T]:
        value = os.getenv(name)
        if default is None:
            return value

        if value is None:
            return default

        expected_type = type(default)
        if isinstance(value, expected_type):
            return value

        try:
            if expected_type is bool:
                return to_bool(value)  # type: ignore
            return expected_type(value)  # type: ignore
        except (TypeError, ValueError):
            raise ValueError(
                f&quot;Unable to convert {name} env var to {expected_type}.&quot;
            )

    return attr.Factory(getenv)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Продолжил имплементацией хранения базовых настроек,&lt;/p&gt;

&lt;pre class=&quot;py&quot;&gt;&lt;code&gt;import attr

from rororo.settings import setup_locale, setup_logging, setup_timezone


@attr.dataclass(frozen=True, slots=True)
class BaseSettings:
    # Base aiohttp settings
    host: str = env_factory(&quot;AIOHTTP_HOST&quot;, &quot;localhost&quot;)
    port: int = env_factory(&quot;AIOHTTP_PORT&quot;, 8080)

    # Base application settings
    debug: bool = env_factory(&quot;DEBUG&quot;, False)
    level: Level = env_factory(&quot;LEVEL&quot;, &quot;dev&quot;)

    # Date &amp; time settings
    time_zone: str = env_factory(&quot;TIME_ZONE&quot;, &quot;UTC&quot;)

    # Locale settings
    first_weekday: int = env_factory(&quot;FIRST_WEEKDAY&quot;, 0)
    locale: str = env_factory(&quot;LOCALE&quot;, &quot;en_US.UTF-8&quot;)

    # Sentry settings
    sentry_dsn: Optional[str] = env_factory(&quot;SENTRY_DSN&quot;)
    sentry_release: Optional[str] = env_factory(&quot;SENTRY_RELEASE&quot;)

    def apply(
        self,
        *,
        loggers: Iterable[str] = None,
        remove_root_handlers: bool = False,
    ) -&gt; None:
        if loggers:
            setup_logging(
                default_logging_dict(*loggers),
                remove_root_handlers=remove_root_handlers,
            )

        setup_locale(self.locale, self.first_weekday)
        setup_timezone(self.time_zone)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;И в конце концов осталось только начать использовать это все в &lt;code&gt;create_app&lt;/code&gt; фабриках и во вью-функциях.&lt;/p&gt;

&lt;pre class=&quot;py mt-3&quot;&gt;&lt;code&gt;def create_app(argv: List[str] = None, **options: Any) -&gt; web.Application:
     settings = Settings()
     settings.apply()

     app = web.Application(...)
     app[&quot;settings&quot;] = settings

     return app
&lt;/code&gt;&lt;/pre&gt;

&lt;pre class=&quot;py&quot;&gt;&lt;code&gt;async def index(request: web.Request) -&gt; web.Response:
     settings: Settings = request.config_dict[&quot;settings&quot;]  # For JEDI autocomplete
     if settings.debug:
         print(&quot;Hello, world!&quot;)
     return web.json_response(True)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;В итоге настройки становится проще инициализировать, переопределять, дебажить. А &lt;code&gt;mypy&lt;/code&gt; говорит отдельное спасибо и точно оберегает от любых погрешностей, работая с базовым функционалом приложения.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;ps.&lt;/b&gt; Но в заключение хочется отметить, что жаль, что &lt;a href=&quot;https://aiohttp.readthedocs.io/en/stable/web_reference.html#aiohttp.web.Application&quot;&gt;web.Application&lt;/a&gt; не ожидает от пользователя никаких настроек и поэтому каждому разработчику приходится ломать голову в поисках того или иного решения для такой тривиальной задачи.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;pss.&lt;/b&gt; Если вы заметили, то &lt;code&gt;env_factory&lt;/code&gt; содержит два &lt;code&gt;# type: ignore&lt;/code&gt; комментария. Хотел бы объяснить зачем они там. Первый на линии с &lt;code&gt;to_bool&lt;/code&gt; вызовом из-за того, что &lt;code&gt;mypy&lt;/code&gt; не понимает, что &lt;code&gt;T is bool&lt;/code&gt; и поэтому выдает ошибку &lt;code&gt;Incompatible return value type&lt;/code&gt;. Ну а во втором случае &lt;code&gt;mypy&lt;/code&gt; ругается на &lt;code&gt;Too many arguments for &quot;object&quot;&lt;/code&gt;. Отакої&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/5392296642326319096'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/5392296642326319096'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2020/01/aiohttpweb.html' title='Базовая настройка aiohttp.web приложений'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-7216197359496844065</id><published>2019-03-10T23:05:00.000+02:00</published><updated>2019-03-14T13:33:35.336+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><category scheme="http://www.blogger.com/atom/ns#" term="unicode"/><title type='text'>Сортировка списка с unicode strings</title><content type='html'>&lt;p&gt;Думал, что после стольких лет Python уже не удивит меня, однако пословица &lt;i&gt;Век живи, век учись&lt;/i&gt; стала для меня как никогда актуальной вчера. Задача была весьма простая: отсортировать список с строками, где строка - это украинское имя, то есть вроде бы все должно быть предельно просто используя Python 3.7.2:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;data = [&#39;Андрій&#39;, &#39;Ігор&#39;, &#39;Євген&#39;, &#39;Віталій&#39;]
assert sorted(data) == [&#39;Андрій&#39;, &#39;Віталій&#39;, &#39;Євген&#39;, &#39;Ігор&#39;]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Но нет, not so fast!&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;$ python
Python 3.7.2 (default, Jan  4 2019, 12:23:06) 
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&gt;&gt;&gt; data = [&#39;Андрій&#39;, &#39;Ігор&#39;, &#39;Євген&#39;, &#39;Віталій&#39;]
&gt;&gt;&gt; sorted(data)
[&#39;Євген&#39;, &#39;Ігор&#39;, &#39;Андрій&#39;, &#39;Віталій&#39;]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Интересно. Значит что-то не так с локалью. Нужно установить правильную локаль и попробовать еще раз.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;$ python
Python 3.7.2 (default, Jan  4 2019, 12:23:06) 
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&gt;&gt;&gt; import locale
&gt;&gt;&gt; locale.setlocale(locale.LC_ALL, &#39;uk_UA.UTF-8&#39;)
&#39;uk_UA.UTF-8&#39;
&gt;&gt;&gt; locale.setlocale(locale.LC_COLLATE, &#39;uk_UA.UTF-8&#39;)
&#39;uk_UA.UTF-8&#39;
&gt;&gt;&gt; data = [&#39;Андрій&#39;, &#39;Ігор&#39;, &#39;Євген&#39;, &#39;Віталій&#39;]
&gt;&gt;&gt; sorted(data)
[&#39;Євген&#39;, &#39;Ігор&#39;, &#39;Андрій&#39;, &#39;Віталій&#39;]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Хм. Странно. Значит нужно пойти почитать доку &lt;a href=&quot;https://docs.python.org/library/locale.html&quot;&gt;locale&lt;/a&gt; и найти там функцию, которая будет делать locale compare. Точно же есть такая.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;$ python
Python 3.7.2 (default, Jan  4 2019, 12:23:06) 
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&gt;&gt;&gt; import locale
&gt;&gt;&gt; locale.setlocale(locale.LC_ALL, &#39;uk_UA.UTF-8&#39;)
&#39;uk_UA.UTF-8&#39;
&gt;&gt;&gt; locale.setlocale(locale.LC_COLLATE, &#39;uk_UA.UTF-8&#39;)
&#39;uk_UA.UTF-8&#39;
&gt;&gt;&gt; data = [&#39;Андрій&#39;, &#39;Ігор&#39;, &#39;Євген&#39;, &#39;Віталій&#39;]
&gt;&gt;&gt; sorted(data, key=locale.strxfrm)
[&#39;Ігор&#39;, &#39;Євген&#39;, &#39;Андрій&#39;, &#39;Віталій&#39;]
&gt;&gt;&gt; import functools
&gt;&gt;&gt; sorted(data, key=functools.cmp_to_key(locale.strcoll))
[&#39;Ігор&#39;, &#39;Євген&#39;, &#39;Андрій&#39;, &#39;Віталій&#39;]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Та ладно. Не верю. Все должно работать. Аргх.&lt;/p&gt;

&lt;p&gt;После того, как эмоции уляглись, вспоминаю про &lt;a href=&quot;http://site.icu-project.org&quot;&gt;ICU&lt;/a&gt; и думаю, ну ок, точно есть биндинги ICU для Python и там должна быть функция, которую можно будет скормить в &lt;code&gt;key&lt;/code&gt; для правильной сортировки значений в списке.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    Устанавливаем ICU в систему,
    &lt;ul&gt;
      &lt;li&gt;
        Для macOS:&lt;br /&gt;
        &lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;$ brew install icu4c
$ export PATH=&quot;/usr/local/opt/icu4c/bin:$PATH&quot;&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
      &lt;li&gt;
        Для Ubuntu Linux:&lt;br /&gt;
        &lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;# apt install libicu-dev icu-devtools&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    Устанавливаем &lt;a href=&quot;https://github.com/ovalhub/pyicu&quot;&gt;PyICU&lt;/a&gt;,&lt;br /&gt;
    &lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;$ poetry add PyICU&lt;/code&gt;&lt;/pre&gt;
    или &lt;i&gt;по старинке&lt;/i&gt;:&lt;br /&gt;
    &lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;$ pip install PyICU&lt;/code&gt;&lt;/pre&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;$ python
Python 3.7.2 (default, Jan  4 2019, 12:23:06) 
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&gt;&gt;&gt; import icu
&gt;&gt;&gt; collator = icu.Collator.createInstance(icu.Locale(&#39;uk_UA.UTF-8&#39;))
&gt;&gt;&gt; data = [&#39;Андрій&#39;, &#39;Ігор&#39;, &#39;Євген&#39;, &#39;Віталій&#39;]
&gt;&gt;&gt; sorted(data, key=collator.getSortKey)
[&#39;Андрій&#39;, &#39;Віталій&#39;, &#39;Євген&#39;, &#39;Ігор&#39;]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Фух! Работает! Так что да, чистая правда: век живи, век учись!&lt;/p&gt;

&lt;p&gt;&lt;b&gt;ps.&lt;/b&gt; В комментариях &lt;code&gt;@xnull&lt;/code&gt; поделился еще одним способом сортировки при помощи &lt;a href=&quot;https://github.com/jtauber/pyuca&quot;&gt;pyuca&lt;/a&gt; библиотеки, которая не требует установки в систему никаких дополнительных зависимостей.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;$ poetry add pyuca  # pip install pyuca&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;$ python
Python 3.7.2 (default, Jan  2 2019, 13:30:18) 
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&gt;&gt;&gt; import pyuca
&gt;&gt;&gt; coll = pyuca.Collator()
&gt;&gt;&gt; data = [&#39;Євген&#39;, &#39;Ігор&#39;, &#39;Андрій&#39;, &#39;Віталій&#39;]
&gt;&gt;&gt; sorted(data, key=coll.sort_key)
[&#39;Андрій&#39;, &#39;Віталій&#39;, &#39;Євген&#39;, &#39;Ігор&#39;]&lt;/code&gt;&lt;/pre&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/7216197359496844065'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/7216197359496844065'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2019/03/how-to-sort-list-with-unicode-strings.html' title='Сортировка списка с unicode strings'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-1424049517400563078</id><published>2018-07-16T15:58:00.000+03:00</published><updated>2019-03-14T14:49:25.219+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="development environment"/><category scheme="http://www.blogger.com/atom/ns#" term="sublime text"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Настройка Sublime Text 3 для Python разработки</title><content type='html'>&lt;p&gt;Выбор редактора (среды для разработки), наверное, главное событие в жизни каждого разработчика. Я тоже поначалу не мог долго определиться какой редактор использовать, но потом, лет 8-9 назад установил &lt;a href=&quot;http://www.sublimetext.com&quot;&gt;Sublime Text&lt;/a&gt; и все - вопрос с редактором был закрыт раз и навсегда. Да, за эти года я иногда пробовал научиться работать в vim, но не смог, ну а на новомодные Atom, PyCharm или VS Code я вообще не смотрел. Поэтому я хотел бы рассказать про то, как я использую Sublime Text 3 и почему мне не нужен IDE в 2018 году.&lt;/p&gt;

&lt;p&gt;Главная причина, почему мне не нужен IDE - это, конечно, база расширений для Саблайма, которая находится на &lt;a href=&quot;https://packagecontrol.io&quot;&gt;packagecontrol.io&lt;/a&gt; и покрывает любой из языков, на которых я программирую. Так как блог о Python, то я расскажу только о расширениях для Python, но у меня также есть установленные плагины для JavaScript, flow, TypeScript, Elm и Rust.&lt;/p&gt;

&lt;p&gt;В Python же все начинается с установки &lt;a href=&quot;https://github.com/MagicStack/MagicPython&quot;&gt;MagicPython&lt;/a&gt; от Юрия Селиванова, расширения, которое добавляет поддержку подсветки всех всех новомодных фич Python, и хоть авторы Sublime Text пытаются улучшать поддержку базового Python от релиза к релизу, нам это не нужно после установки и настройки MagicPython.&lt;/p&gt;

&lt;p&gt;Дальше. Было бы неплохо иметь автодополнение для Python, да? Поэтому следующим делом, устанавливаем &lt;a href=&quot;https://github.com/srusskih/SublimeJEDI&quot;&gt;SublimeJEDI&lt;/a&gt; от Сережи Русских. Благодаря движку JEDI, который используется в IPython, к примеру, на выходе мы получаем автодополнение нашей мечты. Но для этого в настройках проекта нам нужно сделать две вещи,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Задать путь к &lt;code&gt;python_interpreter&lt;/code&gt;, в общем случае это &lt;code&gt;&quot;~/Projects/&amp;lt;project&amp;gt;/.venv/bin/python&quot;&lt;/code&gt;, чтоб JEDI использовал все зависимости, установленные в проекте&lt;/li&gt;
&lt;li&gt;Настроить &lt;code&gt;python_package_paths&lt;/code&gt;. Опять же в общем случае это &lt;code&gt;[&quot;~/Projects/&amp;lt;project&amp;gt;&quot;]&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Все, теперь мы получили автодополнение к любому &lt;code&gt;import&lt;/code&gt; стейтменту и много других фич, опять же благодаря движку JEDI.&lt;/p&gt;

&lt;p&gt;Идем дальше. Линтинг. Тут нам нужны &lt;a href=&quot;https://github.com/SublimeLinter/SublimeLinter-flake8&quot;&gt;SublimeLinter-flake8&lt;/a&gt; и &lt;a href=&quot;https://github.com/fredcallaway/SublimeLinter-contrib-mypy&quot;&gt;SublimeLinter-contrib-mypy&lt;/a&gt; (а куда без type annotations в 2018 то году?). По дефолту, они будут пытаться использовать стандартный Python, установленный в системе, но так как в моем случае набор flake8 плагинов от проекта к проекту отличается, я задаю &lt;code&gt;executable&lt;/code&gt; для этих линтеров в файле настройки проекта (&lt;code&gt;&amp;lt;project&amp;gt;.sublime-project&lt;/code&gt;) как,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&quot;SublimeLinter.linters.flake8.executable&quot;: &quot;~/Projects/&amp;lt;project&amp;gt;/.venv/bin/flake8&quot;&lt;/code&gt; для настройки SublimeLinter-flake8&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;SublimeLinter.linters.mypy.executable&quot;: &quot;~/Projects/&amp;lt;project&amp;gt;/.venv/bin/mypy&quot;&lt;/code&gt; для настройки SublimeLinter-contrib-mypy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Отдельно вы возможно захотите настроить сам SublimeLinter, но так или иначе про ошибки линтера мы узнаем куда быстрее и прямо в редакторе.&lt;/p&gt;

&lt;p&gt;По большому счету с главными расширениями, которые превращают Sublime Text 3 в удобную среду разработки на Python мы закончили, но мне также хотелось бы упомянуть еще некоторые плагины, которые могут точно пригодиться,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jgirardet/sublack&quot;&gt;sublack&lt;/a&gt; - автоформатирование Python файлов при помощи &lt;a href=&quot;https://github.com/ambv/black&quot;&gt;black&lt;/a&gt;. Нужная вещь, когда вы работаете в команде и не хотите на код ревью тратить время о спорах насчет форматирования Python кода.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sindresorhus/editorconfig-sublime&quot;&gt;editorconfig-sublimetext&lt;/a&gt; - также в 2018 году не зачем спорить о табах vs пробелах или отступах. Единократно кладем &lt;code&gt;.editorconfig&lt;/code&gt; в корень проекта, а дальше Sublime Text 3 автоматически применяет необходимые настройки форматирования для любых файлов.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spadgos/sublime-jsdocs&quot;&gt;DocBlockr&lt;/a&gt; - упрощает работу с комментариями в коде. Конечно, этот плагин больше применим для JavaScript кода, но и для Python иногда бывает полезен.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jisaacks/GitGutter&quot;&gt;GitGutter&lt;/a&gt; - просмотр того, как изменился блок кода, прямо в Sublime Text. Очень нужная вещь, для разработчиков, которые не любят бессмысленный закомментированный старый код.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/randy3k/Terminus&quot;&gt;Terminus&lt;/a&gt; - и хоть я постоянно держу открытым &lt;code&gt;Terminal.app&lt;/code&gt; иногда бывает удобно открыть терминал прямо в соседней вкладке Sublime Text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ну и напоследок чуть о внешнем виде. Все это время я использую схему подсветки кода &lt;a href=&quot;https://github.com/theymaybecoders/sublime-tomorrow-theme&quot;&gt;Tomorrow-Night&lt;/a&gt;, так что очень был рад, когда настроил &lt;a href=&quot;https://github.com/ihodev/sublime-boxy&quot;&gt;Boxy Theme&lt;/a&gt; для внешнего вида Sublime Text, используя это же цветовое решение. В итоге, мой Sublime Text выглядит следующим образом,&lt;/p&gt;

&lt;div class=&quot;text-center&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglLeahrbeZ15wefjtsqP9cc1kK-PB3iHtyDOUISlUrx2g8XCbWK2_5WbzAPLJmw7-RWnyHygLKBHAYLUQEtDqGnOthJ38_AHU5n98F8oGq6uS93fKQgNUeeWIgDp23SYfba58Bybb55mA/s1600/Screen+Shot+2018-07-16+at+14.34.43.png&quot;&gt;&lt;img border=&quot;0&quot; class=&quot;img-fluid&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglLeahrbeZ15wefjtsqP9cc1kK-PB3iHtyDOUISlUrx2g8XCbWK2_5WbzAPLJmw7-RWnyHygLKBHAYLUQEtDqGnOthJ38_AHU5n98F8oGq6uS93fKQgNUeeWIgDp23SYfba58Bybb55mA/s640/Screen+Shot+2018-07-16+at+14.34.43.png&quot; width=&quot;640&quot; height=&quot;406&quot; data-original-width=&quot;1600&quot; data-original-height=&quot;1016&quot; /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;А вот шаблон настроек, который я использую для своих проектов,&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;json&quot;&gt;{
    &quot;folders&quot;:
    [
        {
            &quot;file_exclude_patterns&quot;: [
                &quot;.install*&quot;,
                &quot;*.lock&quot;,
                &quot;*-lock.json&quot;
            ],
            &quot;folder_exclude_patterns&quot;: [
                &quot;.*cache&quot;,
                &quot;node_modules&quot;
            ],
            &quot;path&quot;: &quot;/Users/playpauseandstop/Projects/&amp;lt;project&amp;gt;&quot;
        }
    ],
    &quot;settings&quot;: {
        &quot;python_interpreter&quot;: &quot;/Users/playpauseandstop/Projects/&amp;lt;project&amp;gt;/.venv/bin/python&quot;,
        &quot;python_package_paths&quot;: [
            &quot;/Users/playpauseandstop/Projects/&amp;lt;project&amp;gt;&quot;
        ],
        &quot;SublimeLinter.linters.flake8.executable&quot;: &quot;/Users/playpauseandstop/Projects/&amp;lt;project&amp;gt;/.venv/bin/flake8&quot;,
        &quot;SublimeLinter.linters.mypy.executable&quot;: &quot;/Users/playpauseandstop/Projects/&amp;lt;project&amp;gt;/.venv/bin/mypy&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Вот и получается, что Sublime Text 3 с таким набором расширений - это прекрасный редактор для программирования на Python.&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/1424049517400563078'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/1424049517400563078'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2018/07/setup-sublime-text-3-for-python-development.html' title='Настройка Sublime Text 3 для Python разработки'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglLeahrbeZ15wefjtsqP9cc1kK-PB3iHtyDOUISlUrx2g8XCbWK2_5WbzAPLJmw7-RWnyHygLKBHAYLUQEtDqGnOthJ38_AHU5n98F8oGq6uS93fKQgNUeeWIgDp23SYfba58Bybb55mA/s72-c/Screen+Shot+2018-07-16+at+14.34.43.png" height="72" width="72"/></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-2271079024427641801</id><published>2018-07-01T15:19:00.000+03:00</published><updated>2019-03-14T14:59:55.086+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="fast python"/><category scheme="http://www.blogger.com/atom/ns#" term="profiling"/><title type='text'>Fast Python. В поисках медленного кода при помощи стандартного профайлера</title><content type='html'>&lt;p&gt;Думаю все часто сталкивались с ситуацией, когда бизнес приходит и говорит: спасайте, страница Х нашего прекрасного веб-приложения работает медленно. Или, создание репортов стало занимать в два раза больше времени после последнего деплоя. И хорошо, когда ты наверняка знаешь в чем причина и спокойно переходишь к оптимизации медленного кода, но что делать, если ты не уверен, где проблема? Вот про это мы сегодня и поговрим: использование &lt;a href=&quot;https://docs.python.org/library/profile.html&quot;&gt;стандартного профайлера&lt;/a&gt; для поиска медленного кода.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;Сразу оговорюсь, что есть намного более универсальные библиотеки для профайлинга Python приложений, как например, &lt;a href=&quot;https://github.com/nvdv/vprof&quot;&gt;vprof&lt;/a&gt; или &lt;a href=&quot;https://github.com/uber/pyflame&quot;&gt;pyflame&lt;/a&gt;, но я хотел бы рассказать именно про &lt;code&gt;cProfile&lt;/code&gt;.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;Итак, что надо сделать, чтоб отпрофилировать наш код? На самом деле просто запомнить, как запускать профайлер:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ python -m cProfile -o script.prof script.py&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;i&gt;Ну а в Python 3.7 все стало еще приятней, потому что появилась возможность задавать &lt;code&gt;-m&lt;/code&gt; для профайлера как:&lt;/i&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;i&gt;$ python -m cProfile -o project.prof -m project&lt;/i&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;После того как скрипт выполнится, мы получим &lt;code&gt;script.prof&lt;/code&gt; файл, который теперь можно эксплорить при помощи &lt;a href=&quot;http://kcachegrind.sourceforge.net/html/Home.html&quot;&gt;KCacheGrind&lt;/a&gt;. Для этого нужно перевести его в формат, который понимает KCacheGrind, что делает тулза под названием: &lt;a href=&quot;https://pypi.org/project/pyprof2calltree/&quot;&gt;pyprof2calltree&lt;/a&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ pyprof2calltree -i script.prof -k&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;переконвертирует &lt;code&gt;script.prof&lt;/code&gt; в дерево вызовов и откроет приятный глазу интерфейс KCacheGrind.&lt;/p&gt;

&lt;p&gt;Однако не все так просто, описанный выше метод хорош для профайлинга скриптов, но что делать, если нужно таки найти почему наше веб-приложение работает медленно. В этом случае, я пользуюсь следующей техникой:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;В начале &lt;code&gt;project/__main__.py&lt;/code&gt; создаю профайлер и запускаю его, если в окружении есть переменная &lt;code&gt;USE_PROFILER&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Оборачиваю &lt;code&gt;web.run_app(...)&lt;/code&gt; в &lt;code&gt;try/finally&lt;/code&gt; и в &lt;code&gt;finally&lt;/code&gt; печатаю содержимое профайлера в файл&lt;/li&gt;
&lt;li&gt;Потом запускаю приложение как &lt;code&gt;USE_PROFILER=1 python -m project&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;И наконец делаю нагрузку на необходимый URL при помощи &lt;a href=&quot;https://github.com/wg/wrk&quot;&gt;wrk&lt;/a&gt;, например как, &lt;code&gt;wrk -t 4 -c 100 -d 30s --latency http://&amp;lt;URL&amp;gt;/api/slow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Все теперь можно выключать приложение и запускать KCacheGrind&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Если теперь перевести это все на код, то получится вот такой апдейт для &lt;b&gt;project/__main__.py&lt;/b&gt;,&lt;/p&gt;
&lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;import cProfile
import os

from app import create_app


USE_PROFILER = os.environ.get(&#39;USE_PROFILER&#39;) == &#39;1&#39;
profiler = cProfiler.Profile()


if __name__ == &#39;__main__&#39;:
    if USE_PROFILER:
        profiler.enable()

    try:
        sys.exit(web.run_app(create_app()))
    finally:
        if USE_PROFILER:
            filename = &#39;project.prof&#39;
            print(f&#39;Dumping profiler to {filename}&#39;)
            profiler.dump_stats(filename)
            profiler.disable()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;В итоге, в KCacheGrind, находим интересующую вьюху (на моем скриншоте это &lt;code&gt;retrieve_tweets&lt;/code&gt;) и ищем почему же она работает медленно.&lt;/p&gt;

&lt;div class=&quot;text-center&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDWJOmUSg-Z4AEF0MzGsYEchVU4S0c0vtANWA_dtiObB3uylUd0O6jdshcBtxFjG1IxGiem7BtWbij4AB5zPd9IO0A_soGs3tWpTC4mrZMc_H8AfOeuSVp0YEUO747wO1-wvymHT-XgNo/s1600/Screen+Shot+2018-07-01+at+2.02.49+PM.png&quot;&gt;&lt;img border=&quot;0&quot; class=&quot;img-fluid&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDWJOmUSg-Z4AEF0MzGsYEchVU4S0c0vtANWA_dtiObB3uylUd0O6jdshcBtxFjG1IxGiem7BtWbij4AB5zPd9IO0A_soGs3tWpTC4mrZMc_H8AfOeuSVp0YEUO747wO1-wvymHT-XgNo/s640/Screen+Shot+2018-07-01+at+2.02.49+PM.png&quot; width=&quot;640&quot; height=&quot;385&quot; data-original-width=&quot;1354&quot; data-original-height=&quot;814&quot; /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Таким образом легко понять какие вызовы во вью-функциях работают медленно, какой код вызывается слишком часто и какие части кода можно будет отрефакторить или отоптимизировать. Ну и всегда помните, чем больше нагрузки дали на веб-приложение, чем больше собрали профайлинг данных, тем вы получили более точный отчет, на основе которого можно начинать оптимизацию вашего кода.&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/2271079024427641801'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/2271079024427641801'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2018/07/fast-python-cprofile.html' title='Fast Python. В поисках медленного кода при помощи стандартного профайлера'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDWJOmUSg-Z4AEF0MzGsYEchVU4S0c0vtANWA_dtiObB3uylUd0O6jdshcBtxFjG1IxGiem7BtWbij4AB5zPd9IO0A_soGs3tWpTC4mrZMc_H8AfOeuSVp0YEUO747wO1-wvymHT-XgNo/s72-c/Screen+Shot+2018-07-01+at+2.02.49+PM.png" height="72" width="72"/></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-4091571284433887292</id><published>2017-06-28T17:48:00.001+03:00</published><updated>2019-03-14T13:34:45.780+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="fast python"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Fast Python. Парсинг ISO дат</title><content type='html'>&lt;p&gt;Преобразование ISO-даты из строки в объект &lt;a href=&quot;https://docs.python.org/3/library/datetime.html#datetime-objects&quot;&gt;datetime.datetime&lt;/a&gt; (или datetime.date), наверное, одна из самых распространенных и постоянных задач в web-разработке на Python. Количество способов сделать это просто поражает воображение,&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;In [1]: value = &#39;2017-06-28T16:59:27+0000&#39;

In [2]: import datetime

In [3]: datetime.datetime.strptime(value, &#39;%Y-%m-%dT%H:%M:%S%z&#39;)
Out[3]: datetime.datetime(2017, 6, 28, 16, 59, 27, tzinfo=datetime.timezone.utc)

In [4]: from dateutil.parser import parse

In [5]: parse(value)
Out[5]: datetime.datetime(2017, 6, 28, 16, 59, 27, tzinfo=tzutc())

In [6]: import dateparser

In [7]: dateparser.parse(value)
Out[7]: datetime.datetime(2017, 6, 28, 16, 59, 27, tzinfo=&lt;StaticTzInfo &#39;UTC\+00:00&#39;&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;i&gt;Использовались функции: &lt;a href=&quot;https://docs.python.org/3/library/datetime.html#datetime.datetime.strptime&quot;&gt;datetime.datetime.strptime&lt;/a&gt;, &lt;a href=&quot;https://dateutil.readthedocs.io/en/stable/parser.html#dateutil.parser.parse&quot;&gt;dateutil.parser.parse&lt;/a&gt; и &lt;a href=&quot;https://dateparser.readthedocs.io/en/latest/#dateparser.parse&quot;&gt;dateparser.parse&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;А ведь еще есть,&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;In [8]: import arrow

In [9]: arrow.get(value, &#39;YYYY-MM-DDTHH:mm:ssZZ&#39;)
Out[9]: &amp;lt;Arrow [2017-06-28T16:59:27+00:00]&amp;gt;

In [10]: import maya

In [11]: maya.parse(value)
Out[11]: &amp;lt;MayaDT epoch=1498669167.0&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;i&gt;-//-: &lt;a href=&quot;https://arrow.readthedocs.io/en/latest/#arrow.factory.ArrowFactory.get&quot;&gt;arrow.get&lt;/a&gt; и &lt;a href=&quot;https://github.com/kennethreitz/maya#-basic-usage-of-maya&quot;&gt;maya.parse&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;И это только вершина айсберга, потому что библиотек для работ с датами в Python не просто много, а очень много (что говорить, если у Django вообще есть своя &lt;a href=&quot;https://docs.djangoproject.com/en/1.11/ref/utils/#django.utils.dateparse.parse_datetime&quot;&gt;parse_datetime&lt;/a&gt; функция, основанная на регекспе). У той или иной библиотеки работы с датами есть свои плюсы и особенности, но что делать, если нужно быстро конвертировать строки с датами в стандартные объекты &lt;code&gt;datetime.datetime&lt;/code&gt;? Как всегда ответ прост: использовать что-то написанное на C с биндингами в Python, в нашем случае - это &lt;a href=&quot;https://github.com/closeio/ciso8601&quot;&gt;ciso8601&lt;/a&gt;,&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;In [12]: ciso8601.parse_datetime(value)
Out[12]: datetime.datetime(2017, 6, 28, 16, 59, 27, tzinfo=&lt;UTC&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Результаты &lt;code&gt;%timeit&lt;/code&gt; - красноречивы,&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;In [13]: %timeit datetime.datetime.strptime(value, &#39;%Y-%m-%dT%H:%M:%S%z&#39;)
17.9 µs ± 516 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [14]: %timeit parse(value)
99 µs ± 1.1 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [15]: %timeit dateparser.parse(value)
1.61 ms ± 39.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [16]: %timeit ciso8601.parse_datetime(value)
2.08 µs ± 67.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;Итого:&lt;/b&gt; Если нужно быстро парсить ISO даты: используйте &lt;code&gt;ciso8601.parse_datetime&lt;/code&gt;, если нужно быстро парсить не ISO даты: сначала попробуйте стандартный &lt;code&gt;datetime.datetime.strptime&lt;/code&gt;, потом &lt;code&gt;dateutil.parser.parse&lt;/code&gt; и только в случае особой и &lt;b&gt;острой&lt;/b&gt; необходимости &lt;code&gt;dateparser.parse&lt;/code&gt; и прочие библиотеки, основанные на нем.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;ps.&lt;/b&gt; Результаты приведены для Python 3.6.1, на Python 2.7.13 соблюдаются такие же показатели, но в 2.7.13 в стандартном &lt;code&gt;strptime&lt;/code&gt; не было возможности разобрать &lt;code&gt;+0000&lt;/code&gt;, потому что &lt;code&gt;ValueError: &#39;z&#39; is a bad directive in format &#39;%Y-%m-%dT%H:%M:%S%z&#39;&lt;/code&gt;.&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/4091571284433887292'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/4091571284433887292'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2017/06/fast-python-parse-iso-dates.html' title='Fast Python. Парсинг ISO дат'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-27928027141411524</id><published>2015-03-11T09:00:00.000+02:00</published><updated>2019-03-14T13:36:17.061+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="fast python"/><category scheme="http://www.blogger.com/atom/ns#" term="optimization"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Fast Python. Выпуск 1. Обновление словарей</title><content type='html'>&lt;p&gt;Привет! Запускаю раздел &lt;a href=&quot;http://djangonaut.blogspot.com/search/label/fast%20python&quot;&gt;&lt;strong&gt;Fast Python&lt;/strong&gt;&lt;/a&gt;, в котором буду делиться простыми рецептами про то, как ускорить и оптимизировать выполнение кода на Python.&lt;/p&gt;

&lt;p&gt;Первый выпуск будет посвящен обновлению данных в словарях. &lt;a href=&quot;https://docs.python.org/3/library/stdtypes.html#dict&quot;&gt;Словари&lt;/a&gt; - одни из найболее часто используемых типов данных в Python. И сколько времени я с ними не работаю, у меня никогда не возникал вопрос как правильней обновить данные в словаре. Я для себя всегда отвечал на него, что правильней обновлять используя метод &lt;a href=&quot;https://docs.python.org/3/library/stdtypes.html#dict.update&quot;&gt;&lt;code&gt;update&lt;/code&gt;&lt;/a&gt;, но сегодня с утра наткнулся на &lt;a href=&quot;https://twitter.com/bkmontgomery/status/575497098029760512&quot;&gt;твит от Брэда Монтгомери&lt;/a&gt; и мой мир изменился.&lt;/p&gt;

&lt;p&gt;Оказывается, что &lt;strong&gt;более правильным с точки зрения скорости&lt;/strong&gt; является обновление словаря через поочередное присваивание значений, чем единичный апдейт. Не верите? Результаты замеров весьма красноречивы:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python 3.4.3&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;In [2]: timeit(&quot;d = {}; d.update({&#39;first&#39;: &#39;first&#39;, &#39;second&#39;: [&#39;one&#39;, &#39;two&#39;, &#39;three&#39;], &#39;third&#39;: True})&quot;, number=1000000)
Out[2]: 0.9664371280086925

In [3]: timeit(&quot;d = {}; d[&#39;first&#39;] = &#39;first&#39;; d[&#39;second&#39;] = [&#39;one&#39;, &#39;two&#39;, &#39;three&#39;]; d[&#39;third&#39;] = True&quot;, number=1000000)                                      
Out[3]: 0.4200690069992561
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Python 2.7.9&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;In [2]: timeit(&quot;d = {}; d.update({&#39;first&#39;: &#39;first&#39;, &#39;second&#39;: [&#39;one&#39;, &#39;two&#39;, &#39;three&#39;], &#39;third&#39;: True})&quot;, number=1000000)
Out[2]: 1.0035829544067383

In [3]: timeit(&quot;d = {}; d[&#39;first&#39;] = &#39;first&#39;; d[&#39;second&#39;] = [&#39;one&#39;, &#39;two&#39;, &#39;three&#39;]; d[&#39;third&#39;] = True&quot;, number=1000000)
Out[3]: 0.5186400413513184
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Bonus: PyPy 2.4.0&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; timeit(&quot;d = {}; d.update({&#39;first&#39;: &#39;first&#39;, &#39;second&#39;: [&#39;one&#39;, &#39;two&#39;, &#39;three&#39;], &#39;third&#39;: True})&quot;, number=1000000)                                               
0.10811400413513184
&amp;gt;&amp;gt;&amp;gt;&amp;gt; timeit(&quot;d = {}; d[&#39;first&#39;] = &#39;first&#39;; d[&#39;second&#39;] = [&#39;one&#39;, &#39;two&#39;, &#39;three&#39;]; d[&#39;third&#39;] = True&quot;, number=1000000)                                         
0.005925893783569336
&lt;/code&gt;&lt;/pre&gt;

&lt;hr&gt;

&lt;p&gt;&lt;strong&gt;Итого:&lt;/strong&gt; Если вы где-то в коде увидите обновление одиночного или множественных элементов словаря через &lt;code&gt;.update&lt;/code&gt; - смело переписывайте этот фрагмент на использование &lt;code&gt;__setitem__&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong style=&quot;color: green;&quot;&gt;Fast Python:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;py&quot;&gt;data[&#39;key&#39;] = &#39;value&#39;
data[&#39;another-key&#39;] = &#39;another-value&#39;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong style=&quot;color: darkred;&quot;&gt;Slow Python:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;py&quot;&gt;data.update({
    &#39;key&#39;: &#39;value&#39;,
    &#39;another-key&#39;: &#39;another-value&#39;,
})&lt;/code&gt;&lt;/pre&gt;

&lt;hr&gt;

&lt;p&gt;&lt;strong&gt;UPD:&lt;/strong&gt; Михаил Кривушин (&lt;strong&gt;deepwalker&lt;/strong&gt;) в комментариях объяснил почему так происходит:&lt;/p&gt;
&lt;blockquote&gt;По дороге создается еще один словарь, и потом уже из него копируются элементы&lt;br&gt;
&lt;a href=&quot;https://gist.github.com/Deepwalker/c52a74c26df0707dd303&quot;&gt;https://gist.github.com/Deepwalker/c52a74c26df0707dd303&lt;/a&gt;&lt;/blockquote&gt;

&lt;hr&gt;

&lt;p&gt;&lt;strong&gt;UPD2:&lt;/strong&gt; Если ключи словаря не используют специальных символов и являются валидными Python ключами, то лучше использовать синтаксис &lt;code&gt;.update(key=&#39;value&#39;)&lt;/code&gt;, вместо &lt;code&gt;.update({&#39;key&#39;: &#39;value&#39;})&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python 3.4.3&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;In [2]: timeit(&quot;d = {}; d.update(first=&#39;first&#39;, second=[&#39;one&#39;, &#39;two&#39;, &#39;three&#39;], third=True)&quot;, number=1000000)
Out[2]: 0.88018084000214
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Python 2.7.9&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;In [2]: timeit(&quot;d = {}; d.update(first=&#39;first&#39;, second=[&#39;one&#39;, &#39;two&#39;, &#39;three&#39;], third=True)&quot;, number=1000000)
Out[2]: 0.7820448875427246
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;PyPy 2.4.0&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;nohighlight&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; timeit(&quot;d = {}; d.update(first=&#39;first&#39;, second=[&#39;one&#39;, &#39;two&#39;, &#39;three&#39;], third=True)&quot;, number=1000000)
0.010169029235839844
&lt;/code&gt;&lt;/pre&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/27928027141411524'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/27928027141411524'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2015/03/fast-python-1-dict-update.html' title='Fast Python. Выпуск 1. Обновление словарей'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-4269733178139240128</id><published>2015-02-16T11:37:00.000+02:00</published><updated>2015-02-16T11:37:29.349+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="pip"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><category scheme="http://www.blogger.com/atom/ns#" term="virtualenv"/><title type='text'>Upgrade your pip &amp; virtualenv now</title><content type='html'>&lt;p&gt;Странно, что несмотря на очень давний выход &lt;a href=&quot;http://mail.python.org/pipermail/distutils-sig/2014-December/025450.html&quot;&gt;pip 6.0 и virtualenv 12.0&lt;/a&gt;, многие Python разработчики все еще сидят на более ранних версиях этих незаменимых утилит.&lt;/p&gt;

&lt;p&gt;Мой вам совет - обновляйте свой pip &amp; virtualenv сейчас же!&lt;/p&gt;

&lt;p&gt;Главная причина - это, конечно, встроенный в pip, толковый и включенный по умолчанию &lt;a href=&quot;https://pip.pypa.io/en/latest/reference/pip_install.html#caching&quot;&gt;менеджер скачанных зависимостей&lt;/a&gt;. Да, и раньше можно было пользоваться опцией &lt;code&gt;--download-cache&lt;/code&gt; или конфигом:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[install]
download_cache = /path/to/pip-cache
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;в &lt;code&gt;~/.pip/pip.conf&lt;/code&gt;, но старый менеджер загрузок был скорее дополнительным, чем полностью готовым к использованию механизмом. Более детальный ход разработки менеджера загрузок хорошо продемонстрирован &lt;a href=&quot;https://github.com/pypa/pip/issues/1732&quot;&gt;на GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Из остального в новом pip ведется проверка версий относительно &lt;a href=&quot;https://www.python.org/dev/peps/pep-0440/&quot;&gt;PEP 440&lt;/a&gt;, а значит, что, к сожалению, лучше попрощаться с версиями: &lt;code&gt;X.Y-dev&lt;/code&gt;, которые хоть и не попадают на PyPI, но очень часто используются в разработке.&lt;/p&gt;
</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/4269733178139240128'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/4269733178139240128'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2015/02/upgrade-your-pip-virtualenv-now.html' title='Upgrade your pip &amp; virtualenv now'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-3040343992069382204</id><published>2014-08-25T15:12:00.000+03:00</published><updated>2014-08-25T15:12:00.343+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="celery"/><category scheme="http://www.blogger.com/atom/ns#" term="rabbitmq"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Celery воркер зависает на &quot;mingle: searching for neighbors&quot;</title><content type='html'>&lt;p&gt;Сегодня ВНЕЗАПНО все &lt;a href=&quot;http://www.celeryproject.org/&quot;&gt;Celery&lt;/a&gt; воркеры перестали принимать любые задачи, при том что &lt;a href=&quot;http://www.rabbitmq.com/&quot;&gt;RabbitMQ&lt;/a&gt; (используется в качестве брокера) очереди были пустыми, а остальные части системы функционировали нормально. Не помогала ни перезагрузка Celery воркеров, ни перезагрузка RabbitMQ сервера.&lt;/p&gt;

&lt;p&gt;После недолгого копания по логам проблема была локализирована, любой Celery воркер как будто зависал на моменте поиска соседей, оставляя в логах что-то такое:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;2014-08-25 14:29:12 [INFO:celery.worker.consumer] Connected to amqp://guest:**@127.0.0.1:5672//
2014-08-25 14:29:12 [INFO:celery.worker.consumer] mingle: searching for neighbors
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Быстрый гуглинг указал на &lt;a href=&quot;https://github.com/celery/celery/issues/1664&quot; title=&quot;mingle: searching for neighbors&quot;&gt;существующую Celery issue&lt;/a&gt;, а уже в ней и нашелся ответ на проблему. Оказывается на корневом разделе подошло к концу место (было доступно порядка 150 Мб) и Celery в связке с RabbitMQ зависала не показывая никаких признаков жизни. Все починилось банальной чисткой корневого раздела, но осадок остался и на себя, что не поставил уведомления о заканчивающемся месте на корневом разделе и на Celery, что она никаким образом не пытается обработать эту ситуацию и проблему приходится вычислять окольными путями.&lt;/p&gt;
</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/3040343992069382204'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/3040343992069382204'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2014/08/celery-mingle-searching-for-neighbors.html' title='Celery воркер зависает на &quot;mingle: searching for neighbors&quot;'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-7320800546469820945</id><published>2014-05-04T09:58:00.000+03:00</published><updated>2019-03-14T13:38:26.610+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="decimal"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Range для не целых чисел</title><content type='html'>&lt;p&gt;Почему-то никогда не думал, что &lt;a href=&quot;http://docs.python.org/2/library/functions.html#range&quot;&gt;range&lt;/a&gt; (и &lt;a href=&quot;http://docs.python.org/2/library/functions.html#xrange&quot;&gt;xrange&lt;/a&gt;) понимают только целые числа (в Python 3 ситуация такая же) и функцией нельзя воспользоваться, как например:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;py&quot;&gt;range(0., 5., .5)
range(Decimal(&#39;0&#39;), Decimal(&#39;10&#39;), Decimal(&#39;1.5&#39;))&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Поэтому пришлось сделать свою замену:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;py&quot;&gt;import operator


def arange(start, stop=None, step=None):
    &quot;&quot;&quot;
    Implement range function not only for integers as Python&#39;s builtin
    function, but for Decimals and floats as well.

    Returns generator with arithmetic progession, not list.
    &quot;&quot;&quot;
    klass = type(start)
    lt_func = operator.lt

    stop = start if stop is None else stop
    start = klass(0) if start == stop else start
    step = klass(1 if step is None else step)

    assert isinstance(stop, klass), (
        &#39;Start and stop limits have different types, {0!r} != {1!r}.&#39;.
        format(type(start).__name__, type(stop).__name__)
    )
    assert step, &quot;Step shouldn&#39;t be a zero: {0!r}.&quot;.format(step)

    if start &lt; stop and step &lt; 0 or start &gt; stop and step &gt; 0:
        raise StopIteration
    elif start &gt; stop and step &lt; 0:
        lt_func = operator.gt

    while lt_func(start, stop):
        yield start
        start += step
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;В отличии от &lt;code&gt;range&lt;/code&gt; в Python 2 &lt;code&gt;arange&lt;/code&gt; отдает генератор, а не список, что вообщем-то удобней и правильней, ну и функция всегда вернет элементы такого же типа, как были переданы в &lt;code&gt;start&lt;/code&gt; и &lt;code&gt;stop&lt;/code&gt; аргументы, даже если шаг - это целое число или он не указан вовсе.&lt;/p&gt;

&lt;p&gt;Как всегда код оформлен как &lt;a href=&quot;https://gist.github.com/playpauseandstop/8832275&quot;&gt;Gist на GitHub&#39;е&lt;/a&gt;, там еще доктесты и юниттесты.&lt;/p&gt;
</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/7320800546469820945'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/7320800546469820945'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2014/05/range.html' title='Range для не целых чисел'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-4029794148939844930</id><published>2013-04-25T17:18:00.001+03:00</published><updated>2013-04-25T17:18:31.945+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="новости"/><category scheme="http://www.blogger.com/atom/ns#" term="о блоге"/><title type='text'>Миграция с Posterous на Blogger</title><content type='html'>&lt;p&gt;Как вы, наверное, знаете Twitter сначала купил Posterous, а потом его закрыл. И так как на Posterous&#39;е был мой блог, я перенес все посты из него сюда. Сорри, если RSS фид обновился слишком рьяно.&lt;/p&gt;

&lt;p&gt;Если у кого есть схожая проблема и он еще не перевез никуда свой Posterous, вот есть &lt;a href=&quot;https://gist.github.com/playpauseandstop/5460016&quot;&gt;маленький скриптик&lt;/a&gt;, который делает всю работу.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; Так как этот блог стал моим единственным, то я сменил его название на более честное. Впредь тут будут появлятся информация и по Flask&#39;у, и по Django, но в большинстве случаев думаю просто по Python&#39;у.&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/4029794148939844930'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/4029794148939844930'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2013/04/posterous-blogger.html' title='Миграция с Posterous на Blogger'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-2752653177900669368</id><published>2012-12-20T22:49:00.000+02:00</published><updated>2013-04-25T16:51:41.897+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="argparse"/><category scheme="http://www.blogger.com/atom/ns#" term="gunicorn"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Запускаем gunicorn из Python&amp;#39;а</title><content type='html'>&lt;p&gt;Иногда бывает надо запустить &lt;a href=&quot;http://gunicorn.org/&quot;&gt;gunicorn&lt;/a&gt; внутри Python скрипта, например, в &lt;code&gt;manage.py&lt;/code&gt;. Конечно всегда можно воспользоваться &lt;a href=&quot;http://docs.python.org/2/library/subprocess.html#subprocess.call&quot;&gt;&lt;code&gt;subprocess.call&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;import subprocess


app = &#39;package.module:app&#39;
host, port = &#39;0.0.0.0&#39;, 8000
subprocess.call(&#39;gunicorn -b {}:{:d} -w 4 {}&#39;.format(host, port, app))&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Но как-то это не комильфо подумал я и решил найти более труЪ-способ :)&lt;/p&gt;
&lt;p&gt;Решение пришло не сразу, но пришло, надо всего лишь переопределить &lt;a href=&quot;http://docs.python.org/2/library/sys.html#sys.argv&quot;&gt;&lt;code&gt;sys.argv&lt;/code&gt;&lt;/a&gt; и вызвать метод &lt;code&gt;run&lt;/code&gt;,&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;import sys

from gunicorn.app.wsgiapp import run


app = &#39;package.module:app&#39;
host, port = &#39;0.0.0.0&#39;, 8000
sys.argv = [
    sys.argv[0],
    &#39;-b&#39;, &#39;{}:{:d}&#39;.format(host, port),
    &#39;-w&#39;, &#39;4&#39;,
    app
]
run()&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Не очень круто вышло, не находите? А все потому что gunicorn еще использует устаревший &lt;a href=&quot;http://docs.python.org/2/library/optparse.html&quot;&gt;optparse&lt;/a&gt; для парсинга аргументов с коммандной строки, а там задавать список аргументов в &lt;a href=&quot;http://docs.python.org/2/library/optparse.html#tutorial&quot;&gt;&lt;code&gt;parse_args&lt;/code&gt;&lt;/a&gt; совсем не обязательно, ведь по дефолту берется список &lt;code&gt;sys.argv[1:]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Так что на таком простом примере лично мне ясно видно, насколько optparse устарел и как вашему приложению, которое его еще использует надо сломя голову переезжать на &lt;a href=&quot;http://docs.python.org/2/library/argparse.html&quot;&gt;argparse&lt;/a&gt;! И это я еще не рассказал вам о бесподобном управлении под-коммандами в argparse :)&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/2752653177900669368'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/2752653177900669368'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/12/gunicorn-python.html' title='Запускаем gunicorn из Python&amp;#39;а'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-5877298952101686617</id><published>2012-11-29T13:37:00.000+02:00</published><updated>2013-04-25T16:52:38.318+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="gunicorn"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Чиним gunicorn&amp;#39;овский Internal Server Error</title><content type='html'>&lt;p&gt;Если вдруг, после запуска &lt;a href=&quot;http://gunicorn.org/&quot;&gt;gunicorn&lt;/a&gt; перестал работать и показывает ошибку похожую на:&lt;/p&gt;

  &lt;pre class=&quot;bash&quot; name=&quot;code&quot;&gt;&lt;code&gt;Internal Server Error

Traceback:

Traceback (most recent call last):
  File &amp;quot;/Users/playpauseandstop/Projects/project/env/lib/python2.7/site-packages/gunicorn/workers/async.py&amp;quot;, line 45, in handle
    self.handle_request(req, client, addr)
  File &amp;quot;/Users/playpauseandstop/Projects/project/env/lib/python2.7/site-packages/gunicorn/workers/async.py&amp;quot;, line 73, in handle_request
    resp, environ = wsgi.create(req, sock, addr, self.address, self.cfg)
  File &amp;quot;/Users/playpauseandstop/Projects/project/env/lib/python2.7/site-packages/gunicorn/http/wsgi.py&amp;quot;, line 161, in create
    path_info = path_info.split(script_name, 1)[1]
IndexError: list index out of range&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;то это говорит о том, что как&lt;span style=&quot;text-decoration: line-through;&quot;&gt;ая-то сволочь&lt;/span&gt;ое-то приложение установило переменную окружения &lt;code&gt;SCRIPT_NAME&lt;/code&gt;, которую &lt;code&gt;gunicorn&lt;/code&gt; не может нормально обработать. Чтобы пофиксить, просто удаляем переменную окружения,&lt;/p&gt;

  &lt;pre class=&quot;bash&quot; name=&quot;code&quot;&gt;&lt;code&gt;$ unset SCRIPT_NAME&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;и перегружаем страницу (ну можно еще перегрузить &lt;code&gt;gunicorn&lt;/code&gt; для пущей важности). That&#39;s all!&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/5877298952101686617'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/5877298952101686617'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/11/gunicorn-internal-server-error.html' title='Чиним gunicorn&amp;#39;овский Internal Server Error'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-6897911206124241275</id><published>2012-11-08T12:22:00.002+02:00</published><updated>2012-11-08T12:24:22.860+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="django"/><category scheme="http://www.blogger.com/atom/ns#" term="django orm"/><category scheme="http://www.blogger.com/atom/ns#" term="validators"/><title type='text'>Валидация Django моделей, пять советов</title><content type='html'>&lt;p&gt;Не думаю, что для кого-то открою Америку, сказав что в Django есть валидация не только для форм, а и для моделей :) Однако судя по моей практике это чуть спорное, но весьма полезное решение не спешат повсеместно использовать и городят свои велосипеды для проверки значений при создании/обновлении моделей. Поэтому хочу поделиться несколькими простыми советами по этой теме.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Во-первых&lt;/strong&gt;, храните валидаторы отдельно от моделей. Хранение всех валидаторов в &lt;code&gt;models.py&lt;/code&gt; неимоверно раздувает и без того не маленький модуль моделей (а иногда это и пакет), хранение же валидаторов в &lt;code&gt;validators.py&lt;/code&gt; решает эту проблему и логично выносит все операции по проверке значений/уникальности модели в подходящее для этого место. Ну и сюда же, не пишите сложные проверки в &lt;code&gt;Model.clean&lt;/code&gt;, &lt;code&gt;Model.validate_unique&lt;/code&gt; методах, просто вызывайте необходимые функции валидации из &lt;code&gt;validators.py&lt;/code&gt;, например:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;models.py&lt;/strong&gt;&lt;/p&gt;
&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;&lt;code&gt;from django.db import models

from .validators import validate_complex_case, validate_unique


class Card(models.Model):
    ...
    def clean(self):
        validate_complex_case(instance)

    def validate_unique(self, exclude=None):
        validate_unique(self)
        return super(Card, self).validate_unique(exclude)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;validators.py&lt;/strong&gt;&lt;/p&gt;
&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;from django.core.exceptions import ValidationError


def validate_complex_case(instance):
    if instance.name == &#39;XXX&#39; and instance.path != &#39;/path/to/XXX&#39;:
        raise ValidationError(&#39;Please provide proper path for the card.&#39;)


def validate_unique(instance):
    manager = instance._default_manager
    other = manager.filter(name=instance.name)

    if other.count():
        message = &#39;Card with this Name already exists.&#39;
        raise ValidationError({&#39;__all__&#39;: [message]})
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Во-вторых&lt;/strong&gt;, непонятно зачем, но ошибки в проверке на уникальность модели нужно обворачивать в &lt;code&gt;message_dict&lt;/code&gt;, использование простого сообщения приводит к &lt;code&gt;AttributeError&lt;/code&gt;. Хотя с другой стороны это повышает возможности по написанию ошибок в разных полях моделей. Можно, например, подсветить какие именно поля не уникальны и уже сохранены в БД.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;В-третьих&lt;/strong&gt;, не забывайте, что валидаторов может быть сколько угодно много. Старайтесь разбивать большие валидаторы на маленькие атомарные функции и выносить их в &lt;code&gt;core.validators&lt;/code&gt; или &lt;code&gt;project.validators&lt;/code&gt;, чтоб иметь возможность использовать их не только для текущего приложения, а и для любых других приложений в проекте. Аттрибут &lt;code&gt;validators&lt;/code&gt; у поля модели принимает список валидаторов, так что Вы вполне можете писать,&lt;/p&gt;

&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;from django.core.validators import MaxLengthValidator, MinLengthValidator
from django.db import models

from .validators import validate_markup


class Card(models.Model):
    ...
    comment = models.TextField(validators=[
        MinLengthValidator(10), MaxLengthValidator(1000), validate_markup
    ])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;В-четвертых&lt;/strong&gt;, валидация модели автоматически не вызывается, когда вы хотите сохранить модель не из админ-панели или &lt;code&gt;ModelForm&lt;/code&gt;&#39;ы. Т.е., просто добавление валидаторов в поля модели и задание методов &lt;code&gt;clean&lt;/code&gt; или &lt;code&gt;validate_unique&lt;/code&gt; не гарантирует, что неправильные данные не сохранятся в базе данных после &lt;code&gt;Model.objects.create&lt;/code&gt; или &lt;code&gt;instance.save()&lt;/code&gt;. Для того, чтобы обезопасить себя и включить валидацию и для моделей, используйте сигналы:&lt;/p&gt;

&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;&lt;code&gt;from django.db import models
from django.db.models import signals
from django.dispatch import receiver


class Card(models.Model)
    ...


@receiver(signals.pre_save, sender=Card)
def validate_card(instance):
    instance.full_clean()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ну и &lt;strong&gt;в-пятых&lt;/strong&gt;,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Не забывайте про &lt;a href=&quot;https://docs.djangoproject.com/en/dev/ref/validators/#built-in-validators&quot;&gt;стандартные валидаторы Django&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Импортируйте исключение ошибки валидации как &lt;code&gt;from django.core.exceptions import ValidationError&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Для моделей нельзя писать методы &lt;code&gt;clean_FIELD&lt;/code&gt; как для форм&lt;/li&gt;
&lt;li&gt;Валидация модели при вызове &lt;code&gt;instance.full_clean()&lt;/code&gt; идет в следующей последовательности: валидация полей (&lt;code&gt;instance.clean_fields()&lt;/code&gt;), вызов &lt;code&gt;instance.clean()&lt;/code&gt;, проверка на уникальность (&lt;code&gt;instance.validate_unique()&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; Код и принципы данного материала актуальны для Django 1.3 ветки (да я так и не пересел на 1.4 или 1.5 потому что не вижу в этом особого смысла), может в новых версиях что-то поменялось, уточняйте в &lt;a href=&quot;https://docs.djangoproject.com/en/dev/ref/validators/&quot;&gt;документации&lt;/a&gt; :)&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/6897911206124241275'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/6897911206124241275'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/11/django.html' title='Валидация Django моделей, пять советов'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-1818028229055342068</id><published>2012-10-30T22:25:00.000+02:00</published><updated>2012-10-30T23:10:20.843+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="learn python"/><title type='text'>Learn Python</title><content type='html'>&lt;p&gt;Идея вести курсы по Python пришла ко мне довольно давно, как никак за 6 лет активной разработки разнообразных веб-сервисов и приложений на Django, а потом и на Flask, появилось много различных правильных решений, техник и советов, которыми хочется поделится с коллегами и начинающими питонистами. И так удачно совпало, что &lt;a href=&quot;http://asvetlov.blogspot.com&quot;&gt;Андрей Светлов&lt;/a&gt; со своей стороны заинтересовался моей идеей и также решил помочь мне в ее реализации.&lt;/p&gt;

&lt;p&gt;Так что встречайте, курсы по углубленному изучению Python в Киеве. Курсы будут проходить в 2 потока по 12 занятий, один из которых, &lt;a href=&quot;http://learnpython.in.ua/flows#web&quot;&gt;web&lt;/a&gt;, буду вести я, а второй, &lt;a href=&quot;http://learnpython.in.ua/flows#advanced&quot;&gt;advanced&lt;/a&gt; - Андрей. Каждое занятие будет длиться 2 часа, по часу на теоретический материал и на разбор домашнего задания. Главной целью web потока будет создание рабочего веб-приложения и обучение безболезненой работы в команде, используя фреймворки Django или Flask. В свою очередь в advanced потоке будут убиты два зайца, с одной стороны курсы будут направлены на помощь в переходе с Python 2.x на Python 3.x, а с другой стороны на детальное изучение как работает веб на примере создания собственного простого веб-сервера.&lt;/p&gt;

&lt;p&gt;Курсы направлены на программистов уже знакомых с Python&#39;ом, имеющих определенный опыт его применения и которые хотят более детально залезть под &quot;капот&quot; и разобраться как же все устроено. Не говорю, что курсы полностью не подходят для новичков, главное, конечно, стремление, но поверьте, не подготовленным программистам может прийтись очень тяжело. Также еще раз упомяну, что каждую неделю будут задаваться домашние задание, выполнение которых будет необходимым. Так что курсы направлены и на усидчивых программистов тоже :)&lt;/p&gt;

&lt;p&gt;Окончательная стоимость курсов определится после набора групп, ориентировочно 150 грн за занятие. Оптимальный размер группы, 10-15 человек, не хочется терять диалог с посетителями курсов с одной стороны, а с другой стороны превращать все в балаган.&lt;/p&gt;

&lt;p&gt;Больше информации доступно на сайте, &lt;a href=&quot;http://learnpython.in.ua&quot;&gt;Learn Python&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; Также сразу отвечу по поводу удаленных курсов, такая идея есть, но сходу ее реализовывать не охота, так как пилотный выпуск и так будет непредсказуем, а удаленные курсы только усложнят коммуникацию.&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/1818028229055342068'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/1818028229055342068'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/10/learn-python.html' title='Learn Python'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-4702857923301754379</id><published>2012-10-27T16:52:00.000+03:00</published><updated>2013-04-25T16:51:39.591+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="flask"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Изменяем заголовки по умолчанию в имейлах от Flask-Security</title><content type='html'>&lt;p&gt;&lt;a href=&quot;http://packages.python.org/Flask-Security/&quot;&gt;Flask-Security&lt;/a&gt; - незаменимое расширение, если вам надо быстро и безболезнено реализовать поддержку регистрации, логина, восстановления пароля для вашего Flask приложения.&lt;/p&gt;
&lt;p&gt;Однако с ним есть маленькая беда. Дело в том, что все заголовки писем, отправляемых при его помощи захардкожены как строки в коде, а для того или иного приложения есть смысл в замене темы сообщения &lt;code&gt;&#39;Welcome&#39;&lt;/code&gt; на &lt;code&gt;&#39;Подтвердите ваш аккаунт&#39;&lt;/code&gt; и тд.&lt;/p&gt;
&lt;p&gt;Благо кастомизация отпарвки сообщений таки была предусмотрена автором, только не думаю, что именно это он имел ввиду, когда добавлял для стейта расширения аттрибут &lt;code&gt;_send_mail_task&lt;/code&gt;. Так или иначе, если нам нужно взять и поменять какую-то тему сообщения на новую мы можем сделать это так,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;appname/app.py&lt;/strong&gt;&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;from flask import Flask
from flask.ext.security import Security

from appname.utils import send_mail


app = Flask(&#39;appname&#39;)
...
security = Security(app, datastore)
app.extensions[&#39;security&#39;]._send_mail_task = send_mail&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;appname/utils.py&lt;/strong&gt;&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;from flask import current_app
from flask.ext.babel import gettext as _


def send_mail(message):
    if message.subject == &#39;Welcome&#39;:
        message.subject = _(&#39;Confirm your account&#39;)

    mail = current_app.extensions[&#39;mail&#39;]
    mail.send(message)&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Совсем не красивое, но рабочее решение. Также я разместил &lt;a href=&quot;https://github.com/mattupstate/flask-security/issues/43&quot;&gt;новый тикет на трекере проекта&lt;/a&gt;, посмотрим, что скажет автор на мою попытку сделать кастомизацию заголовков более натуральным способом, через задание их в настройках приложения аналогично контекстным сообщениям, которые генерируются Flask-Security.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPD.&lt;/strong&gt; Не только меня волновала эта проблема и посему благодаря &lt;a href=&quot;https://github.com/mattupstate/flask-security/pull/47&quot;&gt;фиксам&lt;/a&gt; &lt;a href=&quot;https://github.com/doobeh&quot;&gt;Anthony Plunkett&lt;/a&gt; в версии &lt;a href=&quot;http://packages.python.org/Flask-Security/changelog.html#version-1-5-1&quot;&gt;1.5.1&lt;/a&gt; заголовки сообщений от &lt;code&gt;Flask-Security&lt;/code&gt; можно задавать в настройках проекта как:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;SECURITY_EMAIL_SUBJECT_REGISTER = &#39;Confirm your account&#39;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;a href=&quot;http://packages.python.org/Flask-Security/configuration.html#email&quot;&gt;Все доступные настройки&lt;/a&gt; описаны в документации.&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/4702857923301754379'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/4702857923301754379'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/10/flask-security.html' title='Изменяем заголовки по умолчанию в имейлах от Flask-Security'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-8362754744059867420</id><published>2012-09-06T18:10:00.000+03:00</published><updated>2013-04-25T16:59:03.053+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="flask"/><category scheme="http://www.blogger.com/atom/ns#" term="reusable apps"/><title type='text'>Flask-Script или улучшенный manage.py для Flask приложений</title><content type='html'>&lt;p&gt;Наверное, ни один из моих Django-проектов не обходился без пары-тройки кастомных команд, благо эта возможность была одним из столбов архитектуры Django-проекта. Да, написание кода для этих кастомных команд иногда заставляло схватываться за голову и вопрошать: &quot;Why so hard?&quot;, но сегодня я не про это.&lt;/p&gt;
&lt;p&gt;А про расширение &lt;a href=&quot;https://github.com/techniq/flask-script&quot;&gt;Flask-Script&lt;/a&gt;, которое добавляет схожий функционал уже в Flask-приложения и проекты. Думаю, про это расширения многие наслышаны, но до недавнего времени использовать его было очень затруднительно из-за того, что автор на какое-то время ушел от дел и форки приложения добавляющие и правящие одну или две неточности появлялись как грибы после дождя. Но, хвала высшим силам, в прошлом месяце Шон Линч решил покончить с беспределом и выпустил версию 0.4.0, которая просто не оставляет нам оправданий на вопрос, почему до сих пор Flask-Script не добавлен в список зависимостей нашего проекта.&lt;/p&gt;
&lt;p&gt;Использовать расширение очень просто и понятно, инициализируем инстанс менеджера, добавляем кастомные команды, запускаем менеджер, если был вызван Python-скрипт. Пример использования, &lt;code&gt;app.py&lt;/code&gt;:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;from flask import Flask
from flask.ext.script import Manager


app = Flask(__name__)
manager = Manager(app)

@manager.command
def hello():
    &amp;quot;&amp;quot;&amp;quot;
    Hello, world!
    &amp;quot;&amp;quot;&amp;quot;
    print(&#39;Hello, world!&#39;)


if __name__ == &#39;__main__&#39;:
    manager.run()&lt;/code&gt;&lt;/pre&gt;



  &lt;pre&gt;&lt;code&gt;(env)$ python app.py
Please provide a command
  hello      Hello, world!
  runserver  Runs the Flask development server i.e. app.run()
  shell      Runs a Python shell inside Flask application context.&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;И так как в дефолтной поставке сразу доступны две команды, &lt;code&gt;runserver&lt;/code&gt; и &lt;code&gt;shell&lt;/code&gt;, мы можем запускать девсервер даже не добавляя строчку &lt;code&gt;app.run()&lt;/code&gt; в наш &lt;code&gt;app.py&lt;/code&gt;, удобно.&lt;/p&gt;
&lt;p&gt;Но описанный подход подходит только для демонстрационных или маленьких приложений, в реальной же жизни мы переносим инициализацию и/или запуск менеджера в давно знакомый нам &lt;code&gt;manage.py&lt;/code&gt;:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;from flask.ext.script import Manager

from app import app


manager = Manager(app)


@manager.command
def hello():
    &amp;quot;&amp;quot;&amp;quot;
    Hello, world!
    &amp;quot;&amp;quot;&amp;quot;
    print(&#39;Hello, world!&#39;)


if __name__ == &#39;__main__&#39;:
    manager.run()&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;em&gt;&lt;strong&gt;Лирическое отступление.&lt;/strong&gt; Если честно, я еще точно не определился с тем как более православно объявлять инстанс менеджера и где размещать все кастомные команды. Внутри, хочется объявлять менеджер в &lt;code&gt;app.py&lt;/code&gt;, а затем все команды описывать в &lt;code&gt;commands.py&lt;/code&gt;, но пока весь код для кастомных команд хранится именно в &lt;code&gt;manage.py&lt;/code&gt;, а вот инстанс менеджера таки инициализируется в &lt;code&gt;app.py&lt;/code&gt;. Однако почему-то не могу я такой подход порекомендовать всем, так что остановлюсь пока на варианте автора расширения.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Для любителей классов предусмотрена возможность наследовать команды от &lt;code&gt;Command&lt;/code&gt; базового класса, но мне по душе ближе использовать функции, этого ой как не хватало в стандартной поставке Django.&lt;/p&gt;
&lt;p&gt;В остальном у проекта есть &lt;a href=&quot;http://packages.python.org/Flask-Script/&quot;&gt;отличная документация&lt;/a&gt;, так что пользуйтесь, пользуйтесь и еще раз пользуйтесь!&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/8362754744059867420'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/8362754744059867420'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/09/flask-script-managepy-flask.html' title='Flask-Script или улучшенный manage.py для Flask приложений'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-5510345014000021197</id><published>2012-07-24T19:00:00.000+03:00</published><updated>2013-04-25T16:51:37.431+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="argparse"/><category scheme="http://www.blogger.com/atom/ns#" term="formatting"/><category scheme="http://www.blogger.com/atom/ns#" term="libraries"/><category scheme="http://www.blogger.com/atom/ns#" term="six"/><title type='text'>argparse, format и six или мои маленькие радости</title><content type='html'>&lt;p&gt;Скажем прямо, &lt;a href=&quot;http://docs.python.org/library/optparse.html&quot;&gt;&lt;code&gt;optparse&lt;/code&gt;&lt;/a&gt; был хорошим решением для Python 2.3, когда в нем была только обертка над &lt;a href=&quot;http://docs.python.org/library/getopt.html&quot;&gt;&lt;code&gt;getopt&lt;/code&gt;&lt;/a&gt;. Но время шло и народ требовал нового решения, составлял PEP&#39;ы и наконец принимал &lt;a href=&quot;http://docs.python.org/library/argparse.html&quot;&gt;&lt;code&gt;argparse&lt;/code&gt;&lt;/a&gt; в состав Python 2.7+. Однако на &lt;code&gt;argparse&lt;/code&gt; я пересаживался долго. Вот прошло уже больше года с моего окончательного перехода на 2.7 ветку, а я все пытался парсить опции и аргументы, приходящие в Python-скрипт, при помощи &lt;code&gt;optparse&lt;/code&gt;. Последние пару недель меня убедили что очень зря! С &lt;code&gt;argparse&lt;/code&gt; я теперь могу разбирать не только опции, но и аргументы. Никакого больше,&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;parser = OptionParser(...)
...
options, args = parser.parse_args()&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;только,&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;parser = ArgumentParser(...)
...
args = parser.parse_args()&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;только так! В целом преимущества нового подхода над старым расписаны в &lt;a href=&quot;http://docs.python.org/library/argparse.html&quot;&gt;документации&lt;/a&gt; и в &lt;a href=&quot;http://argparse.googlecode.com/svn/trunk/doc/argparse-vs-optparse.html&quot;&gt;паре&lt;/a&gt; &lt;a href=&quot;http://www.python.org/dev/peps/pep-0389/&quot;&gt;других&lt;/a&gt; статей. Так что не буду повторятся, просто посоветую и вам использовать &lt;code&gt;argparse&lt;/code&gt; и забить на &lt;code&gt;optparse&lt;/code&gt; as soon as possible.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Примерно похожая на ситуацию с &lt;code&gt;optparse&lt;/code&gt;, у меня была ситуация с форматированием строк. Я продолжал &lt;span style=&quot;text-decoration: line-through;&quot;&gt;грызть кактус&lt;/span&gt; использовать синтаксис &lt;code&gt;&#39;Hello, %s!&#39; % &#39;world&#39;&lt;/code&gt; вместо модного и молодежного &lt;code&gt;&#39;Hello, {0}!&#39;.format(&#39;world&#39;)&lt;/code&gt;. И опять же пару последних недель расставили все по местам. После окончательного ознакомления со всеми возможностями &lt;a href=&quot;http://docs.python.org/library/string.html#format-string-syntax&quot;&gt;Format String Syntax&lt;/a&gt;, я просто понял, что назад дороги нет! Только &lt;code&gt;format&lt;/code&gt;, только так!&lt;/p&gt;
&lt;p&gt;И да, ведь встроенная функция &lt;a href=&quot;http://docs.python.org/library/functions.html#format&quot;&gt;&lt;code&gt;format&lt;/code&gt;&lt;/a&gt; может использоваться не только для форматирования строк, но и для любых других объектов, которые поддерживают метод &lt;code&gt;__format__&lt;/code&gt;. Например, сейчас нет необходимости пользоваться &lt;a href=&quot;http://docs.python.org/library/datetime.html#datetime.date.strftime&quot;&gt;&lt;code&gt;strftime&lt;/code&gt;&lt;/a&gt; для объектов из библиотеки &lt;a href=&quot;http://docs.python.org/library/datetime.html&quot;&gt;&lt;code&gt;datetime&lt;/code&gt;&lt;/a&gt;, просто пишем,&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;format(datetime.date.today(), &#39;%Y-%m-%d&#39;)&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;или даже,&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;&#39;{:%Y-%m-%d}&#39;.format(datetime.date.today())&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;и вуаля!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;А напоследок скажу, что также в последнее время часто задумываюсь о необходимости изучения Python 3 или хотя бы портирования какого-то своего кода на эту ветку. И как по магии, в своем фиде на GitHub нашел полезную библиотеку &lt;a href=&quot;http://packages.python.org/six/&quot;&gt;&lt;code&gt;six&lt;/code&gt;&lt;/a&gt;, которая весьма мне пригодится в этом деле. Вот такие вот дела!&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/5510345014000021197'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/5510345014000021197'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/07/argparse-format-six.html' title='argparse, format и six или мои маленькие радости'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-7086590620081500046</id><published>2012-07-18T18:03:00.000+03:00</published><updated>2013-04-25T16:51:36.344+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="pbs"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Перенаправляем потоки в pbs</title><content type='html'>&lt;p&gt;Мне нравится &lt;a href=&quot;https://github.com/amoffat/pbs/&quot;&gt;pbs&lt;/a&gt;. Эта библиотека с лихвой заменяет мне необходимость программировать всякие мелкие штуки на чистом &lt;code&gt;bash&lt;/code&gt;&#39;е. Однако до сегодня я не особо понимал как реализовать перенаправление потоков. И вот я наконец-то понял, о чем с вами и поделюсь :)&lt;/p&gt;
&lt;p&gt;Итак, предположим, что нам неодходимо эмулировать следующую конструкцию:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;$ git archive HEAD --format=zip --prefix=project/ &amp;gt; ../project.zip&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;В &lt;code&gt;pbs&lt;/code&gt; это будет выглядеть как-то так:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;handler = open(&#39;../project.zip&#39;, &#39;wb+&#39;)
pbs.git(&#39;archive&#39;, &#39;HEAD&#39;, format=&#39;zip&#39;, prefix=&#39;project&#39;, _out=handler)
handler.close()&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Также просто будет перенаправить &lt;code&gt;stderr&lt;/code&gt; в необходимый нам файл, например:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;$ stat does_not_exist.txt 2&amp;gt; error_log&lt;/code&gt;&lt;/pre&gt;



  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;handler = open(&#39;error_log&#39;, &#39;wb+&#39;)
try:
    pbs.stat(&#39;does_not_exist.txt&#39;, _err=handler)
except pbs.ErrorReturnCode:
    pass
handler.close()&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Вот такие пироги! Пользуйтесь &lt;code&gt;pbs&lt;/code&gt;, это по-настоящему удобная и простая библиотека!&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/7086590620081500046'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/7086590620081500046'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/07/pbs.html' title='Перенаправляем потоки в pbs'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-7667618699673386881</id><published>2012-07-13T14:29:00.000+03:00</published><updated>2012-07-13T14:30:02.580+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="pip"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><category scheme="http://www.blogger.com/atom/ns#" term="virtualenv"/><title type='text'>Копируем виртуальные окружения с помощью virtualenv-clone</title><content type='html'>&lt;p&gt;Думаю, что идея копирования (клонирования) виртуальных окружений далеко не нова, особенно для всяких деплоймент-сервисов, которые предоставляют доступ к базе как какого-то коммита, так и дефолтной стейджинг ветки. Однако до сегодняшнего дня я не особо понимал как ее верно реализовать.&lt;/p&gt;

&lt;p&gt;Для начала еще раз поясню саму идею. Есть репозиторий, есть деплоймент сервис, который для каждого коммита может генерировать код/базы данных/бутстрап проекта/что-угодно и выдавать на гора результат в виде готового для доступа URL-адреса. Для вытаскивания кода и расположения его в определенной директории могут использоваться разные подходы, как впрочем и для работы с базой данных. Но бутстрап проекта для уже готовой базы данных хочется сделать просто:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ virtualenv --distribute --system-site-packages env
$ . env/bin/activate
(env)$ pip install -r requirements.txt
(env)$ deactivate
$ cp project/settings_local.py{.deploy,}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;И вроде бы все работает, но чем больше становится зависимостей и чем больше коммитов приходит на деплой тем система начинает работать все медленней и медленней и постоянно задыхается на этапе &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;. Не хорошо!&lt;/p&gt;

&lt;p&gt;Потом появляется решение: надо просто сделать основное виртуальное окружение, а потом для каждого коммита копировать его и накатывать уже свежие изменения файла зависимостей и вуаля. Но вместе с решением приходит и вопрос, а как-то это реализовать? Чтоб оно работало то?&lt;/p&gt;

&lt;p&gt;Первая идея была очень интересной: а что если новое виртуальное окружение создавать в активированом основном виртуальном окружении? YO DAWG, не иначе. В итоге получилось так же если б мы просто создали новое виртуальное окружение, локальные зависимости основного окружения не были доступны в новом окружении.&lt;/p&gt;

&lt;p&gt;Второй идеей было использование &lt;code&gt;--relocatable&lt;/code&gt; опции для основного окружения. Результат: после копирования окружения и установки зависимостей, зависимости обновлялись и в основном окружении, а это нам совсем не нужно.&lt;/p&gt;

&lt;p&gt;Третьей идеей было таки спросить у гугла &quot;python virtualenv copy&quot; на что гугл выдал мне ссылку на &lt;a href=&quot;http://www.doughellmann.com/projects/virtualenvwrapper/&quot;&gt;virtualenvwrapper&lt;/a&gt;, откуда я путем быстрого рисёрча исходников попал на &lt;a href=&quot;https://github.com/edwardgeorge/virtualenv-clone&quot;&gt;virtualenv-clone&lt;/a&gt; и радостно захлопал в ладоши - то, что нужно!&lt;/p&gt;

&lt;p&gt;Так что задача полноценного копирования виртуальных окружений решается просто:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ sudo pip install virtualenv-clone
$ virtualenv-clone env_base env_new
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; Еще одним решением для ускорения установки зависимостей была, есть и остается опция &lt;code&gt;--download-cache&lt;/code&gt; для pip&#39;а. Но хотелось именно решить вопрос с копированием виртуальных окружений.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;зыы.&lt;/strong&gt; Если кого интересует, весь процесс поиска решения доступен в &lt;a href=&quot;https://gist.github.com/3103501&quot;&gt;гисте на гитхабе&lt;/a&gt;. Конечно, комментарии и возражения как всегда приветствуются!&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/7667618699673386881'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/7667618699673386881'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/07/virtualenv-clone.html' title='Копируем виртуальные окружения с помощью virtualenv-clone'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-7399002236996367214</id><published>2012-05-21T07:15:00.000+03:00</published><updated>2013-04-25T16:51:35.648+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="django"/><category scheme="http://www.blogger.com/atom/ns#" term="reusable apps"/><category scheme="http://www.blogger.com/atom/ns#" term="tests"/><title type='text'>Наконец-то, django-discover-runner от Янниса Лейдла</title><content type='html'>&lt;p&gt;Эта была долгая история полная костылей, но кажется лед тронулся и совсем скоро и Django&#39;вский дефолтный тест раннер будет иметь поддержку автоматического поиска тестов в проекте и мы наконец забудем про &lt;code&gt;from test_* import *&lt;/code&gt; как про страшный сон.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/1450104&quot;&gt;Само решение&lt;/a&gt; и &lt;a href=&quot;https://code.djangoproject.com/ticket/17365&quot;&gt;соответствующий тикет&lt;/a&gt; появились довольно давно, а вчера Яннис Лейдл (&lt;a href=&quot;https://github.com/jezdez&quot;&gt;jezdez&lt;/a&gt;, один из Django core-team) оформил решение как отдельный пакет, назвав его &lt;a href=&quot;https://github.com/jezdez/django-discover-runner&quot;&gt;django-discover-runner&lt;/a&gt;. Шаг в верную сторону, но для меня до сих пор не понятно, почему это очевидное изменение не было сделано сразу после включения &lt;code&gt;unittest2&lt;/code&gt; в проект.&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/7399002236996367214'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/7399002236996367214'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/05/django-discover-runner.html' title='Наконец-то, django-discover-runner от Янниса Лейдла'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-1134499847589043687</id><published>2012-05-17T01:09:00.000+03:00</published><updated>2013-04-25T16:51:34.594+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="flask"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Постоянные сессии во Flask&amp;#39;е, один из способов</title><content type='html'>&lt;p&gt;По умолчанию, все содержимое &lt;a href=&quot;http://flask.pocoo.org/docs/api/#flask.session&quot;&gt;&lt;code&gt;flask.session&lt;/code&gt;&lt;/a&gt; будет очищено при закрытии браузера. Однако много когда нам нужно, чтоб данные сессии хранились и после рестарта браузера. Для этих случаев есть аттрибут &lt;code&gt;permanent&lt;/code&gt; и следующий простой сниппет:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;import datetime

from flask import Flask, session


app = Flask(__name__)
app.before_request(lambda: setattr(session, &#39;permanent&#39;, True))
app.permanent_session_lifetime = datetime.timedelta(days=14)&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Последняя строчка сниппета выставляет &lt;a href=&quot;http://flask.pocoo.org/docs/api/#flask.Flask.permanent_session_lifetime&quot;&gt;длину сессии&lt;/a&gt; в 14 дней, во Flask&#39;е же по дефолту используется 31 день для хранения постоянной сессии. Также эту настройку можно указать как &lt;code&gt;PERMANENT_SESSION_LIFETIME&lt;/code&gt; в вашем &lt;code&gt;settings.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;зы.&lt;/strong&gt; Однако также не забывайте, что Flask хранит все данные сессии в кукисах, а не как, например, Django только ключ сессии, а все данные уже считывает с базы данных или другого источника. Так что уместно будет использовать &lt;code&gt;flask.session&lt;/code&gt; как хранилище каких-то ключей, например, токена текущего залогинненого пользователя.&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/1134499847589043687'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/1134499847589043687'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/05/flask.html' title='Постоянные сессии во Flask&amp;#39;е, один из способов'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-3129895494385526703</id><published>2012-05-02T12:52:00.000+03:00</published><updated>2013-04-25T17:06:18.141+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="flask"/><category scheme="http://www.blogger.com/atom/ns#" term="nosetests"/><category scheme="http://www.blogger.com/atom/ns#" term="tips and tricks"/><title type='text'>Окончательно дружим Flask и nosetests</title><content type='html'>&lt;p&gt;Не секрет, что Flask и так хорошо дружит с nosetests, но до сегодняшнего дня был один очень раздражющий момент в их взаимоотношениях :)&lt;/p&gt;
&lt;p&gt;Как мы все знаем nosetests по дефолту захватывает все из stdout/stderr и логгинга, чтоб при запуске тестов вывод не засорялся ненужной нам информацией. Однако в дебаг-моде Flask кладет на всех и устанавливает с помощью &lt;code&gt;flask.logging.create_logger&lt;/code&gt; функции хэндлер, который начинает срать в консоль при каждом удобном случае, причем минуя все ранее установленные хэндлеры. Итог: куча ненужной логгинг информации при запуске тестов как:&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;(env)$ TESTING=1 nosetets -c -v -w &amp;lt;package&amp;gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Не хорошо, но &lt;em&gt;In mock we trust&lt;/em&gt;, так что все что надо - это замокать упомянутую выше функцию в случае, когда мы запускаем тесты в дебаг-моде:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;if TESTING and DEBUG:
    from flask import logging as flask_logging

    def mock_create_logger(app):
        return logging.getLogger(app.logger_name)

    flask_logging.create_logger = mock_create_logger&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Помещаем этот сниппет в &lt;code&gt;settings.py&lt;/code&gt;, затем не забываем загрузить настройки как &lt;code&gt;import settings; app.config.from_object(settings)&lt;/code&gt; в нашем &lt;code&gt;app.py&lt;/code&gt; - и получаем счастье, nosetests уверенно захватывает все нужное и вывод тестов чист и аккуратен.&lt;/p&gt;
&lt;p&gt;Полный гист доступен на &lt;a href=&quot;https://gist.github.com/2575634&quot;&gt;Гитхабе&lt;/a&gt;, если кто-то готов предложить более красивый вариант решения проблемы - жду в комментариях.&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/3129895494385526703'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/3129895494385526703'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/05/flask-nosetests.html' title='Окончательно дружим Flask и nosetests'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-4240229226523946070</id><published>2012-04-21T12:17:00.000+03:00</published><updated>2013-04-25T16:51:32.920+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="dropbox"/><category scheme="http://www.blogger.com/atom/ns#" term="flask"/><category scheme="http://www.blogger.com/atom/ns#" term="reusable apps"/><title type='text'>Flask-Dropbox</title><content type='html'>&lt;p&gt;За что нравится Flask, так это за его концепцию reusable apps. По сравнению с Django на создание по настоящему реюзабельного приложения уходит КУДА меньше времени.&lt;/p&gt;
&lt;p&gt;Вот, например, вчера вечером захотелось поиграться с &lt;a href=&quot;https://www.dropbox.com/developers&quot;&gt;Dropbox API&lt;/a&gt;, а сегодня уже готов &lt;code&gt;Flask-Dropbox&lt;/code&gt; :) Причем готов с тестовым проектом, который позволит вам загружать файлы в Dropbox, просматривать их и удалять.&lt;/p&gt;
&lt;p&gt;Пример использования как всегда прост и неказист: импортируем главный класс и блюпринт, инициализируем их, регистрируем блюпринт с указанием префикса для урлов:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;from flask import Flask
from flask.ext.dropbox import Dropbox, DropboxBlueprint

import settings


app = Flask(__name__)
app.config.from_object(settings)

dropbox = Dropbox(app)
dropbox_blueprint = DropboxBlueprint(dropbox)
app.register_blueprint(dropbox_blueprint, url_prefix=&#39;/dropbox&#39;)&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Единственное на чем следует детально остановится - это настройки. Так как мы имеем дело с API, без них никуда :)&lt;/p&gt;
&lt;p&gt;Во-первых, &lt;strong&gt;обязательно&lt;/strong&gt; нужно будет настроить &lt;code&gt;app.secret_key&lt;/code&gt; или просто &lt;code&gt;SECRET_KEY&lt;/code&gt;, чтобы иметь возможность использовать &lt;code&gt;flask.session&lt;/code&gt; (там будут храниться нужные нам токены).&lt;/p&gt;
&lt;p&gt;Во-вторых, нужно будет создать какое-то приложение в Dropbox developers site, если еще нет такого и получить там &lt;code&gt;DROPBOX_KEY&lt;/code&gt;, &lt;code&gt;DROPBOX_SECRET&lt;/code&gt; и &lt;code&gt;DROPBOX_ACCESS_TYPE&lt;/code&gt;. Без указания этих значений - кина не будет и &lt;code&gt;Dropbox(app)&lt;/code&gt; отдаст вам &lt;code&gt;ValueError&lt;/code&gt; :)&lt;/p&gt;
&lt;p&gt;После укзания настроек все просто :)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Аутентификация.&lt;/strong&gt; &lt;code&gt;dropbox.is_authenticated&lt;/code&gt; проверит нет ли валидного access token&#39;а в текущей сессии, &lt;code&gt;dropbox.login_url&lt;/code&gt; сгенерирует урл для логина при помощи Dropbox, и наконец &lt;code&gt;dropbox.logout_url&lt;/code&gt; отдаст урл, переход по которому вылогинит дропбокс пользователя. После успешного логина получить данные о пользователе можно из словаря &lt;code&gt;dropbox.account_info&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Работа с дропбоксом.&lt;/strong&gt; После того, как пользователь залогинился вся работа с его дропбоксом будет проходить через проперти &lt;code&gt;dropbox.client&lt;/code&gt;, который является прокси к инициализированному инстансу &lt;code&gt;DropboxClient&lt;/code&gt;. Полный список доступных методов последнего доступен &lt;a href=&quot;https://www.dropbox.com/static/developers/dropbox-python-sdk-1.4-docs/&quot;&gt;в документации&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;В остальном код доступен на &lt;a href=&quot;https://github.com/playpauseandstop/Flask-Dropbox&quot;&gt;ГитХабе&lt;/a&gt;, установить можно с &lt;a href=&quot;http://pypi.python.org/pypi/Flask-Dropbox&quot;&gt;PyPI&lt;/a&gt;. Пользуйтесь!&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/4240229226523946070'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/4240229226523946070'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/04/flask-dropbox.html' title='Flask-Dropbox'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-26826730311696682</id><published>2012-04-03T20:44:00.000+03:00</published><updated>2013-04-25T16:51:31.906+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="flask"/><category scheme="http://www.blogger.com/atom/ns#" term="reusable apps"/><title type='text'>Flask-LazyViews</title><content type='html'>&lt;p&gt;Мне не нравится использовать декоратор &lt;code&gt;@app.route&lt;/code&gt; или метод &lt;code&gt;app.add_url_route&lt;/code&gt; для регистрации функций отображения во &lt;a href=&quot;http://flask.pocoo.org/&quot;&gt;Flask&lt;/a&gt; приложениях и блюпринтах, потому что мне намного больше по душе &lt;a href=&quot;http://flask.pocoo.org/docs/patterns/lazyloading/&quot;&gt;паттерн ленивой загрузки этих функций&lt;/a&gt; :)&lt;/p&gt;
&lt;p&gt;Именно так и родился &lt;code&gt;Flask-LazyViews&lt;/code&gt;. Пример использования тривиальный, для приложений:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;from flask import Flask
from flask.ext.lazyviews import LazyViews


app = Flask(__name__)
views = LazyViews(app)

views.add(&#39;/&#39;, &#39;views.home&#39;)
views.add(&#39;/page/&#39;, &#39;views.page&#39;)&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Или для блюпринтов:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;from flask import Blueprint
from flask.ext.lazyviews import LazyViews


blueprint = Blueprint(&#39;test&#39;, __name__)
views = LazyViews(blueprint, &#39;.views&#39;)

views.add(&#39;/&#39;, &#39;test&#39;)
views.add(&#39;/advanced&#39;, &#39;advanced_test&#39;, methods=(&#39;GET&#39;, &#39;POST&#39;))&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Больше информации доступно как всегда на &lt;a href=&quot;https://github.com/playpauseandstop/Flask-LazyViews&quot;&gt;ГитХабе&lt;/a&gt;, установить можно с &lt;a href=&quot;http://pypi.python.org/pypi/Flask-LazyViews&quot;&gt;PyPI&lt;/a&gt;.&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/26826730311696682'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/26826730311696682'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/04/flask-lazyviews.html' title='Flask-LazyViews'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry><entry><id>tag:blogger.com,1999:blog-8645505283412677599.post-2570639618051309844</id><published>2012-03-12T10:36:00.000+02:00</published><updated>2013-04-25T16:51:30.929+03:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="flask"/><category scheme="http://www.blogger.com/atom/ns#" term="redis"/><category scheme="http://www.blogger.com/atom/ns#" term="reusable apps"/><title type='text'>Flask-And-Redis</title><content type='html'>&lt;p&gt;Понадобилось на днях во Flask проекте докрутить поддержку Redis&#39;а. Плюс, хотелось иметь возможность задавать любые настройки в &lt;code&gt;settings&lt;/code&gt; модуле, в итоге появился &lt;a href=&quot;https://github.com/playpauseandstop/Flask-And-Redis&quot;&gt;Flask-And-Redis&lt;/a&gt;, так как в &lt;a href=&quot;https://github.com/satori/flask-redis&quot;&gt;Flaks-Redis&lt;/a&gt; далеко не все можно настроить и оно не Flask-way инициализируется.&lt;/p&gt;
&lt;p&gt;Пример использования есть в репозитории, по быстрому повторю и здесь:&lt;/p&gt;

  &lt;pre class=&quot;python&quot; name=&quot;code&quot;&gt;&lt;code&gt;from flask import Flask
from flask.ext.redis import Redis


app = Flask(__name__)
redis = Redis(app)&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Вуаля :)&lt;/p&gt;</content><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/2570639618051309844'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8645505283412677599/posts/default/2570639618051309844'/><link rel='alternate' type='text/html' href='http://djangonaut.blogspot.com/2012/03/flask-and-redis.html' title='Flask-And-Redis'/><author><name>Igor Davydenko</name><uri>http://www.blogger.com/profile/10744018671065367037</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwcpzjW5njBauzWdNs8pOS852osEjhQ0G_BI3W4BJ7RiuwhKVXOexEa3hrFPm3_AcxVjhglz47rHckXkUcv9aWXDAhqDwbbqvURvIAjZdAEQJLCF_hefI2CtTXnYoddiA/s113/instagram-munich.jpg'/></author></entry></feed>