Compare commits

..

No commits in common. "master" and "py2" have entirely different histories.
master ... py2

32 changed files with 269 additions and 394 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
.idea/
*.log *.log
config.py config.py

View File

@ -1,4 +1,4 @@
Copyright (c) 2020, jar3b Copyright (c) 2016, hellotan
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

259
README.md
View File

@ -1,14 +1,11 @@
# py-phias # py-phias
Python application that can operate with FIAS (Russian Address Object DB) Python application that can operate with FIAS (Russian Address Object DB)
Простое приложение для работы с БД ФИАС, написано для Python 3, использует БД PostgreSQL Простое приложение для работы с БД ФИАС, написано для Python 2.7, использует БД PostgreSQL
## Содержание ## Содержание
- [Возможности](#Возможности) - [Возможности](#Возможности)
- [Установка](#Установка) - [Установка](#Установка)
- [Настройка](#Настройка) - [Настройка](#Настройка)
- [API](#Api)
## Возможности ## Возможности
1. API (выходной формат - JSON), основные функции которого: 1. API (выходной формат - JSON), основные функции которого:
@ -25,14 +22,12 @@ Python application that can operate with FIAS (Russian Address Object DB)
## Установка ## Установка
Протестирована работа на следующих ОС: [Windows](#windows) (8.1) и [Debian](#debian-linux) Jessie
Протестирована работа на следующих ОС: [Windows](#windows) (8.1, 10) и [Debian](#debian-linux) Jessie, Stretch.
Необходима версия Python == 3.5
### Зависимости ### Зависимости
_Внимание_! Только Python 3 (для 2.7 пока есть отдельная ветка), только PostgreSQL, только Sphinx. MySQL/MariaDB, _Внимание_! Только Python 2.7+ (на 3+ не тестировал), только PostgreSQL, только Sphinx. MySQL/MariaDB, ElasticSearch/Solr
ElasticSearch/Solr не поддерживаются и, скорее всего, не будут. не поддерживаются и, скорее всего, не будут.
Для работы приложения необходимо достаточное кол-во RAM (1Gb+) и ~5.5Gb места на диске Для работы приложения необходимо достаточное кол-во RAM (1Gb+) и ~5.5Gb места на диске
(3-3.5Gb для скачивания архива с базой, 350-400Mb для индексов Sphinx, 1Gb для базы). Также необходимы root права (3-3.5Gb для скачивания архива с базой, 350-400Mb для индексов Sphinx, 1Gb для базы). Также необходимы root права
@ -43,38 +38,33 @@ ElasticSearch/Solr не поддерживаются и, скорее всего
Предварительно обязательно установить и настроить: Предварительно обязательно установить и настроить:
1. Python 3, pip 1. Python 2.7.x, pip
Для Windows качаем - ставим, для Debian: Для Windows качаем - ставим, для Debian:
``` ```
sudo apt-get install python3-setuptools sudo apt-get install python-setuptools
sudo easy_install3 pip sudo easy_install pip
sudo pip3 install --upgrade pip sudo pip install --upgrade pip
``` ```
2. PostgreSql 9.5 и выше (из-за синтаксиса _ON CONFLICT ... DO_) 2. PostgreSql 9.5 и выше (из-за синтаксиса _ON CONFLICT ... DO_)
Для Windows, как обычно, [качаем](http://www.enterprisedb.com/products-services-training/pgdownload#windows) - ставим, Для Windows, как обычно, [качаем](http://www.enterprisedb.com/products-services-training/pgdownload#windows) - ставим, для Debian:
для Debian 8 и ниже:
``` ```
sudo sh -c 'echo deb http://apt.postgresql.org/pub/repos/apt/ jessie-pgdg main 9.5 > /etc/apt/sources.list.d/postgresql.list' sudo sh -c 'echo deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main 9.5 > /etc/apt/sources.list.d/postgresql.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update sudo apt-get update
sudo apt-get install postgresql-9.5 sudo apt-get install postgresql-9.5
``` ```
, для Debian 9:
`sudo apt-get install postgresql`
Затем создайте пользователя и базу данных и установите расширение pg_trgm: Затем создайте пользователя и базу данных и установите расширение pg_trgm:
``` ```
sudo adduser --no-create-home fias sudo adduser phias
sudo -u postgres psql sudo -u postgres psql
postgres=# CREATE DATABASE fias_db; postgres=# CREATE DATABASE fias_db;
postgres=# CREATE USER fias WITH password 'fias'; postgres=# CREATE USER phias WITH password 'phias';
postgres=# GRANT ALL privileges ON DATABASE fias_db TO fias; postgres=# GRANT ALL privileges ON DATABASE fias_db TO phias;
postgres=# ALTER USER fias WITH SUPERUSER; postgres=# ALTER USER phias WITH SUPERUSER;
postgres=# \q postgres=# \q
sudo -u fias psql -d fias_db -U fias sudo -u phias psql -d fias_db -U phias
postgres=# CREATE EXTENSION pg_trgm SCHEMA public; postgres=# CREATE EXTENSION pg_trgm SCHEMA public;
postgres=# \q
``` ```
3. Sphinx 2.2.1 и новее: 3. Sphinx 2.2.1 и новее:
@ -89,66 +79,69 @@ ElasticSearch/Solr не поддерживаются и, скорее всего
make make
sudo make install sudo make install
``` ```
, не забудте установить _build-essential_ перед этим (касается Debian).
4. Web-сервер с поддержкой WSGI, любой, по Вашему желанию. 4. Web-сервер с поддержкой WSGI, любой, по Вашему желанию.
### Windows ### Windows
1. Установить lxml, скачав whl [отсюда](http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml) и сделав 1. Установить lxml, скачав [отсюда](https://pypi.python.org/pypi/lxml/3.5.0).
`pip install yourmodulename.whl`. 2. Установить unrar.exe (можно установить WinRar целиком).
2. Есть некоторые проблемы с установкой и работой psycopg2 (Windows 10, VS 2015), если у Вас они присутствуют - качаем 3. Установить sphinxapi последней версии (либо взять из директории Sphinx):
[сборку для Windows](http://www.stickpeople.com/projects/python/win-psycopg/)
3. Если есть проблемы с Python-Levenshtein, скачать [отсюда](http://www.lfd.uci.edu/~gohlke/pythonlibs/#python-levenshtein)
и установить
4. Установить unrar.exe (можно установить WinRar целиком).
5. Установить sphinxapi с поддержкой синтаксиса Python3:
``` ```
pip install https://github.com/jar3b/sphinx-py3-api/zipball/master python -m pip install https://github.com/Romamo/sphinxapi/zipball/master
``` ```
4. Установить приложение, скачав релиз `https://github.com/jar3b/py-phias/archive/v0.0.2.zip`, распакуйте его в удобное
Вам место и запустите оттуда `python -m pip install -r requirements.txt`
### Debian Linux ### Debian Linux
1. Установить unrar (non-free): 1. Установить libxml:
``` ```
sudo sh -c 'echo deb ftp://ftp.us.debian.org/debian/ <deb_name> main non-free > /etc/apt/sources.list.d/non-free.list' sudo apt-get install python-dev libxml2 libxml2-dev libxslt-dev
```
2. Установить unrar (non-free):
```
sudo sh -c 'echo deb ftp://ftp.us.debian.org/debian/ stable main non-free > /etc/apt/sources.list.d/non-free.list'
sudo apt-get update sudo apt-get update
sudo apt-get install unrar sudo apt-get install unrar
``` ```
3. Установить sphinxapi последней версии:
2. Устанавливаем и настраиваем libxml:
```
sudo apt-get install libxml2-dev libxslt1-dev python3-dev python3-lxml
```
3. Увстановить python3-dev, для того, чтобы корректно установился levenshtein
```
sudo apt-get install python3-dev
```
### Общая часть:
1. Установим приложение из репозитория:
``` ```
cd /var/www/ sudo pip install https://github.com/Romamo/sphinxapi/zipball/master
sudo mkdir -p fias-api ```
sudo chown fias: /var/www/fias-api 4. Установить, собственно, приложение:
sudo -H -u fias git clone --branch=py3 https://github.com/jar3b/py-phias.git fias-api - полностью:
cd fias-api
sudo pip3 install -r requirements.txt ```
sudo mkdir -p /var/www/py-phias
sudo chown phias: /var/www/py-phias
wget https://github.com/jar3b/py-phias/archive/v0.0.2.tar.gz
sudo -u phias tar xzf v0.0.1.tar.gz -C /var/www/py-phias --strip-components=1
cd /var/www/py-phias
sudo pip install -r requirements.txt
```
- как repo:
```
sudo mkdir -p /var/www/py-phias
sudo chown phias: /var/www/py-phias
cd /var/www
sudo -u phias -H git clone --branch=master https://github.com/jar3b/py-phias.git py-phias
sudo pip install -r requirements.txt
``` ```
2. Иные пути установки ... (soon)
## Настройка ## Настройка
### Первоначальная настройка базы данных ### Первоначальная настройка базы данных
1. Настроим конфиг, для этого необходимо изменить параметры в Вашем wsgi-entrypoint (в моем случае 1. Настроим конфиг, для этого необходимо изменить параметры в Вашем wsgi-entrypoint (в моем случае
[wsgi.py](wsgi.py)): в строке `from config import *` измените _config_ на имя Вашего [passenger_wsgi.py](passenger_wsgi.py)): в строке `from config import *` измените _config_ на имя Вашего
конфигурационного файла (создается рядом с wsgi app) конфигурационного файла (создается рядом с wsgi app), пример конфига находится в файле
[config.example.py](config.example.py).
2. Создадим базу: 2. Создадим базу:
- из архива `sudo -u fias python3 manage.py -b create -s /tmp/fias_xml.rar` - из архива `sudo -u phias python manage.py -b create -s /tmp/fias_xml.rar`
- из директории `sudo -u fias python3 manage.py -b create -s /tmp/fias_xml_unpacked` - из директории `sudo -u phias python manage.py -b create -s /tmp/fias_xml_unpacked`
- онлайн, с сервера ФНС `sudo -u fias python3 manage.py -b create -s http` - онлайн, с сервера ФНС `sudo -u phias python manage.py -b create -s http`
- Также, можно указать конкретную версию ФИАС олько_ при http загрузке, с ключом `--update-version <num>`, где num - - Также, можно указать конкретную версию ФИАС олько_ при http загрузке, с ключом `--update-version <num>`, где num -
номер версии ФИАС, все доступные версии можно получить, выполнив `manage.py -v`. номер версии ФИАС, все доступные версии можно получить, выполнив `manage.py -v`.
@ -159,134 +152,14 @@ sudo apt-get install python3-dev
указанной в `config.folders.temp`, иначе будет Permission denied при попытке bulk-import. указанной в `config.folders.temp`, иначе будет Permission denied при попытке bulk-import.
3. Проиндексируем Sphinx: 3. Проиндексируем Sphinx:
- Windows: `python manage.py -c -i C:\sphinx\bin\indexer.exe -o C:\sphinx\sphinx.conf` - Windows: `python manage.py -c -i C:\sphinx\bin\indexer.exe -o C:\sphinx\sphinx.conf`
- Debian: `sudo python3 manage.py -c -i indexer -o /usr/local/etc/sphinx.conf` - Debian: `sudo python manage.py -c -i indexer -o /usr/local/sphinx/etc/sphinx.conf`
4. Затем запустим searchd: 4. Затем запустим searchd:
- Windows: - Windows:
- Устанавливаем службу: `C:\Sphinx\bin\searchd --install --config C:\Sphinx\sphinx.conf --servicename sphinxsearch` - Устанавливаем службу: `C:\Sphinx\bin\searchd --install --config C:\Sphinx\sphinx.conf --servicename sphinxsearch`
- и запускаем: `net start sphinxsearch` - и запускаем: `net start sphinxsearch`
- Debian: - Debian:
- Запустим : `sudo searchd --config /usr/local/etc/sphinx.conf` - Запустим : `sudo searchd --config /usr/local/sphinx/etc/sphinx.conf`
- если необходимо, добавьте `searchd --config /usr/local/etc/sphinx.conf` в `/etc/rc.local` для автостарта - если необходимо, добавьте `searchd --config /usr/local/sphinx/etc/sphinx.conf` в `/etc/rc.local` для автостарта
5. Для проверки работы выполните `sudo -H -u fias python3 wsgi.py`, по адресу 5. Настроим WSGI server, я использую nginx + passenger. Конфиг для passenger - [passenger_wsgi.py](passenger_wsgi.py),
`http://example.com:8087/find/москва` конфиг для nginx - [py-phias.conf](https://gist.github.com/jar3b/f8f5d351e0ea8ae2ed8e). Вы можете
Вы должны увидеть результаты запроса. использовать любое приемлемое сочетание.
### Установка Web-сервера (для Debian, на примере nginx + gunicorn, без virtualenv)
- Установим nginx и gunicorn:
```
sudo apt-get install nginx
sudo pip3 install gunicorn
```
- Настройте nginx. Примерно так:
```
cd /etc/nginx/sites-available
sudo wget -O fias-api.conf https://gist.githubusercontent.com/jar3b/f8f5d351e0ea8ae2ed8e/raw/2f1b0d2a6f9ce9db017117993954158ccce049dd/py-phias.conf
sudo nano fias-api.conf
```
, отредактируйте и сохраните файл, затем cоздайте линк
```
sudo cp -l fias-api.conf ../sites-enabled/fias-api.conf
```
- Теперь настроим автозапуск gunicorn. Ниже пример конфига для systemd для запуска как сервис, для этого нужно создать
файл `fias.service` в директории `/etc/systemd/system/`
```
[Unit]
Description=Gunicorn instance to serve fias
After=network.target
[Service]
User=fias
Group=www-data
WorkingDirectory=/var/www/fias-api
ExecStart=/usr/local/bin/gunicorn -k gevent_pywsgi --worker-connections 200 --bind unix:/tmp/fias-api-unicorn.sock -m 007 wsgi:application --log-file /var/log/fias_errors.log
[Install]
WantedBy=multi-user.target
```
- Применим изменения: `sudo systemctl daemon-reload`
- Для запуска сервиса используем `sudo systemctl start fias`, для регистрации в автозапуске `sudo systemctl enable fias`
## Api
- `/normalize/<guid>` - актуализирует AOID или AOGUID, на выходе выдает
```
{"aoid": "1d6185b5-25a6-4fe8-9208-e7ddd281926a"}
```
, где _aoid_ - актуальный AOID.
- `/find/<text>?strong=<0,1>`- полнотекстовый поиск по названию адресного объекта. `<text>` - строка поиска.
Если указан параметр `strong=1`, то в массиве будет один результат, или ошибка. Если же флаг не указан, но будет выдано 10
наиболее релевантных результатов.
На выходе будет массив от 1 до 10 элементов:
```
[
{
"cort": 0,
"text": "обл Псковская, р-н Порховский, д Гречушанка",
"ratio": 1537,
"aoid": "1d6185b5-25a6-4fe8-9208-e7ddd281926a"
},
... (up to 10)
]
```
,где _cort_ - количество несовпавших слов, _text_ - полное название адресного объекта, _ratio_ - рейтинг, _aoid_ -
актуальный AOID.
- `/expand/<aoid>` - "раскрывает" AOID, возвращая массив адресных элементов. `<aoid>` - актуальный или неактуальный
AOID
На выходе будет массив из адресных элементов, упорядоченный по AOLEVEL:
```
[
{
"aoguid": "0c5b2444-70a0-4932-980c-b4dc0d3f02b5",
"shortname": "г",
"aoid": "5c8b06f1-518e-496e-b683-7bf917e0d70b",
"formalname": "Москва",
"aolevel": 1,
"socrname": "Город",
"regioncode": 77
},
{
"aoguid": "10409e98-eb2d-4a52-acdd-7166ca7e0e48",
"shortname": "п",
"aoid": "41451677-aad4-4cb9-ba76-2b0eeb156acb",
"formalname": "Вороновское",
"aolevel": 3,
"socrname": "Поселок",
"regioncode": 77
},
{
"aoguid": "266485f4-a204-4382-93ce-7a47ad934869",
"shortname": "ул",
"aoid": "943c8b81-2491-46ee-aee4-48d0c9fada1a",
"formalname": "Новая",
"aolevel": 7,
"socrname": "Улица",
"regioncode": 77
}
]
```
, все поля из таблицы ADDROBJ.
- `/gettext/<aoid>` - возвращает текст для произвольного _aoid_, тект аналогичен тому, который возвращает `/find/<text>`,
на выходе будет такой массив с одним элементом:
```
[
{
"fullname": "г Москва, п Вороновское, п ЛМС, ул Новая"
}
]
```
- Ошибки. Если при выполнении запроса произошла ошибка, то ответ будет таким, объект с одним полем описания ошибки:
```
{
"error": "'Cannot find sentence.'"
}
```

View File

@ -1,5 +1,7 @@
import os import os
import sys import sys
reload(sys)
cwd_dir = os.getcwd() cwd_dir = os.getcwd()
sys.path.append(cwd_dir) sys.path.append(cwd_dir)
sys.setdefaultencoding("utf-8")

View File

@ -1 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from .common import * from .common import *

View File

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
class BasicConfig: class BasicConfig:
logging = False logging = False
debug_print = False
logfile = "" logfile = ""
def __init__(self): def __init__(self):

View File

@ -1,6 +1,9 @@
import psycopg2.extras # -*- coding: utf-8 -*-
from traceback import format_exc from traceback import format_exc
import psycopg2.extras
from aore.miscutils.exceptions import FiasException from aore.miscutils.exceptions import FiasException

View File

@ -1,3 +1,6 @@
# -*- coding: utf-8 -*-
class DbSchema: class DbSchema:
def __init__(self, name, fieldlist, unique_key, xmltag): def __init__(self, name, fieldlist, unique_key, xmltag):
self.tablename = name self.tablename = name
@ -26,3 +29,4 @@ db_shemas['AOTRIG'] = \
None) None)
allowed_tables = ["ADDROBJ", "SOCRBASE"] allowed_tables = ["ADDROBJ", "SOCRBASE"]

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from bottle import Bottle
class BottleCL(object):
def __init__(self):
self._app = Bottle()
self.init_routes()
def init_routes(self):
pass
def add_route(self, route_path, handler):
self._app.route(route_path, callback=handler)
def add_error(self, error_code, handler):
if not self._app.error_handler:
self._app.error_handler = {error_code: handler}
else:
self._app.error_handler[error_code] = handler
def start(self, **kwargs):
self._app.run(**kwargs)

View File

@ -1,3 +1,6 @@
# -*- coding: utf-8 -*-
class FiasException(Exception): class FiasException(Exception):
def __str__(self): def __str__(self):
return repr(self.args[0]) return repr(self.args[0])

View File

@ -1,6 +1,8 @@
import Levenshtein # -*- coding: utf-8 -*-
import re import re
import Levenshtein
def violet_ratio(pattern, candidate): def violet_ratio(pattern, candidate):
arr_pattern = re.split(r"[ ,:.#$-]+", pattern) arr_pattern = re.split(r"[ ,:.#$-]+", pattern)
@ -11,7 +13,7 @@ def violet_ratio(pattern, candidate):
for i in range(len(arr_pattern) - 1, -1, -1): for i in range(len(arr_pattern) - 1, -1, -1):
max_j = -1 max_j = -1
max_ratio = -1 max_ratio = -1
allowed_nums = list(range(len(arr_candidate) - 1, -1, -1)) allowed_nums = range(len(arr_candidate) - 1, -1, -1)
for j in allowed_nums: for j in allowed_nums:
ratio = Levenshtein.ratio(arr_pattern[i], arr_candidate[j]) ratio = Levenshtein.ratio(arr_pattern[i], arr_candidate[j])
@ -19,10 +21,10 @@ def violet_ratio(pattern, candidate):
max_ratio = ratio max_ratio = ratio
max_j = j max_j = j
result.append(max_j * abs(max_ratio)) result.append(max_j*abs(max_ratio))
if max_j > -1: if max_j > -1:
del allowed_nums[max_j] allowed_nums.remove(max_j)
del arr_candidate[max_j] del arr_candidate[max_j]
return sum(result) - len(arr_candidate) return sum(result) - len(arr_candidate)

View File

@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
import logging import logging
import os import os
from bottle import template from bottle import template
from aore.config import Folders, DatabaseConfig, SphinxConfig from aore.config import Folders, DatabaseConfig, SphinxConfig
@ -19,11 +22,11 @@ class SphinxHelper:
os.makedirs(Folders.temp) os.makedirs(Folders.temp)
# оздаем 3 папки для Сфинкса # оздаем 3 папки для Сфинкса
if not os.path.exists(SphinxConfig.var_dir + '/run'): if not os.path.exists(SphinxConfig.var_dir+ '/run'):
os.makedirs(SphinxConfig.var_dir + '/run') os.makedirs(SphinxConfig.var_dir + '/run')
if not os.path.exists(SphinxConfig.var_dir + '/log'): if not os.path.exists(SphinxConfig.var_dir+ '/log'):
os.makedirs(SphinxConfig.var_dir + '/log') os.makedirs(SphinxConfig.var_dir + '/log')
if not os.path.exists(SphinxConfig.var_dir + '/data'): if not os.path.exists(SphinxConfig.var_dir+ '/data'):
os.makedirs(SphinxConfig.var_dir + '/data') os.makedirs(SphinxConfig.var_dir + '/data')
def configure_indexer(self, indexer_binary, config_filename): def configure_indexer(self, indexer_binary, config_filename):
@ -52,7 +55,7 @@ class SphinxHelper:
logging.info("All indexes were created.") logging.info("All indexes were created.")
# remove temp files # remove temp files
for fname, fpath in self.files.items(): for fname, fpath in self.files.iteritems():
try: try:
os.remove(fpath) os.remove(fpath)
except: except:
@ -81,10 +84,10 @@ class SphinxHelper:
def __dbexport_sugg_dict(self): def __dbexport_sugg_dict(self):
logging.info("Place suggestion dict to DB %s...", self.files['dict.txt']) logging.info("Place suggestion dict to DB %s...", self.files['dict.txt'])
fname = os.path.abspath(Folders.temp + "/suggdict.csv") dict_dat_fname = os.path.abspath(Folders.temp + "/suggdict.csv")
csv_counter = 0 csv_counter = 0
with open(self.files['dict.txt'], "r") as dict_file, open(fname, "w") as exit_file: with open(self.files['dict.txt'], "r") as dict_file, open(dict_dat_fname, "w") as exit_file:
line = None line = None
while line != '': while line != '':
nodes = [] nodes = []
@ -108,11 +111,9 @@ class SphinxHelper:
except: except:
pass pass
self.aodp.bulk_csv(AoXmlTableEntry.OperationType.update, "AOTRIG", csv_counter, fname) self.aodp.bulk_csv(AoXmlTableEntry.OperationType.update, "AOTRIG", csv_counter, dict_dat_fname)
logging.info("Done.") logging.info("Done.")
return fname
def __create_ao_index_config(self): def __create_ao_index_config(self):
fname = os.path.abspath(Folders.temp + "/addrobj.conf") fname = os.path.abspath(Folders.temp + "/addrobj.conf")
logging.info("Creating config %s", fname) logging.info("Creating config %s", fname)
@ -155,7 +156,7 @@ class SphinxHelper:
sphinx_var_path=SphinxConfig.var_dir) sphinx_var_path=SphinxConfig.var_dir)
f = open(out_filename, "w") f = open(out_filename, "w")
for fname, fpath in self.files.items(): for fname, fpath in self.files.iteritems():
if ".conf" in fname: if ".conf" in fname:
with open(fpath, "r") as conff: with open(fpath, "r") as conff:
for line in conff: for line in conff:

View File

@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
def trigram(inp): def trigram(inp):
inp = u"__" + inp + u"__" inp = u"__"+inp+u"__"
output = [] output = []
for i in range(0, len(inp) - 2): for i in range(0, len(inp) - 2):

View File

@ -1,62 +1,58 @@
# -*- coding: utf-8 -*-
import json import json
import logging import logging
from borest import app, Route, Error
from bottle import response, request from bottle import response
from aore.search.fiasfactory import FiasFactory from aore.search.fiasfactory import FiasFactory
from miscutils.bottlecl import BottleCL
class App: class App(BottleCL):
_factory = None
def __init__(self, log_filename): def __init__(self, log_filename):
super(App, self).__init__()
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG, filename=log_filename) logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG, filename=log_filename)
App._factory = FiasFactory() self._factory = FiasFactory()
@Route(r'/expand/<aoid:re:[\w]{8}(-[\w]{4}){3}-[\w]{12}>') def get_app(self):
class Expand(object): return self._app
def get(self, aoid):
response.content_type = 'application/json'
response.set_header('Access-Control-Allow-Origin', '*')
return json.dumps(App._factory.expand(aoid)) def init_routes(self):
self.add_route(r'/expand/<aoid:re:[\w]{8}(-[\w]{4}){3}-[\w]{12}>', self.__expand)
self.add_route(r'/normalize/<aoid:re:[\w]{8}(-[\w]{4}){3}-[\w]{12}>', self.__normalize)
self.add_route(r'/gettext/<aoid:re:[\w]{8}(-[\w]{4}){3}-[\w]{12}>', self.__gettext)
self.add_route(r'/find/<text>', self.__find)
self.add_route(r'/find/<text>/<strong>', self.__find)
self.add_error(404, self.basic_error_handler)
self.add_error(500, self.basic_error_handler)
@Route(r'/normalize/<aoid:re:[\w]{8}(-[\w]{4}){3}-[\w]{12}>') def __expand(self, aoid):
class Normalize(object): response.content_type = 'application/json'
def get(self, aoid): response.set_header('Access-Control-Allow-Origin', '*')
response.content_type = 'application/json'
response.set_header('Access-Control-Allow-Origin', '*')
return json.dumps(App._factory.normalize(aoid)) return json.dumps(self._factory.expand(aoid))
@Route(r'/aoid2aoguid/<aoguid:re:[\w]{8}(-[\w]{4}){3}-[\w]{12}>') def __normalize(self, aoid):
class Convert(object): response.content_type = 'application/json'
def get(self, aoguid): response.set_header('Access-Control-Allow-Origin', '*')
response.content_type = 'application/json'
response.set_header('Access-Control-Allow-Origin', '*')
return json.dumps(App._factory.convert(aoguid)) return json.dumps(self._factory.normalize(aoid))
@Route(r'/find/<text>') def __find(self, text, strong=False):
class Find(object): strong = (strong == "strong")
def get(self, text): response.content_type = 'application/json'
strong = 'strong' in request.query and request.query.strong == '1' response.set_header('Access-Control-Allow-Origin', '*')
response.content_type = 'application/json'
response.set_header('Access-Control-Allow-Origin', '*')
return json.dumps(App._factory.find(text, strong)) return json.dumps(self._factory.find(text, strong))
@Route(r'/gettext/<aoid:re:[\w]{8}(-[\w]{4}){3}-[\w]{12}>') def __gettext(self, aoid):
class GetText(object): response.content_type = 'application/json'
def get(self, aoid): response.set_header('Access-Control-Allow-Origin', '*')
response.content_type = 'application/json'
response.set_header('Access-Control-Allow-Origin', '*')
return json.dumps(App._factory.gettext(aoid)) return json.dumps(self._factory.gettext(aoid))
@staticmethod @staticmethod
@Error([404, 500])
def basic_error_handler(error): def basic_error_handler(error):
response.content_type = 'application/json' response.content_type = 'application/json'
response.set_header('Access-Control-Allow-Origin', '*') response.set_header('Access-Control-Allow-Origin', '*')

View File

@ -1,14 +1,16 @@
# -*- coding: utf-8 -*-
import logging import logging
import psycopg2
import re import re
import traceback import urllib
import urllib.parse
from bottle import template
from uuid import UUID from uuid import UUID
import psycopg2
import traceback
from bottle import template
from aore.config import DatabaseConfig, BasicConfig from aore.config import DatabaseConfig, BasicConfig
from aore.dbutils.dbimpl import DBImpl from aore.dbutils.dbimpl import DBImpl
from .search import SphinxSearch from search import SphinxSearch
class FiasFactory: class FiasFactory:
@ -17,7 +19,6 @@ class FiasFactory:
self.searcher = SphinxSearch(self.db) self.searcher = SphinxSearch(self.db)
self.expand_templ = template('aore/templates/postgre/expand_query.sql', aoid="//aoid") self.expand_templ = template('aore/templates/postgre/expand_query.sql', aoid="//aoid")
self.normalize_templ = template('aore/templates/postgre/normalize_query.sql', aoid="//aoid") self.normalize_templ = template('aore/templates/postgre/normalize_query.sql', aoid="//aoid")
self.convert_templ = template('aore/templates/postgre/convert_query.sql', aoid="//aoid")
self.gettext_templ = template('aore/templates/postgre/gettext_query.sql', aoid="//aoid") self.gettext_templ = template('aore/templates/postgre/gettext_query.sql', aoid="//aoid")
# Проверка, что строка является действительым UUID v4 # Проверка, что строка является действительым UUID v4
@ -37,30 +38,29 @@ class FiasFactory:
if rule == "boolean": if rule == "boolean":
assert isinstance(param, bool), "Invalid parameter type" assert isinstance(param, bool), "Invalid parameter type"
if rule == "uuid": if rule == "uuid":
assert isinstance(param, str) and self.__check_uuid( assert (isinstance(param, str) or isinstance(param, unicode)) and self.__check_uuid(
param), "Invalid parameter value" param), "Invalid parameter value"
if rule == "text": if rule == "text":
assert isinstance(param, str), "Invalid parameter type" assert isinstance(param, str) or isinstance(param, unicode), "Invalid parameter type"
assert len(param) > 3, "Text too short" assert len(param) > 3, "Text too short"
pattern = re.compile(r"[A-za-zА-Яа-я \-,.#№]+") pattern = re.compile(r"[A-za-zА-Яа-я \-,.#№]+")
assert pattern.match(param), "Invalid parameter value" assert pattern.match(param), "Invalid parameter value"
# text - строка поиска # text - строка поиска
# strong - строгий поиск (True) или "мягкий" (False) (с допущением ошибок, опечаток) # strong - строгий поиск (True) или "мягкий" (False) (с допущением ошибок, опечаток)
# Строгий используется при импорте из внешних систем (автоматически), где ошибка критична # Строгий используется при импорте из внешних систем (автоматически), где ошибка критична
def find(self, text, strong=False): def find(self, text, strong=False):
try: try:
text = urllib.parse.unquote(str(text)) text = urllib.unquote(text).decode('utf8')
self.__check_param(text, "text") self.__check_param(text, "text")
self.__check_param(strong, "boolean") self.__check_param(strong, "boolean")
results = self.searcher.find(text, strong) results = self.searcher.find(text, strong)
except Exception as err: except Exception, err:
if BasicConfig.logging: if BasicConfig.logging:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc(err))
if BasicConfig.debug_print: return dict(error=err.args[0])
traceback.print_exc()
return dict(error=str(err))
return results return results
@ -73,33 +73,10 @@ class FiasFactory:
rows = self.db.get_rows(sql_query, True) rows = self.db.get_rows(sql_query, True)
assert len(rows), "Record with this AOID not found in DB" assert len(rows), "Record with this AOID not found in DB"
except Exception as err: except Exception, err:
if BasicConfig.logging: if BasicConfig.logging:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc(err))
if BasicConfig.debug_print: return dict(error=err.args[0])
traceback.print_exc()
return dict(error=str(err))
if len(rows) == 0:
return []
else:
return rows[0]
# Преобразует AOID в AOGUID
def convert(self, aoid: str):
try:
self.__check_param(aoid, "uuid")
sql_query = self.convert_templ.replace("//aoid", aoid)
rows = self.db.get_rows(sql_query, True)
assert len(rows), "Record with this AOID not found in DB"
except Exception as err:
if BasicConfig.logging:
logging.error(traceback.format_exc())
if BasicConfig.debug_print:
traceback.print_exc()
return dict(error=str(err))
if len(rows) == 0: if len(rows) == 0:
return [] return []
@ -117,12 +94,10 @@ class FiasFactory:
sql_query = self.expand_templ.replace("//aoid", normalized_id) sql_query = self.expand_templ.replace("//aoid", normalized_id)
rows = self.db.get_rows(sql_query, True) rows = self.db.get_rows(sql_query, True)
except Exception as err: except Exception, err:
if BasicConfig.logging: if BasicConfig.logging:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc(err))
if BasicConfig.debug_print: return dict(error=err.args[0])
traceback.print_exc()
return dict(error=str(err))
return rows return rows
@ -136,11 +111,9 @@ class FiasFactory:
rows = self.db.get_rows(sql_query, True) rows = self.db.get_rows(sql_query, True)
assert len(rows), "Record with this AOID not found in DB" assert len(rows), "Record with this AOID not found in DB"
except Exception as err: except Exception, err:
if BasicConfig.logging: if BasicConfig.logging:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc(err))
if BasicConfig.debug_print: return dict(error=err.args[0])
traceback.print_exc()
return dict(error=str(err))
return rows return rows

View File

@ -1,16 +1,18 @@
import Levenshtein # -*- coding: utf-8 -*-
import logging import logging
import re import re
import sphinxapi
import time import time
import Levenshtein
import sphinxapi
from aore.config import BasicConfig from aore.config import BasicConfig
from aore.config import SphinxConfig from aore.config import SphinxConfig
from aore.miscutils.exceptions import FiasException from aore.miscutils.exceptions import FiasException
from aore.miscutils.fysearch import violet_ratio from aore.miscutils.fysearch import violet_ratio
from aore.miscutils.trigram import trigram from aore.miscutils.trigram import trigram
from .wordentry import WordEntry from wordentry import WordEntry
from .wordvariation import VariationType from wordvariation import VariationType
class SphinxSearch: class SphinxSearch:
@ -60,7 +62,7 @@ class SphinxSearch:
self.client_show.SetSortMode(sphinxapi.SPH_SORT_EXTENDED, "krank DESC") self.client_show.SetSortMode(sphinxapi.SPH_SORT_EXTENDED, "krank DESC")
def __get_suggest(self, word, rating_limit, count): def __get_suggest(self, word, rating_limit, count):
word_len = len(word) word_len = str(len(word) / 2)
trigrammed_word = '"{}"/1'.format(trigram(word)) trigrammed_word = '"{}"/1'.format(trigram(word))
self.__configure(SphinxConfig.index_sugg, word_len) self.__configure(SphinxConfig.index_sugg, word_len)
@ -93,7 +95,7 @@ class SphinxSearch:
return outlist return outlist
# Получает список объектов (слово) # Получает список объектов (слово)
def __get_word_entries(self, words): def __get_word_entries(self, words, strong):
we_list = [] we_list = []
for word in words: for word in words:
if word != '': if word != '':
@ -106,16 +108,17 @@ class SphinxSearch:
# text - текст найденного адресного объекта # text - текст найденного адресного объекта
# ratio - рейтинг найденного пункта # ratio - рейтинг найденного пункта
# cort - рейтинг количества совпавших слов # cort - рейтинг количества совпавших слов
def find(self, text, strong): def find(self, text, strong):
def split_phrase(phrase): def split_phrase(phrase):
phrase = phrase.lower() phrase = unicode(phrase).lower()
return re.split(r"[ ,:.#$]+", phrase) return re.split(r"[ ,:.#$]+", phrase)
# сплитим текст на слова # сплитим текст на слова
words = split_phrase(text) words = split_phrase(text)
# получаем список объектов (слов) # получаем список объектов (слов)
word_entries = self.__get_word_entries(words) word_entries = self.__get_word_entries(words, strong)
word_count = len(word_entries) word_count = len(word_entries)
# проверяем, есть ли вообще что-либо в списке объектов слов (или же все убрали как частое) # проверяем, есть ли вообще что-либо в списке объектов слов (или же все убрали как частое)
@ -166,23 +169,23 @@ class SphinxSearch:
parsed_ids.append(match['attrs']['aoid']) parsed_ids.append(match['attrs']['aoid'])
results.append( results.append(
dict(aoid=match['attrs']['aoid'], dict(aoid=match['attrs']['aoid'],
text=str(match['attrs']['fullname']), text=unicode(match['attrs']['fullname']),
ratio=match['attrs']['krank'], ratio=match['attrs']['krank'],
cort=i)) cort=i))
# При строгом поиске нам надо еще добавить fuzzy и выбрать самое большое значение при отклонении # При строгом поиске нам надо еще добавить fuzzy и выбрать самое большое значение при отклонении
# выше заданного, по сути переопределяем ratio # выше заданного
if strong: if strong:
for result in results: for result in results:
result['ratio'] = violet_ratio(text, result['text'].lower()) result['strong_rank'] = violet_ratio(text, result['text'].lower())
# Сортируем по убыванию признака # Сортируем по убыванию признака
results.sort(key=lambda x: x['ratio'], reverse=True) results.sort(key=lambda x: x['strong_rank'], reverse=True)
# Если подряд два одинаково релеватных результата - это плохо, на автомат такое отдавать нельзя # Если подряд два одинаково релеватных результата - это плохо, на автомат такое отдавать нельзя
if abs(results[0]['ratio'] - results[1]['ratio']) == 0.0: if abs(results[0]['strong_rank'] - results[1]['strong_rank']) == 0.0:
raise FiasException("No matches") raise FiasException("No matches")
else: else:
results = [results[0]] return results[0]
return results return results

View File

@ -1,13 +1,10 @@
# -*- coding: utf-8 -*-
import re import re
from aore.config import SphinxConfig from aore.config import SphinxConfig
from aore.search.wordvariation import WordVariation, VariationType from aore.search.wordvariation import WordVariation, VariationType
def cleanup_string(word):
return word.replace('-', '').replace('@', '').replace('#', '')
class WordEntry: class WordEntry:
# Варианты распеределния для слов с первыми двумя символами, где: # Варианты распеределния для слов с первыми двумя символами, где:
# 0 - не найдено, 1 - найдено одно, x - найдено много (>1) # 0 - не найдено, 1 - найдено одно, x - найдено много (>1)
@ -54,14 +51,14 @@ class WordEntry:
def __init__(self, db, word): def __init__(self, db, word):
self.db = db self.db = db
self.bare_word = word self.bare_word = str(word)
self.word = cleanup_string(self.bare_word) self.word = self.__cleanify(self.bare_word)
self.word_len = len(self.word) self.word_len = len(unicode(self.word))
self.parameters = dict(IS_FREQ=False, SOCR_WORD=None) self.parameters = dict(IS_FREQ=False, SOCR_WORD=None)
self.ranks = self.__init_ranks() self.ranks = self.__init_ranks()
# Заполняем параметры слова # Заполняем параметры слова
for mt_name, mt_values in self.match_types.items(): for mt_name, mt_values in self.match_types.iteritems():
self.__dict__[mt_name] = False self.__dict__[mt_name] = False
for mt_value in mt_values: for mt_value in mt_values:
self.__dict__[mt_name] = self.__dict__[mt_name] or re.search(mt_value, self.ranks) self.__dict__[mt_name] = self.__dict__[mt_name] or re.search(mt_value, self.ranks)
@ -75,6 +72,9 @@ class WordEntry:
self.MT_LAST_STAR = False self.MT_LAST_STAR = False
self.MT_AS_IS = True self.MT_AS_IS = True
def __cleanify(self, word):
return word.replace('-', '').replace('@', '')
def variations_generator(self, strong, suggestion_func): def variations_generator(self, strong, suggestion_func):
default_var_type = VariationType.normal default_var_type = VariationType.normal
# Если слово встречается часто, ставим у всех вариантов тип VariationType.freq # Если слово встречается часто, ставим у всех вариантов тип VariationType.freq
@ -115,9 +115,8 @@ class WordEntry:
"UNION ALL SELECT COUNT(*), NULL FROM \"AOTRIG\" WHERE word='{}' " \ "UNION ALL SELECT COUNT(*), NULL FROM \"AOTRIG\" WHERE word='{}' " \
"UNION ALL SELECT COUNT(*), MAX(scname) FROM \"SOCRBASE\" WHERE socrname ILIKE '{}'" \ "UNION ALL SELECT COUNT(*), MAX(scname) FROM \"SOCRBASE\" WHERE socrname ILIKE '{}'" \
"UNION ALL SELECT COUNT(*), NULL FROM \"SOCRBASE\" WHERE scname ILIKE '{}'" \ "UNION ALL SELECT COUNT(*), NULL FROM \"SOCRBASE\" WHERE scname ILIKE '{}'" \
"UNION ALL SELECT frequency, NULL FROM \"AOTRIG\" WHERE word='{}';".format(self.word, self.word_len, "UNION ALL SELECT frequency, NULL FROM \"AOTRIG\" WHERE word='{}';".format(
self.word, self.bare_word, self.word, self.word_len, self.word, self.bare_word, self.bare_word, self.word)
self.bare_word, self.word)
result = self.db.get_rows(sql_qry) result = self.db.get_rows(sql_qry)
@ -147,5 +146,8 @@ class WordEntry:
def get_type(self): def get_type(self):
return ", ".join([x for x in self.match_types if self.__dict__[x]]) return ", ".join([x for x in self.match_types if self.__dict__[x]])
def __unicode__(self):
return self.word
def __str__(self): def __str__(self):
return str(self.word) return str(self.word)

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from enum import Enum # Типы вариаций слова from enum import Enum # Типы вариаций слова

View File

View File

@ -1 +0,0 @@
SELECT AOGUID FROM "ADDROBJ" WHERE AOID=(SELECT AOID FROM "ADDROBJ" WHERE AOID='{{ aoid }}' OR AOGUID='{{ aoid }}') AND ACTSTATUS=True AND LIVESTATUS=True AND NEXTID IS NULL LIMIT 1;

View File

@ -1,4 +1,4 @@
import codecs # -*- coding: utf-8 -*-
import os import os
from aore.config import Folders from aore.config import Folders
@ -38,7 +38,7 @@ class AoDataParser:
# Prepare to next iteration # Prepare to next iteration
self.counter = 0 self.counter = 0
self.currentpage += 1 self.currentpage += 1
self.csv_file = codecs.open(self.base_filename.format(self.currentpage), "w", "utf-8") self.csv_file = open(self.base_filename.format(self.currentpage), "w")
exit_nodes = list() exit_nodes = list()
for allowed_field in self.allowed_fields: for allowed_field in self.allowed_fields:

View File

@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
import logging import logging
import os.path import os.path
from traceback import format_exc
import rarfile import rarfile
import requests import requests
from traceback import format_exc
from aore.config import Folders, UnrarConfig from aore.config import Folders, UnrarConfig
from aore.miscutils.exceptions import FiasException from aore.miscutils.exceptions import FiasException
from .aoxmltableentry import AoXmlTableEntry from aoxmltableentry import AoXmlTableEntry
class AoRar: class AoRar:

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
import re import re
from enum import Enum from enum import Enum
@ -20,7 +22,7 @@ class AoXmlTableEntry:
@classmethod @classmethod
def from_dir(cls, file_name, path): def from_dir(cls, file_name, path):
# for extracted into folder # for extracted into folder
return AoXmlTableEntry(file_name, lambda: open(path + file_name, 'rb')) return AoXmlTableEntry(file_name, lambda: open(path + file_name))
def __init__(self, file_name, lamda_open): def __init__(self, file_name, lamda_open):
matchings = re.search('^(AS_)(DEL_)*([A-Z]+)', file_name) matchings = re.search('^(AS_)(DEL_)*([A-Z]+)', file_name)
@ -40,5 +42,8 @@ class AoXmlTableEntry:
def close(self): def close(self):
self.file_descriptor.close() self.file_descriptor.close()
def __unicode__(self):
return "Entry for {} table {}".format(self.operation_type, self.table_name)
def __str__(self): def __str__(self):
return "Entry for {} table {}".format(self.operation_type, self.table_name) return "Entry for {} table {}".format(self.operation_type, self.table_name)

View File

@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
import logging import logging
import psycopg2 import psycopg2
from bottle import template from bottle import template
@ -63,3 +66,4 @@ class DbHandler:
self.db.execute(sql_query) self.db.execute(sql_query)
logging.info("All indexes was deleted.") logging.info("All indexes was deleted.")

View File

@ -1,10 +1,11 @@
from pysimplesoap.client import SoapClient # -*- coding: utf-8 -*-
from pysimplesoap.client import SoapClient
class SoapReceiver: class SoapReceiver:
def __init__(self): def __init__(self):
self.client = SoapClient( self.client = SoapClient(
location="https://fias.nalog.ru/WebServices/Public/DownloadService.asmx", location="http://fias.nalog.ru/WebServices/Public/DownloadService.asmx",
action='http://fias.nalog.ru/WebServices/Public/DownloadService.asmx/', action='http://fias.nalog.ru/WebServices/Public/DownloadService.asmx/',
namespace="http://fias.nalog.ru/WebServices/Public/DownloadService.asmx", namespace="http://fias.nalog.ru/WebServices/Public/DownloadService.asmx",
soap_ns='soap', trace=False, ns=False) soap_ns='soap', trace=False, ns=False)

View File

@ -1,7 +1,10 @@
# -*- coding: utf-8 -*-
import logging import logging
import psycopg2
from os import walk, path from os import walk, path
import psycopg2
from aore.config import DatabaseConfig from aore.config import DatabaseConfig
from aore.dbutils.dbimpl import DBImpl from aore.dbutils.dbimpl import DBImpl
from aore.dbutils.dbschemas import allowed_tables, db_shemas from aore.dbutils.dbschemas import allowed_tables, db_shemas
@ -59,9 +62,9 @@ class Updater:
mode = None mode = None
while not mode: while not mode:
try: try:
mode = int(input('Enter FIAS update version (3 digit):')) mode = int(raw_input('Enter FIAS update version (3 digit):'))
except ValueError: except ValueError:
print("Not a valid fias version, try again.") print "Not a valid fias version, try again."
return mode return mode

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
from lxml import etree from lxml import etree
@ -5,8 +7,7 @@ class XMLParser:
def __init__(self, parse_function): def __init__(self, parse_function):
self.parse_function = parse_function self.parse_function = parse_function
@staticmethod def fast_iter(self, context, func, *args, **kwargs):
def fast_iter(context, func, *args, **kwargs):
for event, elem in context: for event, elem in context:
# print event # print event
func(elem, *args, **kwargs) func(elem, *args, **kwargs)

View File

@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
from aore import config from aore import config
# Config section # Config section
# Address and port where sphinx was listening, # Address and port where sphinx was listening,
# may be a unix socket like 'unix:///tmp/fias-api.sock' # may be a unix socket like 'unix://tmp/pyphias.sock'
# or TCP socket like '127.0.0.1:9312'
config.SphinxConfig.listen = "127.0.0.1:9312" config.SphinxConfig.listen = "127.0.0.1:9312"
# Base sphinx folder # Base sphinx folder
config.SphinxConfig.var_dir = "C:\\Sphinx" config.SphinxConfig.var_dir = "C:\\Sphinx"
# DB config # DB config
config.DatabaseConfig.database = "fias_db" config.DatabaseConfig.database = "fias_db"
config.DatabaseConfig.host = "127.0.0.1" config.DatabaseConfig.host = "192.168.0.1"
config.DatabaseConfig.port = 5432 config.DatabaseConfig.port = 5432
config.DatabaseConfig.user = "postgres" config.DatabaseConfig.user = "postgres"
config.DatabaseConfig.password = "postgres" config.DatabaseConfig.password = "postgres"
@ -22,5 +22,4 @@ config.UnrarConfig.path = "C:\\Program Files\\WinRAR\\unrar.exe"
config.Folders.temp = "E:\\!TEMP" config.Folders.temp = "E:\\!TEMP"
config.BasicConfig.logging = True config.BasicConfig.logging = True
config.BasicConfig.debug_print = False
config.BasicConfig.logfile = "pyphias.log" config.BasicConfig.logfile = "pyphias.log"

View File

@ -1,26 +0,0 @@
from aore import config
# Config section
# Address and port where sphinx was listening,
# may be a unix socket like 'unix:///tmp/fias-api.sock'
# or TCP socket like '127.0.0.1:9312'
config.SphinxConfig.listen = "unix:///tmp/fias-api.sock"
# Base sphinx folder
config.SphinxConfig.var_dir = "/etc/sphinx"
# DB config
config.DatabaseConfig.database = "fias_db"
config.DatabaseConfig.host = "127.0.0.1"
config.DatabaseConfig.port = 5432
config.DatabaseConfig.user = "postgres"
config.DatabaseConfig.password = "postgres"
# Path to unrar, in Linux may be 'unrar'
config.UnrarConfig.path = "unrar"
# Temp folder, in Linux may be '/tmp/myfolder'
config.Folders.temp = "/tmp/fitmp"
config.BasicConfig.logging = True
config.BasicConfig.debug_print = False
config.BasicConfig.logfile = "pyphias.log"

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import logging import logging
import optparse import optparse
import sys import sys
@ -22,7 +23,7 @@ def print_fias_versions():
all_versions = imp.get_update_list() all_versions = imp.get_update_list()
print("Installed version: {}".format(current_version)) print("Installed version: {}".format(current_version))
print("Available updates:") print("Avaliable updates:")
print("Number\t\tDate") print("Number\t\tDate")
for upd in all_versions: for upd in all_versions:
mark_current = (' ', '*')[int(upd['intver']) == current_version] mark_current = (' ', '*')[int(upd['intver']) == current_version]
@ -59,14 +60,6 @@ def get_allowed_updates(updates_str, mode="create"):
yield all_versions[-1] yield all_versions[-1]
else: else:
assert len(user_defined_list) == 1, "Ony single update number allowed for DB create" assert len(user_defined_list) == 1, "Ony single update number allowed for DB create"
processed = False
for ver in all_versions:
if ver['intver'] == user_defined_list[0]:
processed = True
yield ver
else:
if not processed:
raise Exception("Update #%d not found on remote server" % user_defined_list[0])
if mode == "update": if mode == "update":
for uv in all_versions: for uv in all_versions:
uv_ver = uv['intver'] uv_ver = uv['intver']

View File

@ -8,12 +8,11 @@ try:
except ImportError: except ImportError:
assert "No config" assert "No config"
# Create main app # Define main app
phias.App(config.BasicConfig.logfile) phias_app = phias.App(config.BasicConfig.logfile)
# Define wsgi app # Define wsgi app
application = phias.app application = phias_app.get_app()
# Run bottle WSGI server if no external # Run bottle WSGI server if no external
if __name__ == '__main__': if __name__ == '__main__':
application.run(host='0.0.0.0', port=8087, debug=True) phias_app.start(host='0.0.0.0', port=8087, debug=True)

View File

@ -1,10 +1,10 @@
lxml>=3.6.0 lxml>=3.4.0
bottle==0.12.13 bottle>=0.12.9
psycopg2==2.7.5 psycopg2>=2.6.1
PySimpleSOAP==1.16.2 PySimpleSOAP==1.16
python-Levenshtein==0.12.0 python-Levenshtein==0.12.0
rarfile==3.0 enum34>=1.0.0
rarfile
requests>=2.8.1 requests>=2.8.1
soap2py>=1.16 soap2py==1.16
git+https://github.com/jar3b/sphinx-py3-api sphinxapi
bottle-oop-rest>=0.0.5