• 11Dec

    ai_bolit

    Всем привет. Да, знаю, я давно не писал. Ну простите, и как это не банально, был занят. А заставила меня написать одна мысля. И пожалуйста, дочитайте это до конца, потому что или это очень круто или я опять что то не понимаю, и с температурой 38 мне лучше за клаву не садиться.

    Кеш. Я им пользуюсь для того, чтобы данные, которые я долго вычисляю — положить в память куда нить, чтоб если они понадобились — быстро их оттуда взять. Ну а если их там нет, то просто пересчитать и положить. Если вы им пользуетесь также, то читайте дальше иначе напишите комментарий, который начнется со слов: «Тю, блин, а я его совершенно по другому юзаю, глянь…»

    Т.е. на сетах и гетах все сводится к примерно следующему алгоритму.

    1. from django.core.cache import cache
    2.  
    3. def setter(key,l_value,timeout=0):
    4.     val = cache.get(key)
    5.     if val is None:
    6.         val = l_value()
    7.         cache.set(key,val,timeout)
    8.     return val

    где l_value — это ссылка на функцию, значение которой будет получено, в случае если его нет в кеше.

    Вот этот умопомрачительный алгоритм у меня лежит в основе кеширования.

    Хух… если у вас также, то идете дальше. Надеюсь сейчас осталось достаточно народу.

    Прогуливаясь легкой и непринужденной походкой по блогосфере рунете я уже в который раз натыкаюсь на довольно странное решение следующей проблемы.

    Если есть какие либо данные которые системе нужны часто, но вычисляются долго, то их прямое получение по алгоритму, описанному выше — просто убивает систему. Потому что как только они пропадают из кеша — все, кому нужны эти данные — начинают скопом — все вместе их получать. Например статистика по пользователям у вас вычисляется 5 сек, а выводится на главной странице, с посещаемостью 50 чел в сек, значит одновременно эти данные будут получать 250 процессов – что, может привести к смерти.

    Решение рунета — 2 кеша. В один кладем с одним эксперейшеном, в другой с таким же, но чуть больше. Я думаю многие натыкались на такие решения, но вкратце — если заэкспаирилось в первом — берем из второго, но первый, кто узнал, о том, что заэкспаирилось — пересчитывает.

    Тут просто тьма тьмущая узких мест

    1.Старт у системы должен быть особый. Т.е. в нулевой точке в кеше уже должны быть часто доступные данные.
    2.У вас двойные данные в кеше, т.е. две копии, а ведь часто бывает и такое, что трудновычисляемые данные — это и большие данные.
    3.И последнее — если процесс, который вычисляет заекспаревшиеся данные — умирает. То умирают все. Явно теряем в отказоустойчивости.

    Кратко опишу свой алгоритм решения, и построенный на нем джанговый кешовый бекенд (за базовый взят мемкешовый).

    Если в ячейку с ключем класть не данные, а хеш из двух значений — данные, и время, когда их надо обновить. (ТАДАМ избавились от второго пункта)

    А что если ты перед началом вычислений будеш класть в другой системый и уникальный ключ в кеше время, когда первый, начавший вычисления – планирует их закончить. А остольные процессы, которые захотят получить данные и не увидят их — смогут орентироваться на системный ключь, чтоб понимать, что данные скоро будут и их необходимо подождать или мы не дождались и попробуем еще раз. (ТАДАМ избавились от первого и третьего)

    А теперь скучный код. Чтоб легче было читать — его необходимо скрестить с алгоритмом, который я описывал выше. И если функция гет — вернет None то эти данные сразу начнут вычисляться.

    Небольшой рандом необходим, чтоб все процессы сразу не набросились вычислять после первого сдавшегося, а нарастающий таймаут необходим для быстрого избавления от быстрых данные и размеренного ожидания долгих.

    1. from django.core.cache.backends.memcached import CacheClass as BaseCacheClass
    2. from datetime import datetime,timedelta
    3. from time import sleep
    4. from random import random
    5.  
    6. ADDITION_EXP_TIME = 20
    7. TIME_FOR_CREATE = 5
    8.  
    9. class CacheClass(BaseCacheClass):
    10.     def add(self, key, value, timeout=0):
    11.         timeout = timeout or self.default_timeout
    12.         value = {'v':value,'e':datetime.now()+timedelta(seconds=timeout)}
    13.         super(CacheClass,self).add(key,value,timeout+ADDITION_EXP_TIME)
    14.        
    15.     def set(self, key, value, timeout=0):
    16.         timeout = timeout or self.default_timeout
    17.         value = {'v':value,'e':datetime.now()+timedelta(seconds=timeout)}
    18.         super(CacheClass,self).set(key,value,timeout+ADDITION_EXP_TIME)
    19.    
    20.     def get(self,key, default=None):
    21.         wait_next_val = 0
    22.         while True:
    23.             wait_next_val += 0.1
    24.             value = super(CacheClass,self).get(key,default)
    25.             now = datetime.now()
    26.            
    27.             if value is not None and now<value['e']:
    28.                 return value['v']
    29.            
    30.             wait_system_key = 'wait_system__%s__wait_system'%key
    31.             wait_system = super(CacheClass,self).get(wait_system_key)
    32.            
    33.             # if you find expired key first or you don't wait the next person
    34.             if wait_system is None or wait_system<now:
    35.                 super(CacheClass,self).set(wait_system_key,datetime.now()+timedelta(seconds=TIME_FOR_CREATE),TIME_FOR_CREATE + 5)
    36.                 return None
    37.            
    38.             #if somebody already getting a new value
    39.             if value is not None:
    40.                 return value['v']
    41.            
    42.             sleep(random()*wait_next_val)

    И на всякий случай. Если вы все таки считаете это отличной идее. Кладем это в файлик с незамысловатым названием smart_cache.py рядом с settings.py, а в settings.py записываем

    1. CACHE_BACKEND = "smart_cache://127.0.0.1:11211"

    Rating 3.00 out of 5
    [?]

    Tags: , , ,

  • 09Nov

    winpdbХоть простой и нативный pdb и так нам давал все что надо, все же приятно понимать, что есть еще чтото, что может сделать наш девелов приятней. WinPDB – одна из этих приятностей (наткнулся на нее вДжанговкой Вики) . Если коротко – это дебагер с приятным пользовательским интерфейсом, которой кросc-платформенный к слову говоря.

    Пользовать легко.
    Раньше вы коде оставляли:

    1. import pdb; pdb.set_trace()

    А теперь получается чуть длиннее:

    1. import rpdb2; rpdb2.start_embedded_debugger('mysuperpassword')

    На сколько я понял, этот пароль нужен для авторизации дебагера в эту точку прерывания. Т.е. как и pdb, rpdb2 отсанавливает выполение в этой строке.

    Запускаем winpdb. File => Attach. В появившемся окне вводим наш пароль mysuperpassword. В полученном списке выбираем наш.

    Но самое клевое, что теперь мы можем дебагером зацепиться там, где раньше не умели, например wsgi скрипт висит в апаче. Мы можем по средствам этого механизма присосаться и к нему.
    Screenshot-views.py

    P.S. …

    И Django Cheet Sheet, кто еще не знает…
    И надо будет испытать django-tinymce

    Rating 3.00 out of 5
    [?]
  • 01Oct

    Я уже посягал на суверинитет джанги. Но это было давно и не правда. Более того, меня тогда убедили, что делаю я глупости, и я даже убедился сам, в последствии, что на самом деле делаю глупости. Но мысть о том, что urls.py не нужен – не перестает меня беспакоить. Поэтому очередно фин, аморальный бред – называйте как хотите, но мне безумно нравится.

    Идея проста. Вьюха и урла всегда вместе – а значит одно должно быть декоратаром для другого.

    Итак главный urls.py имеет обычный вид

    1. from django.conf.urls.defaults import *
    2.  
    3. urlpatterns = patterns('',
    4.     (r'^someurl/',include('someapp.url_view')),
    5. )

    /someapp/url_view.py – тут у нас сбстно и хранятся вьюхи с урлами. Как видите, декоратор tourl нам земенил запись в urls.py

    1. from django.http import HttpResponse
    2. from tourl import tourl
    3.  
    4. @tourl(r'^and/$')
    5. def and_(request):
    6.     return HttpResponse('and')
    7.  
    8. @tourl(r'^gg/$')
    9. def index(request):
    10.     return HttpResponse('OK')

    /someapp/tourl.py – ну и код самого декоратора

    1. from django.conf.urls.defaults import *
    2. import sys
    3. import functools
    4. def tourl(url_patern,*args,**kwargs):
    5.     def paramed_decorator(func):
    6.         @functools.wraps(func)
    7.         def decorated(self):
    8.             return func(self)
    9.         module =sys.modules[func.__module__]
    10.        
    11.         if not hasattr(module, 'urlpatterns'):
    12.             module.urlpatterns = patterns('',)
    13.              
    14.         module.urlpatterns   += patterns('',
    15.             url(url_patern,decorated,*args,**kwargs),
    16.         )
    17.         return decorated
    18.     return paramed_decorator

    Помоему и симпотично и по производительности не бьет. Вобщем конфетка! Что скажите?

    PS: Добавил снипет.

    PSS: В снипетсах посоветовали добавить functools.wraps

    PSS: А еще можно использовать и так

    1. from django.http import HttpResponse
    2. from tourl import tourl, patterns,url
    3.  
    4.  
    5.  
    6. @tourl(r'^and/$')
    7. def and_(request):
    8.     return HttpResponse('and')
    9.  
    10.  
    11. def index(request):
    12.     return HttpResponse('OK')
    13.  
    14. tourl(r'^gg/$')(index)
    15.  
    16.  
    17. def ordinary(request):
    18.     return HttpResponse('Ordinary')
    19.  
    20. urlpatterns += patterns('',
    21.             url(r'^ord/$',ordinary)
    22.         )

    Rating 3.00 out of 5
    [?]

    Tags: , , , , ,

  • 05Aug
    1. import sys
    2. #from twisted.internet.interfaces import IAddress
    3. name = 'twisted.internet.interfaces.IAddress'
    4.  
    5.  
    6. def import_class(line):
    7.     line = line.split('.')
    8.     mname = '.'.join(line[:-1])
    9.     if mname in sys.modules:
    10.         mname = sys.modules[mname]
    11.     else:
    12.         __import__(mname)
    13.         mname = sys.modules[mname]
    14.     return getattr(mname,line[-1])
    15.  
    16.  
    17. print import_class(name)

    Rating 3.00 out of 5
    [?]

    Tags: ,

  • 19May

    как делать group by в моделях. В доках вещь не очевидная. Работает только с транком.

    1. def count_rubrics(post_filters):
    2.     """get dict rubric id => post count if this rubric
    3.    """
    4.     newobj = M.Post.objects.filter(**post_filters).annotate(count_rubric=Count('rubric')).\
    5.              values('rubric','count_rubric')
    6.     newobj.query.group_by = ['rubric_id']
    7.     ret = {}
    8.     for item in newobj:
    9.         ret[item['rubric']] = item['count_rubric']
    10.     return ret

    Rating 3.00 out of 5
    [?]

    Tags: , , ,

  • 07Apr

    Если вы хотите пользоваться всеми переменными окружения Django, но при это “находится” не врутри какой либо вьюхи, т.е. код запускается не через Http запрос, а к примеру – через крон, то начинаться этот скрипт у вас должен такими словами, и лежать он должен в коре вашего проекта:

    1. #!/usr/bin/python
    2. # coding: utf-8
    3. from django.core.management import setup_environ
    4. import settings
    5. setup_environ(settings)

    Успехов…

    Rating 3.00 out of 5
    [?]

    Tags: , , , ,

  • 14Dec

    Сегодня попробовал, пришел в легкий экстаз….

    В какойнить вьюхе, данные которой вам интересны напишите просто

    1. import pdb
    2. pdb.set_trace()

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

    1. [14/Dec/2008 14:40:21] "GET /myview/ HTTP/1.1" 200 2
    2. > /home/oduvan/www/AppDjango/views.py(10)index()
    3. -> if request.user.is_anonymous:
    4. (Pdb)

    и в консоле поддерживаются следующие основные комманды:
    n – следующая сомманда
    s – зайти в рутину
    r – выйти из рутины
    l [first,[last]] – вывести код, и место, где ты сейчас находишся. Если не указан first и last то выводится текущая позиция. first и last определяет с какой по какую строчки необходимо вывести.
    p – вывести результат операции
    c – продолжать выполнения программы до следующего брейкпоинта
    w – показать текущий стек вызова
    q – выйти.

    Этого мне пока в полне достаточно. Натолкнулся тут. А еще можно почитать на python.org.

    Если чесно, узнай я про енту фикчу прикольную раньше – и спал бы по дольше, и выглядел бы лучше.

    Rating 3.00 out of 5
    [?]

    Tags: , , ,

  • 03Dec

    Буквально пару строк кода добавили массу дополнительных возможностей.

    теперь если функция cp_before возвратит значение – это значит это и будет результатом всего запроса. Появилась функция cp_after которая вызывается в конце всей обработки

    результат работы функций cp__* может быть не обязательно наследник HttpResponse, но и любая другая структура языка, которая уже будет преобразована к оному с помощью функции cp_prepare

    Вот собственно необходимые доработки в классе AddNewUrl :

    1.      def __call__(self,*t,**k):
    2.         if 'before' in self.prefix :
    3.             ret = self.prefix['before'](*t,**k)
    4.             if ret:
    5.                 return ret
    6.         ret =  self.view(*t,**k)
    7.         if 'prepare' in self.prefix:
    8.             newret =   self.prefix['prepare'](ret,*t,**k)
    9.             if newret: ret = newret
    10.         if 'after' in self.prefix:
    11.             self.prefix['after'](*t,**k)
    12.         return ret

    теперь, к примеру задача вывода JSON структуры сводится к

    1. from django.http import HttpResponse
    2. import simplejson as json
    3. class BaseViews(object):
    4.     def prepare_cp(self,response,request):
    5.         return HttpResponse(json.dumps(response))
    6.     def cp__all_rooms(self,request):
    7.         return {'hi':'World','id':request.GET.get('id')}

    Rating 3.00 out of 5
    [?]

    Tags: , , , , ,

  • 01Dec

    Сегодня из интереса написал небольшой модуль сериализации в ХМЛ. На скорую руку. Как по мне – довольно элегантное решение. Как думаете?

    1. def xml_escape(text):
    2.     return str(text).replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;').replace("'",'&apos;')
    3.  
    4. def xmlSerial(name,attr=None,inner=None):
    5.     if attr:
    6.         name_attr = name+' '+' '.join(map(lambda (a,b):a+'="'+xml_escape(b)+'"',attr.items()))+' '
    7.     else:
    8.         name_attr = name
    9.    
    10.     if inner:
    11.         if type(inner) == list:
    12.             inner_str = ''.join(map(lambda a: xmlSerial(*a),inner))
    13.         else:
    14.             inner_str = inner
    15.         return '<'+name_attr+'>'+inner_str+'</'+name+'>'
    16.     else:
    17.         return '<'+name_attr+'/>'

    Вот, как его мона юзать:

    1. print xmlSerial('HI',{'a':'1','c':3,'d':'WOW'},[['RR'],['WOW',{'and_attr':'45t'}],['WIN',{'a':1},'HI IT IS INNER']])
    2. print xmlSerial('HI',{'a':'1','c':3,'d':'WOW'},[['RR'],['WOW',{'and_attr':'45t'}]])
    3. print xmlSerial('HI',{'a':'1','c':3,'d':'WOW'},[['RR'],['WOW']])
    4. print xmlSerial('HI',{'a':'1','c':3,'d':'WOW'},'AND INNER')
    5. print xmlSerial('HI',{'a':'1','c':3,'d':'WOW'})
    6. print xmlSerial('HI',{'a':'1'})
    7. print xmlSerial('HI')

    Rating 3.00 out of 5
    [?]

    Tags: , , , ,

  • 21Nov

    По сути мы к урлам привязываем функции, а значит получается мы используем простое функциональное программирование, и не очевидно, как в такой ситуации воспользоваться всем обилием объектно-ориентированого программирования, которое предоставляет Python.

    Также, Django, продвигает полезную фикчу, которое должно подтолкнуть пользователей всего мира делится своим джанговским кордом, это инклюд одного приложения в другого. И все было бы очень просто и радужно, если б не несколько небольших «но». Это «MIDDLEWARE_CLASSES». Как правило, приложениям, которые вы хотите подключить, необходимо с собой тянуть эти классы. Тем, кто не в курсе, объясню, в них собран функционал, который будет запускатся до и после вызова функции, которая закрепленная за урлом. Но иногда этот функционал необходим только этому апликейшену, а для остальных это просто лишний и не нужный своп. Нет возможности привязать миддл только к определенному апликейшену.

    Вопросы расширяемости. Это вопрос объектно-орентированого программирования, о котором я говорил выше. Его по сути нет. Вам дали апликейшен – пользуйтесь. Все, что можно менять в его работе – лежит в настройках ВСЕГО проекта, либо в параметрах передаваемых самому инклюду. А хотите больше? Копайтесь в моих исходниках и пишите то, что вам надо.

    Вот мое решение.

    1.  
    2. from django.conf.urls.defaults import url
    3.  
    4. class AddNewUrl(object):
    5.     def __init__(self,prefix,view,*t):
    6.         self.prefix = prefix
    7.         self.view = view
    8.         self.t = t
    9.     def __call__(self,*t,**k):
    10.         if 'before' in self.prefix :
    11.             self.prefix['before'](*t,**k)
    12.         return self.view(*t,**k)
    13.  
    14. def newpatterns(prefix, *args):
    15.     pattern_list = []
    16.     for t in args:
    17.         pattern_list.append(newurl(prefix, *t))
    18.     return pattern_list
    19.  
    20. def newurl(prefix,regex,view,*t):
    21.     return url(regex,AddNewUrl(prefix,view,*t),*t)
    22.  
    23.  
    24. def classpatterns(cp):
    25.     o_cp = cp()
    26.     cp_prefix = {}
    27.     if hasattr(o_cp,'before_cp'):
    28.         before_cp = getattr(o_cp,'before_cp')
    29.         if callable(before_cp):
    30.             cp_prefix['before'] = before_cp
    31.     func_list = ()
    32.     for item in dir(o_cp):
    33.         if item.startswith('cp__'):
    34.             item_func = getattr(o_cp,item)
    35.             if callable(item_func):
    36.                 func_list = func_list + ((r'^'+item[4:]+'/$',item_func),)
    37.     #raise str(func_list)
    38.     return newpatterns(cp_prefix,*func_list)

    Теперь урлы и вьювы – объединены в одном классе.
    Если вы хотите написать обработчик урла «^home/$» , то вам можно просто написать функцию cp_home в которой и будет вестись обработка вашего запроса.

    Если у вас есть функция, которую необходимо дергать перед запускам каждого обработчика, то назовите ее before_cp

    Но есть и ограничения, которые я еще не преодолевал, т.к. они пока особо меня не касались:

    Нельзя в полной мере воспользоваться регекспами в урлах.
    Нельзя использовать инклюды внутри такого аппликейшена. А вы думаете они правда нужны при таком построении приложения?
    Приложения такого типа типа лучше не использовать на «первом уровне» а только инклюдить в основное.

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

    Вот пример использования:

    файл ajax/vurl.py

    1.  
    2. from newpatern.defaults import newpatterns, classpatterns
    3. django.http import HttpResponse
    4. class Views(BaseViews):
    5.     def cp__connect(self,request):
    6.           return HttpResponse('HI ALL')
    7.     def cp__NO(self,request):
    8.           return HttpResponse(reqest.GET.get('n'))
    9.  
    10. urlpatterns = classpatterns(Views)

    Обратите внимание, что в этих урлах первым парамером передается self. Да, это экземпляр класса, который создается один раз

    В Ваш файл с урлами просто вставте:

    1. (r'^flashajax/',include('ajax.vurls')),

    Теперь по запросу flashajax/connect/
    в ответ получить “HI ALL”

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

    Что касается меня, то я его уже юзаю в одном из своих проектов, пока успешно. Хотя проект еще не в продакшене, поэтому однозначно судить нельзя

    Rating 4.00 out of 5
    [?]

    Tags: , , , ,

« Previous Entries   

Recent Comments

  • Every body remembers that modern life seems to be not very c...
  • Alexander, спасибо, интересная трактовка. Но целью статьи бы...
  • "И всегда выходит так, что супер силы, супер сразу — это суп...
  • Так, на вскидку - Лукъяненко пишет в жанре фантастика....
  • Не выдержала моя душа, вот по поводу идиотизма которого тебе...