diff --git a/README.md b/README.md index 8d33f6a..4604675 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A **new** bunch of middleware functions to build applications on top of CARTO. - [ ] Protected parametrized queries (i.e. avoiding injection) - [ ] Bind/dynamic parameters in queries (server-side render) - [ ] Postgres data source - - [ ] psycopg2 + - [x] psycopg2 - [ ] SQLAlchemy - [ ] Asyncpg - [x] Cache @@ -25,6 +25,7 @@ A **new** bunch of middleware functions to build applications on top of CARTO. - [x] Redis Cache - [x] Tests - [ ] Documentation + - [x] Sample scripts - [x] Unit tests - [x] Sample scripts diff --git a/src/core/common/query_response.py b/src/core/common/query_response.py new file mode 100644 index 0000000..13da0e2 --- /dev/null +++ b/src/core/common/query_response.py @@ -0,0 +1,15 @@ + +class LongitudeQueryResponse: + def __init__(self, rows=None, fields=None, profiling=None): + self.rows = rows or [] + self.fields = fields or {} + self.profiling = profiling or {} + self._from_cache = False + + + @property + def comes_from_cache(self): + return self._from_cache + + def mark_as_cached(self): + self._from_cache = True diff --git a/src/core/data_sources/base.py b/src/core/data_sources/base.py index 6b283d3..65c4d0a 100644 --- a/src/core/data_sources/base.py +++ b/src/core/data_sources/base.py @@ -146,17 +146,3 @@ def flush_cache(self): if self._cache and self._cache.is_ready: self._cache.flush() - -class LongitudeQueryResponse: - def __init__(self, rows=None, fields=None, profiling=None): - self.rows = rows or [] - self.fields = fields or {} - self.profiling = profiling or {} - self._from_cache = False - - @property - def comes_from_cache(self): - return self._from_cache - - def mark_as_cached(self): - self._from_cache = True diff --git a/src/core/data_sources/carto.py b/src/core/data_sources/carto.py index f63a8d9..4feb75a 100644 --- a/src/core/data_sources/carto.py +++ b/src/core/data_sources/carto.py @@ -1,6 +1,7 @@ from carto.exceptions import CartoException -from .base import DataSource, LongitudeQueryCannotBeExecutedException, LongitudeQueryResponse +from .base import DataSource, LongitudeQueryCannotBeExecutedException +from ..common.query_response import LongitudeQueryResponse from carto.auth import APIKeyAuthClient from carto.sql import BatchSQLClient, SQLClient @@ -65,9 +66,10 @@ def execute_query(self, formatted_query, needs_commit, query_config, **opts): def parse_response(self, response): return LongitudeQueryResponse( - rows=[[v for k, v in dictionary.items()] for dictionary in response['rows']], + rows=response['rows'], fields=response['fields'], profiling={ - 'response_time': response['time'] + 'response_time': response['time'], + 'total_rows': response['total_rows'] } ) diff --git a/src/core/data_sources/postgres/default.py b/src/core/data_sources/postgres/default.py index dfedcfd..fab651c 100644 --- a/src/core/data_sources/postgres/default.py +++ b/src/core/data_sources/postgres/default.py @@ -1,7 +1,7 @@ import psycopg2 import psycopg2.extensions from ..base import DataSource -from ..base import LongitudeQueryResponse +from ...common.query_response import LongitudeQueryResponse from time import time @@ -68,6 +68,8 @@ def _type_as_string(type_id): def parse_response(self, response): if response: - fields_names = {n.name: self._type_as_string(n.type_code).name for n in response['fields']} - return LongitudeQueryResponse(rows=response['rows'], fields=fields_names, profiling=response['profiling']) + raw_fields = response['fields'] + fields_names = {n.name: {'type': self._type_as_string(n.type_code).name} for n in raw_fields} + rows = [{raw_fields[i].name: f for i, f in enumerate(row_data)} for row_data in response['rows']] + return LongitudeQueryResponse(rows=rows, fields=fields_names, profiling=response['profiling']) return None diff --git a/src/core/tests/test_data_source_base.py b/src/core/tests/test_data_source_base.py index 8488ca9..d96a21d 100644 --- a/src/core/tests/test_data_source_base.py +++ b/src/core/tests/test_data_source_base.py @@ -2,7 +2,8 @@ from unittest import TestCase, mock from ..caches.base import LongitudeCache -from ..data_sources.base import DataSource, DataSourceQueryConfig, LongitudeQueryResponse +from ..data_sources.base import DataSource, DataSourceQueryConfig +from ..common.query_response import LongitudeQueryResponse def load_raw_text(filename): diff --git a/src/core/tests/test_data_source_carto.py b/src/core/tests/test_data_source_carto.py index 76710f1..6bc973d 100644 --- a/src/core/tests/test_data_source_carto.py +++ b/src/core/tests/test_data_source_carto.py @@ -54,7 +54,7 @@ def test_setup_can_accept_on_premise_domain(self): def test_succesful_query(self): ds = CartoDataSource() ds._sql_client = mock.MagicMock() - ds._sql_client.send.return_value = {'rows': [], 'time': 42.0, 'fields': {}} + ds._sql_client.send.return_value = {'rows': [], 'time': 42.0, 'fields': {}, 'total_rows': 0} result = ds.query('some query') ds._sql_client.send.assert_called_with('some query', do_post=False, format='json', parse_json=True) self.assertEqual([], result.rows) diff --git a/src/samples/postgres_sample.py b/src/samples/postgres_sample.py index 08f4a1a..2e285d3 100644 --- a/src/samples/postgres_sample.py +++ b/src/samples/postgres_sample.py @@ -37,8 +37,10 @@ needs_commit=True) print(r1.profiling) - r2 = ds.query("insert into users(name, password) values('longitude', 'password')", needs_commit=True) - print(r2.profiling) + for i in range(10): + r2 = ds.query("insert into users(name, password) values('longitude%d', 'password%d')" % (i, i), + needs_commit=True) + print(r2.profiling) r3 = ds.query('select * from users') print(r3.rows)