This repository has been archived by the owner on Apr 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
add stream API #42
Open
preyunk
wants to merge
7
commits into
main
Choose a base branch
from
feature-stream-response
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
add stream API #42
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
b80679a
add stream API
preyunk 86feec0
minor fix, use select_related in yield_fields just like in add_fields
preyunk 69e1e9a
remove debug logger
preyunk 6d50a83
remove redundant code from yield_fields and _add_fields()
preyunk 03d47c8
remove time sleep to check if it is really required
preyunk 6d60845
use queryset iterator to avoid memory error
preyunk 435bd77
merge stream view API in read
preyunk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -232,8 +232,9 @@ def __init__(self, db_name, app_name, model_name, params): | |
requested_fields.extend(self.params.fields) | ||
requested_fields.extend(self.params.order_by) | ||
self.model_config.validate_fields(set(requested_fields)) | ||
self.qset_stream = None | ||
|
||
def _apply_opts(self): | ||
def _apply_opts(self, stream=False): | ||
for opt, qset_opt, opt_type in ModelBuilder._QUERYSET_OPTS: | ||
# offset and limit operation will return None | ||
func = getattr(self.qset, qset_opt, None) | ||
|
@@ -269,7 +270,9 @@ def _apply_opts(self): | |
self.qset = self.qset[:self.params.limit] | ||
elif isinstance(value, list): | ||
# handle values case where property is passed in fields | ||
if qset_opt == 'values' and self.query_has_properties(): | ||
if qset_opt == 'values' and stream: | ||
self.qset_stream = self.yield_fields() | ||
elif qset_opt == 'values' and self.query_has_properties(): | ||
# returns DBRows instance | ||
self.qset = self._add_fields() | ||
else: | ||
|
@@ -292,36 +295,56 @@ def query_has_properties(self): | |
return bool(set(self.params.fields).intersection( | ||
self.model_config.get_properties())) | ||
|
||
def _get_model_fields(self, row): | ||
model_fields = {} | ||
for field in self.params.fields: | ||
attr = row | ||
for ref in field.split('__'): | ||
try: | ||
attr = getattr(attr, ref) | ||
if attr is None: | ||
break | ||
except AttributeError: | ||
raise InvalidModelFieldName( | ||
'Invalid query for field %s in %s.' % (ref, attr)) | ||
model_fields[field] = attr | ||
return model_fields | ||
|
||
def _add_fields(self): | ||
qset_values = DBRows() | ||
self.qset = self.qset.select_related(*self.params.fk_refs_in_fields) | ||
logger.debug('Request parameters: %s \nQuery: %s\n', | ||
self.params.params, self.qset.query) | ||
for row in self.qset: | ||
model_fields = {} | ||
for field in self.params.fields: | ||
attr = row | ||
for ref in field.split('__'): | ||
try: | ||
attr = getattr(attr, ref) | ||
if attr is None: | ||
break | ||
except AttributeError: | ||
raise InvalidModelFieldName( | ||
'Invalid query for field %s in %s.' % (ref, attr)) | ||
model_fields[field] = attr | ||
qset_values.append(model_fields) | ||
return qset_values | ||
try: | ||
for row in self.qset.iterator(): | ||
model_fields = self._get_model_fields(row) | ||
qset_values.append(model_fields) | ||
return qset_values | ||
except FieldError as e: | ||
raise InvalidModelFieldName(str(e)) | ||
|
||
def yield_fields(self): | ||
logger.debug('Request parameters: %s \nQuery: %s\n', | ||
self.params.params, self.qset.query) | ||
self.qset = self.qset.select_related(*self.params.fk_refs_in_fields) | ||
try: | ||
for row in self.qset.iterator(): | ||
model_fields = self._get_model_fields(row) | ||
yield model_fields | ||
except FieldError as e: | ||
raise InvalidModelFieldName(str(e)) | ||
|
||
def queryset(self): | ||
def queryset(self, stream=False): | ||
# construct Q object from dictionary | ||
query = Query(self.params.filter) | ||
if self.params.db_name: | ||
self.qset = self.model_config.model.objects.using( | ||
self.params.db_name).filter(query.Q) | ||
else: | ||
self.qset = self.model_config.model.objects.filter(query.Q) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. handle the stream part using self.yqset |
||
self._apply_opts() | ||
self._apply_opts(stream=stream) | ||
if self.qset_stream: | ||
return self.qset_stream | ||
if isinstance(self.qset, QuerySet): | ||
logger.debug('Request parameters: %s \nQuery: %s\n', | ||
self.params.params, self.qset.query) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright © 2023 VMware, Inc. All rights reserved. | ||
# SPDX-License-Identifier: BSD-2-Clause | ||
|
||
import json | ||
import os | ||
|
||
from django.urls import reverse as url_reverse | ||
from django.test import TestCase, override_settings | ||
from django.test.client import Client | ||
from django.conf import settings | ||
|
||
from machine.models import OperatingSystem, Machine | ||
|
||
|
||
class TestAPIStreamer(TestCase): | ||
fixtures = [os.path.join(settings.BASE_DIR, 'machine_tests.json'), ] | ||
|
||
def setUp(self): | ||
self.client = Client() | ||
|
||
def getURL(self, **kwargs): | ||
db_name = kwargs.get('db_name', 'default') | ||
app_label = kwargs.get('app_label', 'machine') | ||
# default model name is Machine | ||
model_name = kwargs.get('model_name', 'Machine') | ||
url_name = 'bridgeql_django_read' | ||
url_kwargs = { | ||
'db_name': db_name, | ||
'app_label': app_label, | ||
'model_name': model_name | ||
} | ||
return url_reverse(url_name, kwargs=url_kwargs) | ||
|
||
def test_stream_one_field(self): | ||
self.params = { | ||
'filter': { | ||
'name__startswith': 'machine', | ||
}, | ||
'fields': ['os__name', 'pk'] | ||
} | ||
resp = self.client.get( | ||
self.getURL(), {'payload': json.dumps(self.params), 'stream': True}) | ||
if resp.status_code == 200: | ||
streaming_content = [] | ||
for chunk in resp.streaming_content: | ||
streaming_content.append(chunk) | ||
self.assertEqual(len(streaming_content), 100) | ||
|
||
def test_stream_invalid_model_name(self): | ||
self.params = { | ||
'filter': { | ||
'name__startswith': 'os-name-', | ||
}, | ||
'fields': ['name', 'arch'], | ||
} | ||
resp = self.client.get(self.getURL(model_name='InvalidModel'), { | ||
'payload': json.dumps(self.params), 'stream': True}) | ||
self.assertEqual(resp.status_code, 400) | ||
self.assertFalse(resp.json()['success']) | ||
|
||
def test_stream_invalid_field(self): | ||
self.params = { | ||
'filter': { | ||
'name__startswith': 'machine', | ||
}, | ||
'fields': ['os1__name', 'pk'] | ||
} | ||
resp = self.client.get( | ||
self.getURL(), {'payload': json.dumps(self.params), 'stream': True}) | ||
if resp.status_code == 200: | ||
streaming_content = b"" | ||
for chunk in resp.streaming_content: | ||
streaming_content += chunk | ||
streaming_content = streaming_content.decode('utf-8') | ||
err_json = json.loads(streaming_content) | ||
self.assertTrue(err_json['error']) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change the method name to _get_model_row_dict to make it more readable.