def test_optional_argument(): assert parse_url_path('foo/bar/:changes') == { 'path': 'foo/bar', 'changes': None } assert parse_url_path('foo/bar/:changes/42') == { 'path': 'foo/bar', 'changes': 42 } assert parse_url_path('foo/bar/:changes/-42') == { 'path': 'foo/bar', 'changes': -42 } assert build_url_path({ 'path': 'foo/bar', 'changes': None }) == 'foo/bar/:changes' assert build_url_path({ 'path': 'foo/bar', 'changes': 42 }) == 'foo/bar/:changes/42' assert build_url_path({ 'path': 'foo/bar', 'changes': -42 }) == 'foo/bar/:changes/-42'
def get_response_type(context: Context, request: Request, params: dict = None): if params is None and 'path' in request.path_params: path = request.path_params['path'].strip('/') params = parse_url_path(path) elif params is None: params = {} if 'format' in params: return params['format'] if 'accept' in request.headers and request.headers['accept']: formats = { 'text/html': 'html', 'application/xhtml+xml': 'html', } config = context.get('config') for name, exporter in config.exporters.items(): for media_type in exporter.accept_types: formats[media_type] = name media_types, _ = cgi.parse_header(request.headers['accept']) for media_type in media_types.lower().split(','): if media_type in formats: return formats[media_type] return 'json'
def test_no_args(): string = 'foo/bar/:count' params = { 'path': 'foo/bar', 'count': [], } assert parse_url_path(string) == params assert build_url_path(params) == string
def test_format(): string = 'foo/bar/:format/csv' params = { 'path': 'foo/bar', 'format': 'csv', } assert parse_url_path(string) == params assert build_url_path(params) == string
def test_id_integer(): string = 'foo/bar/42' params = { 'path': 'foo/bar', 'id': {'value': 42, 'type': 'integer'}, } assert parse_url_path(string) == params assert build_url_path(params) == string
def test_parse_url_path(): assert parse_url_path('foo/bar') == {'path': 'foo/bar'} assert parse_url_path('foo/bar/:source/deeply/nested/name') == { 'path': 'foo/bar', 'source': 'deeply/nested/name' } assert parse_url_path('foo/bar/3/:source/vrk') == { 'path': 'foo/bar', 'id': { 'value': '3', 'type': 'integer' }, 'source': 'vrk' } assert parse_url_path('foo/bar/:limit/100') == { 'path': 'foo/bar', 'limit': 100 }
def test_id_sha1(): string = 'foo/bar/69a33b149af7a7eeb25026c8cdc09187477ffe21' params = { 'path': 'foo/bar', 'id': { 'value': '69a33b149af7a7eeb25026c8cdc09187477ffe21', 'type': 'sha1', }, } assert parse_url_path(string) == params assert build_url_path(params) == string
def test_sort(): string = 'foo/bar/:sort/a/-b' params = { 'path': 'foo/bar', 'sort': [ {'name': 'a', 'ascending': True}, {'name': 'b', 'ascending': False}, ], } assert parse_url_path(string) == params assert build_url_path(params) == string
def _dependencies(context: Context, model, deps): if deps: command_calls = {} model_names = set() prop_names = [] prop_name_mapping = {} for name, dep in deps.items(): if isinstance(dep, dict): command_calls[name] = dep continue if '.' not in dep: context.error( f"Dependency must be in 'object/name.property' form, got: {dep}." ) model_name, prop_name = dep.split('.', 1) model_names.add(model_name) prop_names.append(prop_name) prop_name_mapping[prop_name] = name if len(model_names) > 1: names = ', '.join(sorted(model_names)) context.error( f"Dependencies are allowed only from single model, but more than one model found: {names}." ) if len(command_calls) > 1: context.error(f"Only one command call is allowed.") if len(command_calls) > 0: if len(model_names) > 0: context.error( f"Only one command call or one model is allowed in dependencies." ) for name, cmd in command_calls.items(): cmd = load(context, Command(), cmd, parent=model, scope='service') for value in cmd(context): yield {name: value} else: model_name = list(model_names)[0] params = parse_url_path(model_name) depmodel = get_model_from_params(model.manifest, params['path'], params) for row in getall(context, depmodel, depmodel.backend, show=prop_names): yield {prop_name_mapping[k]: v for k, v in row.items()} else: yield {}
def dependencies(self, model, deps): if deps: command_calls = {} model_names = set() prop_names = [] prop_name_mapping = {} for name, dep in deps.items(): if isinstance(dep, dict): command_calls[name] = dep continue if '.' not in dep: self.error( f"Dependency must be in 'object/name.property' form, got: {dep}." ) model_name, prop_name = dep.split('.', 1) model_names.add(model_name) prop_names.append(prop_name) prop_name_mapping[prop_name] = name if len(model_names) > 1: names = ', '.join(sorted(model_names)) self.error( f"Dependencies are allowed only from single model, but more than one model found: {names}." ) if len(command_calls) > 1: self.error(f"Only one command call is allowed.") if len(command_calls) > 0: if len(model_names) > 0: self.error( f"Only one command call or one model is allowed in dependencies." ) for name, cmd in command_calls.items(): for value in self.run(model, cmd): yield {name: value} else: model_name = list(model_names)[0] params = parse_url_path(model_name) for row in self.store.getall(params['path'], { 'show': prop_names, 'source': params['source'] }): yield {prop_name_mapping[k]: v for k, v in row.items()} else: yield {}
def prepare(context: Context, params: UrlParams, version: Version, request: Request) -> UrlParams: path = request.path_params['path'].strip('/') p = parse_url_path(path) params.model = p['path'] params.id = p.get('id') params.dataset = p.get('source') params.sort = p.get('sort') params.limit = p.get('limit') if 'changes' in p: params.changes = True params.offset = p['changes'] or -10 else: params.changes = False params.offset = p.get('offset') params.format = get_response_type(context, request, p) params.count = p.get('count') params.params = p # XXX: for backwards compatibility return params
async def homepage(request): global store url_path = request.path_params['path'].strip('/') params = parse_url_path(url_path) path = params['path'] fmt = params.get('format', 'html') if fmt == 'html': header = [] data = [] formats = [] items = [] datasets = [] row = [] loc = get_current_location(path, params) if 'source' in params: formats = [ ('CSV', '/' + build_url_path({ **params, 'format': 'csv' })), ('JSON', '/' + build_url_path({ **params, 'format': 'json' })), ('JSONL', '/' + build_url_path({ **params, 'format': 'jsonl' })), ('ASCII', '/' + build_url_path({ **params, 'format': 'asciitable' })), ] if 'changes' in params: data = get_changes(store, params) header = next(data) data = list(reversed(list(data))) else: if 'id' in params: row = list(get_row(store, params)) else: data = get_data(store, params) header = next(data) data = list(data) else: datasets_by_object = get_datasets_by_object(store) tree = build_path_tree( store.objects['default'].get('model', {}).keys(), datasets_by_object.keys(), ) items = get_directory_content(tree, path) if path in tree else [] datasets = list(get_directory_datasets(datasets_by_object, path)) return templates.TemplateResponse( 'base.html', { 'request': request, 'location': loc, 'formats': formats, 'items': items, 'datasets': datasets, 'header': header, 'data': data, 'row': row, }) elif fmt in ('csv', 'json', 'jsonl', 'asciitable'): async def generator(rows, fmt, params): for data in store.export(rows, fmt, params): yield data if 'changes' in params: rows = store.changes(params) elif 'id' in params: rows = [store.get(params['path'], params['id']['value'], params)] params['wrap'] = False else: rows = store.getall(params['path'], params) media_types = { 'csv': 'text/csv', 'json': 'application/json', 'jsonl': 'application/x-json-stream', 'asciitable': 'text/plain', } if fmt == 'asciitable': params['column_width'] = 42 return StreamingResponse(generator(rows, fmt, params), media_type=media_types[fmt]) else: raise HTTPException(status_code=404)