Google App Engine and SQLite InternalError: unable to open database file

This is quite an old issue and I’m suppressed that is have not been corrected yet. Maybe windows people do rarely use SQLite stub in dev, but it will change soon since it is becoming a default one.

Apparently the problem root is bad TMP folder resolution case the TMP env variable is being stripped down by the GAE dev server.

The workaround for this is putting this in your app.yaml file:

env_variables:
TMP: C:UsersUSERNAMEAppDataLocalTemp

or

env_variables:
TMP: ..temp

Kudos to mattbergin

Advertisements

Django is missing TEMPLATE_STRING_IF_NONE

IMO Django is missing a setting TEMPLATE_STRING_IF_INVALID that you cold set to globally override how None value is rendered.

Some argue that you should:

  1. plan to always have a value — ?! what a perverted idea, all fields required
  2. use empty string as a default — django does that, I do not like it, form fields return empty strings for empty fields
  3. use default_if_none filter — to much typing
  4. use if tag — see 3.

There is a difference between empty string value and no having value at all. But in templates in most cases there is not, and django handles it backwards — it differentiate these in templates but in actual data model None is defaulted to empty string.

Not having a value at all — hence None — mean you do not have to store metadata for a filed, which is good for rg. in GAE.

Put this in your main.py — where you initialize wsgi application — to render empty string for None valued variables:

# Patch template Variable to output empty string for None values
from django.template.base import Variable
_resolve_lookup = Variable._resolve_lookup
def new_resolve_lookup(self, *args, **kwargs):
  o = _resolve_lookup(self, *args, **kwargs)
  return o or u""
Variable._resolve_lookup = new_resolve_lookup

Ustawa o ochronie danych osobowych pozwala walczyć ze spamen

Gotowiec emaila którego należy wysłać w odpowiedzi na SPAM, w zależności od tego czy email zawierał treść oferty (cenę, link do promocji lub podobne) należy wykreślić drugą część pierwszego zdania.

Witam serdecznie,

Przesłali do mnie państwo przesyłkę na adres na adres: (adres@mail), która to w myśl przepisów ustawy o ustawy z dnia 18 lipca 2002 r. o świadczeniu usług drogą elektroniczną (Dz.U. 2002 nr 144 poz. 1204) jest niezamówioną informacją handlową (umieszczenie dyrektywy UOKiK nie sprawia, że spam przestaje być spamem, trzeba zachować także inne wymagania).

Wymieniony adres należy do możliwej do zidentyfikowania — bez poniesienia nadmiernych kosztów — osoby fizycznej. Nie wolno go przetwarzać bez poinformowania i zgody tej osoby. W szczególności nie wolno automatycznie wprowadzać takiego adresu email do zbiorów i baz danych, tj. wyłącznie w wyniku rozstrzygnięcia tylko na podstawie operacji w systemie informatycznym.

W związku z powyższym na podstawie art. 32 ustawy z dnia 29 sierpnia 1997 r. o ochronie danych osobowych (t. j. z 2002, Dz. U. Nr 101, poz. 926 ze zm.) zwracam się do państwa o udzielenie mi następujących informacji:

  1. Nazwę zbioru z którego państwo skorzystali do pobrania adresu.
    W przypadku braku własnego zbioru proszę podać nazwę katalogu, bazy danych lub firmę z której państwo adres pozyskali (proszę nie pisać “z internetu” bo to naiwna odpowiedź).
  2. Kto jest jego administratorem (poprzez określenie jego pełnej nazwy i adresu, a gdy jest to osoba fizyczna – jej imienia i nazwiska oraz miejsca zamieszkania).
  3. Od kiedy dane są przetwarzane (tj. daty ich pozyskania).
  4. Jakie jest źródło pozyskania danych (proszę wskazać osobę lub firmę).
  5. W jaki sposób i komu dane są udostępniane (proszę podać listę osób, firm i instytucji w raz z ich kontaktowym adresem email).
  6. Udzielenie informacji o przesłankach podjęcia rozstrzygnięcia, o którym mowa w art. 26a ust. 2 ochronie danych osobowych (proszę opisać w jaki sposób np. ocenili państwo adres należy do osoby czy firmy).
  7. Podania w powszechnie zrozumiałej formie treści przetwarzanych danych.
  8. Potwierdzenia daty i formy wykonania obowiązku informacyjnego wynikającego z  art. 25 ustawy z dnia 29 sierpnia 1997 r. o ochronie danych osobowych.

