Category: Technology

Posted on in Technology, Python

Lua is a popular embedded scripting language used by many tools and games such as World of Warcraft. There is also a Python port called Lunatic-Python. It wraps around liblua (the C library) and allows you to execute Lua code in Python and Python code in Lua.

I've created a fork of Lunatic-Python in:

https://github.com/kennu/Lunatic-Python

This fork allows you to create multiple Lua states, each of which has its own set of global variables.

Previously, in Python you would execute Lua with a global state like this:

import lua
lua.globals()['msg'] = {'hello':'world'}
lua.execute("print(msg.hello)")

Now you can create multiple independent states using the new_state() function:

import lua
s1 = lua.new_state()
s2 = lua.new_state()
s1.globals()['msg'] = {'hello':'world'}
s2.globals()['msg'] = {'hello':'foobar'}
s1.execute("print(msg.hello)")
s2.execute("print(msg.hello)")

The purpose of this is that you can safely create a new, clean state when you're executing Lua in a Python function, and you don't have to worry about the global state overlapping with some other function or thread.

Please consider this fork experimental for now. It passes the test_lua.py tests but it may still contain memory leaks or other problems. Also, I have not tested it for embedding Python into Lua at all.

Posted on in Technology, Ubuntu

Tuttuun tapaan päivitin taas blogiani pyörittävän Linode-palvelimen Ubuntun tuoreimpaan 11.10-versioon heti sen ilmestyttyä. Päivitys meni läpi aika lailla ongelmitta.

Varnishin default.vcl-tiedostossani oli jotain kustomointeja, jotka vaativat pientä säätämistä, Gunicorn-palvelimesta piti ottaa väliaikaisesti gevent pois käytöstä ja SphinxSearch-paketti ei käynnistynyt ennen kuin loin sille /var/run/sphinxsearch-hakemiston. Melko pientä hienosäätöä siis vain. Yleensä Ubuntu-palvelinpäivityksissä ei voi kuitenkaan olettaa, että ihan kaikki toimisi suoraan, vaan tällaisia pikkukorjauksia joutuu aina tekemään.

Olen jo aiemmin ottanut tavaksi konfiguroida Ubuntu-palvelimet sillä tavoin, että vältän systeemitiedostojen muokkaamista (esimerkiksi /etc/sudoers, /etc/bashrc) ja sijoitan mieluummin kustomoinnit erillisiin alihakemistoihin, joita on yleensä tarjolla useimpiin tarkoituksiin (/etc/sudoers.d, /etc/profile.d).

Noin muuten Ubuntu 11.10 on itseäni kiinnostava versio lähinnä sen takia, että siinä on tuoreehkot versiot Node.js:stä (0.4.9), Redisistä (2.2.11) ja MongoDB:stä (1.8.2). Myös Nginx on vihdoin versiossa 1.0.5. Railsin osalta pitää vielä tutustua tarkemmin, missä mennään Ruby 1.9:n ja Rails 3:n kanssa.

Posted on in Technology, MongoDB, Python, Django

Lähetin tänään patchit kahteen Django-projektiin niiden saattamiseksi toimimaan MongoDB:n kanssa:

github.com/kennu/Django-facebook
bitbucket.org/kennu/django-registration

Django-facebook on moduuli, jonka avulla Djangon käyttäjätietokannan saa helposti integroitua Facebookiin. Facebook-käyttäjä yhdistetään automaattisesti aiempaan Djangon User-objektiin, jolla on sama sähköpostiosoite, tai sitten hänelle luodaan uusi User-objekti. Samalla erilliseen UserProfile-objektiin tallentuu Facebook-käyttäjän ID ja access-token, joita voi sitten käyttää Graph APIssa.

Django-registration puolestaan tarjoaa valmiit toiminnot Django-käyttäjien rekisteröintiin ja sähköpostiaktivointiin. Django-facebook käyttää sitä apunaan uusien käyttäjien luomisessa.

Tekemäni patchit korjaavat pieniä epäyhteensopivuuksia päivämäärien käsittelyssä ja JOIN-kyselyissä siten, että ne toimivat yhteen django-mongodb-enginen kanssa.

Posted on in Technology

Facebook permanently stores all status updates you ever wrote. Even if you don't see them anywhere, they're still there. The only way to delete them is to open them in the browser manually, one-by-one, and click "Remove Post" for each of them.

