Проблема пришла от туда, от куда не ждали. А именно от таких 5-ти строчек кода:

class CustomModel(Base):
    __tablename__ = 'custom_model'
    id = Column(Integer,)
    name = Unicode(100)
    datetime = Column(DateTime, default=datetime.now())

Когда-то давно я или неправильно понял доку, недочитал или прочитал не то, но был уверен в том, что этот код “компилируется” в примерно такой SQL (код приведен только в целях примера и может не работать:) ):

CREATE TABLE custom_model (
     id integer,
     name varchar(20),
    created_at datetime default getdate().now
)

Проблема проявилась в том, что order by по полю created_at не работал. Все дело в том, что вышеприведенный код генерируется в такой SQL:

CREATE TABLE test (
     id integer,
     name varchar(20),
     created_at
)

Т.е. значение по умолчанию выставляются не на уровно СУБД, а на уровне модели при вставке. Более того, код “default=datetime.now()” отрабатывает только раз при создании модели и все значения будут одинаковыми. Для исправления этого достаточно сделать что бы значение по умолчанию вычислялось каждый раз новое. Для этого в параметр default можно передать любой callable объект, например lambda-функцию (default=lambda: datetime.now()). Если копнуть дальше в исходники, что значение default - это инстанс типа ColumnDefault, спрятанный за удобным синтаксисмом.

Что бы значения по умолчанию выставлялись на уровне СУБД в SQLAlchemy необходимо использовать параметр server_default (к слову, onupdate и другие триггеры на сервере ставятся с помощью добавления префикса “server_”, например server_onupdate). Если необходимо в качестве значения по умолчанию задать какое-то выражение (sql expression), то делается это с помощью следующей конструкции:

server_default=text("sysdate")

