{ "info": { "author": "Mark Vartanyan", "author_email": "kolypto@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "[![Build Status](https://api.travis-ci.org/kolypto/py-mongosql.png?branch=master)](https://travis-ci.org/kolypto/py-mongosql)\n[![Pythons](https://img.shields.io/badge/python-3.6%E2%80%933.8-blue.svg)](.travis.yml)\n\n\nMongoSQL\n========\n\nMongoSQL is a JSON query engine that lets you query [SqlAlchemy](http://www.sqlalchemy.org/)\nlike a MongoDB database.\n\nThe main use case is the interation with the UI:\nevery time the UI needs some *sorting*, *filtering*, *pagination*, or to load some\n*related objects*, you won't have to write a single line of repetitive code!\n\nIt will let the API user send a JSON Query Object along with the REST request,\nwhich will control the way the result set is generated:\n\n```javascript\n$.get('/api/user?query=' + JSON.stringify({\n sort: ['first_name-'], // sort by `first_name` DESC\n filter: { age: { $gte: 18 } }, // filter: age >= 18\n join: ['user_profile'], // load related `user_profile`\n limit: 10, // limit to 10 rows\n}))\n```\n\nTired of adding query parameters for pagination, filtering, sorting?\nHere is the ultimate solution.\n\nNOTE: currently, only tested with PostgreSQL.\n\n\n\nTable of Contents\n=================\n\n* Querying\n * Query Object Syntax\n * Operations\n * Project Operation\n * Sort Operation\n * Filter Operation\n * Join Operation\n * Filtering Join Operation\n * Aggregate Operation\n * Group Operation\n * Slice Operation\n * Count Operation\n * JSON Column Support\n* MongoSQL Programming Interface\n * MongoQuery\n * Creating a MongoQuery\n * Reusable\n * Querying: MongoQuery.query()\n * Getting Results: MongoQuery.end()\n * Getting All Sorts of Results\n * MongoQuery Configuration\n * MongoQuery API\n * MongoQuery(model, handler_settings=None)\n * MongoQuery.from_query(query) -> MongoQuery\n * MongoQuery.with_session(ssn) -> MongoQuery\n * MongoQuery.query(**query_object) -> MongoQuery\n * MongoQuery.end() -> Query\n * MongoQuery.end_count() -> CountingQuery\n * MongoQuery.result_contains_entities() -> bool\n * MongoQuery.result_is_scalar() -> bool\n * MongoQuery.result_is_tuples() -> bool\n * MongoQuery.get_final_query_object() -> dict\n * MongoQuery.ensure_loaded(*cols) -> MongoQuery\n * MongoQuery.get_projection_tree() -> dict\n * MongoQuery.get_full_projection_tree() -> dict\n * MongoQuery.pluck_instance(instance) -> dict\n * Handlers\n* CRUD Helpers\n * CrudHelper(model, **handler_settings)\n * StrictCrudHelper\n * CrudViewMixin()\n * @saves_relations(*field_names)\n* Other Useful Tools\n * ModelPropertyBags(model)\n * CombinedBag(**bags)\n * CountingQuery(query)\"\n\nQuerying\n========\n\nIf you know how to query documents in MongoDB, you can query your database with the same language.\nMongoSQL uses the familiar [MongoDB Query Operators](https://docs.mongodb.com/manual/reference/operator/query/)\nlanguage with a few custom additions.\n\nThe Query Object, in JSON format, will let you sort, filter, paginate, and do other things.\nYou would typically send this object in the URL query string, like this:\n\n```\nGET /api/user?query={\"filter\":{\"age\":{\"$gte\":18}}}\n```\n\nThe name of the `query` argument, however, may differ from project to project.\n\n\n\nQuery Object Syntax\n-------------------\n\nA Query Object is a JSON object that the API user can submit to the server to change the way the results are generated.\nIt is an object with the following properties:\n\n* `project`: [Project Operation](#project-operation) selects the fields to be loaded\n* `sort`: [Sort Operation](#sort-operation) determines the sorting of the results\n* `filter`: [Filter Operation](#filter-operation) filters the results, using your criteria\n* `join`: [Join Operation](#join-operation) loads related models\n* `joinf`: [Filtering Join Operation](#filtering-join-operation) loads related models with filtering\n* `aggregate`: [Aggregate Operation](#aggregate-operation) lets you calculate statistics\n* `group`: [Group Operation](#group-operation) determines how to group rows while doing aggregation\n* `skip`, `limit`: [Rows slicing](#slice-operation): paginates the results\n* `count`: [Counting rows](#count-operation) counts the number of rows without producing results\n\nAn example Query Object is:\n\n```javascript\n{\n project: ['id', 'name'], # Only fetch these columns\n sort: ['age+'], # Sort by age, ascending\n filter: {\n # Filter condition\n sex: 'female', # Girls\n age: { $gte: 18 }, # Age >= 18\n },\n join: ['user_profile'], # Load the 'user_profile' relationship\n limit: 100, # Display 100 per page\n skip: 10, # Skip first 10 rows\n}\n```\n\nDetailed syntax for every operation is provided in the relevant sections.\n\nPlease keep in mind that while MongoSQL provides a query language that is rich enough for most typical tasks,\nthere would still be cases when an implementation of a custom API would be better, or even the only option available.\n\nMongoSQL was not designed to be a complete replacement for the SQL; it was designed only to keep you from doing\nrepetitive work :) So it's absolutely fine that some queries that you may have in mind won't be possible with MongoSQL.\n\nOperations\n----------\n\n### Project Operation\n\nProjection corresponds to the `SELECT` part of an SQL query.\n\nIn MongoDB terminology, *projection* is the process of selection a subset of fields from a document.\n\nYour models have many fields, but you do not always need them all. Oftentimes, all you need is just a small number\nof them. That's when you use this operation that *projects* some fields for you.\n\nThe `proj\u00e9ct` operation lets you list the fields that you want to have in the data you get from the API endpoint.\nYou do this by either listing the fields that you need (called *include mode*), or listing the fields that you\n*do not* need (called *exclude mode*).\n\nThe resulting data query on the back-end will only fetch the fields that you've requested, potentially saving a lot\nof bandwidth.\n\nAn example of a projection would look like this:\n\n```javascript\n$.get('/api/user?query=' + JSON.stringify({\n // only include the following fields\n project: ['id', 'first_name', 'last_name'],\n}))\n```\n\n#### Syntax\n\nThe Project operation supports the following syntaxes:\n\n* Array syntax.\n\n Provide an array of field names to be included.\n All the rest will be excluded.\n\n Example:\n\n ```javascript\n { project: ['login', 'first_name'] }\n ```\n\n* String syntax\n\n Give a list of field names, separated by spaces.\n\n Example:\n\n ```javascript\n { project: 'login first_name' }\n ```\n\n* Object syntax.\n\n Provide an object of field names mapped to either a `1` (include) or a `0` (exclude).\n\n Examples:\n\n ```javascript\n { 'a': 1, 'b': 1 } # Include specific fields. All other fields are excluded\n { 'a': 0, 'b': 0 } # Exclude specific fields. All other fields are included\n ```\n\n Note that you can't intermix the two: you either use all `1`s to specify the fields you want included,\n or use all `0`s to specify the fields you want excluded.\n\n NOTE: One special case is a so-called *full projection*: when your projection object mentions every single property\n of a model, then you're allowed to set `1`s to some, and `0`s to others in the same object. Use wisely.\n\n#### Fields Excluded by Default\nNote that some fields that exist on the model may not be included *by default*: this is something that\nback-end developers may have configured with `default_exclude` setting on the server.\n\nYou will not receive those fields unless you explicitly require them.\nThis may be appropriate for some field that contain a lot of data, or require some calculation.\n\nTo include those fields, you have to request them explicitly: just use their name\nin the list of fields that you request.\n\n#### Related Models\nNormally, in order to load a related model (say, user's `user_profile`, or some other data related to this model),\nyou would use the [Join Operation](#join-operation).\n\nHowever, for convenience, you can now also load related models by just giving their name in the projection,\nas if it was a field. For example:\n\n```javascript\n{ project: {\n id: 1,\n name: 1,\n user_articles: 1 // the related model will be loaded\n}}\n```\n\nThis request will load the related `user_articles` for you.\n\nNote that some relationships will be disabled for security reasons.\n### Sort Operation\n\nSorting corresponds to the `ORDER BY` part of an SQL query.\n\nThe UI would normally require the records to be sorted by some field, or fields.\n\nThe sort operation lets the API user specify the sorting of the results,\nwhich makes sense for API endpoints that return a list of items.\n\nAn example of a sort operation would look like this:\n\n```javascript\n$.get('/api/user?query=' + JSON.stringify({\n // sort by age, descending;\n // then sort by first name, alphabetically\n sort: ['age-', 'first_name+'],\n}))\n```\n\n#### Syntax\n\n* Array syntax.\n\n List of column names, optionally suffixed by the sort direction: `-` for `DESC`, `+` for `ASC`.\n The default is `+`.\n\n Example:\n\n ```javascript\n [ 'a+', 'b-', 'c' ] // -> a ASC, b DESC, c DESC\n ```\n\nObject syntax is not supported because it does not preserve the ordering of keys.\n### Filter Operation\nFiltering corresponds to the `WHERE` part of an SQL query.\n\nMongoSQL-powered API endpoints would typically return the list of *all* items, and leave it up to\nthe API user to filter them the way they like.\n\nExample of filtering:\n\n```javascript\n$.get('/api/user?query=' + JSON.stringify({\n // only select grown-up females\n filter: {\n // all conditions are AND-ed together\n age: { $gte: 18, $lte: 25 }, // age 18..25\n sex: 'female', // sex = \"female\"\n }\n}))\n```\n\n#### Field Operators\nThe following [MongoDB query operators](https://docs.mongodb.com/manual/reference/operator/query/)\noperators are supported:\n\nSupports the following MongoDB operators:\n\n* `{ a: 1 }` - equality check: `field = value`. This is a shortcut for the `$eq` operator.\n* `{ a: { $eq: 1 } }` - equality check: `field = value` (alias).\n* `{ a: { $lt: 1 } }` - less than: `field < value`\n* `{ a: { $lte: 1 } }` - less or equal than: `field <= value`\n* `{ a: { $ne: 1 } }` - inequality check: `field != value`.\n* `{ a: { $gte: 1 } }` - greater or equal than: `field >= value`\n* `{ a: { $gt: 1 } }` - greater than: `field > value`\n* `{ a: { $prefix: 1 } }` - prefix: `field LIKE \"value%\"`\n* `{ a: { $in: [...] } }` - any of. Field is equal to any of the given array of values.\n* `{ a: { $nin: [...] } }` - none of. Field is not equal to any of the given array of values.\n* `{ a: { $exists: true } }` - value is not `null`.\n\nSupports the following operators on an `ARRAY` field, for a scalar value:\n\n* `{ arr: 1 }` - containment check: field array contains the given value: `ANY(array) = value`.\n* `{ arr: { $ne: 1 } }` - non-containment check: field array does not contain value: `ALL(array_col) != value`.\n* `{ arr: { $size: 0 } }` - Has a length of N (zero, to check for an empty array)\n\n\nSupports the following operators on an `ARRAY` field, for an array value:\n\n* `{ arr: [...] }` - equality check: two arrays are completely equal: `arr = value`.\n* `{ arr: { $ne: [...] } }` - inequality check: two arrays are not equal: `arr != value`.\n* `{ arr: { $in: [...] } }` - intersection check. Check that the two arrays have common elements.\n* `{ arr: { $nin: [...] } }` - no intersection check. Check that the two arrays have no common elements.\n* `{ arr: { $all: [...] } }` - Contains all values from the given array\n\n#### Boolean Operators\n\nIn addition to comparing fields to a value, the following boolean operators are supported\nthat enable you to make complex queries:\n\n* `{ $or: [ {..criteria..}, .. ] }` - any is true\n* `{ $and: [ {..criteria..}, .. ] }` - all are true\n* `{ $nor: [ {..criteria..}, .. ] }` - none is true\n* `{ $not: { ..criteria.. } }` - negation\n\nExample usage:\n\n```javascript\n$.get('/api/books?query=' + JSON.stringify({\n // either of the two options are fine\n $or: [\n // First option: sci-fi by Gardner Dozois\n { genre: 'sci-fi', editor: 'Gardner Dozois' },\n // Second option: any documentary\n { genre: 'documentary' },\n ]\n}))\n```\n\n#### Related columns\nYou can also filter the data by the *columns on a related model*.\nThis is achieved by using a dot after the relationship name:\n\n```javascript\n$.get('/api/user?query=' + JSON.stringify({\n filter: {\n // Fields of the 'user' model\n first_name: 'John',\n last_name: 'Doe',\n // Field of a related 'address' model\n 'address.zip': '100098',\n }\n}))\n```\n### Join Operation\nJoining corresponds to the `LEFT JOIN` part of an SQL query (although implemented as a separate query).\n\nIn the back-end database, the data is often kept in a *normalized form*:\nitems of different types are kept in different places.\nThis means that whenever you need a related item, you'll have to explicitly request it.\n\nThe Join operation lets you load those related items.\n\nPlease keep in mind that most relationships would be disabled on the back-end because of security concerns about\nexposing sensitive data. Therefore, whenever a front-end developer needs to have a relationship loaded,\nit has to be manually enabled on the back-end! Please feel free to ask.\n\nExamples follow.\n\n#### Syntax\n\n* Array syntax.\n\n In its most simple form, all you need to do is just to provide the list of names of the relationships that you\n want to have loaded:\n\n ```javascript\n $.get('/api/user?query=' + JSON.stringify({\n join: ['user_profile', 'user_posts'],\n }))\n ```\n\n* Object syntax.\n\n This syntax offers you great flexibility: with a nested Query Object, it is now posible to apply operations\n to related entities: select just a few fields (projection), sort it, filter it, even limit it!\n\n The nested Query Object supports projections, sorting, filtering, even joining further relations, and\n limiting the number of related entities that are loaded!\n\n In this object syntax, the object is an embedded Query Object. For instance:\n\n ```javascript\n $.get('/api/user?query=' + JSON.stringify({\n join: {\n // Load related 'posts'\n posts: {\n filter: { rating: { $gte: 4.0 } }, // Only load posts with raing > 4.0\n sort: ['date-'], // newest first\n skip: 0, // first page\n limit: 100, // 100 per page\n },\n\n // Load another relationship\n 'comments': null, # No specific options, just load\n }\n }\n }))\n ```\n\n Note that `null` can be used to load a relationship without custom querying.\n### Filtering Join Operation\nThe [Join Operation](#join-operation) has the following behavior:\nwhen you requested the loading of a relation, and there were no items found, an empty value is returned\n(a `null`, or an empty array).\n\n```javascript\n// This one will return all users\n// (even those that have no articles)\n$.get('/api/user?query=' + JSON.stringify({\n join: [\"articles\"] // Regular Join: `join`\n}))\n```\n\nThis `joinf` Filtering Join operation does just the same thing that `join` does;\nhowever, if there were no related items, the primary one is also removed.\n\n```javascript\n// This one will return *only those users that have articles*\n// (users with no articles will be excluded)\n$.get('/api/user?query=' + JSON.stringify({\n joinf: [\"articles\"] // Filtering Join: `joinf`\n}))\n```\n\nThis feature is, quite honestly, weird, and is only available for backward-compatibility with a bug that existed\nin some early MongoSQL versions. It has proven to be useful in some cases, so the bug has been given a name and a\nplace within the MongoSQL library :)\n\nNote that `joinf`` does not support `skip` and `limit`\non nested entities because of the way it's implemented with Postgres.\n### Aggregate Operation\nAggregation corresponds to the `SELECT ...` part of an SQL query with aggregation functions.\n\nSometimes the API user wouldn't need the data itself, but rather some statistics on that data: the smallest value,\nthe largest value, the average value, the sum total of all values.\n\nThis is what aggregation does: lets the API user execute statistical queries on the data.\nIts features are limited, but in the spirit of MongoSQL, will save some routine work for back-end developers.\n\nExample:\n```javascript\n$.get('/api/user?query=' + JSON.stringify({\n // The youngest and the oldest\n min_age: { $min: 'age' },\n max_age: { $max: 'age' },\n\n // SUM(1) for every user produces the total number of users\n number_of_users: { $sum: 1 },\n\n // Count the number of youngsters: age < 18\n // This is a SUM() of a boolean expression, which gives 1 for every matching row.\n youngster_count: { $sum: { age: { $lt: 18 } } },\n}))\n```\n\nNote that for security reasons, aggregation must be manually enabled for every field on the back-end.\n\n#### Syntax\nThe syntax is an object that declares custom field names to be used for keeping results:\n\n aggregate: { computed-field-name: }\n\nThe *expression* can be:\n\n* Column name: essentially, projecting a column into the result set so that you can have the original value\n\n Example:\n\n ```javascript\n aggregate: {\n age: 'age'\n }\n ```\n\n This is only useful when combined with the [Group Operation](#group-operation).\n It is disabled by default on the back-end.\n\n* Aggregation functions:\n\n * `{ $min: operand }` - smallest value\n * `{ $max: operand }` - largest value\n * `{ $avg: operand }` - average value\n * `{ $sum: operand }` - sum of values\n\n The *operand* can be:\n\n * Column name: to apply the aggregation function to a column\n\n Example:\n\n ```javascript\n aggregate: {\n min_age: { $min: 'age' }\n }\n ```\n\n * Boolean expression: see [Filter Operation](#filter-operation).\n\n This is a very useful trick.\n Because the result of a boolean expression is `1` when it's true, you can take a `$sum` of them,\n and count the number of rows that match that condition.\n\n Example:\n\n ```javascript\n // Count the number of youngsters: age < 18\n // This is a SUM() of a boolean expression, which gives 1 for every matching row.\n aggregate: {\n youngster_count: { $sum: { age: { $lt: 18 } } },\n }\n ```\n\n * Integer value (only supported by `$sum` operator)\n\n Example:\n\n ```javascript\n // Gives the total number of rows\n aggregate: {\n total: { $sum: 1 } // one for every row. Can be 2 or 3 if you like\n }\n ```\n\nNote that aggregation often makes sense only when used together with the [Group Operation](#group-operation).\n### Group Operation\nGrouping corresponds to the `GROUP BY` part of an SQL query.\n\nBy default, the [Aggregate Operation](#aggregate-operation) gives statistical results over all rows.\n\nFor instance, if you've asked for `{ avg_age: { $avg: 'age' } }`, you'll get the average age of all users.\n\nOftentimes this is not enough, and you'll want statistics calculated over groups of items.\nThis is what the Group Operation does: specifies which field to use as the \"group\" indicator.\n\nBetter start with a few examples.\n\n#### Example #1: calculate the number of users of every specific age.\nWe use the `age` field as the group discriminator, and the total number of users is therefore calculated per group.\nThe result would be: something like:\n\n age 18: 25 users\n age 19: 20 users\n age 21: 35 users\n ...\n\nThe code:\n\n```javascript\n$.get('/api/user?query=' + JSON.stringify({\n // The statistics\n aggregate: {\n age: 'age', // Get the unadulterated column value\n count: { $sum: 1 }, // The count\n },\n // The discriminator\n group: ['age'], // we do not discriminate by sex this time... :)\n}))\n```\n\n#### Example #2: calculate teh average salary per profession\n\n```javascript\n$.get('/api/user?query=' + JSON.stringify({\n prof: 'profession',\n salary: { '$avg': 'salary' }\n },\n group: ['profession_id'],\n}))\n```\n\n#### Syntax\nThe Group Operator, as you have seen, receives an array of column names.\n### Slice Operation\nSlicing corresponds to the `LIMIT .. OFFSET ..` part of an SQL query.\n\nThe Slice operation consists of two optional parts:\n\n* `limit` would limit the number of items returned by the API\n* `skip` would shift the \"window\" a number of items\n\nTogether, these two elements implement pagination.\n\nExample:\n\n```javascript\n$.get('/api/user?query=' + JSON.stringify({\n limit: 100, // 100 items per page\n skip: 200, // skip 200 items, meaning, we're on the third page\n}))\n```\n\nValues: can be a number, or a `null`.\n### Count Operation\nSlicing corresponds to the `SELECT COUNT(*)` part of an SQL query.\n\nSimply, return the number of items, without returning the items themselves. Just a number. That's it.\n\nExample:\n\n```javascript\n$.get('/api/user?query=' + JSON.stringify({\n count: 1,\n}))\n```\n\nThe `1` is the *on* switch. Replace it with `0` to stop counting.\n\nNOTE: In MongoSQL 2.0, there is a way to get both the list of items, *and* their count *simultaneously*.\nThis would have way better performance than two separate queries.\nPlease have a look: [CountingQuery](#countingqueryquery) and [MongoQuery.end_count()](#mongoqueryend_count---countingquery).\n\n\nJSON Column Support\n-------------------\n\nA `JSON` (or `JSONB`) field is a column that contains an embedded object,\nwhich itself has fields too. You can access these fields using a dot.\n\nGiven a model fields:\n\n```javascript\nmodel.data = { rating: 5.5, list: [1, 2, 3], obj: {a: 1} }\n```\n\nYou can reference JSON field's internals:\n\n```javascript\n'data.rating'\n'data.list.0'\n'data.obj.a'\n'data.obj.z' // gives NULL when a field does not exist\n```\n\nOperations that support it:\n\n* [Sort](#sort-operation) and [Group](#group-operation) operations:\n\n ```javascript\n $.get('/api/user?query=' + JSON.stringify({\n sort: ['data.rating'] // JSON field sorting\n }))\n ```\n\n* [Filter](#filter-operation) operation:\n\n ```javascript\n $.get('/api/user?query=' + JSON.stringify({\n filter: {\n 'data.rating': { $gte: 5.5 }, // JSON field condition\n }\n }))\n ```\n\n or this is how you test that a property is missing:\n\n ```javascript\n { 'data.rating': null } // Test for missing property\n ```\n\n *CAVEAT*: PostgreSQL is a bit capricious about data types, so MongoSql tries to guess it *using the operand you provide*.\n Hence, when filtering with a property known to contain a `float`-typed field, please provide a `float` value!.\n\n* [Aggregate](#aggregate-operation):\n\n ```javascript\n $.get('/api/user?query=' + JSON.stringify({\n aggregate: {\n avg_rating: { $avg: 'data.rating' }\n }\n }))\n ```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nMongoSQL Programming Interface\n==============================\n\nMongoQuery\n----------\n### Creating a MongoQuery\n`MongoQuery` is the main tool that lets you execute JSON Query Objects against an SqlAlchemy-handled database.\n\nThere are two ways to use it:\n\n1. Construct `MongoQuery` manually, giving it your model:\n\n ```python\n from mongosql import MongoQuery\n from .models import User # Your model\n\n ssn = Session()\n\n # Create a MongoQuery, using an initial Query (possibly, with some initial filtering applied)\n mq = MongoQuery(User).from_query(ssn.query(User))\n ```\n\n2. Use the convenience mixin for your Base:\n\n ```python\n from sqlalchemy.ext.declarative import declarative_base\n from mongosql import MongoSqlBase\n\n Base = declarative_base(cls=(MongoSqlBase,))\n\n class User(Base):\n #...\n ```\n\n Using this Base, your models will have a shortcut method which returns a `MongoQuery`:\n\n ```python\n User.mongoquery(session)\n User.mongoquery(query)\n ```\n\n With `mongoquery()`, you can construct a query from a session:\n\n ```python\n mq = User.mongoquery(session)\n ```\n\n .. or from an [sqlalchemy.orm.Query](https://docs.sqlalchemy.org/en/latest/orm/query.html),\n which allows you to apply some initial filtering:\n\n ```python\n mq = User.mongoquery(\n session.query(User).filter_by(active=True) # Only query active users\n )\n ```\n\n### Reusable\nA `MongoQuery` object itself is not reusable: it can make just one query.\n\nHowever, it makes sense to save some initialization and keep it ready for new requests.\nFor performance reasons, this has to be done manually with the `Reusable` wrapper:\n\n```python\nmq_factory = Reusable(User.mongoquery(session))\n```\n\nThe wrapped object has all the methods of a `MongoQuery`, but will make a proper copy when used.\nThink of it as a factory.\n\n### Querying: `MongoQuery.query()`\nOnce a `MongoQuery` is prepared, you can give it a QueryObject:\n\n```python\n# QueryObject\nquery_object = {\n 'filter': {\n 'sex': 'f',\n 'age': { '$gte': 18, '$lte': 25 }, # 18..25 years\n },\n 'order': ['weight+'], # slims first\n 'limit': 50, # just enough :)\n}\n\n# MongoQuery\nmq = User.mongoquery(ssn).query(**query_object)\n```\n\n### Getting Results: `MongoQuery.end()`\nBecause `MongoQuery` is just a wrapper around an SqlAlchemy's `Query`, you can get that `Query`\nand get results out of it:\n\n```python\n# By calling the `MongoQuery.end()` method, you get an SqlAlchemy `Query`:\nq = mq.end() # SqlALchemy Query\n\n# Execute the query and fetch results\ngirls = q.all()\n```\n\n### Getting All Sorts of Results\nLet's remember that the Query generated by MongoQuery can return three sorts of results:\n\n1. Entities. When the API user has requested an entity of a list of them.\n3. Integer. When the API user has used `{count: 1}`.\n2. Tuples. This is what you get when the API user has used the [Aggregate Operation](#aggregate-operation).\n\n`MongoQuery` has three methods that help you detect what you get:\n\n1. `MongoQuery.result_contains_entities()`\n2. `MongoQuery.result_is_scalar()`\n3. `MongoQuery.result_is_tuples()`\n\nHere's how to use it:\n\n```python\ndef get_result(mq: MongoQuery, query: Query):\n # Handle: Query Object has count\n if mq.result_is_scalar():\n return {'count': query.scalar()}\n\n # Handle: Query Object has group_by and yields tuples\n if mq.result_is_tuples():\n # zip() column names together with the values, and make it into a dict\n return {\n 'results': [dict(zip(row.keys(), row))\n for row in query]\n }\n\n # Regular result: entities\n return {\n 'users': query.all()\n }\n```\n\nMost likely, you won't need to handle that at all: just use [CRUD Helpers](#crud-helpers)\nthat implement most of this logic for you.\n\nMongoQuery Configuration\n------------------------\n\n`MongoQuery` has plenty of settings that lets you configure the way queries are made,\nto fine-tune their security limitations, and to implement some custom behaviors.\n\nThese settings can be nicely kept in a [MongoQuerySettingsDict](mongosql/util/settings_dict.py)\nand given to MongoQuery as the second argument.\n\nExample:\n\n```python\nfrom mongosql import MongoQuery, MongoQuerySettingsDict\n\nmq = MongoQuery(models.User, MongoQuerySettingsDict(\n bundled_project=dict(\n # can only join to the following relations\n allowed_relations=('articles', 'comments'),\n # configure nested queries\n related=dict(\n manager=dict(\n force_exclude=('password',),\n )\n ),\n # enable aggregation for columns\n aggregate_columns=('age',),\n ),\n))\n```\n\nThe available settings are:\n\n\n* `default_projection`: (for: project)\n The default projection to use when no input was provided.\n When an input value is given, `default_projection` is not used at all: it overrides the default\n completely. If you want to merge some default into every projection, use some of the following settings:\n `default_exclude`, `force_include`, `force_exclude`\n\n NOTE: If you want the API to return *all fields* by default, use `None`. If you want the API to\n return *no fields* by default, use an empty list `[]`.\n This is because `None` is seen as \"no default\", and MongoSQL uses its internal default of including\n all fields; but `[]` is seen as an instruction \"to include no fields by default\".\n\n* `default_exclude`: (for: project)\n A list of attributes that are excluded from every projection.\n The only way to load these attributes would be to request them explicitly.\n Use this for properties that contain a lot of data, or require extra queries.\n\n* `default_exclude_properties`: (for: project)\n When `True`, all `@property` and `@hybrid_property` attributes\n will be excluded by default (put into `default_exclude`).\n This is a convenivent shortcut.\n Use `default_include_properties` to overrule.\n\n* `default_unexclude_properties`: (for: project)\n The list of `@property` and `@hybrid_property` attributes that won't be excluded:\n they will be treated like the rest of the columns.\n\n* `bundled_project`: (for: project)\n The dict that declares columns that depend on other columns being loaded.\n When you have a property that depends on some columns, and the user wants it loaded, the setting\n got to have the name of the property mapped to the list of dependent columns.\n Example: {'full_name': ['first_name', 'last_name']}\n The additional columns would be loaded quietly, without being included into the projection.\n\n* `force_include`: (for: project)\n A list of attributes that will always be loaded and included into the output.\n\n* `force_exclude`: (for: project)\n A list of attributes that will always be unloaded and excluded from the output.\n No matter what you do, you can't access them.\n\n* `ensure_loaded`: (for: project)\n A list of columns that will be loaded even when the user didn't request them.\n These columns will be loaded quietly, however, without being included into the projection.\n Use case: columns which your code requires. It would break without them, in case the user excludes them.\n You wouldn't want to force include them, but you'd like to include them 'quietly'.\n\n* `raiseload_col`: (for: project)\n Granular `raiseload`: only raise when columns are lazy loaded\n\n* `raiseload_rel`: (for: join)\n Granular `raiseload`: only raise when relations are lazy loaded\n\n* `raiseload`: (for: project, join)\n Raise an exception when a column or a relationship that was not loaded\n is accessed by the application.\n This would result in an additional SQL query, which is very slow.\n\n This is a performance safeguard: when the API user does not want certain columns,\n they are not loaded. However, when the application tries to access them.\n When `raiseload_col=True`, you'll need to load all the columns & relationships manually\n (with `undefer()` and `joinedload()`), or by using `MongoQuery.ensure_loaded()`.\n\n* `aggregate_columns`: (for: aggregate)\n List of column names for which aggregation is enabled.\n All columns for which aggregation is not explicitly enabled are disabled.\n\n* `aggregate_labels`: (for: aggregate)\n Whether to enable labelling columns (aliases).\n This features is mostly useless,\n but exists here to complete compatilibility with MongoDB queries.\n\n* `force_filter`: (for: filter)\n A dictionary with a filter that will be forced onto every request;\n or a Python `callable(model)` that returns a filtering condition for Query.filter().\n\n* `scalar_operators`: (for: filter)\n A dict of additional operators for scalar columns.\n A better way to declare global operators would be to subclass MongoFilter\n and declare the additional operators inside the class.\n\n* `array_operators`: (for: filter)\n A dict of additional operators for array columns.\n\n* `allowed_relations`: (for: join)\n An explicit list of relationships that can be loaded by the user.\n All other relationships will raise a DisabledError when a 'join' is attempted.\n\n* `banned_relations`: (for: join)\n An list of relationships that cannot be loaded by the user: DisabledError will be raised.\n\n* `max_items`: (for: limit)\n The maximum number of items that can be loaded with this query.\n The user can never go any higher than that, and this value is forced onto every query.\n\n* `legacy_fields`: (for: everything)\n The list of fields (columns, relationships) that used to exist, but do not anymore.\n These fields will be quietly ignored by all handlers. Note that they will still appear in projections\n from `project` and `join` handlers. If you rely on them, your code will have to be able to ignore\n those fields as well.\n\n This is implemented for introducing breaking changes into the code when developers might still refer\n to the old column which is simply not there anymore.\n\n When a relationship or a column has disappeared from the model, the recommended\n backwards-compatible approach is to have it both in `legacy_fields` and `force_include`,\n and a @property that provides some fake value for compatibility.\n This way, clients will always get something, even though they cannot join manually anymore.\n\n* `aggregate_enabled`: Enable/disable the `aggregate` handler\n\n* `count_enabled`: Enable/disable the `count` handler\n\n* `filter_enabled`: Enable/disable the `filter` handler\n\n* `group_enabled`: Enable/disable the `group` handler\n\n* `join_enabled`: Enable/disable the `join` handler\n\n* `joinf_enabled`: Enable/disable the `joinf` handler\n\n* `limit_enabled`: Enable/disable the `limit` handler\n\n* `project_enabled`: Enable/disable the `project` handler\n\n* `sort_enabled`: Enable/disable the `sort` handler\n\n* `related`: Settings for queries on related models, based on the relationship name.\n\n For example, when a `User` has a relationship named 'articles',\n you can put the 'articles' key into this setting, and configure\n how queries to the related models are made.\n\n This way, you can define a completely different set of settings when a model is\n queried through another model's relationship.\n\n ```python\n related = dict(\n # handler_settings for nested queries may be configured per relationship\n relation-name: dict,\n relation-name: lambda: dict,\n relation-name: None, # will fall back to '*'\n # The default\n # If there's no default, or gives None, `related_models` will be used\n '*': lambda relationship_name, target_model: dict | None,\n )\n # or\n related = lambda: dict\n ```\n\n* `related_models`: When configuring every relationship seems to be too much, and you just want to define\n common settings for every model, use this setting instead of 'related'.\n\n It will automatically configure every relationship based on the target model.\n\n ```python\n related_models = dict(\n # handler_settings for nested queries may be configured per model\n # note that you're supposed to use models, not their names!\n Model: dict,\n Model: lambda: dict,\n Model: None, # will fall back to '*'\n # The default\n # If there's no default, or it yields None, the default handler_settings is used\n '*': lambda relationship_name, target_model: dict | None,\n # Example:\n '*': lambda *args: dict(join=False) # disallow further joins\n )\n # or\n related_models = lambda: dict\n ```\n\n It can also be used as a default, when there's no custom configuration provided in\n the 'related' settings.\n\n The 'related_models' setting actually enables you to have one global dict that will\n define the \"default\" rules that apply to an entity, no matter how it's loaded:\n directly, or through a relationship of another model.\n\n ```python\n # Collect all your settings into one global dict\n all_settings = {\n User: user_settings,\n Article: article_settings,\n Comment: comment_settings,\n }\n\n # and reference it recursively from every model:\n user_settings = dict(\n related_models=lambda: all_settings\n )\n ```\n\n Be careful, though: if every model inherits its `allowed_relations`,\n it would be possible to get almost any object through a series of nested joins!\n\n\n\n\nMore settings are available through the [CRUD helper](#crud-helpers) settings,\nwhich is an extension of [MongoQuery Configuration](#mongoquery-configuration):\n\n\n* `writable_properties`: Are `@property` model attributes writable?\n\n When `False`, and incoming JSON object will only be allowed to set/modify real\n columns. The only way to save a value for a `@property` would be to use the\n `@saves_relations` decorator and handle the value manually.\n\n When `True`, even `@property` and `@hybrid_property` objects will be writable.\n Note that validation, as with other fields, is up to you.\n In order to be completely writable, it also has to be in the `rw_fields` list.\n\n* `ro_fields`: The list of read-only fields.\n\n These fields can only be modified in the code.\n Whenever any of those fields is submitted to the API endpoint, it's ignored,\n and even removed from the incoming entity dict.\n\n* `rw_fields`: The list of writable fields.\n\n When you have too many `ro_fields`, it may be easier to provide a list of\n those that are writable; all the rest become read-only.\n\n* `const_fields`: The list of constant fields.\n\n These fields can only be set when an object is created, but never changed\n when it is modified.\n\n* `query_defaults`: Default values for every Query Object.\n\n This is the default Query Object that provides the defaults for every query.\n For instance, this may be the default `limit: 100`, or a default `project` operator.\n\n* `**mongoquery_settings`: more settings for `MongoQuery` (as described above)\n\n\n\nMongoQuery API\n--------------\n\n### `MongoQuery(model, handler_settings=None)`\nMongoQuery is a wrapper around SqlAlchemy's `Query` that can safely execute JSON Query Objects\n\n### `MongoQuery.from_query(query) -> MongoQuery`\nSpecify a custom sqlalchemy query to work with.\n\nIt can have, say, initial filtering already applied to it.\nIt no default query is provided, _from_query() will use the default.\n\n\nArguments:\n\n\n* `query: Query`: Initial sqlalchemy query to work with (e.g. with initial filters pre-applied)\n\n\n\n\nReturns `MongoQuery`\n\n\n\n\n\n### `MongoQuery.with_session(ssn) -> MongoQuery`\nQuery with the given sqlalchemy Session\n\n\nArguments:\n\n\n* `ssn: Session`: The SqlAlchemy `Session` to use for querying\n\n\n\n\nReturns `MongoQuery`\n\n\n\n\n\n### `MongoQuery.query(**query_object) -> MongoQuery`\nBuild a MongoSql query from an object\n\n\nArguments:\n\n\n* `**query_object`: The Query Object to execute.\n\n\n\n\nReturns `MongoQuery`\n\n\n\nExceptions:\n\n\n* `InvalidRelationError`: Invalid relationship name provided in the input\n\n* `InvalidColumnError`: Invalid column name provided in the input\n\n* `InvalidQueryError`: syntax error for any of the Query Object sections\n\n* `InvalidQueryError`: unknown Query Object operations provided (extra keys)\n\n\n\n\n\n### `MongoQuery.end() -> Query`\nGet the resulting sqlalchemy `Query` object\n\n\n\n\nReturns `Query`\n\n\n\n\n\n### `MongoQuery.end_count() -> CountingQuery`\nGet the result, and also count the total number of rows.\n\nBe aware that the cost will be substantially higher than without the total number,\nbut still cheaper than two separate queries.\n\nNumbers: this gives about 50% boost to small result sets, and about 15% boost to larger result sets.\n\nSee [CountingQuery](#countingqueryquery) for more details.\n\n\n\n\nReturns `CountingQuery`\n\n\n\n\n\nExample:\n\n```python\nq = User.mongoquery(ssn).query(...).end_count()\n\n# Get the count\nq.count # -> 127\n\n# Get results\nlist(q) # -> [User, ...]\n\n# (!) only one actual SQL query was made\n```\n\n\n### `MongoQuery.result_contains_entities() -> bool`\nTest whether the result will contain entities.\n\nThis is normally the case in the absence of 'aggregate', 'group', and 'count' queries.\n\n\n\n\nReturns `bool`\n\n\n\n\n\n### `MongoQuery.result_is_scalar() -> bool`\nTest whether the result is a scalar value, like with count\n\nIn this case, you'll fetch it like this:\n\n MongoQuery(...).end().scalar()\n\n\n\n\nReturns `bool`\n\n\n\n\n\n### `MongoQuery.result_is_tuples() -> bool`\nTest whether the result is a list of keyed tuples, like with group_by\n\nIn this case, you might fetch it like this:\n\n res = MongoQuery(...).end()\n return [dict(zip(row.keys(), row)) for row in res], None\n\n\n\n\nReturns `bool`\n\n\n\n\n\n### `MongoQuery.ensure_loaded(*cols) -> MongoQuery`\nEnsure the given columns, relationships, and related columns are loaded\n\nDespite any projections and joins the user may be doing, make sure that the given `cols` are loaded.\nThis will ensure that every column is loaded, every relationship is joined, and none of those is included\ninto `projection` and `pluck_instance`.\n\nThis method is to be used by the application code to handle the following situation:\n* The API user has requested only fields 'a', 'b', 'c' to be loaded\n* The application code needs field 'd' for its operation\n* The user does not want to see no 'd' in the output.\nSolution: use ensure_loaded('d'), and then pluck_instance()\n\nLimitations:\n\n1. If the user has requested filtering on a relationship, you can't use ensure_loaded() on it.\n This method will raise an InvalidQueryError().\n This makes sense, because if your application code relies on the presence of a certain relationship,\n it certainly needs it fully loaded, and unfiltered.\n2. If the request contains no entities (e.g. 'group' or 'aggregate' handlers are used),\n this method would throw an AssertionError\n\nIf all you need is just to know whether something is loaded or not, use MongoQuery.__contains__() instead.\n\nRemember that every time you use ensure_loaded() on a relationship, you disable the possibility of filtering for it!\n\n\nArguments:\n\n\n* `*cols`: Column names ('age'), Relation names ('articles'), or Related column names ('articles.name')\n\n\n\n\nReturns `MongoQuery`\n\n\n\nExceptions:\n\n\n* `ValueError`: invalid column or relationship name given.\n It does not throw `InvalidColumnError` because that's likely your error, not an error of the API user :)\n\n* `InvalidQueryError`: cannot merge because the relationship has a filter on it\n\n\n\n\n\n### `MongoQuery.get_final_query_object() -> dict`\nGet the final Query Object dict (after all handlers have applied their defaults).\n\nThis Query Object will contain the name of every single handler, including those that were not given any input.\n\n\n\n\nReturns `dict`\n\n\n\n\n\n### `MongoQuery.get_projection_tree() -> dict`\nGet a projection-like dict that maps every included column to 1,\nand every relationship to a nested projection dict.\n\n\n\n\nReturns `dict`: the projection\n\n\n\n\n\nExample:\n\n```python\nMongoQuery(User).query(join={'articles': dict(project=('id',))}).handler_join.projection\n#-> {'articles': {'id': 1}}\n```\n\nThis is mainly useful for debugging nested Query Objects.\n\n\n### `MongoQuery.get_full_projection_tree() -> dict`\nGet a full projection tree that mentions every column, but only those relationships that are loaded\n\n\n\n\nReturns `dict`\n\n\n\n\n\n### `MongoQuery.pluck_instance(instance) -> dict`\nPluck an sqlalchemy instance and make it into a dict\n\nThis method should be used to prepare an object for JSON encoding.\nThis makes sure that only the properties explicitly requested by the user get included\ninto the result, and *not* the properties that your code may have loaded.\n\nProjection and Join properties are considered.\n\n\nArguments:\n\n\n* `instance: object`: object\n\n\n\n\nReturns `dict`\n\n\n\n\n\n\n### Handlers\nIn addition to this, `MongoQuery` lets you inspect the internals of the MongoQuery.\nEvery handler is available as a property of the `MongoQuery`:\n\n* `MongoQuery.handler_project`: [handlers.MongoProject](mongosql/handlers/project.py)\n* `MongoQuery.handler_sort`: [handlers.MongoSort](mongosql/handlers/sort.py)\n* `MongoQuery.handler_group`: [handlers.MongoGroup](mongosql/handlers/group.py)\n* `MongoQuery.handler_join`: [handlers.MongoJoin](mongosql/handlers/join.py)\n* `MongoQuery.handler_joinf`: [handlers.MongoFilteringJoin](mongosql/handlers/joinf.py)\n* `MongoQuery.handler_filter`: [handlers.MongoFilter](mongosql/handlers/filter.py)\n* `MongoQuery.handler_aggregate`: [handlers.MongoAggregate](mongosql/handlers/aggregate.py)\n* `MongoQuery.handler_limit`: [handlers.MongoLimit](mongosql/handlers/limit.py)\n* `MongoQuery.handler_count`: [handlers.MongoCount](mongosql/handlers/count.py)\n\nSome of them have methods which may be useful for the application you're building,\nespecially if you need to get some information out of `MongoQuery`.\n\n\n\n\n\nCRUD Helpers\n============\n\nMongoSql is designed to help with data selection for the APIs.\nTo ease the pain of implementing CRUD for all of your models,\nMongoSQL comes with a CRUD helper that exposes MongoSQL capabilities for querying to the API user.\nTogether with [RestfulView](https://github.com/kolypto/py-flask-jsontools#restfulview)\nfrom [flask-jsontools](https://github.com/kolypto/py-flask-jsontools),\nCRUD controllers are extremely easy to build.\n\n## `CrudHelper(model, writable_properties=True, **handler_settings)`\nCrud helper: an object that helps implement CRUD operations for an API endpoint:\n\n* Create: construct SqlAlchemy instances from the submitted entity dict\n* Read: use MongoQuery for querying\n* Update: update SqlAlchemy instances from the submitted entity using a dict\n* Delete: use MongoQuery for deletion\n\nSource: [mongosql/crud/crudhelper.py](mongosql/crud/crudhelper.py)\n\nThis object is supposed to be initialized only once;\ndon't do it for every query, keep it at the class level!\n\nMost likely, you'll want to keep it at the class level of your view:\n\n```python\nfrom .models import User\nfrom mongosql import CrudHelper\n\nclass UserView:\n crudhelper = CrudHelper(\n # The model to work with\n User,\n # Settings for MongoQuery\n **MongoQuerySettingsDict(\n allowed_relations=('user_profile',),\n )\n )\n # ...\n```\n\nNote that during \"create\" and \"update\" operations, this class lets you write values\nto column attributes, and also to @property that are writable (have a setter).\nIf this behavior (with writable properties) is undesirable,\nset `writable_properties=False`\n\nThe following methods are available:\n\n### `CrudHelper.query_model(query_obj=None, from_query=None) -> MongoQuery`\nMake a MongoQuery using the provided Query Object\n\nNote that you have to provide the MongoQuery yourself.\nThis is because it has to be properly configured with handler_settings.\n\n\nArguments:\n\n\n* `query_obj: Union[Mapping, NoneType] = None`: The Query Object to use\n\n* `from_query: Union[sqlalchemy.orm.query.Query, NoneType] = None`: An optional Query to initialize MongoQuery with\n\n\n\n\nReturns `MongoQuery`\n\n\n\nExceptions:\n\n\n* `exc.DisabledError`: A feature is disabled; likely, due to a configuration issue. See handler_settings.\n\n* `exc.InvalidQueryError`: There is an error in the Query Object that the user has made\n\n* `exc.InvalidRelationError`: Invalid relationship name specified in the Query Object by the user\n\n* `exc.InvalidColumnError`: Invalid column name specified in the Query Object by the user\n\n\n\n\n\n### `CrudHelper.create_model(entity_dict) -> object`\nCreate an instance from entity dict.\n\nThis method lets you set the value of columns and writable properties,\nbut not relations. Use @saves_relations to handle additional fields.\n\n\nArguments:\n\n\n* `entity_dict: Mapping`: Entity dict\n\n\n\n\nReturns `object`: Created instance\n\n\n\nExceptions:\n\n\n* `InvalidColumnError`: invalid column\n\n* `InvalidQueryError`: validation errors\n\n\n\n\n\n### `CrudHelper.update_model(entity_dict, instance) -> object`\nUpdate an instance from an entity dict by merging the fields\n\n- Attributes are copied over\n- JSON dicts are shallowly merged\n\nNote that because properties are *copied over*,\nthis operation does not replace the entity; it merely updates the entity.\n\nIn other words, this method does a *partial update*:\nonly updates the fields that were provided by the client, leaving all the rest intact.\n\n\nArguments:\n\n\n* `entity_dict: Mapping`: Entity dict\n\n* `instance: object`: The instance to update\n\n\n\n\nReturns `object`: New instance, updated\n\n\n\nExceptions:\n\n\n* `InvalidColumnError`: invalid column\n\n* `InvalidQueryError`: validation errors\n\n\n\n\n\n\n\n## `StrictCrudHelper`\nA Strict Crud Helper imposes defaults and limitations on the API user:\n\nSource: [mongosql/crud/crudhelper.py](mongosql/crud/crudhelper.py)\n\n- Read-only fields can not be set: not with create, nor with update\n- Constant fields can be set initially, but never be updated\n- Defaults for Query Object provide the default values for every query, unless overridden\n\nThe following behavior is implemented:\n\n* By default, all fields are writable\n* If ro_fields is provided, these fields become read-only, all other fields are writable\n* If rw_fields is provided, ony these fields are writable, all other fields are read-only\n* If const_fields, it is seen as a further limitation on rw_fields: those fields would be writable,\n but only once.\n\n### `StrictCrudHelper(model, writable_properties=True, ro_fields=None, rw_fields=None, const_fields=None, query_defaults=None, **handler_settings)`\nInitializes a strict CRUD helper\n\nNote: use a `**StrictCrudHelperSettingsDict()` to help you with the argument names and their docs!\n\n\nArguments:\n\n\n* `model: DeclarativeMeta`: The model to work with\n\n* `writable_properties: bool = True`: \n\n* `ro_fields: Union[Iterable[str], Callable, NoneType] = None`: List of read-only property names, or a callable which gives the list\n\n* `rw_fields: Union[Iterable[str], Callable, NoneType] = None`: List of writable property names, or a callable which gives the list\n\n* `const_fields: Union[Iterable[str], Callable, NoneType] = None`: List of property names that are constant once set, or a callable which gives the list\n\n* `query_defaults: Union[Iterable[str], Callable, NoneType] = None`: Defaults for every Query Object: Query Object will be merged into it.\n\n* `**handler_settings`: Settings for the `MongoQuery` used to make queries\n\n\n\n\n\n\n\n\nExample:\n\n```python\nfrom .models import User\nfrom mongosql import StrictCrudHelper, StrictCrudHelperSettingsDict\n\nclass UserView:\n crudhelper = StrictCrudHelper(\n # The model to work with\n User,\n # Settings for MongoQuery and StrictCrudHelper\n **StrictCrudHelperSettingsDict(\n # Can never be set of modified\n ro_fields=('id',),\n # Can only be set once\n const_fields=('login',),\n # Relations that can be `join`ed\n allowed_relations=('user_profile',),\n )\n )\n # ...\n```\n\n\n\n\n## `CrudViewMixin()`\nA mixin class for implementations of CRUD views.\n\nThis class is supposed to be re-initialized for every request.\n\nTo implement a CRUD view:\n1. Implement some method to extract the Query Object from the request\n2. Set `crudhelper` at the class level, initialize it with the proper settings\n3. Implement the `_get_db_session()` and the `_get_query_object()` methods\n4. If necessary, implement the `_save_hook()` to customize new & updated entities\n5. Override `_method_list()` and `_method_get()` to customize its output\n6. Override `_method_create()`, `_method_update()`, `_method_delete()` and implement saving to the DB\n7. Use [`@saves_relations`](#saves_relationsfield_names) method decorator to handle custom fields in the input dict\n\nFor an example on how to use CrudViewMixin, see this implementation:\n[tests/crud_view.py](tests/crud_view.py)\n\nAttrs:\n _mongoquery (MongoQuery):\n The MongoQuery object used to process this query.\n\n### `CrudViewMixin._get_db_session() -> Session`\n(Abstract method) Get a DB session to be used for queries made in this view\n\n\n\n\nReturns `Session`: sqlalchemy.orm.Session\n\n\n\n\n\n### `CrudViewMixin._get_query_object() -> Mapping`\n(Abstract method) Get the Query Object for the current query.\n\nNote that the Query Object is not only supported for get() and list() methods, but also for\ncreate(), update(), and delete(). This enables the API use to request a relationship right away.\n\n\n\n\nReturns `Mapping`\n\n\n\n\n\n\n### `CrudViewMixin._method_get(*filter, **filter_by) -> object`\n(CRUD method) Fetch a single entity: as in READ, single entity\n\nNormally, used when the user has supplied a primary key:\n\n GET /users/1\n\n\nArguments:\n\n\n* `*filter`: Additional filter() criteria\n\n* `**filter_by`: Additional filter_by() criteria\n\n\n\n\nReturns `object`\n\n\n\nExceptions:\n\n\n* `exc.InvalidQueryError`: Query Object errors made by the user\n\n* `sqlalchemy.orm.exc.MultipleResultsFound`: Multiple found\n\n* `sqlalchemy.orm.exc.NoResultFound`: Nothing found\n\n\n\n\n\n### `CrudViewMixin._method_list(*filter, **filter_by) -> Iterable[object]`\n(CRUD method) Fetch a list of entities: as in READ, list of entities\n\nNormally, used when the user has supplied no primary key:\n\n GET /users/\n\nNOTE: Be careful! This methods does not always return a list of entities!\nIt can actually return:\n1. A scalar value: in case of a 'count' query\n2. A list of dicts: in case of an 'aggregate' or a 'group' query\n3. A list or entities: otherwise\n\nPlease use the following MongoQuery methods to tell what's going on:\nMongoQuery.result_contains_entities(), MongoQuery.result_is_scalar(), MongoQuery.result_is_tuples()\n\nOr, else, override the following sub-methods:\n_method_list_result__entities(), _method_list_result__groups(), _method_list_result__count()\n\n\nArguments:\n\n\n* `*filter`: Additional filter() criteria\n\n* `**filter_by`: Additional filter_by() criteria\n\n\n\n\nReturns `Iterable[object]`\n\n\n\nExceptions:\n\n\n* `exc.InvalidQueryError`: Query Object errors made by the user\n\n\n\n\n\n### `CrudViewMixin._method_create(entity_dict) -> object`\n(CRUD method) Create a new entity: as in CREATE\n\nNormally, used when the user has supplied no primary key:\n\n POST /users/\n {'name': 'Hakon'}\n\n\nArguments:\n\n\n* `entity_dict: dict`: Entity dict\n\n\n\n\nReturns `object`: The created instance (to be saved)\n\n\n\nExceptions:\n\n\n* `exc.InvalidQueryError`: Query Object errors made by the user\n\n\n\n\n\n### `CrudViewMixin._method_update(entity_dict, *filter, **filter_by) -> object`\n(CRUD method) Update an existing entity by merging the fields: as in UPDATE\n\nNormally, used when the user has supplied a primary key:\n\n POST /users/1\n {'id': 1, 'name': 'Hakon'}\n\n\nArguments:\n\n\n* `entity_dict: dict`: Entity dict\n\n* `*filter`: Criteria to find the previous entity\n\n* `**filter_by`: Criteria to find the previous entity\n\n\n\n\nReturns `object`: The updated instance (to be saved)\n\n\n\nExceptions:\n\n\n* `exc.InvalidQueryError`: Query Object errors made by the user\n\n* `sqlalchemy.orm.exc.MultipleResultsFound`: Multiple entities found with the filter condition\n\n* `sqlalchemy.orm.exc.NoResultFound`: The entity not found\n\n\n\n\n\n### `CrudViewMixin._method_delete(*filter, **filter_by) -> object`\n(CRUD method) Delete an existing entity: as in DELETE\n\nNormally, used when the user has supplied a primary key:\n\n DELETE /users/1\n\nNote that it will load the entity from the database prior to deletion.\n\n\nArguments:\n\n\n* `*filter`: Criteria to find the previous entity\n\n* `**filter_by`: Criteria to find the previous entity\n\n\n\n\nReturns `object`: The instance to be deleted\n\n\n\nExceptions:\n\n\n* `exc.InvalidQueryError`: Query Object errors made by the user\n\n* `sqlalchemy.orm.exc.MultipleResultsFound`: Multiple entities found with the filter condition\n\n* `sqlalchemy.orm.exc.NoResultFound`: The entity not found\n\n\n\n\n\n\n### `CrudViewMixin._mongoquery_hook(mongoquery) -> MongoQuery`\n(Hook) A hook invoked in _mquery() to modify MongoQuery, if necessary\n\nThis is the last chance to modify a MongoQuery.\nRight after this hook, it end()s, and generates an sqlalchemy Query.\n\nUse self._current_crud_method to tell what is going on: create, read, update, delete?\n\n\nArguments:\n\n\n* `mongoquery: MongoQuery`: \n\n\n\n\nReturns `MongoQuery`\n\n\n\n\n\n### `CrudViewMixin._save_hook(new, prev=None)`\n(Hook) Hooks into create(), update() methods, before an entity is saved.\n\nThis allows to make some changes to the instance before it's actually saved.\nThe hook is provided with both the old and the new versions of the instance (!).\n\n\nArguments:\n\n\n* `new: object`: The new instance\n\n* `prev: object = None`: Previously persisted version (is provided only when updating).\n\n\n\n\n\n\n\n\n\n\n## `@saves_relations(*field_names)`\nA decorator that marks a method that handles saving some related models (or any other custom values)\n\nWhenever a relationship is marked for saving with the help of this decorator,\nit is plucked out of the incoming JSON dict, and after an entity is created,\nit is passed to the method that this decorator decorates.\n\nIn addition to saving relationships, a decorated mthod can be used to save any custom properties:\nthey're plucked out of the incoming entity dict, and handled manually anyway.\nNote that all attributes that do not exist on the model are plucked out, and the only way to handle them\nis through this method.\n\nNOTE: this method is executed before _save_hook() is.\n\nExample usage:\n\n```python\nfrom mongosql import saves_relations\nfrom mongosql import ABSENT # unique marker used to detect values not provided\n\nclass UserView(CrudViewMixin):\n @saves_relations('articles')\n def save_articles(self, new: object, prev: object = None, articles = ABSENT):\n if articles is not ABSENT:\n ... # articles-saving logic\n```\n\nNOTE: the handler method is called with two positional arguments, and the rest being keyword arguments:\n\n save_articles(new_instance, prev_instance, **relations_to_be_saved)\n\nNOTE: If the user did not submit any related entity, the method is still called, with relationship argument = None.\n\nMultiple relations can be provided: in this case, all of them are handled with one method.\n\n\n\n\n\nOther Useful Tools\n==================\n\n## `ModelPropertyBags(model)`\nModel Property Bags is the class that lets you get information about the model's columns.\n\nThis is the class that binds them all together: Columns, Relationships, PKs, etc.\nAll the meta-information about a certain Model is stored here:\n\n- Columns\n- Relationships\n- Primary keys\n- Nullable columns\n- Properties and Hybrid Properties\n- Columns of related models\n- Writable properties\n\nWhenever it's too much to inspect several properties, use a `CombinedBag()` over them,\nwhich lets you get a column from a number of bags.\n\n## `CombinedBag(**bags)`\nA bag that combines elements from multiple bags.\n\nThis one is used when something can handle both columns and relationships, or properties and\ncolumns. Because this depends on what you're doing, this generalized implementation is used.\n\nIn order to initialize it, you give them the bags you need as a dict:\n\n cbag = CombinedBag(\n col=bags.columns,\n rel=bags.related_columns,\n )\n\nNow, when you get an item, you get the aliased name that you have used:\n\n bag_name, bag, col = cbag['id']\n bag_name #-> 'col'\n bag #-> bags.columns\n col #-> User.id\n\nThis way, you can always tell which bag has the column come from, and handle it appropriately.\n\n## `CountingQuery(query)`\n`Query` object wrapper that can count the rows while returning results\n\nThis is achieved by SELECTing like this:\n\n SELECT *, count(*) OVER() AS full_count\n\nIn order to be transparent, this class eliminates all those tuples in results and still returns objects\nlike a normal query would. The total count is available through a property.\n\n\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/kolypto/py-mongosql", "keywords": "sqlalchemy", "license": "BSD", "maintainer": "", "maintainer_email": "", "name": "mongosql", "package_url": "https://pypi.org/project/mongosql/", "platform": "any", "project_url": "https://pypi.org/project/mongosql/", "project_urls": { "Homepage": "https://github.com/kolypto/py-mongosql" }, "release_url": "https://pypi.org/project/mongosql/2.0.7/", "requires_dist": [ "sqlalchemy (!=1.2.9,>=1.2.0)" ], "requires_python": "", "summary": "A JSON query engine with SqlAlchemy as a back-end", "version": "2.0.7" }, "last_serial": 5940213, "releases": { "1.0.0-0": [ { "comment_text": "built for Linux-3.13.0-32-generic-x86_64-with-glibc2.4", "digests": { "md5": "a85821f78b3477f166978662530397a5", "sha256": "9a43d16a996f65843185b96471649d5b8aff30f103ad2a4aebbdae738b2855de" }, "downloads": -1, "filename": "mongosql-1.0.0-0.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "a85821f78b3477f166978662530397a5", "packagetype": "bdist_dumb", "python_version": "any", "requires_python": null, "size": 51831, "upload_time": "2014-07-31T17:43:49", "url": "https://files.pythonhosted.org/packages/17/2a/d1cfd16d83bd0ad391db1587901ef42f33d177bfe17bc8322fc89c2ece37/mongosql-1.0.0-0.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "205e3075736180f9882e8d8570e857ce", "sha256": "f6046bcc26940b310bfafa765679d8a69ec16d4546e094af236a81b977ef4c06" }, "downloads": -1, "filename": "mongosql-1.0.0-0.tar.gz", "has_sig": false, "md5_digest": "205e3075736180f9882e8d8570e857ce", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33076, "upload_time": "2014-07-31T17:43:45", "url": "https://files.pythonhosted.org/packages/92/ff/431e21362cd2e7e4fff0504c75b9fc93ed5d0ae0ca71d8dad9cbedcfb9db/mongosql-1.0.0-0.tar.gz" } ], "1.0.1-0": [ { "comment_text": "built for Linux-3.13.0-32-generic-x86_64-with-glibc2.4", "digests": { "md5": "5cd07cf7a43345fc5702fcd5eb378f0b", "sha256": "86bfae49e08f3dce9e42dedb7bd3eb9d2ede50ada07f2306c52ff51848e55b9c" }, "downloads": -1, "filename": "mongosql-1.0.1-0.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "5cd07cf7a43345fc5702fcd5eb378f0b", "packagetype": "bdist_dumb", "python_version": "any", "requires_python": null, "size": 51826, "upload_time": "2014-08-08T09:40:53", "url": "https://files.pythonhosted.org/packages/e9/29/6780c3a5b1291370ab443e1884bfa4355b1413824eec02238a447985e885/mongosql-1.0.1-0.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "60bb4cbd805303c81fb04a6a2283aad7", "sha256": "628da10ba2066fbad816ac8fa0e50937b77d2db6f5e29a7693e9292daacefb44" }, "downloads": -1, "filename": "mongosql-1.0.1-0.tar.gz", "has_sig": false, "md5_digest": "60bb4cbd805303c81fb04a6a2283aad7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33128, "upload_time": "2014-08-08T09:40:49", "url": "https://files.pythonhosted.org/packages/26/70/2bfe7e648d92482c9741e1b4d04fda8c69f58df94bb7ba268e930536fca5/mongosql-1.0.1-0.tar.gz" } ], "1.0.1-1": [ { "comment_text": "built for Linux-3.13.0-32-generic-x86_64-with-glibc2.4", "digests": { "md5": "9bae6662c596e5613709990c4d2fb07d", "sha256": "4acd5e333503bfe0944f91cb3949ded348ef42aac43510d266e8c918c95e046d" }, "downloads": -1, "filename": "mongosql-1.0.1-1.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "9bae6662c596e5613709990c4d2fb07d", "packagetype": "bdist_dumb", "python_version": "any", "requires_python": null, "size": 52179, "upload_time": "2014-08-08T18:16:42", "url": "https://files.pythonhosted.org/packages/9d/f5/8f03c93e8688594979f5deb871546ba911cd4f80aa2dbdd58f62c0080d5b/mongosql-1.0.1-1.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "88798e1f19f10e0e3b26e4438746b7b6", "sha256": "577facf2c0111b0753d7955b72695e79a6aee2a35e6398743f998c60f4de6ed7" }, "downloads": -1, "filename": "mongosql-1.0.1-1.tar.gz", "has_sig": false, "md5_digest": "88798e1f19f10e0e3b26e4438746b7b6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33266, "upload_time": "2014-08-08T18:16:38", "url": "https://files.pythonhosted.org/packages/35/1b/77ebae87faa79c6d9dcf1c5a8644de5df36e0f7f9129865828764eceef19/mongosql-1.0.1-1.tar.gz" } ], "1.0.2-0": [ { "comment_text": "built for Linux-3.13.0-32-generic-x86_64-with-glibc2.4", "digests": { "md5": "0ab527616a966a61ea550fe8122d063f", "sha256": "69981519fae3fc7627cee581ee8039fa543df7889400d4d7331dda13f9b86cd7" }, "downloads": -1, "filename": "mongosql-1.0.2-0.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "0ab527616a966a61ea550fe8122d063f", "packagetype": "bdist_dumb", "python_version": "any", "requires_python": null, "size": 52808, "upload_time": "2014-08-08T23:59:52", "url": "https://files.pythonhosted.org/packages/1b/3f/b8d62915ce2fa8e9908a312e8f0ecc645bc3c1e096970f82a603c5fb4050/mongosql-1.0.2-0.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "53f02bf38df8f6ffeb19692e3f875ca0", "sha256": "5c929ee64d4ba19452c31789a470cee8465e64de520fd993963d9f0293f5097c" }, "downloads": -1, "filename": "mongosql-1.0.2-0.tar.gz", "has_sig": false, "md5_digest": "53f02bf38df8f6ffeb19692e3f875ca0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33438, "upload_time": "2014-08-08T23:59:48", "url": "https://files.pythonhosted.org/packages/73/6e/151ec7f78a4d771b43753f0a4b8cf25d3636bfe42f056d17db7273dc23a1/mongosql-1.0.2-0.tar.gz" } ], "1.0.2-1": [ { "comment_text": "built for Linux-3.13.0-32-generic-x86_64-with-glibc2.4", "digests": { "md5": "6b00ffb0c5749f1457effcf08e1e7252", "sha256": "c6078f621c2f927463a7d59a869c633c0403c9d8ecf6da01618722915c980ce6" }, "downloads": -1, "filename": "mongosql-1.0.2-1.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "6b00ffb0c5749f1457effcf08e1e7252", "packagetype": "bdist_dumb", "python_version": "any", "requires_python": null, "size": 52892, "upload_time": "2014-08-09T00:33:21", "url": "https://files.pythonhosted.org/packages/63/f0/9532ecd274f9f9995ea2e47c8f42c1fc8d453f314b147d38efb2ae0bb6d4/mongosql-1.0.2-1.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "a424392b5a18e78dd682994c171d0231", "sha256": "1d4521e7ff7301f4fb4a20e408d840dcfdc2e74f41a3d0676942cc477be09e93" }, "downloads": -1, "filename": "mongosql-1.0.2-1.tar.gz", "has_sig": false, "md5_digest": "a424392b5a18e78dd682994c171d0231", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33467, "upload_time": "2014-08-09T00:33:17", "url": "https://files.pythonhosted.org/packages/77/1f/9ed1c7ce1bca4d756a5578076cf3cc776a86f8efac4a1a54ead2b31e74cd/mongosql-1.0.2-1.tar.gz" } ], "1.0.3-0": [ { "comment_text": "built for Linux-3.13.0-32-generic-x86_64-with-glibc2.4", "digests": { "md5": "8953cb054eec9ed530779970a2f09115", "sha256": "ebcaf01cd2e1b3514852e4d1d3ee68cd2a150222055a88d82f4edd4d229776a2" }, "downloads": -1, "filename": "mongosql-1.0.3-0.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "8953cb054eec9ed530779970a2f09115", "packagetype": "bdist_dumb", "python_version": "any", "requires_python": null, "size": 53245, "upload_time": "2014-08-09T01:06:20", "url": "https://files.pythonhosted.org/packages/23/d8/a5f6455a90442f7b7b95b7c8084086240937d8a3919069a23407326d6dd1/mongosql-1.0.3-0.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "0126e23d235e03f48c7908a660830210", "sha256": "3b8357b39c7c2ba13a96b72b352ca0d733657d022bf76f5a73ffeea4c59048c3" }, "downloads": -1, "filename": "mongosql-1.0.3-0.tar.gz", "has_sig": false, "md5_digest": "0126e23d235e03f48c7908a660830210", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33715, "upload_time": "2014-08-09T01:06:16", "url": "https://files.pythonhosted.org/packages/a8/81/0936d3021d2f11cafd94d458dbd1ee7fe6af8d38700a5998fb9f23bc8032/mongosql-1.0.3-0.tar.gz" } ], "1.1.0-0": [ { "comment_text": "built for Linux-3.13.0-32-generic-x86_64-with-glibc2.4", "digests": { "md5": "1ae4b5ef59aa1d4fb559bd58b9126dc0", "sha256": "53a63c503db30983d859b02617635e1ab66b387627d3a62f2f094d3b2902d3d3" }, "downloads": -1, "filename": "mongosql-1.1.0-0.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "1ae4b5ef59aa1d4fb559bd58b9126dc0", "packagetype": "bdist_dumb", "python_version": "any", "requires_python": null, "size": 52967, "upload_time": "2014-08-09T17:45:29", "url": "https://files.pythonhosted.org/packages/d6/ba/14932925a10db727122fda59f0ef784f5589d9ef258c8d183d1e5ca1e50e/mongosql-1.1.0-0.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "625b4ea548611c3676a63af5b901c745", "sha256": "ab1d1857fb28617d52c5dc7054a7219a7149b85d85c9648bcfd172c6374f5445" }, "downloads": -1, "filename": "mongosql-1.1.0-0.tar.gz", "has_sig": false, "md5_digest": "625b4ea548611c3676a63af5b901c745", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33588, "upload_time": "2014-08-09T17:45:26", "url": "https://files.pythonhosted.org/packages/d2/f6/9eaa93741e25641e1ff370242ad49d6a62f48690196203c14fc8ac0d6b7c/mongosql-1.1.0-0.tar.gz" } ], "1.1.0-1": [ { "comment_text": "built for Linux-3.13.0-32-generic-x86_64-with-glibc2.4", "digests": { "md5": "c81930e1b09f32845d7896eea77b7c65", "sha256": "85c4adef782b7cc29b387c379fecbd6be491c028359b442a4c8c0edf7b491f36" }, "downloads": -1, "filename": "mongosql-1.1.0-1.linux-x86_64.tar.gz", "has_sig": false, "md5_digest": "c81930e1b09f32845d7896eea77b7c65", "packagetype": "bdist_dumb", "python_version": "any", "requires_python": null, "size": 52937, "upload_time": "2014-08-09T18:05:09", "url": "https://files.pythonhosted.org/packages/03/62/916d0e10857f67214e6d667105e037ea722d9172becbc0761993a14a42f3/mongosql-1.1.0-1.linux-x86_64.tar.gz" }, { "comment_text": "", "digests": { "md5": "41e2bca4ee93ee26c38dc63afedd6d47", "sha256": "b4b22b806acc730f2088ed58e8820719313617b8f7d47aa5c82c95cac34b4968" }, "downloads": -1, "filename": "mongosql-1.1.0-1.tar.gz", "has_sig": false, "md5_digest": "41e2bca4ee93ee26c38dc63afedd6d47", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33566, "upload_time": "2014-08-09T18:05:05", "url": "https://files.pythonhosted.org/packages/4e/ed/63c6952db9b97c66fd7acc43709b2af1d6c4ac7002dcb03f95f3031492b2/mongosql-1.1.0-1.tar.gz" } ], "1.1.0-2": [ { "comment_text": "", "digests": { "md5": "42f9adf9ae4bde3c0dc8d0940c2d8918", "sha256": "183c04a9a9fd2c017e2502e6c4d924e7791c4c45ee72ed30143093fce0de751f" }, "downloads": -1, "filename": "mongosql-1.1.0_2-py2-none-any.whl", "has_sig": false, "md5_digest": "42f9adf9ae4bde3c0dc8d0940c2d8918", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 37352, "upload_time": "2014-08-14T15:43:49", "url": "https://files.pythonhosted.org/packages/f5/f7/0782b9e982c12f2c780e3bde892b66b2f6a495aaa2ef6d41dc3082b2772a/mongosql-1.1.0_2-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "601c9bd05bfaa9b3bbb5589f0a42136c", "sha256": "a6a1de17bc1c027d70b62a43171589f981e3faf740b721610156dfcc6caaa88f" }, "downloads": -1, "filename": "mongosql-1.1.0-2.tar.gz", "has_sig": false, "md5_digest": "601c9bd05bfaa9b3bbb5589f0a42136c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33588, "upload_time": "2014-08-14T15:43:42", "url": "https://files.pythonhosted.org/packages/07/87/e4c46c2a25457db0aa47f1836569eab250d8c7ecbfd3848adcf419767281/mongosql-1.1.0-2.tar.gz" } ], "1.1.1-0": [ { "comment_text": "", "digests": { "md5": "db9edd11f265ae51eae4411d8766a3f7", "sha256": "1861f6aadf2d4a08b760af47a0cf469739ab75c706faa2479d146e7e32f17f40" }, "downloads": -1, "filename": "mongosql-1.1.1_0-py2-none-any.whl", "has_sig": false, "md5_digest": "db9edd11f265ae51eae4411d8766a3f7", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 37578, "upload_time": "2014-08-17T19:15:39", "url": "https://files.pythonhosted.org/packages/dd/6f/7e8b131f8f8ffa224d9115733069419530be20dda5bb41fa4f8515220e68/mongosql-1.1.1_0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "182faa1fd3a7235a27db6c22ad46c488", "sha256": "6790662d00c4388cd680ccbe1fb61acdfa3e08daa75797e98af4755bf2c4f422" }, "downloads": -1, "filename": "mongosql-1.1.1-0.tar.gz", "has_sig": false, "md5_digest": "182faa1fd3a7235a27db6c22ad46c488", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34044, "upload_time": "2014-08-17T19:15:36", "url": "https://files.pythonhosted.org/packages/7d/ab/274564199f13d84976625ae426237c2feb935ba4f493d246d5892077f58d/mongosql-1.1.1-0.tar.gz" } ], "1.1.1-1": [ { "comment_text": "", "digests": { "md5": "1809554992169ef3e5f087233e0b94b9", "sha256": "6005a89eb50653edfe227ff7ac808f7d0b1268ae15b9c2bb71333e4af368000f" }, "downloads": -1, "filename": "mongosql-1.1.1_1-py2-none-any.whl", "has_sig": false, "md5_digest": "1809554992169ef3e5f087233e0b94b9", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 37627, "upload_time": "2014-09-09T11:44:04", "url": "https://files.pythonhosted.org/packages/f8/69/8a92c3b203491a4aa85cb18e33b54ed1cfafbce1b1b42a79e7a19d531d19/mongosql-1.1.1_1-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "5d3e0d5fffde77cea97e641c015ae05f", "sha256": "deaa4fb043abbf0a866d2d4d1b2e812435160fd5eb76edbe17919b96a1f1669d" }, "downloads": -1, "filename": "mongosql-1.1.1-1.tar.gz", "has_sig": false, "md5_digest": "5d3e0d5fffde77cea97e641c015ae05f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34089, "upload_time": "2014-09-09T11:44:00", "url": "https://files.pythonhosted.org/packages/fc/cc/09d3e240f05e9854cf3cdcc30df3230ae9ed455366b57033dbd57ff8a9da/mongosql-1.1.1-1.tar.gz" } ], "1.2.0-0": [ { "comment_text": "", "digests": { "md5": "9e5d11b75277957cc1f0b1e5d26a912e", "sha256": "2f3549e8cb44c844cc5718cc7194514576c35ca57bf579fa28fb322c0d82b8f5" }, "downloads": -1, "filename": "mongosql-1.2.0_0-py2-none-any.whl", "has_sig": false, "md5_digest": "9e5d11b75277957cc1f0b1e5d26a912e", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 37599, "upload_time": "2014-09-10T18:16:16", "url": "https://files.pythonhosted.org/packages/3a/31/791f4db528b7e953ef84771bead5d61a89ca2d18904e653afee068b8a83b/mongosql-1.2.0_0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "7b517ded5b6a195e31f285ef9c2e5598", "sha256": "58ea5087ef3f43da430d83293654d50b2049e3df36118844ed8d1254eafc0df1" }, "downloads": -1, "filename": "mongosql-1.2.0-0.tar.gz", "has_sig": false, "md5_digest": "7b517ded5b6a195e31f285ef9c2e5598", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33841, "upload_time": "2014-09-10T18:16:13", "url": "https://files.pythonhosted.org/packages/f3/3d/e2e555a4c74ad2e3180e70b5801ed4c7ef0fb59a1d902eeb06842bb865bf/mongosql-1.2.0-0.tar.gz" } ], "1.2.1-0": [ { "comment_text": "", "digests": { "md5": "960b9e121dc82898cfcf91b86c8510ee", "sha256": "93eecf73262b1af4572af29b634a29b3394947e356354b9602b6814b8b77f9fc" }, "downloads": -1, "filename": "mongosql-1.2.1_0-py2-none-any.whl", "has_sig": false, "md5_digest": "960b9e121dc82898cfcf91b86c8510ee", "packagetype": "bdist_wheel", "python_version": "2.7", "requires_python": null, "size": 28857, "upload_time": "2014-10-07T23:10:40", "url": "https://files.pythonhosted.org/packages/be/71/d3d8b0ea1df01d5f0b637e2cd0f840d6b6b8171d100259bf061dd1820da6/mongosql-1.2.1_0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "363bd9df388361a18ce05e281892d494", "sha256": "400c8bce2cf834c6b4415e4866a4055db3f1388f891020fb4b7d0e24787e060c" }, "downloads": -1, "filename": "mongosql-1.2.1-0.tar.gz", "has_sig": false, "md5_digest": "363bd9df388361a18ce05e281892d494", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 26541, "upload_time": "2014-10-07T23:10:38", "url": "https://files.pythonhosted.org/packages/36/b0/86c8fcc88a4e4d4a3f5598e883565d5ae38534fcb155e8aaf93e1fcd34b1/mongosql-1.2.1-0.tar.gz" } ], "2.0.0": [ { "comment_text": "", "digests": { "md5": "f7659df161d413d2cc3224e7e243f773", "sha256": "c41a31bf03ebf2786f6e22d688f73544d6036939240236f01ebedfe26d8bcf5c" }, "downloads": -1, "filename": "mongosql-2.0.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "f7659df161d413d2cc3224e7e243f773", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 123839, "upload_time": "2019-06-18T22:17:48", "url": "https://files.pythonhosted.org/packages/1c/11/325ce61b0cdbff42fbcb354e9ef46e49e6b6a16cf1ce508f0aec6ac54ce8/mongosql-2.0.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "9175c80140be8f4860d24c10b66c6b89", "sha256": "fc132f49f16e8f3bb991ea2193360b2f39fea263c9556ad4cdb4863c090455d2" }, "downloads": -1, "filename": "mongosql-2.0.0.tar.gz", "has_sig": false, "md5_digest": "9175c80140be8f4860d24c10b66c6b89", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 139141, "upload_time": "2019-06-18T22:17:51", "url": "https://files.pythonhosted.org/packages/a5/54/1f0fe59f74aaf50751f56e11624cab01389dac3b1fb3e656ac4d1c153640/mongosql-2.0.0.tar.gz" } ], "2.0.1": [ { "comment_text": "", "digests": { "md5": "fc7b0601a34f690d080e0d0e1fd642c0", "sha256": "fdc5a6cf0ca5afd1854ab8d65b9c35bc82750c0613d4fe290448c7c84f4f2670" }, "downloads": -1, "filename": "mongosql-2.0.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "fc7b0601a34f690d080e0d0e1fd642c0", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 124326, "upload_time": "2019-06-28T23:44:42", "url": "https://files.pythonhosted.org/packages/a4/c2/dc361f2c5d1edb87596d816b5b08e8fafe80d38c68d7f6759ef931314cd7/mongosql-2.0.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "113322223b0cdde44406d603b44ab7f3", "sha256": "a937a597bd60b8937ed19a5afe2024d4cb3ad9d8b2c963cee1682925ed1f91dd" }, "downloads": -1, "filename": "mongosql-2.0.1.tar.gz", "has_sig": false, "md5_digest": "113322223b0cdde44406d603b44ab7f3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 139596, "upload_time": "2019-06-28T23:44:45", "url": "https://files.pythonhosted.org/packages/2f/52/366ee12fb4426e4b5104628a3ca90b6742c81272e9c425cc9ff31d7f11b0/mongosql-2.0.1.tar.gz" } ], "2.0.2": [ { "comment_text": "", "digests": { "md5": "f6ecc741ab670bae14f51945c5194e51", "sha256": "cd4b95717fd2da11e01e22ce8131cf256fd6a1a0895d7f085e59f01f9644af60" }, "downloads": -1, "filename": "mongosql-2.0.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "f6ecc741ab670bae14f51945c5194e51", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 125337, "upload_time": "2019-06-29T01:45:07", "url": "https://files.pythonhosted.org/packages/a2/89/c617beac5e9c44ddf1bb19ca0c206fc67ff52d80567cd001cd9b7d02f2f5/mongosql-2.0.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "e9bee42fb3f5e8714527e76e8e733641", "sha256": "9b6ff2809fe30619745bd55c5308c7e42f64b174eeb1eb83795fc9f9ed30f5c2" }, "downloads": -1, "filename": "mongosql-2.0.2.tar.gz", "has_sig": false, "md5_digest": "e9bee42fb3f5e8714527e76e8e733641", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 140806, "upload_time": "2019-06-29T01:45:10", "url": "https://files.pythonhosted.org/packages/ce/f1/955c89f599c3fd067557736d7985a2768a1cb3f55b17b6eb75cc4d8f31f2/mongosql-2.0.2.tar.gz" } ], "2.0.3": [ { "comment_text": "", "digests": { "md5": "55c4b1a247b195782da8268160e0e295", "sha256": "4dd171a34b6595d7dd4026c65c91524d9b6102e39e5d42c2d7f704f2d9df66df" }, "downloads": -1, "filename": "mongosql-2.0.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "55c4b1a247b195782da8268160e0e295", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 127384, "upload_time": "2019-07-08T07:30:17", "url": "https://files.pythonhosted.org/packages/36/80/2b66bfdfee31bc6d8af5a6887cdbbb618f13f1055f3aca5eea05df9efd22/mongosql-2.0.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "45cc63893a3c78a690a58a27fcc04d66", "sha256": "4dcb2a2f0e9a819038c025b060978aafb482891bce33640ee4eff73c772740c0" }, "downloads": -1, "filename": "mongosql-2.0.3.tar.gz", "has_sig": false, "md5_digest": "45cc63893a3c78a690a58a27fcc04d66", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 143891, "upload_time": "2019-07-08T07:30:22", "url": "https://files.pythonhosted.org/packages/6a/3f/386cb2c6d95647d229ad2ebd5dfb1a6f89c1a52acf26f288c3bbbe5b3d17/mongosql-2.0.3.tar.gz" } ], "2.0.4": [ { "comment_text": "", "digests": { "md5": "3dc9edd6ca8ec137a49ac327471300c4", "sha256": "611bdd2a5e90b2e3c9185ea99871a80c6c47f179283d28659cf9ea78a131349c" }, "downloads": -1, "filename": "mongosql-2.0.4-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "3dc9edd6ca8ec137a49ac327471300c4", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 129674, "upload_time": "2019-07-26T11:59:25", "url": "https://files.pythonhosted.org/packages/07/b1/74f4a3b664f8d89267128016990d648d3e5d8e5018d6a90362a3df5309ad/mongosql-2.0.4-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "aea13fc8270b7ec6f6aa92decb75500b", "sha256": "7968c874892570f9f9d753be63f02e97dd89845a3e67db21f75ca2640a28fb24" }, "downloads": -1, "filename": "mongosql-2.0.4.tar.gz", "has_sig": false, "md5_digest": "aea13fc8270b7ec6f6aa92decb75500b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 146841, "upload_time": "2019-07-26T11:59:28", "url": "https://files.pythonhosted.org/packages/54/51/a931f9e601d6f0d6f6a21fe6df2ca7fd2b00d737bdd7d64f3b546c7a20d3/mongosql-2.0.4.tar.gz" } ], "2.0.5": [ { "comment_text": "", "digests": { "md5": "bc2565f91c636a319187de48155892d4", "sha256": "9e40ac3282205fbfa8ad672d92383e2bdf052de63cbdf3d4bf50591825541840" }, "downloads": -1, "filename": "mongosql-2.0.5-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "bc2565f91c636a319187de48155892d4", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 133271, "upload_time": "2019-08-27T15:39:14", "url": "https://files.pythonhosted.org/packages/fb/c0/67151e0010c9e45a72a7da7c8764b99d2a63d76d420e93255efa382d2903/mongosql-2.0.5-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6643ea1fdfcd232cd652d497616f7cdd", "sha256": "57505dca1857e9b4ef02da80bd23042997e3a7934efcdfccb976558a6075c9fe" }, "downloads": -1, "filename": "mongosql-2.0.5.tar.gz", "has_sig": false, "md5_digest": "6643ea1fdfcd232cd652d497616f7cdd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 149994, "upload_time": "2019-08-27T15:39:19", "url": "https://files.pythonhosted.org/packages/ea/0d/14c77e5862476fb8221f191d39b59292468c618f09d74050705502f2faf7/mongosql-2.0.5.tar.gz" } ], "2.0.6": [ { "comment_text": "", "digests": { "md5": "8dd53784b818276cc2bb98e758a9980c", "sha256": "74fc58be4519451525adc2defcdf499370748f5b04167ed1bb8757df14d59244" }, "downloads": -1, "filename": "mongosql-2.0.6-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "8dd53784b818276cc2bb98e758a9980c", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 133747, "upload_time": "2019-09-16T22:40:57", "url": "https://files.pythonhosted.org/packages/a9/a4/84deaee532ee91d720452a14ee27ab0dbe99d452eab50aab1fc9d735933f/mongosql-2.0.6-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ef56e059c9551b757c9027ccac1e2ce4", "sha256": "6a88135c9080cc77ef0f5663c0e7a1ad7a352d8eb1765cc05bf923c4caaab848" }, "downloads": -1, "filename": "mongosql-2.0.6.tar.gz", "has_sig": false, "md5_digest": "ef56e059c9551b757c9027ccac1e2ce4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 150559, "upload_time": "2019-09-16T22:41:00", "url": "https://files.pythonhosted.org/packages/9a/2d/4340838219b69c914f74b067b4956d5868ee65b0daeff131f7ce035ff820/mongosql-2.0.6.tar.gz" } ], "2.0.6.post2": [ { "comment_text": "", "digests": { "md5": "30e011e743704cc659ffe7a711a53d35", "sha256": "058fd4205ca04221ef0b4538f162a8fa74c4c50ce44d564f01251b4e36ecba5a" }, "downloads": -1, "filename": "mongosql-2.0.6.post2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "30e011e743704cc659ffe7a711a53d35", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 134144, "upload_time": "2019-09-18T21:10:06", "url": "https://files.pythonhosted.org/packages/9b/2c/c3b68016bb9407b9a9396ed4aeb0f52a0e8ec314f99c25c9d349baeb8289/mongosql-2.0.6.post2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "edd1e346068eb2a02d8a90b09f7c174b", "sha256": "682fe6e07aa044c07e32f6fc11ea166313a4311f7f5f4c6d70142829aaa29d62" }, "downloads": -1, "filename": "mongosql-2.0.6.post2.tar.gz", "has_sig": false, "md5_digest": "edd1e346068eb2a02d8a90b09f7c174b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 150928, "upload_time": "2019-09-18T21:10:10", "url": "https://files.pythonhosted.org/packages/cb/6b/0a1685c1e7349295986f0b3f19d61314b1f199ac69e0a97edfcd3117184b/mongosql-2.0.6.post2.tar.gz" } ], "2.0.6.post3": [ { "comment_text": "", "digests": { "md5": "ff2ca5aff1d43090c09919d0ad9fe059", "sha256": "4d6dfa5598fb03f25a6d818198fb4318c886668a78e546948791dc7da0b15069" }, "downloads": -1, "filename": "mongosql-2.0.6.post3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "ff2ca5aff1d43090c09919d0ad9fe059", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 134119, "upload_time": "2019-09-19T13:37:52", "url": "https://files.pythonhosted.org/packages/2b/e5/bcc413ee8bf2d6a79e308be770f429d8ec2b3c0ca135f0e9a8a6a4b595e3/mongosql-2.0.6.post3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2199a5dd15bc08468afa19ed78938c53", "sha256": "f28c8d7caf1a67f941e554cade18570b1cb57a75ca3503ca40c85a67c44b3d35" }, "downloads": -1, "filename": "mongosql-2.0.6.post3.tar.gz", "has_sig": false, "md5_digest": "2199a5dd15bc08468afa19ed78938c53", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 150923, "upload_time": "2019-09-19T13:37:55", "url": "https://files.pythonhosted.org/packages/90/30/48bcb9880892b21f98af6ef9df1ca56a5c5b01f305695df34a560fba4057/mongosql-2.0.6.post3.tar.gz" } ], "2.0.7": [ { "comment_text": "", "digests": { "md5": "c88d38a195c40fc3bdd490ba92e9abb6", "sha256": "82ddbd83561d1e465c4fbcc8cd5284bd76073e4f59de1f11d9508257a9568b30" }, "downloads": -1, "filename": "mongosql-2.0.7-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "c88d38a195c40fc3bdd490ba92e9abb6", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 135284, "upload_time": "2019-10-07T17:01:58", "url": "https://files.pythonhosted.org/packages/c3/4f/836e77cca2d728892b5b0d5e055e54f24f0c4b03f455f7b359d8de6c863f/mongosql-2.0.7-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4067595a92b53035f36931bcfff79017", "sha256": "843038773868015638bad82a74d3e90bc750aff15d3e2c7e16a3401923267d75" }, "downloads": -1, "filename": "mongosql-2.0.7.tar.gz", "has_sig": false, "md5_digest": "4067595a92b53035f36931bcfff79017", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 152267, "upload_time": "2019-10-07T17:02:02", "url": "https://files.pythonhosted.org/packages/5e/f8/23421b683c341e5544e1d0f5ca1fbbeb5e8c08c46d5780eb1f3e4e7c132c/mongosql-2.0.7.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "c88d38a195c40fc3bdd490ba92e9abb6", "sha256": "82ddbd83561d1e465c4fbcc8cd5284bd76073e4f59de1f11d9508257a9568b30" }, "downloads": -1, "filename": "mongosql-2.0.7-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "c88d38a195c40fc3bdd490ba92e9abb6", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 135284, "upload_time": "2019-10-07T17:01:58", "url": "https://files.pythonhosted.org/packages/c3/4f/836e77cca2d728892b5b0d5e055e54f24f0c4b03f455f7b359d8de6c863f/mongosql-2.0.7-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4067595a92b53035f36931bcfff79017", "sha256": "843038773868015638bad82a74d3e90bc750aff15d3e2c7e16a3401923267d75" }, "downloads": -1, "filename": "mongosql-2.0.7.tar.gz", "has_sig": false, "md5_digest": "4067595a92b53035f36931bcfff79017", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 152267, "upload_time": "2019-10-07T17:02:02", "url": "https://files.pythonhosted.org/packages/5e/f8/23421b683c341e5544e1d0f5ca1fbbeb5e8c08c46d5780eb1f3e4e7c132c/mongosql-2.0.7.tar.gz" } ] }