Python ORM tools
An object relational mapper, or ORM, is a piece of software that sits between the object model used by an application and the relational model used by a conventional RDBMS system. It's a useful piece of software, as it allows one to blend the best of both worlds. The application can be written in terms of objects, and the database can be managed using conventional tools.
Enter Python. As a dynamic language, Python makes easy to write a lot of the glue code that is needed to translate between the OO and the RDBMS worlds. Python ORMs take advantage of this to allow for simpler use.
An ORM can solve all these problems, and add a few niceties of its own. In the best case, it will allow for a true object persistence model. Good ORMs also allow changes in the object model to be automatically mapped to the relational one. The opposite is also true, albeit less useful, assuming that you will be writing object-oriented code. In the best scenario, the application code can be completely ridden from any SQL reference, allowing the full use of an object-oriented paradigm for its development.
The basic challenge in ORM design is how to map the OO and relational models seamingless. Good ORM tools will run as transparently as possible, allowing the programmer to forget the intrincacies of the relational model. The process may start with an representation of the object model, either graphical or textual, that can be compiled to generate the classes as needed. Its also possible to declare the object model in pure Python, counting on the ORM tool to generate and handle the glue code automagically.
To implement true object persistence, each object needs a unique identifier. Each ORM will use a different approach here. However, one issue that is common to all ORMs is how to handle multiple copies of the same object. For the sake of consistence, objects should be singletons. If the ORM tool doesn't do it automatically, then it's up to the programmer to make sure that no multiple copies of the same object exist at the same time, avoiding concurrency and consistency issues.
Last, we have the age old tradeoff: ease of use versus power. Simpler ORMs are lighter, generally easier to learn, but may fall short on the amount of automation allowed. More powerful ORMs are the opposite -- they fully support all expected functionality, but may be too complex or heavyweight for simpler applications. In any case, ORMs may require a too strict approach that may not fit the programmer's mental model, so chosing the better one is as much a matter of choice as it is a matter of intrinsic quality.
The approach used by SQLObject is very easy to understand and use in actual production. A few things are surprisingly easy; for instance, many-to-many relationships normally resort to an intermediate table to store the relations; SQLObject manages this intermediate table automatically, and the user may never need to know that it exists. Creating new tables is also a breeze. On the other hand, some of the more complex relational constructs are not directly supported, such as left joins. The primary key is always an integer (actually, there are a few workarounds, but there are compelling reasons to use it as-is). There are also a few quirks; for example, being as pythonic as it is, one is tempted to declare inherited object types. In relational database design, the descendant is usually a table that implements only the additional fields, and store the ancestor fields in the original table; a relationship is used to form the full record. But due to the way database entities are mapped to objects, the end result is not a true inherited related database design; fields are duplicated on the descendant object, making it unusable as a means of database modeling. Transaction support is also missing.
Summary. SQLObject is a fantastic tool for simple database applications. It helps a big part of the problem for small apps -- the management overhead of the database itself for some repetitive tasks. For complex ones, it still does a good job, but if you push its limits, then you have to start doing manual database coding, with little help of the framework.
The framework can be broadly split in two parts. The design part is a relational database modeling tool that generates the object-oriented abstraction layer. The runtime code is an object-oriented framework that implements object persistency and consistency.
The design tools accepts models in both Python or XML format. The former allows the schema to be written in a conventional text editor. The later works well with interactive tools that generate the entity description. One such a tool, ZModelizationTool (for Zope), is provided as part of the package. The configuration allows the full specification of relationships and joins. Solid knowledge of relational database theory is required to take full advantage of the design tool. Besides that, default values are handled nicely, making global changes relatively easy. The following database adapters are supported, and provided as part of the package: MySQL, Oracle, PostgreSQL and SQLite.
Once processed, the schema is converted into a package of Python classes. Each entity is mapped to a class that inherits its behavior from a common CustomObject ancestor. This hierarchy allows hooks for actions such as data validation. But the best part of the framework is the EditingContext class. It creates a graph of all object instances in memory, guaranteeing consistency. Each row in the underlying dataset is uniquely mapped to an object. All relationships are also mapped into the EditingContext. One can think about it as a object oriented database state map -- in a sense, the equivalent, in object oriented terms, to a standard database connection.
Summary. Modeling is a well structured tool, based on solid and well tested concepts. The documentation of this project is well structured and professional looking, but still incomplete at parts, and the diagrams still need some polishment. Although ambitious, the project is focused and well managed. It's a powerful tool, and some careful code tuning is needed to take full advantage of its power. An 1.0 release will be most welcome.
SQLObject is by far the easiest and more pythonic of all the approaches we evaluated so far. Its power comes from the fact that it makes routine tasks vary simple -- in some cases, fully automatic and transparent. Its programming model is really easy and non intrusive. Its shortcomings should not be evident for most applications. Modeling, on the other hand, is more like a full fledged framework for database applications. The EditingContext concept is great, and would be in fact a more than welcome addition to the SQLObjects model. For my own applications, SQLObjects seem to be better suited - it is simpler, easier to learn, and cleaner than Modeling. But I'm sure that Modeling has its place for more complex apps.
A little bit of history
I have very little experience with real-world ORMs, but the concept itself is not new for me. I used to write business applications a long time ago (circa 1990), long before RDMBS became popular in the PC world. In those times we used to rely on flat file formats and external index libraries, such as the Turbo Power - a set of Pascal libraries to support B-Tree indexes and mergesort methods. When Turbo Pascal 5.0 (and shortly after, 5.5) arrived with the notion of objects, I wrote a small class library of my own to encapsulate records as objects. All records were descendant of TRecord, which declared a few abstract methods to load and save records to the database, and to manage the keys used in the index files. Its descendants had to declare real methods. Turbo Pascal didn't allow for the type of magic that dynamic languages such as Python do, so a lot of it was repretitive hand-crafted work. Even parts of code that were seemingly similar in structure were hard to automate at that time.Enter Python. As a dynamic language, Python makes easy to write a lot of the glue code that is needed to translate between the OO and the RDBMS worlds. Python ORMs take advantage of this to allow for simpler use.
What is a ORM good for?
Well, now we know what a ORM is, and how did we arrive here today. But why should one use a ORM? Well, implementing a business application using SQL can be a tedious task. Records in SQL are simple tuples of data. If you want to use this data in a object-oriented fashion, you have to convert this representation to an object, and vice-versa. SQL is also based on sets of data. It means that there is not such a concept as working with individual records in SQL. Another issue is that any changes to the object model used by the application imply equivalent changes both to the relational database model, and to the code used in the adaptation layer. If this code is spread over the application, the process will not only be tedious, but also error prone. Due to the nature of SQL (and Python's, to a certain extent), most errors will only get caught at runtime, which is too late for most situations.An ORM can solve all these problems, and add a few niceties of its own. In the best case, it will allow for a true object persistence model. Good ORMs also allow changes in the object model to be automatically mapped to the relational one. The opposite is also true, albeit less useful, assuming that you will be writing object-oriented code. In the best scenario, the application code can be completely ridden from any SQL reference, allowing the full use of an object-oriented paradigm for its development.
Challenges in ORM design
This short document doesn't intend to be a treaty on how ORMs are designed. However, some of the pitfalls are important and obvious enough to deserve special treatment.The basic challenge in ORM design is how to map the OO and relational models seamingless. Good ORM tools will run as transparently as possible, allowing the programmer to forget the intrincacies of the relational model. The process may start with an representation of the object model, either graphical or textual, that can be compiled to generate the classes as needed. Its also possible to declare the object model in pure Python, counting on the ORM tool to generate and handle the glue code automagically.
To implement true object persistence, each object needs a unique identifier. Each ORM will use a different approach here. However, one issue that is common to all ORMs is how to handle multiple copies of the same object. For the sake of consistence, objects should be singletons. If the ORM tool doesn't do it automatically, then it's up to the programmer to make sure that no multiple copies of the same object exist at the same time, avoiding concurrency and consistency issues.
Last, we have the age old tradeoff: ease of use versus power. Simpler ORMs are lighter, generally easier to learn, but may fall short on the amount of automation allowed. More powerful ORMs are the opposite -- they fully support all expected functionality, but may be too complex or heavyweight for simpler applications. In any case, ORMs may require a too strict approach that may not fit the programmer's mental model, so chosing the better one is as much a matter of choice as it is a matter of intrinsic quality.
Some Python ORM tools
The current state of ORM tools for Python is difficult to assess at first glance. There are many tools available, but some are old, not maintained, or not widely supported. I've collected a few links and my impressions as follows.PDO
PDO is not a full fledged ORM. It's rather a lightweight object-oriented layer for the DB-API. It adds a few helper methods and accommodate the slight differences between the available database drivers within a single framework, making the transition easier. However, it falls really short of a true ORM. There is no automatic mapping of the database rows to objects, and the database structure has to be created and managed manually. It would probably be better to treat PDO as part of the underlying DB-API, but that's out of the scope of this document.SQLObject
SQLObject is a very pythonic approach to ORM. Objects are declared in Python using a very simple syntax, and a metaclasses-based approach makes the translation between the objects and the database representation fully transparent. Each record in the database is represented by a object, generated on the fly according to the defined structure. Fields are automatically mapped to attributes. Joins and relationships -- including many to many -- are also implemented in a fairly straighforward way. Each object is uniquely associated with a row in the database, guaranteeing consistency. And better -- everything is done at run time, with no need for intermediate steps or external support tools.The approach used by SQLObject is very easy to understand and use in actual production. A few things are surprisingly easy; for instance, many-to-many relationships normally resort to an intermediate table to store the relations; SQLObject manages this intermediate table automatically, and the user may never need to know that it exists. Creating new tables is also a breeze. On the other hand, some of the more complex relational constructs are not directly supported, such as left joins. The primary key is always an integer (actually, there are a few workarounds, but there are compelling reasons to use it as-is). There are also a few quirks; for example, being as pythonic as it is, one is tempted to declare inherited object types. In relational database design, the descendant is usually a table that implements only the additional fields, and store the ancestor fields in the original table; a relationship is used to form the full record. But due to the way database entities are mapped to objects, the end result is not a true inherited related database design; fields are duplicated on the descendant object, making it unusable as a means of database modeling. Transaction support is also missing.
Summary. SQLObject is a fantastic tool for simple database applications. It helps a big part of the problem for small apps -- the management overhead of the database itself for some repetitive tasks. For complex ones, it still does a good job, but if you push its limits, then you have to start doing manual database coding, with little help of the framework.
Modeling
Modeling is an ambitious project that aims to write a port of Apple's Enterprise Object Framework (abbreviated henceforth EOF) in pure Python. The author had extensive previous experience using the EOF, and decided to write a port after missing the convenience that a good ORM brings to programming. The EOF uses the Entity-Relationship Model [1], which provides the academic foundation for this type of work.The framework can be broadly split in two parts. The design part is a relational database modeling tool that generates the object-oriented abstraction layer. The runtime code is an object-oriented framework that implements object persistency and consistency.
The design tools accepts models in both Python or XML format. The former allows the schema to be written in a conventional text editor. The later works well with interactive tools that generate the entity description. One such a tool, ZModelizationTool (for Zope), is provided as part of the package. The configuration allows the full specification of relationships and joins. Solid knowledge of relational database theory is required to take full advantage of the design tool. Besides that, default values are handled nicely, making global changes relatively easy. The following database adapters are supported, and provided as part of the package: MySQL, Oracle, PostgreSQL and SQLite.
Once processed, the schema is converted into a package of Python classes. Each entity is mapped to a class that inherits its behavior from a common CustomObject ancestor. This hierarchy allows hooks for actions such as data validation. But the best part of the framework is the EditingContext class. It creates a graph of all object instances in memory, guaranteeing consistency. Each row in the underlying dataset is uniquely mapped to an object. All relationships are also mapped into the EditingContext. One can think about it as a object oriented database state map -- in a sense, the equivalent, in object oriented terms, to a standard database connection.
Summary. Modeling is a well structured tool, based on solid and well tested concepts. The documentation of this project is well structured and professional looking, but still incomplete at parts, and the diagrams still need some polishment. Although ambitious, the project is focused and well managed. It's a powerful tool, and some careful code tuning is needed to take full advantage of its power. An 1.0 release will be most welcome.
Conclusion
Choosing a ORM is a integral part of the design choices that have to be done at the start of the development cycle. In a sense, it should be easier to change the back-end database than to change ORMs -- assuming all databases support the same API (which is not actually true, but it's a good aproximation for this purpose).SQLObject is by far the easiest and more pythonic of all the approaches we evaluated so far. Its power comes from the fact that it makes routine tasks vary simple -- in some cases, fully automatic and transparent. Its programming model is really easy and non intrusive. Its shortcomings should not be evident for most applications. Modeling, on the other hand, is more like a full fledged framework for database applications. The EditingContext concept is great, and would be in fact a more than welcome addition to the SQLObjects model. For my own applications, SQLObjects seem to be better suited - it is simpler, easier to learn, and cleaner than Modeling. But I'm sure that Modeling has its place for more complex apps.
32 Comments:
At September 3, 2004 at 11:11 AM, Anonymous said…
SQLObject does support transactions; see http://sqlobject.org/docs/SQLObject.html#transactions
At November 10, 2005 at 12:45 PM, Anonymous said…
Hi there Carlos Ribeiro, I had been out looking for some new information on discounted notes when I found your site and Python ORM tools. Though not just what I was searching for, it drew my attention. An interesting post and I thank you for it.
At November 10, 2005 at 2:44 PM, Anonymous said…
Hi Carlos Ribeiro, Out surfing for information on real estate note & happened upon your site. While Python ORM tools wasn't exactly spot on, it did strike a note with me. Thank you for the really good read.
At November 14, 2005 at 1:07 PM, Anonymous said…
Hi Blogger, Out surfing for information on real estate note & happened upon your site. While Python ORM tools wasn't exactly spot on, it did strike a note with me. Thank you for the really good read.
At November 16, 2005 at 11:18 PM, Anonymous said…
Hi Carlos Ribeiro, Out surfing for information on business notes & happened upon your site. While Python ORM tools wasn't exactly spot on, it did strike a note with me. Thank you for the really good read.
At November 18, 2005 at 9:52 AM, Anonymous said…
Hi there: I was just surfing around trying to locate the latest information about business brokers, when I came across your site, Python ORM tools, not exactly what I had in mind, but interesting non the less. I now see why when I was looking for information related to business brokers, It directed me to your site. Thanks for the info.
At August 13, 2007 at 2:48 PM, Anonymous said…
SQLAlchemy is another popular ORM tool with webframeworks like TurboGears and Pylons for example
http://www.sqlalchemy.org/
At August 18, 2007 at 12:48 PM, Anonymous said…
Great article! Thanks.
At August 18, 2007 at 6:22 PM, Anonymous said…
Thanks for interesting article.
At September 9, 2007 at 3:47 PM, Anonymous said…
Nice! Nice site! Good resources here. I will bookmark!
At September 10, 2007 at 1:32 AM, Anonymous said…
I see first time your site guys. I like you :)
At September 10, 2007 at 9:51 AM, Anonymous said…
Excellent website. Good work. Very useful. I will bookmark!
At July 1, 2008 at 7:22 AM, dalloliogm said…
What about django?
Can it be considered as an ORM tool?
At December 9, 2011 at 7:53 AM, razvan said…
man, delete all these crappy comments... including this. it doesn't add any value to your post.
At August 7, 2012 at 1:32 PM, pay per head horse racing said…
This is really satisfied by the nice services in this blog that to really providing the wonderful info is visible in this blog
At February 23, 2013 at 4:45 PM, Anonymous said…
Interestingly, in the еlеctric сuгrent соmmunitу of intereѕts, agrеemеnt just nοw approхimatelу anything you have done ahead fellatio tο get
him into the Ϻooԁ. While diаgnoѕing
malаԁy iѕ not іn my lanԁ of ԁivinе service I am which Аt that plаce are no blocκaded zones, and the infamous Yoni and lingam masѕages could be corρoгаtе intο the acaԁemic session as wеll.
Shе'll make you wonder why all girls aren't like
her she's every you reaching for that sorcerous instant of raptus. Their 4 workforce delivery me kindred ang lalaki.
my web-site ... web page
At July 8, 2015 at 5:55 PM, chenlina said…
chenlina20150709
louis vuitton outlet
jordan 11 concord
lebron 11
toms shoes
concord 11
chanel bags
timberland boots
adidas wings
ralph lauren outlet
toms wedges
ray ban sunglasses
michael kors outlet
ed hardy clothing
chi flat iron
ray ban glasses
louis vuitton
ray bans
coach outlet store online
hollister clothing
michael kors
louboutins
oakley sunglasses
toms wedges
toms shoes
michael kors outlet online sale
mulberry uk
lululemon sale
louis vuitton outlet
air max 90
polo ralph lauren
tory burch handbags
retro 11
pandora uk
abercrombie and fitch new york
abercrombie store
abercrombie
michael kors handbags
true religion sale
marc jacobs outlet
p90x
At October 5, 2015 at 1:36 AM, Unknown said…
qihang1005,burberry outlet
hermes uk
nike free run
toms outlet
ugg slippers
nike air huarache
juicy couture
ralph lauren pas cher
vans shoes
christian louboutin uk
ugg outlet
nike air max
ugg boots
new balance outlet
fitflops sale clearance
adidas shoes uk
nike tn
snapbacks wholesale
rolex replica watches
hollister uk
toms shoes
jordan shoes
mizuno running shoes
ray ban outlet
pandora jewelry
ugg boots sale
true religion jeans
adidas superstar
mont blanc
louis vuitton
coach outlet canada
fake oakley sunglasses
louis vuitton borse
ray ban sunglasses outlet
q
At December 3, 2015 at 9:29 PM, chenlina said…
chenlina20151204
ray ban sunglasses outlet
coach factory outlet
louis vuitton outlet
pandora jewelry
jordan 11s
christian louboutin shoes
louis vuitton
ugg boots
cheap uggs
ray bans
ray ban sunglasses outlet
the north face outlet
ugg boots for women
ugg boots
louis vuitton handbags
coach outlet online
oakley outlet
the north face outlet
adidas superstars
louis vuitton outlet online
michael kors outlet online
toms wedges
cheap ray ban sunglasses
oakley sunglasses
nike air max
ugg sale
ugg australia
the north face uk
canada goose jackets
replica watches
oakley sunglasses
cheap oakley sunglasses
hollister uk
canada goose outlet
michael kors outlet
uggs on sale
louboutin pas cher
cheap oakley sunglasses
michael kors outlet
nike roshe run women
as
At March 4, 2016 at 7:51 PM, Anonymous said…
ninest123 16.03
prada outlet, nike outlet, longchamp outlet, burberry outlet, gucci handbags, ralph lauren polo, cheap jordans, louis vuitton outlet, louis vuitton outlet online, oakley sunglasses, uggs on sale, louis vuitton outlet, tiffany jewelry, replica watches, ralph lauren outlet, michael kors outlet, longchamp outlet, cheap oakley sunglasses, ray ban sunglasses, michael kors handbags, replica watches, michael kors outlet online, oakley sunglasses, uggs on sale, louboutin shoes, christian louboutin, ray ban sunglasses, uggs outlet, nike free, christian louboutin, uggs outlet, tiffany jewelry, nike air max, oakley sunglasses, longchamp bags, uggs on sale, michael kors outlet online, michael kors outlet online, nike air max, louis vuitton handbags, oakley sunglasses, louis vuitton, ray ban sunglasses, louboutin uk, michael kors, tory burch outlet, burberry factory outlet, prada handbags
At March 4, 2016 at 7:51 PM, Anonymous said…
lunette oakley pas cher, nike roshe run pas cher, lululemon outlet, hollister uk, jordan pas cher, coach purses, lunette ray ban pas cher, nike tn pas cher, hogan sito ufficiale, abercrombie and fitch, new balance, longchamp soldes, nike blazer pas cher, true religion outlet, true religion outlet, michael kors, replica handbags, michael kors, sac guess pas cher, ray ban uk, nike air max, mulberry uk, vans pas cher, abercrombie and fitch UK, louboutin pas cher, nike air max pas cher, coach outlet store online, nike free pas cher, true religion jeans, polo lacoste pas cher, true religion outlet, nike air max uk, ralph lauren pas cher, kate spade outlet, longchamp pas cher, nike air force, polo ralph lauren uk, hermes pas cher, michael kors uk, north face uk, timberland pas cher, vanessa bruno pas cher, nike free, michael kors outlet online, coach outlet, nike air max uk, burberry pas cher, north face pas cher, converse
At March 4, 2016 at 7:52 PM, Anonymous said…
swarovski jewelry, coach outlet, links of london uk, canada goose, canada goose pas cher, moncler, barbour, ugg pas cher, louis vuitton, supra shoes, converse, wedding dresses uk, pandora charms, canada goose uk, karen millen uk, ugg,uggs,uggs canada, canada goose outlet, louis vuitton uk, pandora uk, replica watches, moncler, moncler pas cher, ugg,ugg australia,ugg italia, moncler, swarovski uk, canada goose outlet, canada goose jackets, moncler, converse shoes outlet, bottes ugg pas cher, barbour jackets uk, canada goose jackets, hollister, marc jacobs, thomas sabo uk, ugg uk, toms shoes, moncler uk, sac louis vuitton, sac louis vuitton, gucci, pandora jewelry, moncler jackets, juicy couture outlet, pandora jewelry, montre pas cher, juicy couture outlet, canada goose, louis vuitton, moncler outlet, ray ban, lancel
ninest123 16.03
At May 9, 2016 at 5:47 PM, Unknown said…
new jordans
Jordan 11 Retro 72 10
coach outlet online
coach store
michael kors handbags
2015 michael kors outlet
michael kors wallets
michael kors handbags on sale
macys michael kors
Michael Kors Outlet Online Store
nike hyperdunk 2015
jordans 11
nike air max 2014
nike air max 2016
nike free 3.0
nike free run womens
Red Bottom Shoes
red sole shoes
mbt shoes outlet
mbt canada
fitflops on sale
fitflops sale
Nike Kobe 9
Kobe 9 Elite
christian louboutin
christian louboutin sale
jordan 11 legend blue
jordans 2016
At December 29, 2016 at 6:04 AM, ركن كلين said…
ركن كلين لتنظيف بالرياض
تعتبر أعمال تنظيف المجالس من أهم المجالات التى لابد من الاهتمام بها لكى تحافظ على سلامة الاثاث الخاص بك ؛ فسرعان ما يتعرض الاثاث للاتساخات نتيجة كثيرة الاستعمال بالاضافه الى الاطفال اللذين لديهم دور كبير فى تلوث الاثاث واتساخه ؛ لذلك نحن كأفضل شركة تنظيف مجالس بالرياض نعتمد على أدوات مميزة وحديثة من أجل التخلص من تلوث كافه أشكال المجالس من (شركة تنظيف كنب بالرياض. شركة تنظيف موكيت بالرياض .شركة تنظيف مجالس بالرياض)بالاضافه الى كافه أشكال المجالس الأخرى .
At December 30, 2016 at 9:25 AM, ركن كلين said…
شركة مكافحة ثعابين بالرياض
شركة مكافحة الحمام بالرياض
شركة مكافحة فئران بالرياض
شركة مكافحة النمل الابيض بالرياض
At April 29, 2018 at 8:01 PM, Cara Mengobati Radang Usus Secara Alami said…
This article is interesting and useful. Thank you for sharing. And let me share an article about health that God willing will be very useful. Thank you :)
Cara Mengatasi sakit pundak dan Leher kaku
Cara Menghilangkan Benjolan di Bibir
Obat Nyeri haid
Tips Menghilangkan Wajah kusam
Cara Mengobati Bisul
solusi masalah kewanitaan
________________
At September 6, 2018 at 12:07 AM, Cara Menghilangkan Bopeng said…
Thanks for the information, this is very useful. Allow me to share a health article here, which gods are beneficial to us. Thank you :)
Obat Pelancar Haid paling Ampuh
Cara Membersihkan flek di Paru-paru
Pengobatan Herbal Rematik/Rheumatiod Arthritis
Obat Herbal Walatra Berry Jus
Cara Mengobati Telapak Kaki Berlubang
Obat Koreng Bernanah Alami
At January 25, 2019 at 8:38 PM, Bahaya Nyeri Tenggorokan said…
The article is very interesting. And I also want to share articles about health, I'm sure this will be useful. Read and share it. Thank you very much :)
Pengobatan Kaki Bengkak secara Efektif
Cara Menghilangkan Benjolan di Kepala
At January 30, 2020 at 3:25 AM, mwww-office.com said…
Office Setup is an independent support provider on On-Demand Remote Technical Services For Microsoft Office products. Use of Microsoft Name, logo, trademarks & Product Images is only for reference and in no way intended to suggest that office.com/setup Technology has any business association with Microsoft Office.
At June 4, 2021 at 11:05 AM, الفارس كلين said…
شركه تنظيف منازل بالدمام
شركه تنظيف خزانات بالدمام
شركه تنظيف مكيفات بالدمام
شركه مكافحه حشرات بالدمام
شركه تنظيف كنب وسجاد بالخرج
شركه تنظيف منازل بالقطيف
شركه تنظيف خزانات بالقطيف
شركه مكافحه حشرات بالقطيف
شركه مكافحه حشرات براس تنوره
شركه تنظيف مكيفات براس تنوره
At June 29, 2021 at 4:27 AM, Murphy loe said…
We are proud to say that all our products are not displayed online without going through an intensive quality assurance process.
Buy clonezapam powder online
Buy cocaine online
Buy bio cocaine online
actavis-promethazine-codeine for sale online
Buy cocaine online
Buy marijuana online
Buy Martian Moonrock online
Buy kurupts moonrock online
Buy blue dream online
Buy Death Star Marijuana online
Buy vyvanse online
Buy ritalin online
Buy Godfather OG online
Buy Pineapple Express online
At July 10, 2021 at 8:26 PM, rabab saad said…
شركة رش مبيد بحائل
شركة تنظيف شقق بحائل
شركة مكافحة الحشرات بحائل
شركة تنظيف خزانات بحائل
شركة مكافحة الحمام بحائل
شركة تنظيف مكيفات بحائل
شركة تنظيف مجالس بحائل
شركة تنظيف فلل بحائل
شركة نقل اثاث بحائل
Post a Comment
<< Home