How to run CKAN tests

Published 1/4/2018 by e0ne in Python

CKAN is an open-source DMS (data management system) for powering data hubs and data portals. CKAN makes it easy to publish, share and use data. It powers datahub.io, catalog.data.gov and europeandataportal.eu/data/en/dataset among many other sites. http://ckan.org/

Open source world is interesting and challenging. Sometimes it’s easy and cheap. Sometimes it’s hard to contribute and costs a lot. Looking into the CKAN I was surprised that it’s used by government portals. That’s why I tried to use it a bit. Here is my short manual that extends an official one how to run functional and unit tests.

Unfortunately, I don’t have enough time to make a pull request (maybe you can do it instead of me:) ), so I just make a blog now.

The main issues with tests run are:

 

  • Documentation doesn’t cover all steps
  • CKAN uses outdated versions of Solr and Node.JS
  • Some bugs in tests which will be described later

 

All these things were found (or even reverse-engineered) in sources and CKAN’s CI results. You can found all needed data in the manual, GitHub (https://github.com/ckan/ckan/blob/master/circle.yml and https://github.com/ckan/ckan/blob/master/.circleci-matrix.yml)  and CI report for any pull request (https://github.com/ckan/ckan/pulls). I use the same versions as CI does.

 

I use Ubuntu 16.04 LTS distro in my environment. I strongly recommend to do it inside some virtual machine or containers. So it won’t break anything on your desktop or laptop.

1. Getting sources

Let’s go! First of all, you need to clone sources:

git clone https://github.com/ckan/ckan.git

 

2. Node.JS installation

For UI integration test you need to install Node.JS v. 0.10.33. It won’t work on the latest version for sure. 

curl -O https://nodejs.org/dist/v0.10.33/node-v0.10.33.tar.g

tar pxzf  node-v0.10.33.tar.gz

cd node-v0.10.33

./configure && make

sudo make install

 

You can install required npm packages now to run tests in the future:

npm install -g mocha-phantomjs@3.5.0 phantomjs@~1.9.1

 

3. PostgreSQL installation

I used that version of PostgreSQL which is available on my Linux distro:

apt install postgresql

apt install postgresql-server-dev-9.5

4. Redis

It should be simple, just run:

apt install redis-server

 

5. Python dependencies.

I use virtualenv wherever it’s possible:

cd ~/ckan

virtualenv .venv && . .venv/bin/activate

Once virtualenv is ready and activated, it’s time to install python packages:

pip install -r requirement-setuptools.txt

pip install -r requirements.txt

pip install -r dev-requirements.txt

python setup.py develop 

 

6. Database configuration

Configure some environment variables to get everything working. I used the same values as we’ve got in the Circle CI configuration:

export CKAN_POSTGRES_DB=ckan_test

export CKAN_POSTGRES_USER=ckan_default

export CKAN_POSTGRES_PWD=pass

export CKAN_DATASTORE_POSTGRES_DB=datastore_test

export CKAN_DATASTORE_POSTGRES_WRITE_USER=ckan_default

export CKAN_DATASTORE_POSTGRES_READ_USER=datastore_default

export CKAN_DATASTORE_POSTGRES_READ_PWD=passexport

Create required databases and grant permissions:

sudo -E -u postgres ./bin/postgres_init/1_create_ckan_db.sh

sudo -E -u postgres ./bin/postgres_init/2_create_ckan_datastore_db.sh

sed -i -e 's/.*datastore.read_url.*/ckan.datastore.read_url = postgresql:\/\/datastore_default:pass@\/datastore_test/' test-core.ini

paster datastore -c test-core.ini set-permissions | sudo -u postgres psql

 

7. Solr installation and configuration

To get tests passed I use Solr v. 4.3.1. There is a filed bug about Solr version. CKAN tests don’t work with Solr 6.x now:

curl-O  http://archive.apache.org/dist/lucene/solr/4.3.1/solr-4.3.1.tgz

tar zxvf solr-4.3.1.tgz

Now you have to start Solr. You can run it as a daemon or run it in a separate terminal:

cd solr-4.3.1/example/

java -jar start.jar

 

Solr initialization is required too:

export SOLR_HOME=~/solr-4.3.1

cd ~/ckan

./bin/solr_init/create_core.sh

 

8. Initialize test data

paster db init -c test-core.ini

 

9. Run test CKAN server

paster serve test-core.ini

10. Finally, run UI tests

mocha-phantomjs http://localhost:5000/base/test/index.html

11. Run unit and functional tests

To run all tests you need to execute the following command:

nosetests --ckan --reset-db --with-pylons=test-core.ini --nologcapture ckan ckanext

Unfortunately, you’ll have some failed bugs due to the https://github.com/ckan/ckan/issues/3675 :(. To successfully run all tests, you should use segments. E.g.:

nosetests --ckan --reset-db --with-pylons=test-core.ini --nologcapture --segments=abc ckan ckanext

 


This test is too slow

Published 7/28/2017 by e0ne in Python

Sometimes we need to understand why unit-test is so slow. Sometimes I’m to lazy to go deep to understand why.

That’s why I’ve created a very simple profiled class to make unit-tests profiling fast and simple. I used only cProfile, so it will work on any Python project. It’s so simple, so I can’t talk about it more. You can install it via ‘pip install ProfiledTest’ and use it like:

class SampleTest(ProfiledTest, unittest.TestCase):
    def test_sample(self):
        self.assertTrue(True)

 

GitHub url: https://github.com/e0ne/profiled_test


Проблема пришла от туда, от куда не ждали. А именно от таких 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 я ничего не нашел:(.

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

 

 



Если коротко, то типы в 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 и другие.

 

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

 

 

 


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

Иногда нужно очень быстро поднять 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


Давно решил не писать отзывы к библиотекам/фреймворкам, но эта поражает меня уже второй раз так, что в твиттере не помещается вся мысль.

TestFixtures (http://packages.python.org/testfixtures/) - приятное дополнение, а в некоторых случаях, и замена Mock.

То, что она умеет делать mock’и объектов - этим никого не удивишь. Вся прелесть TestFixtures в том, что в ней уже из коробки доступны те самые вещи, которые часто приходится писать самому, тем самым изобретать свой велосипед:(.

Начиная от небольших функций, вроде generator и wrap, библиотека включает в себя то, из-за чего лично я ее использую: различные helper’ы для тестирования логгирования и вывода в потоки (stream’s) (когда-то очень помогла, найти ошибку и некорректным перехватом исключения и, вследствии чего, потерей части логов), а также всякие полезности для тестирования работы с датами и исключеними. 

Для меня это темерь однозначно must use в юнит-тестах.


4 января вышел релиз кандидат fullstack-фрейморка для разработки веб-приложения Django. Обзоры, наверено, не писали/читали только ленивые. Но пишут, в основном, про мажорные фичи, из-за которых и выпускают релиз. Я перевел свой небольшой прототипчик одного приложения на Django 1.5 RC и поюзал некоторые минорные нововведения, о которых пишут мало, но которые почти делают каждый релиз тем, из-за чего часто хочется использовать именно его. Из того, что мне понравилось - это:

  • изменения в template engine: теперь True, False, None воспринимаются так же, как и в python;
  • дополнительные батарейки для работы с временными зонами - мелочь, а очень приятно;
  • исправленна ошибка OutOfMemory при использовании команды dumpdata - особенно полезно на небольших хостингах;
  • mod_wsgi auth handler - для тех, кто все еще использует Apache и Basic авторизацию;
  • в debug конфигурации приложения логи дополнительно выводятся в консоль;
  • user_login_failed событие - понятно что это такое, +1 к секьюрити: легче блокировать ботов от перебора паролей и плюс к защите от DDoS;
  • loaddata имеет опцию для игнорирования колонок, которых больше нет в модели - просто в восторге от этой фичи, имхо, она для меня теперь станет неаменимой при разработке, кода модель активно меняется, а django south использовать еще рано (в момент разработки, а не при выходе в production).
Из всего вышесказанного, кроме mod_wsgi auth handler попробовал все и хоче сказать: дявол кроется в деталях, они делают любой продукт именно таким, чтоб им (не) хотелось пользоваться.

Ну и не могу сказать про одно мажорное изменения - эксперементальная поддержка Python 3.2+! Осталось подождать и/или портировать нужные зависимости для проектов и можно начинать использовать. На свой страх и риск, конечно, т.к. production код должен быть как можно стабильнее, а не в статусе "эксперементальная фича". Хотя, time to market никто не отменял...

Подробности на оффициальном сайте Django:


В этор раз идет подготовка сразу к двум KhakrivPy, соответственно докладчиков нужно найти больше.

26-го января пройдет KharkivPy #0, посвященный функциональным языкам программирования (Erlang, Lisp, Haskel, Scala, etc). На данный момент к нам в гости с докладом согласился приехать из Киева Владимир Кирилов (http://kirillov.im/, @darkproger) с докладом про Erlang, тема уточняется.

Ищем докладчиков, которые расскажут что-то интересное о других языках и/или про Erlang. Опыт в продкашн приветствуется.

2-го марта традиционный KharkivPy #7, на который тоже ищутся докладчики.

Желающие поделиться своим опытом могут обращаться ко мне  по телефону(+380919050626), скайпу (e0ne-user) или email'у (e0ne@e0ne.info)ю

Так де 19-го января в Киеве пройдет KyivPy, туда так же требуются докладчики. Подробности тут.

 

P.S. Этот пост написан в рамках акции, в которой я принимаю участие, которая запущена Стефани Бус и называется #back2blog, подразумевает написание 10 постов в блог за 10 дней. Это 1й пост из 10.


Lettuce и Python3

Published 12/3/2012 by e0ne in Python

 

Решил я для своих маленьких и уютных домашних проектов (pet project'ов) использовать Python 3.3. Казалось бы, ничто не предвещало беды. Ну кроме как отсутстие поддержки Python 3.x у некоторых библиотек. В частности, Lettuce(http://lettuce.it/).

Но так, как я уже выбрал не самый простой, на данный момент, путь (да, я про python3), то отступать было не куда, решил портировать Lettuce под Python 3.3. Возможно, свою роль в этом сыграли еще свежие воспоминания о UA Pycon 2012, в частности, доклад Михаила Коробова “Как всем перейти на Python 3.x” (http://ua.pycon.org/talks/26).

Дальше все понеслось и после нескольких часов ковыряния в исходниках Lettuce и его зависимостей, github fork, py2to3, http://wiki.python.org/moin/PortingPythonToPy3k, http://lettuce.it/dev/index.html, https://groups.google.com/forum/?fromgroups=#!topic/lettuce-developers/MaOPzOuMQzg и постоянных попытках запустить это все получилась первая рабочая версия:), commit, push, кофе, кофе, печеньки... До беты, конечно, еще писать и писать (фиксить и фиксить), но начало уже есть  и отступать некуда, ибо сзади остались только Python 2.x и большой enterprise.

Ссылки на GitHub repos:

 

 

Продолжение следует...

 


Переодически сталкиваюсь с этой проблемой и приходится гуглить. Решил, записать, что бы проще искать.

Собственно, проблема выглядит так:

File "/home/e0ne/src/project/.venv/app/lib/python2.7/locale.py", line 496, in getdefaultlocale
return _parse_localename(localename)
File "/home/e0ne/src/project/.venv/app/lib/python2.7/locale.py", line 428, in _parse_localename
raise ValueError, 'unknown locale: %s' % localename
ValueError: unknown locale: UTF-8

Проблема заключается в том, что для текущего сеанса шелла(bash, etc) не настроена системная локаль. Ошибка позникала как под Linux(Ubuntu, RHEL-based), так под Mac OS. Фиксится просто:

Добавляем в ~/.bashrc следующие строки:

export LANG="en_US.UTF-8"
export LC_COLLATE="en_US.UTF-8"
export LC_CTYPE="en_US.UTF-8"
export LC_MESSAGES="en_US.UTF-8"
export LC_MONETARY="en_US.UTF-8"
export LC_NUMERIC="en_US.UTF-8"
export LC_TIME="en_US.UTF-8"
export LC_ALL=

Вместо "en_US" нужно(можно) подставить нужное значение. Таже, можно выполнить эти строки в шелле и это будет работать до конца сеанса.