К слову о  Django - там все работает так же, только про server_default я ничего не нашел:(.

Ссылки по теме:

 

 



Выбор версии языка программирования, фреймворка - сложный вопрос, который всегда бурно обсуждался и будет обсуждаться. В enterprise мире часто, но не всегда, используют старые и проверенные инструменты. В то время как Python 2.7 все еще нет из коробки в RedHat/CentOS/др дистрибутивах, в некоторых уже используется Python 3.3, пусть и не в качестве системного. В мире opensource - наоборот, часто используют только самое-самое новое. Но это правило не относится к разным фреймворкам. Представьте, что завтра, например, Django будет поддерживать только Python 3.4, который еще не зарелизился. Никто им пользоваться не будет. Вот и приходится поддерживать несколько версия языка программирования. Похожая ситуация с paramiko, nose и другими популярными проектами/инструментами/библиотеками.

Сегодня, пытаясь настроить Travis CI для небольшокго плагина для nose (https://github.com/mahmoudimus/nose-timer), столкнулся с проблемой немного удивился в отличии списка поддерживаемых версия питона nose, а в следствии и nose-timer, и Travis CI. Travis работает только с несколькими самыми используемыми версиями - 2.6, 2.7, 3.2 и 3.3 (http://about.travis-ci.org/docs/user/languages/python/). Для меня это показатель: раз такой популярный инструмент поддерживает эти версии, то следует задуматься какие версии python'а нужно поддерживать в своих библиотеках.

 

P.S. А тем временем стал доступен Python 3.4.0 beta 2.


Если коротко, то типы в Python делятся либо на встроенные и пользовательские, либо на mutable и immutable (сразу так и не подобрал подходящего перевода на русский язык). Ну а если немного подробнее, то как-то так:

Рассмотрим простой пример кода (здесь и далее примеры кода будут писаться и выполняться в ipython’е):

In [1]: a = 1000
In [2]: a
Out[2]: 1000
In [3]: type(a)
Out[3]: int

Здесь мы объявляем переменую создаем объект (object) a, со значением(value) 1000 типа(type) int. Объекты в python - это абстрация над данными. Поэтому любой объект содержит в себе идентификатор(identity), тип и значение. Идентификатор - это адрес объекта в памяти, который никогда не меняется. Но мы можем присвоить объекту новое значение, написав, следующий код:

In [4]: a = 'hello'
In [5]: a
Out[5]: 'hello'
In [6]: type(a)
Out[6]: str

Теперь мы присвоили объекту a новое значение типа string. За кулисами этого всего, интерпритатор сделал, примерно, такое: создал новый объект ‘hello’, и поменялл ссылку переменной a на новый объект в памяти. При этом старое значение 1000 все еще хранится в памяти и будет удалено сборщиком мусора, т.к. на него нет ни одной ссылки. Тоже самое происходит и присвоении переменной нового значений такого же типа:

In [7]: b = 1000
In [8]: id(b)
Out[8]: 40235856
In [9]: b = 1001
In [10]: id(b)
Out[10]: 39189680

Интересный эффект будет при использовании целых чисел в диапазоне от -5, до 256 - их id всегда будет одинаковый, т.к. для улучшения быстродействия интерпритатор при старте создает объекты с этими значениями.

Стандартные типы в CPython делятся на два типа: mutable и immutable. Mutable — это обекты, значения которых могут быть изменены(например, list), а immutable — это обекты, значения которых не может меняться (например, string). Но это не значит, что если у нас есть переменная immutable типа, то ее нельзя изменить. Например, с типом string все привыкли работать так:

In [11]: text = 'Hello'
In [12]: text
Out[12]: 'Hello'
In [13]: text = 'Hello, World'
In [14]: text
Out[14]: 'Hello, World'

Как мы видим, начение переменной text поменялось. На самом деле, при присвоении переменной text нового значения, в памяти создается новый объект, и переменная text начинает на него ссылаться. В этом легко убедится при помощи функции id - получить идентификатор до и после изменения переменной.

Mutable типы ведут себя так:
In [15]: l = [1, 2, 3]
In [16]: l
Out[16]: [1, 2, 3]
In [17]: id(l)
Out[17]: 139901299808088
In [18]: l.append(4)
In [19]: l
Out[19]: [1, 2, 3, 4]
In [20]: id(l)
Out[20]: 139901299808088

Основываясь на этом свойстве(и не только), ключами в словаре (dict) могут быть только immutable обекты:

In [21]: d = {'one': 1}
In [22]: type(d)
Out[22]: dict
In [23]: d[1]=1
In [24]: d
Out[24]: {1: 1, 'one': 1}
In [25]: d[l] = 'list'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-4f49b18d7af7> in <module>() ----> 1 d[l] = 'list'
TypeError: unhashable type: 'list'

Immutable типы в Python — это числа(numbers), строки (strings) и кортежи (tuples).

Для облегчения привжу список некоторых всторенных типов (built-in types):

 

  • Numbers (числа) — int, long, float, complex(3+4j), Decimal, Fraction
  • Sequence Types (последовательности) — str, unicode, list, tuple, bytearray, buffer, xrange
  • Sets — set, frozenset
  • Maps (хеш-таблицы) — dict
  • Files, context managers и другие.

 

Ссылки по теме:

 

 

 


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

По сути, всем все-равно в какой IDE пишете код, если он работает так, как надо и написан вовремя. Унификация средст разработки внутри компании/команды лишь облегчает жизть менеджерам, ИТ, новым членам команды и упрощает коммуникацию между разработчиками. Ведь на много легче один раз написать инструкцию по установки всего нужного для запуска проекта ПО, например, для Ubuntu 13.10 x64 и запуск проекта в PyCharm, чем каждый раз сталкиваться с проблемой как запустить X в окружении Y.

Но каждый разработчик в праве решать сам чем он будет пользоваться. Вот только к выбору инструментов нужно подходить “с умом”. Главное - хорошо знать инструменты, которыми вы пользуетесь. Иначе будет ситуация, когда используется Photoshop только для изменения размера картинки. Досконально знать OS, IDE, текстовый редактор, которым пользуетесь каждый день не только приятно, но и полезно. Это может экономить массу времени. Всем известно, что использования горячих клавиш, в некоторых условиях, в разы быстрее, чем совершать нужные действия мышкой. Аналогичная ситуация с разнообразными инструментами автоматизации. Если мне нужно выполнить действиее более 2-3х раз - я пишу небольшой скрипт на bash или python. Иногда скрипты получаются не очень и маленькими, иногда в помощь приходят fabric/chef/puppet. Готовые настройки для используемых редактором и систем (профайлы bash, zsh) - упрощают жизь и экономят время, которое можно потратить на более полезные и интересные вещи.

Все вышесказанное всем известно и описано в множестве книг и постов. Захотелось повторить, т.к. я в очередной раз наступил на эти грабли: потратил почти час на написание простого скрипта на bash’е, аналог которого на python мне удалось написать за 10 минут. Да, я узнал кое-что новое о bash, но это не мой основной язык программирования, и скрипт писался исключительно для себя (и не важно, что любой может скачать его с github и пользоваться), но я потратил массу времени из-за того, что выбрал не тот инструмент. Нельзя путать процесс обучения и процесс разаботки. Между ними очень тонкая грань, которую легко перейти и потерять время напрасно.


Всегда недолюбливал Apache из-за формата его конфига. Конфиг Nginx’а мне вседа было порще читать и писать. Да и статику им раздавать хорошо и быстро, поэтому от Apache я, по возможности, отказываюсь. Но частая проблема с nginx в том, что приходится собирать необходимые модули из исходников самому, со всемы вытикаюющими плюсами и минусами. И, как полагается любому популярному и быстро развивающемуся проекту, документация по сборке этих самих модулей не всегда полная и актуальная.

Ниже (очень) короткое изложение того, что мне пришлось сделать для сборки mod_security. Надеюсь, это сэкономит кому-то время и нервы.

Оффициальная документация расположена тут и тут. При этом сложилось впечатление, что на github’е она более актуальная и полная. По ним можно собрать все, при условии, что у вас уже стоят нужные пакеты. Мне понадобились:

$ sudo apt-get install autoconf automake libtool libcurl4-openssl-dev

Тажке, не забыть выполнить “./autogen.sh” перед “./configure”. Почему-то, в секции “Installation for NGINX” документации написаны только общие сведения. Мне, например, не хотелось и не нужно было собирать mod_security для apache, устанавливать пакет apache2-dev и т.д. Поэтому выполнил configure со следующими параметами:

$ ./configure --prefix=/usr --disable-apache2-module --enable-standalone-module

Далее сборка, установка и настройка у меня прошла без проблем.



Как всегда, в рубрике “мысли в слух” звучит только исключительно мнение автора и может не соответствовать действительности.

Безусловно, всем, ну ладно, почти всем, хочется писать только на самых последний версиях фреймворков, использовать самые новые технологии и навсегда забыть о так называемом legacy code. Но мало кто из разработчиков думает о том, когда и зачем это нужно, а когда - невозможно. Разберем пример выбора фреймворка Х на примере нескольких случаев.

Случай #1. Работает - не трожь!

Очень распространенный и нелюбимый мною случай. Проекту N лет, заказчика он устраивает, клиенты довольны. Но проект еще развивается, нужно не только фиксить баги, но и улучшать текущую, добавлять нувою функциональность. Но что бы добавить одну простую новую функцию, нужно написать “много” кода, который уже есть в новой версии фрреймворка X, который используется. А новая версия может быть несовместима со старой. А если и совместима - нет гарантии что все быстро и хорошо заработает. Часто бывает что проще и дешевле переписать с нуля. И вот тут должен выключаться режим разработчика и включаться режим владельца бизнеса. На основе естимейтов по переходу на новый фреймворк, можно посчитать что дешевле: дописать старое или написать, частично или полностью, новое. Наверняка, в каких-то правильных книжках по менеджменту (возможно, не только для ИТ) и/или економики уже есть готовые формулы для этого, но я не встречал такого. Самое сложное здесь то, что формула будет не такой простой, как может показаться изначально.

Например:

Функция А будет сделана(разработанна, протестированна, задеплоенная - вообщем, готова для использования) за 30 часов на старом фреймворке и за 20 на новом. Умножая кол-во часов на n денег легко посчитать как дешевле. Но тут нужно учитывать всевозможные риски с новым фреймворком - его баги, баги после миграции, стоимость изучения и т.д.

Пример выше сильно упрощен и я не сталкивался с таким в работе. Обычно, все выглядит так (цифры придуманы):

Функция А будет сделана(разработанна, протестированна, задеплоенная - вообщем, готова для использования) за 10 часов на старом фреймворке и за 50 на новом. Разница по деньгам и времени - в 5 раз. Тажело будет уговорить менеджера и/или заказчика. Но тут на решение должны влиять другие факторы, а именно:

 

  • каждая новая фича будет стоить дешевле из-за того, что разработчики уже выучат матчасть, набьют новые шишшки с новым фреймворком и т.д., в последствии чего - в идеальных условиях, время разработки будет стремится к времени на разработку этой же фичи на старом фремйворке - n часов.
  • во время миграции на новый фреймворк, пользователи продукта не будут видеть новых версий, что сильно влияет на time to market (TTM), может пказаться, что продукт больше не развивается.
  • на протяжении этого всего времени, нужно будет поддерживать старую/текущую версию продукта, что тоже стоит денег.
  • переписанный продукт на новый фреймворк X - часто является скорее новым продуктом, чем новой версией старого, со всемы вытикающими последствиями.

 

 

Случай #2. “Теперь мы используем фреймворк X версии Y” или “у нас новый проект на фреймворке X версии Y”.

Казалось бы все хорошо, фреймворк X версии Y - самая последняя версия самого крутого фреймворка, использующего самые новые технологии. Все хотят это испльзовать и работать с этим. Но пройдет сколько-то времени (месяц, два, год), а мы пишем все тот же проект, на все тех же технологиях, которые были актуальны и популярны полгода-год-два назад. И все сводится к вышеописанному сценарию #1.

 

Случай #3. Идеальный процесс разработки продукта.

Раз это описание идеального процесса, пусть это будет что-то типа continuous development. Все лучшее от разнообразных Agile методологий, Kaizen и т.д. и заказчик готов платить достаточно денег при соблюдении таких условий:

 

  • продукт будет выпущен в оговоренные сроки
  • дена дальшейшей разработки будет снижаться
  • цена поддержки будет низкой

 

Допустим, дата релиза запланированна на весну (сейчас декабрь). К тому времени должна выйти новая версия хорошо известного фреймворка X, а сейчас доступна совсем не production ready alpha. Т.к. авторы этого фреймворка всегда выпускают релизы вовремя(у нас же идеальная ситуация:) ), то можно начать писать на нем. Но т.к. у нас в руках очень сырая и нестабильная alpha версия, то нам нужно обезопасить себя от всяческих рисков и проблем, связанных с багами и недоработками фреймворка. Я вижу только один действительно рабочий способ - внедрение continuous integration процесса в полной его мере: покрытие всяческими тестами и постоянный их запуск. Таким образом, особенно при хорошем покрытии кода unit-тестами, мы можем отделить ошибки которые связаны с нашим кодом, от тех, что зависят от фреймворка и/или сторонних библиотек.

Таким образом мы берем на себя технологический риски, связанные с “сырым” фреймворком и возможные потери денег как на разработку продукта, так и на возможную недополученную прибыль от его использования и/или продажи. Но если все будет хорошо, и проект успешно выйдет в production, то мы получим следующие плюсы от этого:

 

  • малое значение TTM - продукт выходит на рынок как только пояляется новая технология/фреймворк
  • вы становитесь early adopters выбранного вами фреймворка
  • качество продукта будет достаточно высоким (см. о continuous integration)
  • счастливые разработчики, использующие самые последние технологии и фрейморки
  • разработка следующей версии продукта стартует быстрее (есть наборы тестов, CI) и так же может быть на еще более новой версии фреймворка

 

Минусов значительно меньше, но они более существенные - цена этого всего выше. И в лучшем случае - это только время и деньги. Добавьте сюда еще всевозможные риски разработки продукта в таких условиях (мало или совсем нет опыта с технологиями, фреймворк может зарелизиться посже или вообще не станет production ready, и др.) - станет не все так радостно и просто. Вообщем, как говорится, думайте сами, решайте сами...

 

Ссылки по теме:



Вот так неожиданно для себя узнал что Selenium Webdriver работает без Java. До этого всегда был уверен, что такого не может быть. Возможно, это произошло с релизом Selenium 2, возможно нет. Но мой мир уже не будет таким как прежде. 

Не поверил, пока сам не убедился в этом. Пришлось ставить виртуалку без Java и проверять следующий код:


Build succeeded, 156 warnings

Published 11/18/2013 by e0ne in Offtopic

Запустил “make build” и получил 156 ворнингов:(... Хотя, достаточно быстро получил более радужное число - 69, что тоже не мало. Нужно фиксить дальше, а пока - немного очень IMHO на эту тему.

Отключать или нет параметр “mark warnings as errors” (название может отличаться, но суть остается той же) - часто решают для конкретного проекта и/или команды. Иногда это не мешает работы продукта. Я бы сказал что в 98% случаев это не мешает, зато остаются 2%. И баги, попавшие в те самые 2%, часто являются самыми трудновоспроизводимимы и затратными по времени на исправления.

Итак, из-за чего появляются warnings? Самые частиы причины - это:

  • использование deprecated и/или недокументированного API.
  • несоответствие кода гайдлайнам (guidelines) и/или принятым стандартам языка программирования, фреймворка и т.д.
  • проблемы с настройкой окружения (environment).

Что плохого в использовании deprecated API? Ничего, если приложение будет удалено не посже, чем сразу после перговго запуска. В противном случае, рано или поздно, после очередного обновления используемой платформы и/или фреймворка что-то перестанет работать, т.к. deprecated код будет удален. Таким образом, ваш код, который использует устаревший API фреймворка, автоматически становится deprecated :). Готовы ли вы писать заведомо legacy код?

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

Код, который не соответствует стандартным гайдлайнам, как минимум, - странный. IMHO. Нет, я понимаю, что при использовании StyleCop (для C#) практически невозможно писать код, соответствующий всем его требованиям, но для того же Python’а, держать код в соответствии с PEP-8 достаточно просто. Трудности бывают только с 80-ю символами в строке. С PyLint все немного сложнее. Но никто не заставляет выполнять все требования. Главное - здравый смысл и правильная настройка инструментов, которые используются.

Проблемы с настройкой окружения. Тут не буду много писать, т.к. эта тема тянет на отдельный пост в блоге, который уже давно хочется написать. Скажу только то, что один и тот же код (id коммита, tag, и т.д.) должен, по возможности, собираться одинаково на всех используемых окружениях. Это, как правило, рабочий ПК разработчиков и тестировщиков и билд-серверы. Иначе где-то что-то пойдет не так.

Не попал в мой классификацию еще один случай - содержимое stderr. Бывает, что только туда пишуться какие-либо предупреждения, которые остаются непрочитанными. Давно уже руки тянуться помечать все билды как failed, если что-то есть в stderr, но пока это сделать не получается, т.к. “хороший продукт !== работающий и/или безбажный продукт”, а “хороший продукт == радующий пользователя/заказчика продукт”. Как-то так.



Разберем ситуацию, ставшую достаточно распространенной в наше время. Есть распределенная команда, допустим в двух городах (А и Б), которая работает над одним крупным проектом. Сборка проекта происходит на Jenkins’е в городе А и длится, например 3 минут. Скачать готовый продукт из города А в город Б можно в среднем за 30 минут (в зависимости от времени суток, загрузки канала, фазы луны и т.д.). Итого: команде из города Б нужно ждать час(60 минут) чтобы запустить/протестировать свежий билд. Очевидно, это достаточно много. Нужно ускорять.

Рассматривать вопросы вынесения этого всего (CI) в облако и прочее я не буду по двум причинам:

 

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

 

Также я пропущу вопросы синхронизации репозиториев с исходниками и прочие инфраструктурные вещи, не относящиеся к данной теме.

Решать данную проблему буду так: в обоих офисах настраивается Jenkins-нода, на которой можно собирать свежий билд. Главная задача - 2 одновременных билда одного проекта в разных офисах.

Делается это, примерно, так:

 

  1. Заходим на наш Jenkins и устанавливаем нужные плагины (Jenkins => Manage Jenkins =>  Manage Plugins => Available). Нам нужен “Throttle Concurrent Builds Plugin” (https://wiki.jenkins-ci.org/display/JENKINS/Throttle+Concurrent+Builds+Plugin) и NodeLabel Parameter Plugin (https://wiki.jenkins-ci.org/display/JENKINS/NodeLabel+Parameter+Plugin).
  2. Настраиваем Slave node. Документация лежит тут: https://wiki.jenkins-ci.org/display/JENKINS/Step+by+step+guide+to+set+up+master+and+slave+machines.
  3. Настраиваем нашу Job’у для параллельного выполнения на двух нодах. Я для примера создал Fake-long-job, которая выполняет простой bash-скрипт “sleep 20;”.
    • Отмечаем флаг “This build is parameterized”
    • Добавляем параметр Node

    • Выбираем на каких нодах можно выполнять билд

    • Настраиваем одновременные билды. В моем случае их может быть 2, по одному на каждой ноде

      .
    • Нажимаем кнопку Save.
  4. Проверяем, что все работает, запустив два билда на разных нодах.

 

Выше я описал только базовые настройки, которые подошли мне. Вам, возможно, прийдется дополнительно настраивать что либо еще.


Скорее заметка для себя. Постоянно забываю об этом ключе.

Иногда нужно очень быстро поднять HTTP-сервер с минимальным функционалом. Например, нужно проверить HTML+JavaScript, который работает только через веб-сервер. Я, конечно понимаю, что у меня под рукой всегда есть, как минимум nginx, и поднять на нем сайт, который будет раздавать статические фалы, делов на 5-10 минут. Но, во-первых, это долго, а во-вторых, этот самый nginx у меня находится на виртуалке, пусть и включенной в 90% времени на ноутбуке. А тут прийдется не только веб-сервер настроить, но и расшаренные папки в VirtualBox-е, и так далее...

Единственное, что у меня действительно всегда есть под рукой, это Python (уже несколько лет не пользуюсь ОС Windows, а на всех *nix он есть из коробки). Поднять простой тестовый веб-сервер на питоне можно всего одной командой:

$ python -m SimpleHTTPServer 8100

После этой команды, у вас локально запустится веб-сервер на 8100-м порту, который покажет содержимое текущей папки.

Работает это благодаря ключу -m, который указывает интерпритатору о том, что необходимо выполнить модуль как скрипт. Это идентично команде "$ python /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/SimpleHTTPServer.py", но писать значительно проще. Единственное ограничение, что этот модуль должен быть доступен в sys.path