Zgodnie z art. 33 ustawy z dnia 29 sierpnia 1997 r. o ochronie danych osobowych (t. j. z 2002, Dz. U. Nr 101, poz. 926 ze zm.) proszę o udzielenie odpowiedzi w ciągu 30 dni.

Pozwolę sobie zwrócić uwagę, że nie udzielenie odpowiedzi podlega karze grzywny, karze ograniczenia wolności lub pozbawienia wolności do roku, zgodnie z art. 54 ustawy z dnia 29 sierpnia 1997 r. o ochronie danych osobowych (t. j. z 2002, Dz. U. Nr 101, poz. 926 ze zm.)

Nie jest moim celem dyskusja co jest a co nie danymi osobowymi oraz czy przesłana wiadomość była informacją handlową, proszę o tym porozmawiać z prawnikiem.

To czy użyty adres jest publicznie dostępny jest nieistotne, jest prawnie chroniony przed przetwarzaniem i jego przetwarzanie jest regulowanie odpowiednimi przepisami — tak, zbieranie adresów email i wysłanie wiadomości jest przetwarzaniem.

Ustawa o ochronie danych osobowych daje mi prawo do zadania pytań, a na państwa firmę nakłada obowiązek udzielenia odpowiedzi niezależnie od tego czy państwa firma mi coś przesłała.

Jeszcze raz proszę o udzielenie odpowiedzi na zadane pytania, w przypadku braku odpowiedzi lub odpowiedzi wątpliwych, skierują skargę do GIODO z wnioskiem o ukaranie i przeprowadzenie w państwa firmie kontroli.

Oczywiście należy wziąć pod uwagę, że niektóry spamerzy — ci poza jurysdykcją polskiego prawa — mają w nosie nasze przepisy i nic nie wskóracie, ale pozostałe firmy się zwykle przestraszą i skasują email.

Removing Models from Google App Engine at minimum cost

There come times when data schema changes so you’ll have to remove some models by the bunch. AFAIK there is no currently a „TRUNCATE TABLE MyKind” or “DROP TABLE MyKind” equivalent in Google App Engine data store, so you’ll have to write an ordinary request handler to do the bidding or use MapReduce.

I have wrote some utility code to do background processing on the very first versions of GAE — when there was no MapReduce available — and I’m happy with that, but I surely would like to compare my own solution with MapReduce based once — maybe some other time…

Regardless of what type of solution you’ll use, you’ll need to remember about indexes and how the writing cost is calculated — deleting is considered writing. Each time you write something you’ll update an index, this count toward data store the quotas where you”ll pay for each write (I mean delete): 2 Writes + 2 Writes per indexed property value + 1 Write per composite index value

So it’s good thing to remove all composite indexes on the model before you’ll start deleting them in bulk, it’s not immediate so remember to check indexes section in the app engine console. Other thing you might do is to disable indexing on all the model properties — I’m not sure thou if it will impact the index update on delete, it’ probably will not (need to check that some day), but there is harm in disabling those indexes also.

I use a simple push task queue handler:

def purge_audit_logs(request):
    url = reverse('purge_audit_logs')
    q = AuditLog.query()
    seq, cursor, more  = fetch_page(request, q, page_size=50, keys_only=True)
    ndb.delete_multi(seq)
    if more:
        schedule_next(request, url, cursor=cursor, queue_name="cleanup")
    return HttpResponse("OK")

This will do the purging in serial minimizing the cost and allowing you to do things slowly, but if you’ll what a fast solution use fan-out and a faster queue:

def purge_audit_logs(request):
    url = reverse('purge_audit_logs')
    q = AuditLog.query()
    seq, cursor, more  = fetch_page(request, q, page_size=50, keys_only=True)
    if more:
        schedule_next(request, url, cursor=cursor, queue_name="cleanup")
    ndb.delete_multi(seq)
    return HttpResponse("OK")

And here are my utility functions for task queues handlers:

def fetch_page(request, query, page_size=30, **query_options):
    cursor = request.POST.get("cursor", None)
    if cursor:
        cursor = Cursor(urlsafe=cursor)
    col, cursor, more = query.fetch_page(page_size, start_cursor=cursor, **query_options)
    if cursor:
        cursor = cursor.urlsafe()
    return col, cursor, more

def schedule_next(url, queue_name='deferred', cursor=None):
    task = Task(countdown=0, url=url, params={'cursor': cursor})
    Queue(queue_name).add(task)

These are simplified, in reality these are do much more, like task naming to disallow duplicate task scheduling for the same data. Task can occasionally run twice, so you’ll need to make them idempotent.