The Graph API only allows you to delete status updates that were originally created by your own application. So it doesn't help here.

To make deleting old status updates a bit more automated, I combined two tools:

Using this Python script, you can generate a Selenium Test Case that loops through all your old status updates (older than 30 days by default) and deletes them by clicking the relevant buttons on the page:

You can run it like this:

./fbdel.py > fbdel.html

The script will open a web browser and ask you to authenticate the fbconsole application. Then it takes a while and generates the fbdel.html file, printing out the status updates to stderr.

Once the script is finished, you can open fbdel.html in Selenium. You will see a long list of actions and you can click them individually if you just want to see what the script is doing. To run them all, click the "Play entire test suite" button. Please understand you will do this at your own risk.

It's very likely that the script will timeout and break at some point. You can always run fbdel.py again to generate a new Test Case with the remaining status updates. Also I am not certain whether FQL allows you to access you entire status update history. For me, it seemed to return about 2 years of them and I'm still trying to check whether there are older ones lurking out there somewhere.

Finally, remember that this will only delete status updates. If you want to clean up your entire history, you need to do the same for your shared links, likes, checkins, photos, comments, application-specific updates (e.g. Twitter) and so on.

Posted on in Technology, MongoDB

Olen tässä lueskellut The Definitive Guide To MongoDB -nimistä kirjaa (Kindle), joka on hyvä katsaus MongoDB-tietokannan toimintaperiaatteisiin. Huomasin, että MongoDB:ssä on paljon sellaisia ominaisuuksia, joita ei tule välttämättä ajatelleeksi sitä pintapuolisesti kokeillessa.

Vaikka MongoDB käyttää JSONin kaltaisia BSON-tietorakenteita dokumenttien kuvaamiseen, se erottelee tietotyypit paljon tarkemmin kuin JSON. Tyypillinen esimerkki tästä ovat päivämäärät, joita JSON ei määrittee lainkaan erilliseksi tyypikseen. MongoDB tallentaa ne siten, että JavaScript-shellissä käsiteltyinä päivämäärät ovat automaattisesti Date-objekteja.

64-bittisyys haasteena

MongoDB erottelee toisistaan erilaiset numeeriset tyypit, joita JavaScript käsittelee aina liukulukuina. BSONissa mahdollisia tyyppejä ovat tavu (byte), 32-bittinen kokonaisluku (int32), 64-bittinen kokonaisluku (int64) sekä 64-bittinen liukuluku (double). Näiden kanssa on syytä olla tarkkana, sillä JavaScript-shellissä toimiessa kaikki luvut ovat oletuksena liukulukuja.

MongoDB:hen siis tallentuu aina liukuluku, kun JavaScriptistä käsin syötetään dataa. Tämä voi aiheuttaa ongelmia lähinnä suurissa lukuarvoissa, sillä osaa 64-bittisistä kokonaisluvuista ei suoraan vastaa mikään 64-bittinen liukuluku. JavaScript ei pysty käsittelemään esimerkiksi lukua 9223372036854770001, vaan se muuttuu muotoon 9223372036854770000.

Ratkaisuna ongelmaan MongoDB:n JavaScript-shell tarjoaa NumberLong-tyypin, jota voi käyttää 64-bittisten kokonaislukujen tallentamiseen ja käsittelyyn. Esimerkiksi näin:

db.tests.insert({intval:NumberLong('9223372036854770001')})

{ "_id" : ObjectId("..."), "intval" : NumberLong("9223372036854770001") }

NumberLongia on käytettävä myös $inc-operaattorin kanssa, jotta lukuarvo säilyy kokonaislukuna:

db.tests.update({$inc:{intval:NumberLong('1')}})

{ "_id" : ObjectId("..."), "intval" : NumberLong("9223372036854770002") }

Silloin kun MongoDB:hen tallennettua dataa käsitellään esimerkiksi Python-sovelluksesta käsin pymongo-kirjastolla, datatyypit menevät automaattisesti oikein, sillä Python ymmärtää eron liukulukujen ja kokonaislukujen välillä. Tyypiksi tulee automaattisesti int64 (NumberLong), jos lukuarvo on yli 32-bittinen.

ObjectId

MongoDB-dokumenttien tunnisteena käytettävä ObjectId on oma mielenkiintoinen tietorakenteensa. Useimmissa muissa tietokannoissa ID-arvot ovat kokonaislukuja tai merkkijonoja, mutta MongoDB käsittelee niitä objekteina.

ObjectId koostuu 12 tavusta, jotka esitetään yleensä heksadesimaalissa muodossa ObjectId('4e71009ce0395a172f000001'). Tavuilla on seuraavanlainen merkitys:

0-3: Timestamp (4e71009c) = päivämäärä 2011-09-14 22:29:32
4-6: Machine ID (e0395a) = MD5('MacBookAir.local') kolme ens. tavua
7-8: Process ID (172f) = 5935 ttys002    0:00.28 python
9-11: Counter (000001) = laskuri

Näistä ehkä mielenkiintoisin on aikaleima, josta voidaan siis aina päätellä, milloin jokin MongoDB:hen tallennettu dokumentti on luotu. MongoDB-shellissä leiman voi tarkistaa näin:

ObjectId('4e71009ce0395a172f000001').getTimestamp()
ISODate("2011-09-14T19:29:32Z")

Eräs ObjectId-tunnisteiden tärkeimpiä ominaisuuksia on se, että niitä ei generoida palvelimella, vaan tietokanta-ajureissa. Toisin kuin esimerkiksi MySQL:n autoincrement-kentissä, sovellus tietää siis jo etukäteen, millä ID:llä dokumentti tullaan tallentamaan tietokantaan. Tämä tarkoittaa, että mitään LAST_INSERT_ID() -kikkailua ei tarvita relaatioiden luomiseen. Kaikki viittaukset dokumentien välille voidaan määritellä valmiiksi, ja sitten vain tallennetaan kaikki dokumentit kerralla.

Posted on in Technology, Ruby on Rails

MongoMapper is a great MongoDB ORM for Ruby on Rails. It basically allows you to use MongoDB as a drop-in replacement for MySQL/PostgreSQL.

However, it has one particular weakness: It is impossible to filter MongoDB queries by exact array values. MongoMapper converts array queries into $in operations, which means that all documents that contain any of the specified array values will be returned.

For example, assume you have two MongoDB documents:

{ "name": "A", "items": [1, 2, 3] }

{ "name": "B", "items": [1, 2] }

The following MongoMapper query will return both documents, even if you might have expected only the first one:

MyModel.where(:items => [1, 2, 3])

My $exact fork on GitHub

I've created a fork of plucky, the query engine used by MongoMapper, which adds a new pseudo operator called $exact. My fork can be found here:

kennu/plucky (master, based on v0.4.1)
kennu/plucky/tree/backport_exact_v3 (backported to v0.3.8)

I've also submitted my change set as a pull request to the author of plucky.

You can use the fork in your Rails Gemfile like this (you'll need the v0.3.8 version with current mongo_mapper):

gem 'plucky', :git => 'https://github.com/kennu/plucky.git', :branch => 'backport_exact_v3'
gem 'mongo_mapper'

After running bundle install, you can specify queries in the this format:

MyModel.where(:items => { :$exact => [1, 2, 3] })

This query will return only the first document of the previous example above.

Remember, though, that you must keep your arrays in a specific order for these queries to work. An array of [1, 2, 3] won't match if you query it as [2, 3, 1].

Posted on in Technology, Apple

Windows 7:llä on ongelmia kytkeytyä Mac OS X Lionin levyjakoihin oletusasetuksilla. Se väittää, että mikään käyttäjätunnus- ja salasanayhdistelmä ei kelpaa, vaikka samat toimisivat Linuxista käsin oikein.

Ongelma korjaantuu avaamalla Windowsista Regedit ja lisäämällä seuraava arvo asetuksiin:

Key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa
Value name: LmCompatibilityLevel
Value type: DWORD
Value: 1

Uudelleenkäynnistyksen jälkeen Windows saa jälleen yhteyden Mac OS X:n levyjakoihin. Jos ongelmia vielä on, kannattaa tarkistaa, että yhteyttä yritetään oikealla domainilla/workgroupilla. Oletuksena Windows 7 käyttää jostain syystä Windows-koneen omaa nimeä eikä palvelimen nimeä.