Rewritting Bags from scratch using a namedtuple approach, along with other (less major) updates.
New bag implementation improves a lot how bonobo works, even if this is highly backward incompatible (sorry, that's needed, and better sooner than later). * New implementation uses the same approach as python's namedtuple, by dynamically creating the python type's code. This has drawbacks, as it feels like not the right way, but also a lot of benefits that cannot be achieved using a regular approach, especially the constructor parameter order, hardcoded. * Memory usage is now much more efficient. The "keys" memory space will be used only once per "io type", being spent in the underlying type definition instead of in the actual instances. * Transformations now needs to use tuples as output, which will be bound to its "output type". The output type can be infered from the tuple length, or explicitely set by the user using either `context.set_output_type(...)` or `context.set_output_fields(...)` (to build a bag type from a list of field names). Jupyter/Graphviz integration is more tight, allowing to easily display graphs in a notebook, or displaying the live transformation status in an html table instead of a simple <div>. For now, context processors were hacked to stay working as before but the current API is not satisfactory, and should be replaced. This new big change being unreasonable without some time to work on it properly, it is postponed for next versions (0.7, 0.8, ...). Maybe the best idea is to have some kind of "local services", that would use the same dependency injection mechanism as the execution-wide services. Services are now passed by keywoerd arguments only, to avoid confusion with data-arguments.
This commit is contained in:
@ -1,4 +1,11 @@
|
||||
[style]
|
||||
based_on_style = pep8
|
||||
column_limit = 120
|
||||
allow_multiline_lambdas = false
|
||||
allow_multiline_dictionary_keys = false
|
||||
coalesce_brackets = true
|
||||
dedent_closing_brackets = true
|
||||
join_multiple_lines = true
|
||||
spaces_before_comment = 2
|
||||
split_before_first_argument = true
|
||||
split_complex_comprehension = true
|
||||
|
||||
2
Makefile
2
Makefile
@ -1,4 +1,4 @@
|
||||
# Generated by Medikit 0.4.1 on 2017-11-12.
|
||||
# Generated by Medikit 0.4.2 on 2017-11-26.
|
||||
# All changes will be overriden.
|
||||
|
||||
PACKAGE ?= bonobo
|
||||
|
||||
@ -43,9 +43,10 @@ python.add_requirements(
|
||||
'fs >=2.0,<2.1',
|
||||
'graphviz >=0.8,<0.9',
|
||||
'jinja2 >=2.9,<3',
|
||||
'mondrian >=0.4,<0.5',
|
||||
'mondrian >=0.5,<0.6',
|
||||
'packaging >=16,<17',
|
||||
'psutil >=5.4,<6',
|
||||
'python-slugify >=1.2,<1.3',
|
||||
'requests >=2,<3',
|
||||
'stevedore >=1.27,<1.28',
|
||||
'whistle >=1.0,<1.1',
|
||||
|
||||
66
RELEASE-0.6.rst
Normal file
66
RELEASE-0.6.rst
Normal file
@ -0,0 +1,66 @@
|
||||
Problems
|
||||
========
|
||||
|
||||
Failed to display Jupyter Widget of type BonoboWidget.
|
||||
If you're reading this message in Jupyter Notebook or JupyterLab, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.
|
||||
If you're reading this message in another notebook frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ jupyter nbextension enable --py widgetsnbextension
|
||||
$ jupyter nbextension install --py --symlink bonobo.contrib.jupyter
|
||||
$ jupyter nbextension enable --py bonobo.contrib.jupyter
|
||||
|
||||
|
||||
Todo
|
||||
====
|
||||
|
||||
* Pretty printer
|
||||
|
||||
|
||||
Options for Bags
|
||||
================
|
||||
|
||||
tuple only
|
||||
|
||||
pros : simple
|
||||
cons :
|
||||
- how to name columns / store headers ?
|
||||
- how to return a dictionary
|
||||
|
||||
|
||||
|
||||
yield keys('foo', 'bar', 'baz')
|
||||
|
||||
|
||||
yield 'a', 'b', 'c'
|
||||
|
||||
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
* Bags changed to something way closer to namedtuples.
|
||||
* Better at managing memory
|
||||
* Less flexible for kwargs usage, but much more standard and portable from one to another version of python
|
||||
* More future proof for different execution strategies
|
||||
* May lead to changes in your current transformation
|
||||
|
||||
* A given transformation now have an input and a output "type" which is either manually set by the user or
|
||||
detected from the first item sent through a queue. It is a restiction on how bonobo can be used, but
|
||||
will help having better predicatability.
|
||||
|
||||
* No more "graph" instance detection. This was misleading for new users, and not really pythonic. The
|
||||
recommended way to start with bonobo is just to use one python file with a __main__ block, and if the
|
||||
project grows, include this file in a package, either new or existing one. The init cli changed to
|
||||
help you generate files or packages. That also means that we do not generate things with cookiecutter
|
||||
anymore.
|
||||
|
||||
* Jupyter enhancements
|
||||
|
||||
* Graphviz support
|
||||
|
||||
* New nodes in stdlib
|
||||
|
||||
* Registry, used for conversions but also for your own integrations.
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -13,16 +13,14 @@ from bonobo.nodes import (
|
||||
PickleWriter,
|
||||
PrettyPrinter,
|
||||
RateLimited,
|
||||
SetFields,
|
||||
Tee,
|
||||
Update,
|
||||
arg0_to_kwargs,
|
||||
count,
|
||||
identity,
|
||||
kwargs_to_arg0,
|
||||
noop,
|
||||
)
|
||||
from bonobo.nodes import LdjsonReader, LdjsonWriter
|
||||
from bonobo.structs import Bag, ErrorBag, Graph, Token
|
||||
from bonobo.structs import Graph
|
||||
from bonobo.util import get_name
|
||||
from bonobo.util.environ import parse_args, get_argument_parser
|
||||
|
||||
@ -133,7 +131,7 @@ def inspect(graph, *, plugins=None, services=None, strategy=None, format):
|
||||
|
||||
|
||||
# data structures
|
||||
register_api_group(Bag, ErrorBag, Graph, Token)
|
||||
register_api_group(Graph)
|
||||
|
||||
# execution strategies
|
||||
register_api(create_strategy)
|
||||
@ -181,12 +179,10 @@ register_api_group(
|
||||
PickleWriter,
|
||||
PrettyPrinter,
|
||||
RateLimited,
|
||||
SetFields,
|
||||
Tee,
|
||||
Update,
|
||||
arg0_to_kwargs,
|
||||
count,
|
||||
identity,
|
||||
kwargs_to_arg0,
|
||||
noop,
|
||||
)
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
__version__ = '0.6.dev0'
|
||||
__version__ = '0.6.0a0'
|
||||
|
||||
@ -49,15 +49,15 @@ class ConvertCommand(BaseCommand):
|
||||
)
|
||||
|
||||
def handle(
|
||||
self,
|
||||
input_filename,
|
||||
output_filename,
|
||||
reader=None,
|
||||
reader_option=None,
|
||||
writer=None,
|
||||
writer_option=None,
|
||||
option=None,
|
||||
transformation=None
|
||||
self,
|
||||
input_filename,
|
||||
output_filename,
|
||||
reader=None,
|
||||
reader_option=None,
|
||||
writer=None,
|
||||
writer_option=None,
|
||||
option=None,
|
||||
transformation=None
|
||||
):
|
||||
reader_factory = default_registry.get_reader_factory_for(input_filename, format=reader)
|
||||
reader_options = _resolve_options((option or []) + (reader_option or []))
|
||||
|
||||
@ -1,23 +1,29 @@
|
||||
from bonobo.commands import BaseCommand
|
||||
|
||||
|
||||
def get_versions(*, all=False, quiet=None):
|
||||
import bonobo
|
||||
from bonobo.util.pkgs import bonobo_packages
|
||||
|
||||
yield _format_version(bonobo, quiet=quiet)
|
||||
|
||||
if all:
|
||||
for name in sorted(bonobo_packages):
|
||||
if name != 'bonobo':
|
||||
try:
|
||||
mod = __import__(name.replace('-', '_'))
|
||||
try:
|
||||
yield _format_version(mod, name=name, quiet=quiet)
|
||||
except Exception as exc:
|
||||
yield '{} ({})'.format(name, exc)
|
||||
except ImportError as exc:
|
||||
yield '{} is not importable ({}).'.format(name, exc)
|
||||
|
||||
|
||||
class VersionCommand(BaseCommand):
|
||||
def handle(self, *, all=False, quiet=False):
|
||||
import bonobo
|
||||
from bonobo.util.pkgs import bonobo_packages
|
||||
|
||||
print(_format_version(bonobo, quiet=quiet))
|
||||
if all:
|
||||
for name in sorted(bonobo_packages):
|
||||
if name != 'bonobo':
|
||||
try:
|
||||
mod = __import__(name.replace('-', '_'))
|
||||
try:
|
||||
print(_format_version(mod, name=name, quiet=quiet))
|
||||
except Exception as exc:
|
||||
print('{} ({})'.format(name, exc))
|
||||
except ImportError as exc:
|
||||
print('{} is not importable ({}).'.format(name, exc))
|
||||
for line in get_versions(all=all, quiet=quiet):
|
||||
print(line)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--all', '-a', action='store_true')
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
from bonobo.config.configurables import Configurable
|
||||
from bonobo.config.functools import transformation_factory
|
||||
from bonobo.config.options import Method, Option
|
||||
from bonobo.config.processors import ContextProcessor
|
||||
from bonobo.config.services import Container, Exclusive, Service, requires, create_container
|
||||
from bonobo.config.processors import ContextProcessor, use_context, use_context_processor, use_raw_input, use_no_input
|
||||
from bonobo.config.services import Container, Exclusive, Service, use, create_container
|
||||
from bonobo.util import deprecated_alias
|
||||
|
||||
use = requires
|
||||
requires = deprecated_alias('requires', use)
|
||||
|
||||
# Bonobo's Config API
|
||||
__all__ = [
|
||||
@ -16,5 +18,10 @@ __all__ = [
|
||||
'Service',
|
||||
'create_container',
|
||||
'requires',
|
||||
'transformation_factory',
|
||||
'use',
|
||||
'use_context',
|
||||
'use_context_processor',
|
||||
'use_no_input',
|
||||
'use_raw_input',
|
||||
]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from bonobo.util import isoption, iscontextprocessor, sortedlist
|
||||
from bonobo.errors import AbstractError
|
||||
from bonobo.util import isoption, iscontextprocessor, sortedlist
|
||||
|
||||
__all__ = [
|
||||
'Configurable',
|
||||
@ -18,6 +18,7 @@ class ConfigurableMeta(type):
|
||||
super().__init__(what, bases, dict)
|
||||
|
||||
cls.__processors = sortedlist()
|
||||
cls.__processors_cache = None
|
||||
cls.__methods = sortedlist()
|
||||
cls.__options = sortedlist()
|
||||
cls.__names = set()
|
||||
@ -47,7 +48,9 @@ class ConfigurableMeta(type):
|
||||
|
||||
@property
|
||||
def __processors__(cls):
|
||||
return (processor for _, processor in cls.__processors)
|
||||
if cls.__processors_cache is None:
|
||||
cls.__processors_cache = [processor for _, processor in cls.__processors]
|
||||
return cls.__processors_cache
|
||||
|
||||
def __repr__(self):
|
||||
return ' '.join((
|
||||
@ -65,7 +68,7 @@ except:
|
||||
else:
|
||||
|
||||
class PartiallyConfigured(_functools.partial):
|
||||
@property # TODO XXX cache this shit
|
||||
@property # TODO XXX cache this
|
||||
def _options_values(self):
|
||||
""" Simulate option values for partially configured objects. """
|
||||
try:
|
||||
@ -142,8 +145,8 @@ class Configurable(metaclass=ConfigurableMeta):
|
||||
if len(extraneous):
|
||||
raise TypeError(
|
||||
'{}() got {} unexpected option{}: {}.'.format(
|
||||
cls.__name__,
|
||||
len(extraneous), 's' if len(extraneous) > 1 else '', ', '.join(map(repr, sorted(extraneous)))
|
||||
cls.__name__, len(extraneous), 's'
|
||||
if len(extraneous) > 1 else '', ', '.join(map(repr, sorted(extraneous)))
|
||||
)
|
||||
)
|
||||
|
||||
@ -153,8 +156,8 @@ class Configurable(metaclass=ConfigurableMeta):
|
||||
if _final:
|
||||
raise TypeError(
|
||||
'{}() missing {} required option{}: {}.'.format(
|
||||
cls.__name__,
|
||||
len(missing), 's' if len(missing) > 1 else '', ', '.join(map(repr, sorted(missing)))
|
||||
cls.__name__, len(missing), 's'
|
||||
if len(missing) > 1 else '', ', '.join(map(repr, sorted(missing)))
|
||||
)
|
||||
)
|
||||
return PartiallyConfigured(cls, *args, **kwargs)
|
||||
@ -189,9 +192,11 @@ class Configurable(metaclass=ConfigurableMeta):
|
||||
position += 1
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
""" You can implement a configurable callable behaviour by implemenenting the call(...) method. Of course, it is also backward compatible with legacy __call__ override.
|
||||
"""
|
||||
return self.call(*args, **kwargs)
|
||||
raise AbstractError(
|
||||
'You must implement the __call__ method in your configurable class {} to actually use it.'.format(
|
||||
type(self).__name__
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def __options__(self):
|
||||
@ -200,6 +205,3 @@ class Configurable(metaclass=ConfigurableMeta):
|
||||
@property
|
||||
def __processors__(self):
|
||||
return type(self).__processors__
|
||||
|
||||
def call(self, *args, **kwargs):
|
||||
raise AbstractError('Not implemented.')
|
||||
|
||||
15
bonobo/config/functools.py
Normal file
15
bonobo/config/functools.py
Normal file
@ -0,0 +1,15 @@
|
||||
import functools
|
||||
|
||||
import itertools
|
||||
|
||||
|
||||
def transformation_factory(f):
|
||||
@functools.wraps(f)
|
||||
def _transformation_factory(*args, **kwargs):
|
||||
retval = f(*args, **kwargs)
|
||||
retval.__name__ = f.__name__ + '({})'.format(
|
||||
', '.join(itertools.chain(map(repr, args), ('{}={!r}'.format(k, v) for k, v in kwargs.items())))
|
||||
)
|
||||
return retval
|
||||
|
||||
return _transformation_factory
|
||||
@ -46,7 +46,7 @@ class Option:
|
||||
title = Option(str, required=True, positional=True)
|
||||
keyword = Option(str, default='foo')
|
||||
|
||||
def call(self, s):
|
||||
def __call__(self, s):
|
||||
return self.title + ': ' + s + ' (' + self.keyword + ')'
|
||||
|
||||
example = Example('hello', keyword='bar')
|
||||
@ -116,6 +116,22 @@ class RemovedOption(Option):
|
||||
return self.value
|
||||
|
||||
|
||||
class RenamedOption(Option):
|
||||
def __init__(self, target, *, positional=False):
|
||||
super(RenamedOption, self).__init__(required=False, positional=False)
|
||||
self.target = target
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
raise ValueError(
|
||||
'Trying to get value from renamed option {}, try getting {} instead.'.format(self.name, self.target)
|
||||
)
|
||||
|
||||
def clean(self, value):
|
||||
raise ValueError(
|
||||
'Trying to set value of renamed option {}, try setting {} instead.'.format(self.name, self.target)
|
||||
)
|
||||
|
||||
|
||||
class Method(Option):
|
||||
"""
|
||||
A Method is a special callable-valued option, that can be used in three different ways (but for same purpose).
|
||||
@ -154,9 +170,15 @@ class Method(Option):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *, required=True, positional=True, __doc__=None):
|
||||
def __init__(self, *, default=None, required=True, positional=True, __doc__=None):
|
||||
super().__init__(None, required=required, positional=positional, __doc__=__doc__)
|
||||
|
||||
# If a callable is provided as default, then use self as if it was used as a decorator
|
||||
if default is not None:
|
||||
if not callable(default):
|
||||
raise ValueError('Method defaults should be callable, if provided.')
|
||||
self(default)
|
||||
|
||||
def __get__(self, inst, type_):
|
||||
x = super(Method, self).__get__(inst, type_)
|
||||
if inst:
|
||||
@ -164,10 +186,12 @@ class Method(Option):
|
||||
return x
|
||||
|
||||
def __set__(self, inst, value):
|
||||
if not hasattr(value, '__call__'):
|
||||
if not callable(value):
|
||||
raise TypeError(
|
||||
'Option of type {!r} is expecting a callable value, got {!r} object (which is not).'.format(
|
||||
type(self).__name__, type(value).__name__
|
||||
'Option {!r} ({}) is expecting a callable value, got {!r} object: {!r}.'.format(
|
||||
self.name,
|
||||
type(self).__name__,
|
||||
type(value).__name__, value
|
||||
)
|
||||
)
|
||||
inst._options_values[self.name] = self.type(value) if self.type else value
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
from collections import Iterable
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from inspect import signature
|
||||
|
||||
from bonobo.config import Option
|
||||
from bonobo.errors import UnrecoverableTypeError
|
||||
from bonobo.util import deprecated_alias, ensure_tuple
|
||||
|
||||
_CONTEXT_PROCESSORS_ATTR = '__processors__'
|
||||
_raw = object()
|
||||
_args = object()
|
||||
_none = object()
|
||||
|
||||
INPUT_FORMATS = {_raw, _args, _none}
|
||||
|
||||
|
||||
class ContextProcessor(Option):
|
||||
@ -51,18 +58,11 @@ class ContextProcessor(Option):
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.func(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def decorate(cls, cls_or_func):
|
||||
try:
|
||||
cls_or_func.__processors__
|
||||
except AttributeError:
|
||||
cls_or_func.__processors__ = []
|
||||
|
||||
def decorator(processor, cls_or_func=cls_or_func):
|
||||
cls_or_func.__processors__.append(cls(processor))
|
||||
return cls_or_func
|
||||
|
||||
return decorator
|
||||
class bound(partial):
|
||||
@property
|
||||
def kwargs(self):
|
||||
return self.keywords
|
||||
|
||||
|
||||
class ContextCurrifier:
|
||||
@ -70,18 +70,47 @@ class ContextCurrifier:
|
||||
This is a helper to resolve processors.
|
||||
"""
|
||||
|
||||
def __init__(self, wrapped, *initial_context):
|
||||
def __init__(self, wrapped, *args, **kwargs):
|
||||
self.wrapped = wrapped
|
||||
self.context = tuple(initial_context)
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.format = getattr(wrapped, '__input_format__', _args)
|
||||
self._stack, self._stack_values = None, None
|
||||
|
||||
def __iter__(self):
|
||||
yield from self.wrapped
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if not callable(self.wrapped) and isinstance(self.wrapped, Iterable):
|
||||
return self.__iter__()
|
||||
return self.wrapped(*self.context, *args, **kwargs)
|
||||
def _bind(self, _input):
|
||||
try:
|
||||
bind = signature(self.wrapped).bind
|
||||
except ValueError:
|
||||
bind = partial(bound, self.wrapped)
|
||||
if self.format is _args:
|
||||
return bind(*self.args, *_input, **self.kwargs)
|
||||
if self.format is _raw:
|
||||
return bind(*self.args, _input, **self.kwargs)
|
||||
if self.format is _none:
|
||||
return bind(*self.args, **self.kwargs)
|
||||
raise NotImplementedError('Invalid format {!r}.'.format(self.format))
|
||||
|
||||
def __call__(self, _input):
|
||||
if not callable(self.wrapped):
|
||||
if isinstance(self.wrapped, Iterable):
|
||||
return self.__iter__()
|
||||
raise UnrecoverableTypeError('Uncallable node {}'.format(self.wrapped))
|
||||
try:
|
||||
bound = self._bind(_input)
|
||||
except TypeError as exc:
|
||||
raise UnrecoverableTypeError((
|
||||
'Input of {wrapped!r} does not bind to the node signature.\n'
|
||||
'Args: {args}\n'
|
||||
'Input: {input}\n'
|
||||
'Kwargs: {kwargs}\n'
|
||||
'Signature: {sig}'
|
||||
).format(
|
||||
wrapped=self.wrapped, args=self.args, input=_input, kwargs=self.kwargs, sig=signature(self.wrapped)
|
||||
)) from exc
|
||||
return self.wrapped(*bound.args, **bound.kwargs)
|
||||
|
||||
def setup(self, *context):
|
||||
if self._stack is not None:
|
||||
@ -89,14 +118,11 @@ class ContextCurrifier:
|
||||
|
||||
self._stack, self._stack_values = list(), list()
|
||||
for processor in resolve_processors(self.wrapped):
|
||||
_processed = processor(self.wrapped, *context, *self.context)
|
||||
try:
|
||||
_append_to_context = next(_processed)
|
||||
except TypeError as exc:
|
||||
raise TypeError('Context processor should be generators (using yield).') from exc
|
||||
_processed = processor(self.wrapped, *context, *self.args, **self.kwargs)
|
||||
_append_to_context = next(_processed)
|
||||
self._stack_values.append(_append_to_context)
|
||||
if _append_to_context is not None:
|
||||
self.context += ensure_tuple(_append_to_context)
|
||||
self.args += ensure_tuple(_append_to_context)
|
||||
self._stack.append(_processed)
|
||||
|
||||
def teardown(self):
|
||||
@ -139,3 +165,42 @@ def resolve_processors(mixed):
|
||||
|
||||
|
||||
get_context_processors = deprecated_alias('get_context_processors', resolve_processors)
|
||||
|
||||
|
||||
def use_context(f):
|
||||
def context(self, context, *args, **kwargs):
|
||||
yield context
|
||||
|
||||
return use_context_processor(context)(f)
|
||||
|
||||
|
||||
def use_context_processor(context_processor):
|
||||
def using_context_processor(cls_or_func):
|
||||
nonlocal context_processor
|
||||
|
||||
try:
|
||||
cls_or_func.__processors__
|
||||
except AttributeError:
|
||||
cls_or_func.__processors__ = []
|
||||
|
||||
cls_or_func.__processors__.append(ContextProcessor(context_processor))
|
||||
return cls_or_func
|
||||
|
||||
return using_context_processor
|
||||
|
||||
|
||||
def _use_input_format(input_format):
|
||||
if input_format not in INPUT_FORMATS:
|
||||
raise ValueError(
|
||||
'Invalid input format {!r}. Choices: {}'.format(input_format, ', '.join(sorted(INPUT_FORMATS)))
|
||||
)
|
||||
|
||||
def _set_input_format(f):
|
||||
setattr(f, '__input_format__', input_format)
|
||||
return f
|
||||
|
||||
return _set_input_format
|
||||
|
||||
|
||||
use_no_input = _use_input_format(_none)
|
||||
use_raw_input = _use_input_format(_raw)
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import inspect
|
||||
import pprint
|
||||
import re
|
||||
import threading
|
||||
import types
|
||||
@ -73,13 +75,13 @@ class Container(dict):
|
||||
return cls
|
||||
return super().__new__(cls, *args, **kwargs)
|
||||
|
||||
def args_for(self, mixed):
|
||||
def kwargs_for(self, mixed):
|
||||
try:
|
||||
options = dict(mixed.__options__)
|
||||
except AttributeError:
|
||||
options = {}
|
||||
|
||||
return tuple(option.resolve(mixed, self) for name, option in options.items() if isinstance(option, Service))
|
||||
return {name: option.resolve(mixed, self) for name, option in options.items() if isinstance(option, Service)}
|
||||
|
||||
def get(self, name, default=None):
|
||||
if not name in self:
|
||||
@ -156,7 +158,7 @@ class Exclusive(ContextDecorator):
|
||||
self.get_lock().release()
|
||||
|
||||
|
||||
def requires(*service_names):
|
||||
def use(*service_names):
|
||||
def decorate(mixed):
|
||||
try:
|
||||
options = mixed.__options__
|
||||
|
||||
@ -1,13 +1,20 @@
|
||||
from bonobo.structs.tokens import Token
|
||||
class Token:
|
||||
"""Factory for signal oriented queue messages or other token types."""
|
||||
|
||||
def __init__(self, name):
|
||||
self.__name__ = name
|
||||
|
||||
def __repr__(self):
|
||||
return '<{}>'.format(self.__name__)
|
||||
|
||||
|
||||
BEGIN = Token('Begin')
|
||||
END = Token('End')
|
||||
|
||||
INHERIT_INPUT = Token('InheritInput')
|
||||
LOOPBACK = Token('Loopback')
|
||||
NOT_MODIFIED = Token('NotModified')
|
||||
DEFAULT_SERVICES_FILENAME = '_services.py'
|
||||
DEFAULT_SERVICES_ATTR = 'get_services'
|
||||
|
||||
EMPTY = tuple()
|
||||
|
||||
TICK_PERIOD = 0.2
|
||||
|
||||
ARGNAMES = '_argnames'
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import os
|
||||
|
||||
# https://developers.google.com/api-client-library/python/guide/aaa_oauth
|
||||
# pip install google-api-python-client (1.6.4)
|
||||
|
||||
import httplib2
|
||||
from apiclient import discovery
|
||||
from oauth2client import client, tools
|
||||
@ -7,11 +10,10 @@ from oauth2client.file import Storage
|
||||
from oauth2client.tools import argparser
|
||||
|
||||
HOME_DIR = os.path.expanduser('~')
|
||||
GOOGLE_SCOPES = ('https://www.googleapis.com/auth/spreadsheets', )
|
||||
GOOGLE_SECRETS = os.path.join(HOME_DIR, '.cache/secrets/client_secrets.json')
|
||||
|
||||
|
||||
def get_credentials():
|
||||
def get_credentials(*, scopes):
|
||||
"""Gets valid user credentials from storage.
|
||||
|
||||
If nothing has been stored, or if the stored credentials are invalid,
|
||||
@ -27,8 +29,11 @@ def get_credentials():
|
||||
|
||||
store = Storage(credential_path)
|
||||
credentials = store.get()
|
||||
if not credentials or credentials.invalid:
|
||||
flow = client.flow_from_clientsecrets(GOOGLE_SECRETS, GOOGLE_SCOPES)
|
||||
|
||||
# see https://developers.google.com/api-client-library/python/auth/web-app
|
||||
# kw: "incremental scopes"
|
||||
if not credentials or credentials.invalid or not credentials.has_scopes(scopes):
|
||||
flow = client.flow_from_clientsecrets(GOOGLE_SECRETS, scopes)
|
||||
flow.user_agent = 'Bonobo ETL (https://www.bonobo-project.org/)'
|
||||
flags = argparser.parse_args(['--noauth_local_webserver'])
|
||||
credentials = tools.run_flow(flow, store, flags)
|
||||
@ -36,8 +41,15 @@ def get_credentials():
|
||||
return credentials
|
||||
|
||||
|
||||
def get_google_spreadsheets_api_client():
|
||||
credentials = get_credentials()
|
||||
def get_google_spreadsheets_api_client(scopes=('https://www.googleapis.com/auth/spreadsheets', )):
|
||||
credentials = get_credentials(scopes=scopes)
|
||||
http = credentials.authorize(httplib2.Http())
|
||||
discoveryUrl = 'https://sheets.googleapis.com/$discovery/rest?version=v4'
|
||||
return discovery.build('sheets', 'v4', http=http, discoveryServiceUrl=discoveryUrl, cache_discovery=False)
|
||||
|
||||
|
||||
def get_google_people_api_client(scopes=('https://www.googleapis.com/auth/contacts', )):
|
||||
credentials = get_credentials(scopes=scopes)
|
||||
http = credentials.authorize(httplib2.Http())
|
||||
discoveryUrl = 'https://people.googleapis.com/$discovery/rest?version=v1'
|
||||
return discovery.build('people', 'v1', http=http, discoveryServiceUrl=discoveryUrl, cache_discovery=False)
|
||||
|
||||
@ -63,6 +63,10 @@ class UnrecoverableError(Exception):
|
||||
because you know that your transformation has no point continuing runnning after a bad event."""
|
||||
|
||||
|
||||
class UnrecoverableTypeError(UnrecoverableError, TypeError):
|
||||
pass
|
||||
|
||||
|
||||
class UnrecoverableValueError(UnrecoverableError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import bonobo
|
||||
|
||||
|
||||
def get_argument_parser(parser=None):
|
||||
parser = bonobo.get_argument_parser(parser=parser)
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
'-l',
|
||||
type=int,
|
||||
default=None,
|
||||
help='If set, limits the number of processed lines.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--print',
|
||||
'-p',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='If set, pretty prints before writing to output file.'
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def get_graph_options(options):
|
||||
_limit = options.pop('limit', None)
|
||||
_print = options.pop('print', False)
|
||||
|
||||
return {
|
||||
'_limit': (bonobo.Limit(_limit), ) if _limit else (),
|
||||
'_print': (bonobo.PrettyPrinter(), ) if _print else (),
|
||||
}
|
||||
|
||||
84
bonobo/examples/datasets/__main__.py
Normal file
84
bonobo/examples/datasets/__main__.py
Normal file
@ -0,0 +1,84 @@
|
||||
import bonobo
|
||||
from bonobo import examples
|
||||
from bonobo.contrib.opendatasoft import OpenDataSoftAPI as ODSReader
|
||||
from bonobo.nodes.basics import UnpackItems, Rename, Format
|
||||
|
||||
|
||||
def get_coffeeshops_graph(graph=None, *, _limit=(), _print=()):
|
||||
graph = graph or bonobo.Graph()
|
||||
|
||||
producer = graph.add_chain(
|
||||
ODSReader(
|
||||
dataset='liste-des-cafes-a-un-euro',
|
||||
netloc='opendata.paris.fr'
|
||||
),
|
||||
*_limit,
|
||||
UnpackItems(0),
|
||||
Rename(
|
||||
name='nom_du_cafe',
|
||||
address='adresse',
|
||||
zipcode='arrondissement'
|
||||
),
|
||||
Format(city='Paris', country='France'),
|
||||
*_print,
|
||||
)
|
||||
|
||||
# Comma separated values.
|
||||
graph.add_chain(
|
||||
bonobo.CsvWriter(
|
||||
'coffeeshops.csv',
|
||||
fields=['name', 'address', 'zipcode', 'city'],
|
||||
delimiter=','
|
||||
),
|
||||
_input=producer.output,
|
||||
)
|
||||
|
||||
# Name to address JSON
|
||||
# graph.add_chain(
|
||||
# bonobo.JsonWriter(path='coffeeshops.dict.json'),
|
||||
# _input=producer.output,
|
||||
# )
|
||||
|
||||
# Standard JSON
|
||||
graph.add_chain(
|
||||
bonobo.JsonWriter(path='coffeeshops.json'),
|
||||
_input=producer.output,
|
||||
)
|
||||
|
||||
# Line-delimited JSON
|
||||
graph.add_chain(
|
||||
bonobo.LdjsonWriter(path='coffeeshops.ldjson'),
|
||||
_input=producer.output,
|
||||
)
|
||||
|
||||
return graph
|
||||
|
||||
|
||||
all = 'all'
|
||||
graphs = {
|
||||
'coffeeshops': get_coffeeshops_graph,
|
||||
}
|
||||
|
||||
|
||||
def get_services():
|
||||
return {'fs': bonobo.open_fs(bonobo.get_examples_path('datasets'))}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = examples.get_argument_parser()
|
||||
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('--all', '-a', action='store_true', default=False)
|
||||
group.add_argument('--target', '-t', choices=graphs.keys(), nargs='+')
|
||||
|
||||
with bonobo.parse_args(parser) as options:
|
||||
graph_options = examples.get_graph_options(options)
|
||||
graph_names = list(
|
||||
sorted(graphs.keys()) if options['all'] else options['target']
|
||||
)
|
||||
|
||||
graph = bonobo.Graph()
|
||||
for name in graph_names:
|
||||
graph = graphs[name](graph, **graph_options)
|
||||
|
||||
bonobo.run(graph, services=get_services())
|
||||
@ -1,7 +0,0 @@
|
||||
from os.path import dirname
|
||||
|
||||
import bonobo
|
||||
|
||||
|
||||
def get_services():
|
||||
return {'fs': bonobo.open_fs(dirname(__file__))}
|
||||
1
bonobo/examples/datasets/coffeeshops.csv
Normal file
1
bonobo/examples/datasets/coffeeshops.csv
Normal file
@ -0,0 +1 @@
|
||||
"['name', 'address', 'zipcode', 'city']"
|
||||
|
@ -1,182 +1,181 @@
|
||||
{"les montparnos": "65 boulevard Pasteur, 75015 Paris, France",
|
||||
"Coffee Chope": "344Vrue Vaugirard, 75015 Paris, France",
|
||||
"Caf\u00e9 Lea": "5 rue Claude Bernard, 75005 Paris, France",
|
||||
"Le Bellerive": "71 quai de Seine, 75019 Paris, France",
|
||||
"Le drapeau de la fidelit\u00e9": "21 rue Copreaux, 75015 Paris, France",
|
||||
"O q de poule": "53 rue du ruisseau, 75018 Paris, France",
|
||||
"Le caf\u00e9 des amis": "125 rue Blomet, 75015 Paris, France",
|
||||
"Le chantereine": "51 Rue Victoire, 75009 Paris, France",
|
||||
"Le M\u00fcller": "11 rue Feutrier, 75018 Paris, France",
|
||||
"Ext\u00e9rieur Quai": "5, rue d'Alsace, 75010 Paris, France",
|
||||
"La Bauloise": "36 rue du hameau, 75015 Paris, France",
|
||||
"Le Dellac": "14 rue Rougemont, 75009 Paris, France",
|
||||
"Le Bosquet": "46 avenue Bosquet, 75007 Paris, France",
|
||||
"Le Sully": "6 Bd henri IV, 75004 Paris, France",
|
||||
"Le Felteu": "1 rue Pecquay, 75004 Paris, France",
|
||||
"Le bistrot de Ma\u00eblle et Augustin": "42 rue coquill\u00e8re, 75001 Paris, France",
|
||||
"D\u00e9d\u00e9 la frite": "52 rue Notre-Dame des Victoires, 75002 Paris, France",
|
||||
"Cardinal Saint-Germain": "11 boulevard Saint-Germain, 75005 Paris, France",
|
||||
"Le Reynou": "2 bis quai de la m\u00e9gisserie, 75001 Paris, France",
|
||||
"Aux cadrans": "21 ter boulevard Diderot, 75012 Paris, France",
|
||||
"Le Saint Jean": "23 rue des abbesses, 75018 Paris, France",
|
||||
"La Renaissance": "112 Rue Championnet, 75018 Paris, France",
|
||||
"Le Square": "31 rue Saint-Dominique, 75007 Paris, France",
|
||||
"Les Arcades": "61 rue de Ponthieu, 75008 Paris, France",
|
||||
"Le Kleemend's": "34 avenue Pierre Mend\u00e8s-France, 75013 Paris, France",
|
||||
"Assaporare Dix sur Dix": "75, avenue Ledru-Rollin, 75012 Paris, France",
|
||||
"Caf\u00e9 Pierre": "202 rue du faubourg st antoine, 75012 Paris, France",
|
||||
"Caf\u00e9 antoine": "17 rue Jean de la Fontaine, 75016 Paris, France",
|
||||
"Au cerceau d'or": "129 boulevard sebastopol, 75002 Paris, France",
|
||||
"La Caravane": "Rue de la Fontaine au Roi, 75011 Paris, France",
|
||||
"Le Pas Sage": "1 Passage du Grand Cerf, 75002 Paris, France",
|
||||
"Le Caf\u00e9 Livres": "10 rue Saint Martin, 75004 Paris, France",
|
||||
"Le Chaumontois": "12 rue Armand Carrel, 75018 Paris, France",
|
||||
"Drole d'endroit pour une rencontre": "58 rue de Montorgueil, 75002 Paris, France",
|
||||
"Le pari's caf\u00e9": "104 rue caulaincourt, 75018 Paris, France",
|
||||
"Le Poulailler": "60 rue saint-sabin, 75011 Paris, France",
|
||||
"Chai 33": "33 Cour Saint Emilion, 75012 Paris, France",
|
||||
"L'Assassin": "99 rue Jean-Pierre Timbaud, 75011 Paris, France",
|
||||
"l'Usine": "1 rue d'Avron, 75020 Paris, France",
|
||||
"La Bricole": "52 rue Liebniz, 75018 Paris, France",
|
||||
"le ronsard": "place maubert, 75005 Paris, France",
|
||||
"Face Bar": "82 rue des archives, 75003 Paris, France",
|
||||
"American Kitchen": "49 rue bichat, 75010 Paris, France",
|
||||
"La Marine": "55 bis quai de valmy, 75010 Paris, France",
|
||||
"Le Bloc": "21 avenue Brochant, 75017 Paris, France",
|
||||
"La Recoleta au Manoir": "229 avenue Gambetta, 75020 Paris, France",
|
||||
"Le Pareloup": "80 Rue Saint-Charles, 75015 Paris, France",
|
||||
"La Brasserie Gait\u00e9": "3 rue de la Gait\u00e9, 75014 Paris, France",
|
||||
"Caf\u00e9 Zen": "46 rue Victoire, 75009 Paris, France",
|
||||
"O'Breizh": "27 rue de Penthi\u00e8vre, 75008 Paris, France",
|
||||
"Le Petit Choiseul": "23 rue saint augustin, 75002 Paris, France",
|
||||
"Invitez vous chez nous": "7 rue Ep\u00e9e de Bois, 75005 Paris, France",
|
||||
"La Cordonnerie": "142 Rue Saint-Denis 75002 Paris, 75002 Paris, France",
|
||||
"Le Supercoin": "3, rue Baudelique, 75018 Paris, France",
|
||||
"Populettes": "86 bis rue Riquet, 75018 Paris, France",
|
||||
"Au bon coin": "49 rue des Cloys, 75018 Paris, France",
|
||||
"Le Couvent": "69 rue Broca, 75013 Paris, France",
|
||||
"La Br\u00fblerie des Ternes": "111 rue mouffetard, 75005 Paris, France",
|
||||
"L'\u00c9cir": "59 Boulevard Saint-Jacques, 75014 Paris, France",
|
||||
"Le Chat bossu": "126, rue du Faubourg Saint Antoine, 75012 Paris, France",
|
||||
"Denfert caf\u00e9": "58 boulvevard Saint Jacques, 75014 Paris, France",
|
||||
"Le Caf\u00e9 frapp\u00e9": "95 rue Montmartre, 75002 Paris, France",
|
||||
"La Perle": "78 rue vieille du temple, 75003 Paris, France",
|
||||
"Le Descartes": "1 rue Thouin, 75005 Paris, France",
|
||||
"Bagels & Coffee Corner": "Place de Clichy, 75017 Paris, France",
|
||||
"Le petit club": "55 rue de la tombe Issoire, 75014 Paris, France",
|
||||
"Le Plein soleil": "90 avenue Parmentier, 75011 Paris, France",
|
||||
"Le Relais Haussmann": "146, boulevard Haussmann, 75008 Paris, France",
|
||||
"Le Malar": "88 rue Saint-Dominique, 75007 Paris, France",
|
||||
"Au panini de la place": "47 rue Belgrand, 75020 Paris, France",
|
||||
"Le Village": "182 rue de Courcelles, 75017 Paris, France",
|
||||
"Pause Caf\u00e9": "41 rue de Charonne, 75011 Paris, France",
|
||||
"Le Pure caf\u00e9": "14 rue Jean Mac\u00e9, 75011 Paris, France",
|
||||
"Extra old caf\u00e9": "307 fg saint Antoine, 75011 Paris, France",
|
||||
"Chez Fafa": "44 rue Vinaigriers, 75010 Paris, France",
|
||||
"En attendant l'or": "3 rue Faidherbe, 75011 Paris, France",
|
||||
"Br\u00fblerie San Jos\u00e9": "30 rue des Petits-Champs, 75002 Paris, France",
|
||||
"Caf\u00e9 de la Mairie (du VIII)": "rue de Lisbonne, 75008 Paris, France",
|
||||
"Caf\u00e9 Martin": "2 place Martin Nadaud, 75001 Paris, France",
|
||||
"Etienne": "14 rue Turbigo, Paris, 75001 Paris, France",
|
||||
"L'ing\u00e9nu": "184 bd Voltaire, 75011 Paris, France",
|
||||
"L'Olive": "8 rue L'Olive, 75018 Paris, France",
|
||||
"Le Biz": "18 rue Favart, 75002 Paris, France",
|
||||
"Le Cap Bourbon": "1 rue Louis le Grand, 75002 Paris, France",
|
||||
"Le General Beuret": "9 Place du General Beuret, 75015 Paris, France",
|
||||
"Le Germinal": "95 avenue Emile Zola, 75015 Paris, France",
|
||||
"Le Ragueneau": "202 rue Saint-Honor\u00e9, 75001 Paris, France",
|
||||
"Le refuge": "72 rue lamarck, 75018 Paris, France",
|
||||
"Le sully": "13 rue du Faubourg Saint Denis, 75010 Paris, France",
|
||||
"Le Dunois": "77 rue Dunois, 75013 Paris, France",
|
||||
"La Montagne Sans Genevi\u00e8ve": "13 Rue du Pot de Fer, 75005 Paris, France",
|
||||
"Le Caminito": "48 rue du Dessous des Berges, 75013 Paris, France",
|
||||
"Le petit Bretonneau": "Le petit Bretonneau - \u00e0 l'int\u00e9rieur de l'H\u00f4pital, 75018 Paris, France",
|
||||
"La chaumi\u00e8re gourmande": "Route de la Muette \u00e0 Neuilly",
|
||||
"Club hippique du Jardin d\u2019Acclimatation": "75016 Paris, France",
|
||||
"Le bal du pirate": "60 rue des bergers, 75015 Paris, France",
|
||||
"Le Zazabar": "116 Rue de M\u00e9nilmontant, 75020 Paris, France",
|
||||
"L'antre d'eux": "16 rue DE MEZIERES, 75006 Paris, France",
|
||||
"l'orillon bar": "35 rue de l'orillon, 75011 Paris, France",
|
||||
"zic zinc": "95 rue claude decaen, 75012 Paris, France",
|
||||
"Les P\u00e8res Populaires": "46 rue de Buzenval, 75020 Paris, France",
|
||||
"Epicerie Musicale": "55bis quai de Valmy, 75010 Paris, France",
|
||||
"Le relais de la victoire": "73 rue de la Victoire, 75009 Paris, France",
|
||||
"Le Centenaire": "104 rue amelot, 75011 Paris, France",
|
||||
"Cafe de grenelle": "188 rue de Grenelle, 75007 Paris, France",
|
||||
"Ragueneau": "202 rue Saint Honor\u00e9, 75001 Paris, France",
|
||||
"Caf\u00e9 Pistache": "9 rue des petits champs, 75001 Paris, France",
|
||||
"La Cagnotte": "13 Rue Jean-Baptiste Dumay, 75020 Paris, France",
|
||||
"Le Killy Jen": "28 bis boulevard Diderot, 75012 Paris, France",
|
||||
"Caf\u00e9 beauveau": "9 rue de Miromesnil, 75008 Paris, France",
|
||||
"le 1 cinq": "172 rue de vaugirard, 75015 Paris, France",
|
||||
"Les Artisans": "106 rue Lecourbe, 75015 Paris, France",
|
||||
"Peperoni": "83 avenue de Wagram, 75001 Paris, France",
|
||||
"Le Brio": "216, rue Marcadet, 75018 Paris, France",
|
||||
"Tamm Bara": "7 rue Clisson, 75013 Paris, France",
|
||||
"Caf\u00e9 dans l'aerogare Air France Invalides": "2 rue Robert Esnault Pelterie, 75007 Paris, France",
|
||||
"bistrot les timbr\u00e9s": "14 rue d'alleray, 75015 Paris, France",
|
||||
"Caprice caf\u00e9": "12 avenue Jean Moulin, 75014 Paris, France",
|
||||
"Caves populaires": "22 rue des Dames, 75017 Paris, France",
|
||||
"Au Vin Des Rues": "21 rue Boulard, 75014 Paris, France",
|
||||
"Chez Prune": "36 rue Beaurepaire, 75010 Paris, France",
|
||||
"L'In\u00e9vitable": "22 rue Linn\u00e9, 75005 Paris, France",
|
||||
"L'anjou": "1 rue de Montholon, 75009 Paris, France",
|
||||
"Botak cafe": "1 rue Paul albert, 75018 Paris, France",
|
||||
"Bistrot Saint-Antoine": "58 rue du Fbg Saint-Antoine, 75012 Paris, France",
|
||||
"Chez Oscar": "11/13 boulevard Beaumarchais, 75004 Paris, France",
|
||||
"Le Piquet": "48 avenue de la Motte Picquet, 75015 Paris, France",
|
||||
"L'avant comptoir": "3 carrefour de l'Od\u00e9on, 75006 Paris, France",
|
||||
"le chateau d'eau": "67 rue du Ch\u00e2teau d'eau, 75010 Paris, France",
|
||||
"Les Vendangeurs": "6/8 rue Stanislas, 75006 Paris, France",
|
||||
"maison du vin": "52 rue des plantes, 75014 Paris, France",
|
||||
"Le Tournebride": "104 rue Mouffetard, 75005 Paris, France",
|
||||
"Le Fronton": "63 rue de Ponthieu, 75008 Paris, France",
|
||||
"Le BB (Bouchon des Batignolles)": "2 rue Lemercier, 75017 Paris, France",
|
||||
"La cantine de Zo\u00e9": "136 rue du Faubourg poissonni\u00e8re, 75010 Paris, France",
|
||||
"Chez Rutabaga": "16 rue des Petits Champs, 75002 Paris, France",
|
||||
"Les caves populaires": "22 rue des Dames, 75017 Paris, France",
|
||||
"Le Plomb du cantal": "3 rue Ga\u00eet\u00e9, 75014 Paris, France",
|
||||
"Trois pi\u00e8ces cuisine": "101 rue des dames, 75017 Paris, France",
|
||||
"La Brocante": "10 rue Rossini, 75009 Paris, France",
|
||||
"Le Zinc": "61 avenue de la Motte Picquet, 75015 Paris, France",
|
||||
"Chez Luna": "108 rue de M\u00e9nilmontant, 75020 Paris, France",
|
||||
"Le bar Fleuri": "1 rue du Plateau, 75019 Paris, France",
|
||||
"La Libert\u00e9": "196 rue du faubourg saint-antoine, 75012 Paris, France",
|
||||
"La cantoche de Paname": "40 Boulevard Beaumarchais, 75011 Paris, France",
|
||||
"Le Saint Ren\u00e9": "148 Boulevard de Charonne, 75020 Paris, France",
|
||||
"Caf\u00e9 Clochette": "16 avenue Richerand, 75010 Paris, France",
|
||||
"L'europ\u00e9en": "21 Bis Boulevard Diderot, 75012 Paris, France",
|
||||
"NoMa": "39 rue Notre Dame de Nazareth, 75003 Paris, France",
|
||||
"le lutece": "380 rue de vaugirard, 75015 Paris, France",
|
||||
"O'Paris": "1 Rue des Envierges, 75020 Paris, France",
|
||||
"Rivolux": "16 rue de Rivoli, 75004 Paris, France",
|
||||
"Brasiloja": "16 rue Ganneron, 75018 Paris, France",
|
||||
"Institut des Cultures d'Islam": "19-23 rue L\u00e9on, 75018 Paris, France",
|
||||
"Canopy Caf\u00e9 associatif": "19 rue Pajol, 75018 Paris, France",
|
||||
"Petits Freres des Pauvres": "47 rue de Batignolles, 75017 Paris, France",
|
||||
"Le Lucernaire": "53 rue Notre-Dame des Champs, 75006 Paris, France",
|
||||
"L'Angle": "28 rue de Ponthieu, 75008 Paris, France",
|
||||
"Le Caf\u00e9 d'avant": "35 rue Claude Bernard, 75005 Paris, France",
|
||||
"Caf\u00e9 Dupont": "198 rue de la Convention, 75015 Paris, France",
|
||||
"Le S\u00e9vign\u00e9": "15 rue du Parc Royal, 75003 Paris, France",
|
||||
"L'Entracte": "place de l'opera, 75002 Paris, France",
|
||||
"Panem": "18 rue de Crussol, 75011 Paris, France",
|
||||
"Au pays de Vannes": "34 bis rue de Wattignies, 75012 Paris, France",
|
||||
"l'El\u00e9phant du nil": "125 Rue Saint-Antoine, 75004 Paris, France",
|
||||
"L'\u00e2ge d'or": "26 rue du Docteur Magnan, 75013 Paris, France",
|
||||
"Le Comptoir": "354 bis rue Vaugirard, 75015 Paris, France",
|
||||
"L'horizon": "93, rue de la Roquette, 75011 Paris, France",
|
||||
"L'empreinte": "54, avenue Daumesnil, 75012 Paris, France",
|
||||
"Caf\u00e9 Victor": "10 boulevard Victor, 75015 Paris, France",
|
||||
"Caf\u00e9 Varenne": "36 rue de Varenne, 75007 Paris, France",
|
||||
"Le Brigadier": "12 rue Blanche, 75009 Paris, France",
|
||||
"Waikiki": "10 rue d\"Ulm, 75005 Paris, France",
|
||||
"Le Parc Vaugirard": "358 rue de Vaugirard, 75015 Paris, France",
|
||||
"Pari's Caf\u00e9": "174 avenue de Clichy, 75017 Paris, France",
|
||||
"Melting Pot": "3 rue de Lagny, 75020 Paris, France",
|
||||
"le Zango": "58 rue Daguerre, 75014 Paris, France",
|
||||
"Chez Miamophile": "6 rue M\u00e9lingue, 75019 Paris, France",
|
||||
"Le caf\u00e9 Monde et M\u00e9dias": "Place de la R\u00e9publique, 75003 Paris, France",
|
||||
"Caf\u00e9 rallye tournelles": "11 Quai de la Tournelle, 75005 Paris, France",
|
||||
"Brasserie le Morvan": "61 rue du ch\u00e2teau d'eau, 75010 Paris, France",
|
||||
"L'entrep\u00f4t": "157 rue Bercy 75012 Paris, 75012 Paris, France"}
|
||||
[{"zipcode": 75015, "address": "344Vrue Vaugirard", "prix_salle": "-", "geoloc": [48.839512, 2.303007], "name": "Coffee Chope", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.303007, 48.839512]}, "recordid": "3c276428d45ad68ccdf6875e4ddcfe95d0c0d4cf", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75010, "address": "5, rue d'Alsace", "prix_salle": "-", "geoloc": [48.876737, 2.357601], "name": "Ext\u00e9rieur Quai", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.357601, 48.876737]}, "recordid": "97ad81cd1127a8566085ad796eeb44a06bec7514", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75004, "address": "6 Bd henri IV", "prix_salle": "-", "geoloc": [48.850852, 2.362029], "name": "Le Sully", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.362029, 48.850852]}, "recordid": "aa4294c1b8d660a23db0dc81321e509bae1dae68", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "53 rue du ruisseau", "prix_salle": "-", "geoloc": [48.893517, 2.340271], "name": "O q de poule", "prix_terasse": "1", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.340271, 48.893517]}, "recordid": "a81362dbed35247555fb105bd83ff2906904a66e", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "1 Passage du Grand Cerf", "prix_salle": "-", "geoloc": [48.864655, 2.350089], "name": "Le Pas Sage", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.350089, 48.864655]}, "recordid": "7ced86acbd5ccfba229bcc07d70d0d117aee16a5", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "112 Rue Championnet", "prix_salle": "-", "geoloc": [48.895825, 2.339712], "name": "La Renaissance", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.339712, 48.895825]}, "recordid": "5582c8572bd7637bf305b74c1c0bdb74a8e4247f", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "Rue de la Fontaine au Roi", "prix_salle": "-", "geoloc": [48.868581, 2.373015], "name": "La Caravane", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.373015, 48.868581]}, "recordid": "50bb0fa06e562a242f115ddbdae2ed9c7df93d57", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75009, "address": "51 Rue Victoire", "prix_salle": "1", "geoloc": [48.875155, 2.335536], "name": "Le chantereine", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.335536, 48.875155]}, "recordid": "eb8a62feeedaf7ed8b8c912305270ee857068689", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "11 rue Feutrier", "prix_salle": "1", "geoloc": [48.886536, 2.346525], "name": "Le M\u00fcller", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.346525, 48.886536]}, "recordid": "62c552f167f671f88569c1f2d6a44098fb514c51", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "21 rue Copreaux", "prix_salle": "1", "geoloc": [48.841494, 2.307117], "name": "Le drapeau de la fidelit\u00e9", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.307117, 48.841494]}, "recordid": "5120ea0b9d7387766072b90655166486928e25c8", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "125 rue Blomet", "prix_salle": "1", "geoloc": [48.839743, 2.296898], "name": "Le caf\u00e9 des amis", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.296898, 48.839743]}, "recordid": "865f62415adc5c34e3ca38a1748b7a324dfba209", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75004, "address": "10 rue Saint Martin", "prix_salle": "-", "geoloc": [48.857728, 2.349641], "name": "Le Caf\u00e9 Livres", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349641, 48.857728]}, "recordid": "7ef54a78802d49cafd2701458df2b0d0530d123b", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75007, "address": "46 avenue Bosquet", "prix_salle": "-", "geoloc": [48.856003, 2.30457], "name": "Le Bosquet", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.30457, 48.856003]}, "recordid": "d701a759e08a71f4bbb01f29473274b0152135d0", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "12 rue Armand Carrel", "prix_salle": "-", "geoloc": [48.889426, 2.332954], "name": "Le Chaumontois", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.332954, 48.889426]}, "recordid": "e12ff00a644c91ad910ddc63a770c190be93a393", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75013, "address": "34 avenue Pierre Mend\u00e8s-France", "prix_salle": "-", "geoloc": [48.838521, 2.370478], "name": "Le Kleemend's", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.370478, 48.838521]}, "recordid": "0f6cd1ee7751b00c9574efcfdcf66fa0e857d251", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "202 rue du faubourg st antoine", "prix_salle": "-", "geoloc": [48.849861, 2.385342], "name": "Caf\u00e9 Pierre", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.385342, 48.849861]}, "recordid": "f9de9d0fb5e92f047a6f1986a31f9dd4d38bcb36", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75008, "address": "61 rue de Ponthieu", "prix_salle": "-", "geoloc": [48.872202, 2.304624], "name": "Les Arcades", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.304624, 48.872202]}, "recordid": "67eaf58afc856077c0680601e453e75c0922c9c0", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75007, "address": "31 rue Saint-Dominique", "prix_salle": "-", "geoloc": [48.859031, 2.320315], "name": "Le Square", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.320315, 48.859031]}, "recordid": "678558317bc9ad46652e5b1643e70c2142a76e7e", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "75, avenue Ledru-Rollin", "prix_salle": "-", "geoloc": [48.850092, 2.37463], "name": "Assaporare Dix sur Dix", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.37463, 48.850092]}, "recordid": "667474321887d08a3cc636adf043ad354b65fa61", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "129 boulevard sebastopol", "prix_salle": "-", "geoloc": [48.86805, 2.353313], "name": "Au cerceau d'or", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.353313, 48.86805]}, "recordid": "c9ef52ba2fabe0286700329f18bbbbea9a10b474", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "21 ter boulevard Diderot", "prix_salle": "-", "geoloc": [48.845927, 2.373051], "name": "Aux cadrans", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.373051, 48.845927]}, "recordid": "ed5f98686856bf4ddd2b381b43ad229246741a90", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75016, "address": "17 rue Jean de la Fontaine", "prix_salle": "-", "geoloc": [48.851662, 2.273883], "name": "Caf\u00e9 antoine", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.273883, 48.851662]}, "recordid": "ab6d1e054e2e6ae7d6150013173f55e83c05ca23", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75008, "address": "rue de Lisbonne", "prix_salle": "-", "geoloc": [48.877642, 2.312823], "name": "Caf\u00e9 de la Mairie (du VIII)", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.312823, 48.877642]}, "recordid": "7de8a79b026ac63f453556612505b5bcd9229036", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "5 rue Claude Bernard", "prix_salle": "-", "geoloc": [48.838633, 2.349916], "name": "Caf\u00e9 Lea", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349916, 48.838633]}, "recordid": "fecd8900cf83027f74ceced9fc4ad80ac73b63a7", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "11 boulevard Saint-Germain", "prix_salle": "-", "geoloc": [48.849293, 2.354486], "name": "Cardinal Saint-Germain", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.354486, 48.849293]}, "recordid": "e4a078c30c98082896787f4e4b41a07554392529", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "52 rue Notre-Dame des Victoires", "prix_salle": "-", "geoloc": [48.869771, 2.342501], "name": "D\u00e9d\u00e9 la frite", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.342501, 48.869771]}, "recordid": "ccb2ba2f98043e8eefd5fda829dee1ea7f1d2c7a", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "36 rue du hameau", "prix_salle": "-", "geoloc": [48.834051, 2.287345], "name": "La Bauloise", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.287345, 48.834051]}, "recordid": "c9fe10abd15ede7ccaeb55c309898d30d7b19d0e", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75019, "address": "71 quai de Seine", "prix_salle": "-", "geoloc": [48.888165, 2.377387], "name": "Le Bellerive", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.377387, 48.888165]}, "recordid": "4e0b5c2d33d7c25fc54c51171f3d37e509959fc0", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75001, "address": "42 rue coquill\u00e8re", "prix_salle": "-", "geoloc": [48.864543, 2.340997], "name": "Le bistrot de Ma\u00eblle et Augustin", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.340997, 48.864543]}, "recordid": "52acab12469af291984e9a70962e08c72b058e10", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75009, "address": "14 rue Rougemont", "prix_salle": "-", "geoloc": [48.872103, 2.346161], "name": "Le Dellac", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.346161, 48.872103]}, "recordid": "4d1d627ecea2ffa279bb862f8ba495d95ca75350", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75004, "address": "1 rue Pecquay", "prix_salle": "-", "geoloc": [48.859645, 2.355598], "name": "Le Felteu", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.355598, 48.859645]}, "recordid": "2c1fa55460af282266d86fd003af4f929fdf4e7d", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75001, "address": "2 bis quai de la m\u00e9gisserie", "prix_salle": "-", "geoloc": [48.85763, 2.346101], "name": "Le Reynou", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.346101, 48.85763]}, "recordid": "d4ddd30ab3e721a317fc7ea89d5b9001255ce9f4", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "23 rue des abbesses", "prix_salle": "-", "geoloc": [48.884646, 2.337734], "name": "Le Saint Jean", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.337734, 48.884646]}, "recordid": "51b47cf167b7f32eeebb108330956694d75d4268", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "65 boulevard Pasteur", "prix_salle": "-", "geoloc": [48.841007, 2.31466], "name": "les montparnos", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.31466, 48.841007]}, "recordid": "2aaca891ffd0694c657a43889516ab72afdfba07", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75006, "address": "16 rue DE MEZIERES", "prix_salle": "-", "geoloc": [48.850323, 2.33039], "name": "L'antre d'eux", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.33039, 48.850323]}, "recordid": "4ff4337934c66f61e00d1d9551f7cdddba03e544", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "58 rue de Montorgueil", "prix_salle": "-", "geoloc": [48.864957, 2.346938], "name": "Drole d'endroit pour une rencontre", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.346938, 48.864957]}, "recordid": "3451657f880abe75d0c7e386fc698405556c53e8", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "104 rue caulaincourt", "prix_salle": "-", "geoloc": [48.889565, 2.339735], "name": "Le pari's caf\u00e9", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.339735, 48.889565]}, "recordid": "e8c34a537b673fcb26c76e02deca4f5a728929dc", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "60 rue saint-sabin", "prix_salle": "-", "geoloc": [48.859115, 2.368871], "name": "Le Poulailler", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.368871, 48.859115]}, "recordid": "325ea74ba83f716dde87c08cffd36f7df7722a49", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "33 Cour Saint Emilion", "prix_salle": "-", "geoloc": [48.833595, 2.38604], "name": "Chai 33", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.38604, 48.833595]}, "recordid": "528de8d5d8780bee83145637e315483d48f5ae3c", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "99 rue Jean-Pierre Timbaud", "prix_salle": "-", "geoloc": [48.868741, 2.379969], "name": "L'Assassin", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.379969, 48.868741]}, "recordid": "fac0483890ff8bdaeb3feddbdb032c5112f24678", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75020, "address": "1 rue d'Avron", "prix_salle": "-", "geoloc": [48.851463, 2.398691], "name": "l'Usine", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.398691, 48.851463]}, "recordid": "fee1e3eb103bbc98e19e45d34365da0f27166541", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "52 rue Liebniz", "prix_salle": "-", "geoloc": [48.896305, 2.332898], "name": "La Bricole", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.332898, 48.896305]}, "recordid": "4744e866c244c59eec43b3fe159542d2ef433065", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "place maubert", "prix_salle": "-", "geoloc": [48.850311, 2.34885], "name": "le ronsard", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.34885, 48.850311]}, "recordid": "49a390322b45246bc2c1e50fcd46815ad271bca0", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75003, "address": "82 rue des archives", "prix_salle": "-", "geoloc": [48.863038, 2.3604], "name": "Face Bar", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.3604, 48.863038]}, "recordid": "d96e16ebf2460bb2f6c34198918a071233725cbc", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75010, "address": "49 rue bichat", "prix_salle": "-", "geoloc": [48.872746, 2.366392], "name": "American Kitchen", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.366392, 48.872746]}, "recordid": "6b9395475cbbbbbacbaaeb070f71d31c2d183dc4", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75010, "address": "55 bis quai de valmy", "prix_salle": "-", "geoloc": [48.870598, 2.365413], "name": "La Marine", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.365413, 48.870598]}, "recordid": "d4d2f92d27f38de59e57744f434781e61283551c", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75017, "address": "21 avenue Brochant", "prix_salle": "-", "geoloc": [48.889101, 2.318001], "name": "Le Bloc", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.318001, 48.889101]}, "recordid": "e425882ee969d1e8bffe7234336ae40da88c8439", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75020, "address": "229 avenue Gambetta", "prix_salle": "-", "geoloc": [48.874697, 2.405421], "name": "La Recoleta au Manoir", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.405421, 48.874697]}, "recordid": "02de82cffb2918beafb740f4e924029d470b07a1", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "80 Rue Saint-Charles", "prix_salle": "-", "geoloc": [48.847344, 2.286078], "name": "Le Pareloup", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.286078, 48.847344]}, "recordid": "0227ca95f76bb6097ae0a0e6f455af2624d49ae3", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75014, "address": "3 rue de la Gait\u00e9", "prix_salle": "-", "geoloc": [48.840771, 2.324589], "name": "La Brasserie Gait\u00e9", "prix_terasse": "1", "date": "2013-08-22", "prix_comptoir": null, "geometry": {"type": "Point", "coordinates": [2.324589, 48.840771]}, "recordid": "e7c4cba08749c892a73db2715d06623d9e0c2f67", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75009, "address": "46 rue Victoire", "prix_salle": "-", "geoloc": [48.875232, 2.336036], "name": "Caf\u00e9 Zen", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.336036, 48.875232]}, "recordid": "5e9a6172f8b64cd098a5f2cf9b1d42567fe0a894", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75008, "address": "27 rue de Penthi\u00e8vre", "prix_salle": "1", "geoloc": [48.872677, 2.315276], "name": "O'Breizh", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.315276, 48.872677]}, "recordid": "ead3108add27ef41bb92517aca834f7d7f632816", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "23 rue saint augustin", "prix_salle": "1", "geoloc": [48.868838, 2.33605], "name": "Le Petit Choiseul", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.33605, 48.868838]}, "recordid": "de601277ca00567b62fa4f5277e4a17679faa753", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "7 rue Ep\u00e9e de Bois", "prix_salle": "1", "geoloc": [48.841526, 2.351012], "name": "Invitez vous chez nous", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.351012, 48.841526]}, "recordid": "1d2373bdec8a07306298e3ee54894ac295ee1d55", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "142 Rue Saint-Denis 75002 Paris", "prix_salle": "1", "geoloc": [48.86525, 2.350507], "name": "La Cordonnerie", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.350507, 48.86525]}, "recordid": "5c9bf60617a99ad75445b454f98e75e1a104021d", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "3, rue Baudelique", "prix_salle": "1", "geoloc": [48.892244, 2.346973], "name": "Le Supercoin", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.346973, 48.892244]}, "recordid": "68a4ee10f1fc4d2a659501e811d148420fa80e95", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "86 bis rue Riquet", "prix_salle": "1", "geoloc": [48.890043, 2.362241], "name": "Populettes", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.362241, 48.890043]}, "recordid": "8cc55d58d72621a7e91cf6b456731d2cb2863afc", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "49 rue des Cloys", "prix_salle": "1", "geoloc": [48.893017, 2.337776], "name": "Au bon coin", "prix_terasse": "1", "date": "2012-10-18", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.337776, 48.893017]}, "recordid": "0408f272c08c52e3cae035ffdeb8928698787ea9", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75013, "address": "69 rue Broca", "prix_salle": "1", "geoloc": [48.836919, 2.347003], "name": "Le Couvent", "prix_terasse": "-", "date": "2012-10-18", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.347003, 48.836919]}, "recordid": "729b2f228d3fd2db6f78bb624d451f59555e4a04", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "111 rue mouffetard", "prix_salle": "1", "geoloc": [48.840624, 2.349766], "name": "La Br\u00fblerie des Ternes", "prix_terasse": "-", "date": "2012-10-18", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349766, 48.840624]}, "recordid": "233dd2a17620cd5eae70fef11cc627748e3313d5", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75014, "address": "59 Boulevard Saint-Jacques", "prix_salle": "-", "geoloc": [48.832825, 2.336116], "name": "L'\u00c9cir", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.336116, 48.832825]}, "recordid": "4a44324a5a806801fd3e05af89cad7c0f1e69d1e", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "126, rue du Faubourg Saint Antoine", "prix_salle": "-", "geoloc": [48.850696, 2.378417], "name": "Le Chat bossu", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.378417, 48.850696]}, "recordid": "d1d02463f7c90d38cccffd898e15f51e65910baf", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75014, "address": "58 boulvevard Saint Jacques", "prix_salle": "-", "geoloc": [48.834157, 2.33381], "name": "Denfert caf\u00e9", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.33381, 48.834157]}, "recordid": "f78f406e5ccb95cb902ce618ed54eba1d4776a3c", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "95 rue Montmartre", "prix_salle": "-", "geoloc": [48.867948, 2.343582], "name": "Le Caf\u00e9 frapp\u00e9", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.343582, 48.867948]}, "recordid": "4dd2a924a7b2c5f061cecaba2f272548a8c83c6c", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75003, "address": "78 rue vieille du temple", "prix_salle": "-", "geoloc": [48.859772, 2.360558], "name": "La Perle", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.360558, 48.859772]}, "recordid": "476c54e643613ec36a9b1c533f32122fd873f3c3", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "1 rue Thouin", "prix_salle": "-", "geoloc": [48.845047, 2.349583], "name": "Le Descartes", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349583, 48.845047]}, "recordid": "e9cb0b0d6b6b512e9ecab889267ba342a4f0ea93", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75014, "address": "55 rue de la tombe Issoire", "prix_salle": "-", "geoloc": [48.830151, 2.334213], "name": "Le petit club", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.334213, 48.830151]}, "recordid": "c6ca1166fa7fd7050f5d1c626a1e17050116c91e", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "90 avenue Parmentier", "prix_salle": "-", "geoloc": [48.865707, 2.374382], "name": "Le Plein soleil", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.374382, 48.865707]}, "recordid": "95cf5fb735bd19826db70a3af4fe72fce647d4e5", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75008, "address": "146, boulevard Haussmann", "prix_salle": "-", "geoloc": [48.875322, 2.312329], "name": "Le Relais Haussmann", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.312329, 48.875322]}, "recordid": "6c5f68f47c916638342b77bd5edfc30bd7051303", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75007, "address": "88 rue Saint-Dominique", "prix_salle": "-", "geoloc": [48.859559, 2.30643], "name": "Le Malar", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.30643, 48.859559]}, "recordid": "c633cb01686aa5f3171bf59976dc9b7f23cbca54", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75020, "address": "47 rue Belgrand", "prix_salle": "-", "geoloc": [48.864628, 2.408038], "name": "Au panini de la place", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.408038, 48.864628]}, "recordid": "66dedd35fcbbbb24f328882d49098de7aa5f26ba", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75017, "address": "182 rue de Courcelles", "prix_salle": "-", "geoloc": [48.88435, 2.297978], "name": "Le Village", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.297978, 48.88435]}, "recordid": "1e07eaf8a93875906d0b18eb6a897c651943589a", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "41 rue de Charonne", "prix_salle": "-", "geoloc": [48.853381, 2.376706], "name": "Pause Caf\u00e9", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.376706, 48.853381]}, "recordid": "60d98c3236a70824df50e9aca83e7d7f13a310c5", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "14 rue Jean Mac\u00e9", "prix_salle": "-", "geoloc": [48.853253, 2.383415], "name": "Le Pure caf\u00e9", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.383415, 48.853253]}, "recordid": "66707fb2e707d2145fc2eb078a1b980a45921616", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "307 fg saint Antoine", "prix_salle": "-", "geoloc": [48.848873, 2.392859], "name": "Extra old caf\u00e9", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.392859, 48.848873]}, "recordid": "039ec7dcb219cfc434547b06938ba497afeb83b4", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75010, "address": "44 rue Vinaigriers", "prix_salle": "-", "geoloc": [48.873227, 2.360787], "name": "Chez Fafa", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.360787, 48.873227]}, "recordid": "1572d199f186bf86d7753fe71ac23477a7a8bd2c", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "3 rue Faidherbe", "prix_salle": "-", "geoloc": [48.850836, 2.384069], "name": "En attendant l'or", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.384069, 48.850836]}, "recordid": "de5789bb4a4ffbd244cded8dc555639dbe7d2279", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "30 rue des Petits-Champs", "prix_salle": "-", "geoloc": [48.866993, 2.336006], "name": "Br\u00fblerie San Jos\u00e9", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.336006, 48.866993]}, "recordid": "b736e5fa17396ee56a642212ccd0ab29c7f2bef1", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75001, "address": "2 place Martin Nadaud", "prix_salle": "-", "geoloc": [48.856434, 2.342683], "name": "Caf\u00e9 Martin", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.342683, 48.856434]}, "recordid": "25fbf857029c54d57909c158e3039349b77344ed", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75001, "address": "14 rue Turbigo, Paris", "prix_salle": "-", "geoloc": [48.863675, 2.348701], "name": "Etienne", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.348701, 48.863675]}, "recordid": "0190edd7b0766c6d3e43093deb41abe9446c1b22", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "184 bd Voltaire", "prix_salle": "-", "geoloc": [48.854584, 2.385193], "name": "L'ing\u00e9nu", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.385193, 48.854584]}, "recordid": "7fcef7475ec632d5c61c9c36c1d52a402ad2e9e8", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "8 rue L'Olive", "prix_salle": "-", "geoloc": [48.890605, 2.361349], "name": "L'Olive", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.361349, 48.890605]}, "recordid": "35286273f281c8c5082b3d3bd17f6bbf207426f9", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "18 rue Favart", "prix_salle": "-", "geoloc": [48.871396, 2.338321], "name": "Le Biz", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.338321, 48.871396]}, "recordid": "cf2f6b9e283aaeeca2214cc1fe57b45e45668e25", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "1 rue Louis le Grand", "prix_salle": "-", "geoloc": [48.868109, 2.331785], "name": "Le Cap Bourbon", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.331785, 48.868109]}, "recordid": "339030e95e0846b41a8e2b91045456c4e4a50043", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "9 Place du General Beuret", "prix_salle": "-", "geoloc": [48.84167, 2.303053], "name": "Le General Beuret", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.303053, 48.84167]}, "recordid": "12b37036d28d28b32ebe81756ad15eb68372947f", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "95 avenue Emile Zola", "prix_salle": "-", "geoloc": [48.846814, 2.289311], "name": "Le Germinal", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.289311, 48.846814]}, "recordid": "4e03831a64e886a28a7232e54f2812c1ced23c5a", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75001, "address": "202 rue Saint-Honor\u00e9", "prix_salle": "-", "geoloc": [48.862655, 2.337607], "name": "Le Ragueneau", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.337607, 48.862655]}, "recordid": "e303131b2600d4c2287749a36bf7193d2fa60bd7", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "72 rue lamarck", "prix_salle": "-", "geoloc": [48.889982, 2.338933], "name": "Le refuge", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.338933, 48.889982]}, "recordid": "f64310461736a769c6854fdefb99b9f2e7b230a9", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75010, "address": "13 rue du Faubourg Saint Denis", "prix_salle": "-", "geoloc": [48.870294, 2.352821], "name": "Le sully", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.352821, 48.870294]}, "recordid": "166be8588ff16e50838fa6a164d1e580497b795d", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "60 rue des bergers", "prix_salle": "-", "geoloc": [48.842128, 2.280374], "name": "Le bal du pirate", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.280374, 48.842128]}, "recordid": "93ff6e35406a074a6ba2667d2b286abf91132f6a", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "95 rue claude decaen", "prix_salle": "-", "geoloc": [48.838769, 2.39609], "name": "zic zinc", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.39609, 48.838769]}, "recordid": "8fdc739d64ff1f01973235301e3ec86791016759", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "35 rue de l'orillon", "prix_salle": "-", "geoloc": [48.870247, 2.376306], "name": "l'orillon bar", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.376306, 48.870247]}, "recordid": "6e6e9695ef04c3fdbd4cfa19f0cfb0c0f93f4933", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75020, "address": "116 Rue de M\u00e9nilmontant", "prix_salle": "-", "geoloc": [48.869848, 2.394247], "name": "Le Zazabar", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.394247, 48.869848]}, "recordid": "22edfe92a72ce1bb0eea972508b5caa7af2db2df", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "22 rue Linn\u00e9", "prix_salle": "-", "geoloc": [48.845458, 2.354796], "name": "L'In\u00e9vitable", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.354796, 48.845458]}, "recordid": "6893cb08e99319091de9ba80305f22e0ce4cc08d", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75013, "address": "77 rue Dunois", "prix_salle": "-", "geoloc": [48.83336, 2.365782], "name": "Le Dunois", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.365782, 48.83336]}, "recordid": "c3be1246dbc4ca5734d5bbd569436ba655105248", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75001, "address": "202 rue Saint Honor\u00e9", "prix_salle": "-", "geoloc": [48.862655, 2.337607], "name": "Ragueneau", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.337607, 48.862655]}, "recordid": "e946d9e8f8c5a130f98eca945efadfd9eec40dcb", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75013, "address": "48 rue du Dessous des Berges", "prix_salle": "1", "geoloc": [48.826608, 2.374571], "name": "Le Caminito", "prix_terasse": null, "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.374571, 48.826608]}, "recordid": "5d9ad8bcfbbc20adaec2aa7dcdb71326868c7686", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75010, "address": "55bis quai de Valmy", "prix_salle": "1", "geoloc": [48.870598, 2.365413], "name": "Epicerie Musicale", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.365413, 48.870598]}, "recordid": "8ffea133d93c608d337bc0129f4f9f3d5cad8dae", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "Le petit Bretonneau - \u00e0 l'int\u00e9rieur de l'H\u00f4pital", "prix_salle": "1", "geoloc": null, "name": "Le petit Bretonneau", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {}, "recordid": "dd1ffdbd55c2dc651201302b8015258f7d35fd35", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "104 rue amelot", "prix_salle": "1", "geoloc": [48.862575, 2.367427], "name": "Le Centenaire", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.367427, 48.862575]}, "recordid": "e47d25752c2c8bcab5efb0e3a41920ec7a8a766a", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "13 Rue du Pot de Fer", "prix_salle": "1", "geoloc": [48.842833, 2.348314], "name": "La Montagne Sans Genevi\u00e8ve", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": null, "geometry": {"type": "Point", "coordinates": [2.348314, 48.842833]}, "recordid": "314d170601f81e3a3f26d8801f0fbee39981c788", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75020, "address": "46 rue de Buzenval", "prix_salle": "1", "geoloc": [48.851325, 2.40171], "name": "Les P\u00e8res Populaires", "prix_terasse": "-", "date": "2012-10-18", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.40171, 48.851325]}, "recordid": "e75e8a3fe6212a0e576beec82f0128dd394e56fa", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75007, "address": "188 rue de Grenelle", "prix_salle": "-", "geoloc": [48.857658, 2.305613], "name": "Cafe de grenelle", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.305613, 48.857658]}, "recordid": "72e274046d671e68bc7754808feacfc95b91b6ed", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75009, "address": "73 rue de la Victoire", "prix_salle": "-", "geoloc": [48.875207, 2.332944], "name": "Le relais de la victoire", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.332944, 48.875207]}, "recordid": "01c21abdf35484f9ad184782979ecd078de84523", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75016, "address": "Route de la Muette \u00e0 Neuilly\nClub hippique du Jardin d\u2019Acclimatation", "prix_salle": "1", "geoloc": null, "name": "La chaumi\u00e8re gourmande", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": null, "geometry": {}, "recordid": "438ec18d35793d12eb6a137373c3fe4f3aa38a69", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "216, rue Marcadet", "prix_salle": "-", "geoloc": [48.891882, 2.33365], "name": "Le Brio", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.33365, 48.891882]}, "recordid": "49e3584ce3d6a6a236b4a0db688865bdd3483fec", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75017, "address": "22 rue des Dames", "prix_salle": "-", "geoloc": [48.884753, 2.324648], "name": "Caves populaires", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.324648, 48.884753]}, "recordid": "af81e5eca2b84ea706ac2d379edf65b0fb2f879a", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75014, "address": "12 avenue Jean Moulin", "prix_salle": "-", "geoloc": [48.827428, 2.325652], "name": "Caprice caf\u00e9", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.325652, 48.827428]}, "recordid": "88e07bdb723b49cd27c90403b5930bca1c93b458", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75013, "address": "7 rue Clisson", "prix_salle": "-", "geoloc": [48.832964, 2.369266], "name": "Tamm Bara", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.369266, 48.832964]}, "recordid": "baf59c98acafbe48ed9e91b64377da216a20cbcc", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75009, "address": "1 rue de Montholon", "prix_salle": "-", "geoloc": [48.876577, 2.348414], "name": "L'anjou", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.348414, 48.876577]}, "recordid": "e6f5949dca40548aad296208c61c498f639f648c", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75007, "address": "2 rue Robert Esnault Pelterie", "prix_salle": "-", "geoloc": [48.862599, 2.315086], "name": "Caf\u00e9 dans l'aerogare Air France Invalides", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.315086, 48.862599]}, "recordid": "6d175eb48c577fafdfc99df0ab55da468cf17164", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "10 rue d\"Ulm", "prix_salle": "-", "geoloc": [48.844854, 2.345413], "name": "Waikiki", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.345413, 48.844854]}, "recordid": "fb1e2bc2ae55d3d47da682c71093a12fa64fbd45", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75010, "address": "36 rue Beaurepaire", "prix_salle": "-", "geoloc": [48.871576, 2.364499], "name": "Chez Prune", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.364499, 48.871576]}, "recordid": "19399ede5b619761877822185bbb4c98b565974c", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75014, "address": "21 rue Boulard", "prix_salle": "-", "geoloc": [48.833863, 2.329046], "name": "Au Vin Des Rues", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.329046, 48.833863]}, "recordid": "87263873b6f8346b5777844be0122a307f29fcab", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "14 rue d'alleray", "prix_salle": "-", "geoloc": [48.838137, 2.301166], "name": "bistrot les timbr\u00e9s", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.301166, 48.838137]}, "recordid": "74b6d7113e5b14eb8e890663f061208bf4ff6728", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75008, "address": "9 rue de Miromesnil", "prix_salle": "-", "geoloc": [48.871799, 2.315985], "name": "Caf\u00e9 beauveau", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.315985, 48.871799]}, "recordid": "2759f5cdda4bcb88ca3ae2f7299b37b8e62596c8", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75001, "address": "9 rue des petits champs", "prix_salle": "-", "geoloc": [48.866259, 2.338739], "name": "Caf\u00e9 Pistache", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.338739, 48.866259]}, "recordid": "6d2675cdc912118d0376229be8e436feca9c8af7", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75020, "address": "13 Rue Jean-Baptiste Dumay", "prix_salle": "-", "geoloc": [48.874605, 2.387738], "name": "La Cagnotte", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.387738, 48.874605]}, "recordid": "f7085c754c0c97e418d7e5213753f74bd396fc27", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "172 rue de vaugirard", "prix_salle": "-", "geoloc": [48.842462, 2.310919], "name": "le 1 cinq", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.310919, 48.842462]}, "recordid": "17e917723fc99d6e5bd77eb9633ac2e789a9a6d9", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "28 bis boulevard Diderot", "prix_salle": "-", "geoloc": [48.84591, 2.375543], "name": "Le Killy Jen", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.375543, 48.84591]}, "recordid": "93132bd8b3ae67dfcc8cf8c1166e312ac4acb9b9", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "106 rue Lecourbe", "prix_salle": "-", "geoloc": [48.842868, 2.303173], "name": "Les Artisans", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.303173, 48.842868]}, "recordid": "c37ef573b4cb2d1e61795d6a9ef11de433dc9a99", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75001, "address": "83 avenue de Wagram", "prix_salle": "-", "geoloc": [48.865684, 2.334416], "name": "Peperoni", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.334416, 48.865684]}, "recordid": "9461f859ca009ced25555fa6af1e6867dda9223e", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "380 rue de vaugirard", "prix_salle": "-", "geoloc": [48.833146, 2.288834], "name": "le lutece", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.288834, 48.833146]}, "recordid": "ddd13990c1408700085366bd4ba313acd69a44ea", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "16 rue Ganneron", "prix_salle": "-", "geoloc": [48.886431, 2.327429], "name": "Brasiloja", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.327429, 48.886431]}, "recordid": "d679bb1642534278f4c0203d67be0bafd5306d81", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75004, "address": "16 rue de Rivoli", "prix_salle": "-", "geoloc": [48.855711, 2.359491], "name": "Rivolux", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.359491, 48.855711]}, "recordid": "bdd2b008cc765c7fe195c037b830cd2628420a2f", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "21 Bis Boulevard Diderot", "prix_salle": "-", "geoloc": [48.845898, 2.372766], "name": "L'europ\u00e9en", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.372766, 48.845898]}, "recordid": "693c0da7d4db24781ed161c01a661c36074a94fa", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75003, "address": "39 rue Notre Dame de Nazareth", "prix_salle": "-", "geoloc": [48.867465, 2.357791], "name": "NoMa", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.357791, 48.867465]}, "recordid": "60d8b670810cc95eb0439dd0c238f8205ea8ef76", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75020, "address": "1 Rue des Envierges", "prix_salle": "-", "geoloc": [48.871595, 2.385858], "name": "O'Paris", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.385858, 48.871595]}, "recordid": "297c040284a05efe35c69bb621505e6acfdcdda4", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75010, "address": "16 avenue Richerand", "prix_salle": "-", "geoloc": [48.872402, 2.366532], "name": "Caf\u00e9 Clochette", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.366532, 48.872402]}, "recordid": "a561a941f538e8a1d321bf8d98576d06be037962", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "40 Boulevard Beaumarchais", "prix_salle": "-", "geoloc": [48.856584, 2.368574], "name": "La cantoche de Paname", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.368574, 48.856584]}, "recordid": "5ba2aaec9f1de9d01e65be95215cab13c693cdf3", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75020, "address": "148 Boulevard de Charonne", "prix_salle": "-", "geoloc": [48.856496, 2.394874], "name": "Le Saint Ren\u00e9", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.394874, 48.856496]}, "recordid": "4d60b350d04d4b1bf4bfd4dd6cc59687dc792c74", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "196 rue du faubourg saint-antoine", "prix_salle": "1", "geoloc": [48.850055, 2.383908], "name": "La Libert\u00e9", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.383908, 48.850055]}, "recordid": "ee94e76326f8dcbe3500afec69f1a21eb1215ad0", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "16 rue des Petits Champs", "prix_salle": "1", "geoloc": [48.866737, 2.33716], "name": "Chez Rutabaga", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.33716, 48.866737]}, "recordid": "a420ea4608440b8dc8e0267fe8cc513daa950551", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75017, "address": "2 rue Lemercier", "prix_salle": "1", "geoloc": [48.885367, 2.325325], "name": "Le BB (Bouchon des Batignolles)", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.325325, 48.885367]}, "recordid": "20986cbfe11018bd0aba8150a49db1c435f7642d", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75009, "address": "10 rue Rossini", "prix_salle": "1", "geoloc": [48.873175, 2.339193], "name": "La Brocante", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.339193, 48.873175]}, "recordid": "2e10601c35669394d43936a771b18408be0338ba", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75014, "address": "3 rue Ga\u00eet\u00e9", "prix_salle": "1", "geoloc": [48.840771, 2.324589], "name": "Le Plomb du cantal", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.324589, 48.840771]}, "recordid": "6fb510614e00b065bf16a5af8e2c0eaf561a5654", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75017, "address": "22 rue des Dames", "prix_salle": "1", "geoloc": [48.884753, 2.324648], "name": "Les caves populaires", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.324648, 48.884753]}, "recordid": "d650c509a0aa8ed7b9c9b88861263f31463bbd0e", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75020, "address": "108 rue de M\u00e9nilmontant", "prix_salle": "-", "geoloc": [48.869519, 2.39339], "name": "Chez Luna", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.39339, 48.869519]}, "recordid": "736f4d996f1f8b7c3a0ce2abfeebfcce2a4bab13", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75019, "address": "1 rue du Plateau", "prix_salle": "-", "geoloc": [48.877903, 2.385365], "name": "Le bar Fleuri", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.385365, 48.877903]}, "recordid": "be55720646093788ec161c6cadc5ad8059f4b90b", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75017, "address": "101 rue des dames", "prix_salle": "-", "geoloc": [48.882939, 2.31809], "name": "Trois pi\u00e8ces cuisine", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.31809, 48.882939]}, "recordid": "7bbbfb755020a2c25cce0067601994ce5ee4193f", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "61 avenue de la Motte Picquet", "prix_salle": "-", "geoloc": [48.849497, 2.298855], "name": "Le Zinc", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.298855, 48.849497]}, "recordid": "e7c35a94454518de6de5bbecbc015fc37f7aea14", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75010, "address": "136 rue du Faubourg poissonni\u00e8re", "prix_salle": "-", "geoloc": [48.880669, 2.349964], "name": "La cantine de Zo\u00e9", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349964, 48.880669]}, "recordid": "0edc473b3432a869b8ed66b6c4c989766b699947", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75006, "address": "6/8 rue Stanislas", "prix_salle": "-", "geoloc": [48.844057, 2.328402], "name": "Les Vendangeurs", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.328402, 48.844057]}, "recordid": "e9766ea36f6293bf670ed938bff02b975d012973", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75006, "address": "3 carrefour de l'Od\u00e9on", "prix_salle": "-", "geoloc": [48.852053, 2.338779], "name": "L'avant comptoir", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.338779, 48.852053]}, "recordid": "fe843d2f43dcaac9129f5b36dc367558dfd3b3e4", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "1 rue Paul albert", "prix_salle": "1", "geoloc": [48.886504, 2.34498], "name": "Botak cafe", "prix_terasse": "1", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.34498, 48.886504]}, "recordid": "9e19c375e612f5fb803ec6a27881858619207812", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75010, "address": "67 rue du Ch\u00e2teau d'eau", "prix_salle": "-", "geoloc": [48.872722, 2.354594], "name": "le chateau d'eau", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.354594, 48.872722]}, "recordid": "05bb6a26ec5bfbba25da2d19a5f0e83d69800f38", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "58 rue du Fbg Saint-Antoine", "prix_salle": "-", "geoloc": [48.85192, 2.373229], "name": "Bistrot Saint-Antoine", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.373229, 48.85192]}, "recordid": "daa3908ddf69d378fec5b4548494727e1121adc4", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75004, "address": "11/13 boulevard Beaumarchais", "prix_salle": "-", "geoloc": [48.854685, 2.368487], "name": "Chez Oscar", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.368487, 48.854685]}, "recordid": "c73be0483480c59e6ab6bc3a906c8d9dd474887f", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75008, "address": "63 rue de Ponthieu", "prix_salle": "-", "geoloc": [48.87226, 2.304441], "name": "Le Fronton", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.304441, 48.87226]}, "recordid": "85a8200d3e3aed7724d3207ed8b1ee5ec50c1f90", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "48 avenue de la Motte Picquet", "prix_salle": "-", "geoloc": [48.851, 2.300378], "name": "Le Piquet", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.300378, 48.851]}, "recordid": "460e2adc95fd172f753b1b6ed296c2711639d49d", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "104 rue Mouffetard", "prix_salle": "-", "geoloc": [48.841089, 2.349565], "name": "Le Tournebride", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349565, 48.841089]}, "recordid": "8a7a23ed68366f70ab939c877cbdce46f19d75c7", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75014, "address": "52 rue des plantes", "prix_salle": "-", "geoloc": [48.828704, 2.322074], "name": "maison du vin", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.322074, 48.828704]}, "recordid": "482507a8f0fe4960f94372b6fa12b16e7d4f2a93", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "11 Quai de la Tournelle", "prix_salle": "-", "geoloc": [48.849821, 2.355337], "name": "Caf\u00e9 rallye tournelles", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.355337, 48.849821]}, "recordid": "91d88e321a75b6a8c4dea816c399fda77c41f9d1", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75010, "address": "61 rue du ch\u00e2teau d'eau", "prix_salle": "-", "geoloc": [48.872498, 2.355136], "name": "Brasserie le Morvan", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.355136, 48.872498]}, "recordid": "6ec62058995948fc18d331f01a2d03acc0d9e0fa", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75019, "address": "6 rue M\u00e9lingue", "prix_salle": "1", "geoloc": [48.874879, 2.386064], "name": "Chez Miamophile", "prix_terasse": "1", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.386064, 48.874879]}, "recordid": "13924872737e4fc640a43da58937c3777c2ac753", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "18 rue de Crussol", "prix_salle": "-", "geoloc": [48.864269, 2.36858], "name": "Panem", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.36858, 48.864269]}, "recordid": "67bdf3a6989f80749a1ba33a17b1370de0a0e1cd", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75017, "address": "47 rue de Batignolles", "prix_salle": "-", "geoloc": [48.885662, 2.319591], "name": "Petits Freres des Pauvres", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.319591, 48.885662]}, "recordid": "e27fd00149514bbfad7dd7e8f9b0c677df2d3f25", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "198 rue de la Convention", "prix_salle": "-", "geoloc": [48.837212, 2.296046], "name": "Caf\u00e9 Dupont", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.296046, 48.837212]}, "recordid": "4d40e6d864dae81c152a05cb98e30933bde96aa1", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75008, "address": "28 rue de Ponthieu", "prix_salle": "1", "geoloc": [48.871002, 2.30879], "name": "L'Angle", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.30879, 48.871002]}, "recordid": "c40bd2d1f98b415e539c27cf68518d060ebab51e", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "19-23 rue L\u00e9on", "prix_salle": "1", "geoloc": [48.888023, 2.353467], "name": "Institut des Cultures d'Islam", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.353467, 48.888023]}, "recordid": "68d6d37b846e39bd8554e1f8f75974b486b0f27b", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75018, "address": "19 rue Pajol", "prix_salle": "1", "geoloc": [48.886044, 2.360781], "name": "Canopy Caf\u00e9 associatif", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.360781, 48.886044]}, "recordid": "ff73bafb514bb68eb925c81aee43c3a58ac3c70d", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75002, "address": "place de l'opera", "prix_salle": "-", "geoloc": [48.870287, 2.332491], "name": "L'Entracte", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.332491, 48.870287]}, "recordid": "0039cd8bceb5e281677a158f832a660789088071", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75003, "address": "15 rue du Parc Royal", "prix_salle": "-", "geoloc": [48.858709, 2.362701], "name": "Le S\u00e9vign\u00e9", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.362701, 48.858709]}, "recordid": "adcc8b4f78f05ba7b24b0593e1516dfb7b415f91", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75005, "address": "35 rue Claude Bernard", "prix_salle": "-", "geoloc": [48.839687, 2.347254], "name": "Le Caf\u00e9 d'avant", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.347254, 48.839687]}, "recordid": "b904fc48763938eee2169ba25aad2ffcc0dd6a9f", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75006, "address": "53 rue Notre-Dame des Champs", "prix_salle": "-", "geoloc": [48.844244, 2.330407], "name": "Le Lucernaire", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.330407, 48.844244]}, "recordid": "cc72af04314fd40e16ff611c799d378515043508", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75009, "address": "12 rue Blanche", "prix_salle": "-", "geoloc": [48.877599, 2.332111], "name": "Le Brigadier", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.332111, 48.877599]}, "recordid": "978d4bc68c9ebf81029d3e77274d2107777b8a75", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75013, "address": "26 rue du Docteur Magnan", "prix_salle": "-", "geoloc": [48.826494, 2.359987], "name": "L'\u00e2ge d'or", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.359987, 48.826494]}, "recordid": "40bffbdc0c9ed1cbce820fed875d7c21d8964640", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75017, "address": "Place de Clichy", "prix_salle": "-", "geoloc": [48.883717, 2.326861], "name": "Bagels & Coffee Corner", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.326861, 48.883717]}, "recordid": "262facde9b8c4568c9ba7fbce8f069ff8c76948d", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "10 boulevard Victor", "prix_salle": "-", "geoloc": [48.835843, 2.278501], "name": "Caf\u00e9 Victor", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.278501, 48.835843]}, "recordid": "e5817ec44ac5a7ea2e4a34b6a2e13d535156642b", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "54, avenue Daumesnil", "prix_salle": "-", "geoloc": [48.845337, 2.379024], "name": "L'empreinte", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.379024, 48.845337]}, "recordid": "b96ddd35cbbf5d93aaff79487afdf083b5ff0817", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75011, "address": "93, rue de la Roquette", "prix_salle": "-", "geoloc": [48.857312, 2.379055], "name": "L'horizon", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.379055, 48.857312]}, "recordid": "84c6b7335e7f82ac942c4f398723ec99076f148d", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "34 bis rue de Wattignies", "prix_salle": "-", "geoloc": [48.835878, 2.395723], "name": "Au pays de Vannes", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.395723, 48.835878]}, "recordid": "a17869dbb9d0d5b1e5ed7bb288053900b04ee944", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75007, "address": "36 rue de Varenne", "prix_salle": "-", "geoloc": [48.85413, 2.323539], "name": "Caf\u00e9 Varenne", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.323539, 48.85413]}, "recordid": "a26ec0d5fca47b8de77d862ad8a99b75bb520a09", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75004, "address": "125 Rue Saint-Antoine", "prix_salle": "-", "geoloc": [48.855161, 2.360218], "name": "l'El\u00e9phant du nil", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.360218, 48.855161]}, "recordid": "7b7ceefd1f9ed85041265c9577e0dc8bee01d45a", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "354 bis rue Vaugirard", "prix_salle": "-", "geoloc": [48.8357, 2.292961], "name": "Le Comptoir", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.292961, 48.8357]}, "recordid": "59d8fa304e535f4eb41f9746028034c9b30cbde4", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75015, "address": "358 rue de Vaugirard", "prix_salle": "-", "geoloc": [48.835451, 2.292515], "name": "Le Parc Vaugirard", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.292515, 48.835451]}, "recordid": "19f655206a8446959c8e796c2b3cb9001890f985", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75014, "address": "58 rue Daguerre", "prix_salle": "-", "geoloc": [48.834972, 2.327007], "name": "le Zango", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.327007, 48.834972]}, "recordid": "e1b54109015316a822747f788128f997a3478050", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75020, "address": "3 rue de Lagny", "prix_salle": "-", "geoloc": [48.848887, 2.399972], "name": "Melting Pot", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.399972, 48.848887]}, "recordid": "fd0de2cbf73e0a728cd73e4e2a9a4a9c646f76f2", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75017, "address": "174 avenue de Clichy", "prix_salle": "-", "geoloc": [48.892366, 2.317359], "name": "Pari's Caf\u00e9", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.317359, 48.892366]}, "recordid": "831446ae203f89de26d3300e625c20717e82d40a", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75012, "address": "157 rue Bercy 75012 Paris", "prix_salle": "-", "geoloc": [48.842146, 2.375986], "name": "L'entrep\u00f4t", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.375986, 48.842146]}, "recordid": "d8746118429eb118f38ecbee904636d9b33fa8ba", "city": "Paris", "country": "France"},
|
||||
{"zipcode": 75003, "address": "Place de la R\u00e9publique", "prix_salle": "-", "geoloc": [48.867092, 2.363288], "name": "Le caf\u00e9 Monde et M\u00e9dias", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.363288, 48.867092]}, "recordid": "af04c90f25e25daf7f5cbbab1bc740bac26541d4", "city": "Paris", "country": "France"}]
|
||||
181
bonobo/examples/datasets/coffeeshops.ldjson
Normal file
181
bonobo/examples/datasets/coffeeshops.ldjson
Normal file
@ -0,0 +1,181 @@
|
||||
{"zipcode": 75015, "address": "344Vrue Vaugirard", "prix_salle": "-", "geoloc": [48.839512, 2.303007], "name": "Coffee Chope", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.303007, 48.839512]}, "recordid": "3c276428d45ad68ccdf6875e4ddcfe95d0c0d4cf", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75010, "address": "5, rue d'Alsace", "prix_salle": "-", "geoloc": [48.876737, 2.357601], "name": "Ext\u00e9rieur Quai", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.357601, 48.876737]}, "recordid": "97ad81cd1127a8566085ad796eeb44a06bec7514", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75004, "address": "6 Bd henri IV", "prix_salle": "-", "geoloc": [48.850852, 2.362029], "name": "Le Sully", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.362029, 48.850852]}, "recordid": "aa4294c1b8d660a23db0dc81321e509bae1dae68", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "53 rue du ruisseau", "prix_salle": "-", "geoloc": [48.893517, 2.340271], "name": "O q de poule", "prix_terasse": "1", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.340271, 48.893517]}, "recordid": "a81362dbed35247555fb105bd83ff2906904a66e", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "1 Passage du Grand Cerf", "prix_salle": "-", "geoloc": [48.864655, 2.350089], "name": "Le Pas Sage", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.350089, 48.864655]}, "recordid": "7ced86acbd5ccfba229bcc07d70d0d117aee16a5", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "112 Rue Championnet", "prix_salle": "-", "geoloc": [48.895825, 2.339712], "name": "La Renaissance", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.339712, 48.895825]}, "recordid": "5582c8572bd7637bf305b74c1c0bdb74a8e4247f", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "Rue de la Fontaine au Roi", "prix_salle": "-", "geoloc": [48.868581, 2.373015], "name": "La Caravane", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.373015, 48.868581]}, "recordid": "50bb0fa06e562a242f115ddbdae2ed9c7df93d57", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75009, "address": "51 Rue Victoire", "prix_salle": "1", "geoloc": [48.875155, 2.335536], "name": "Le chantereine", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.335536, 48.875155]}, "recordid": "eb8a62feeedaf7ed8b8c912305270ee857068689", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "11 rue Feutrier", "prix_salle": "1", "geoloc": [48.886536, 2.346525], "name": "Le M\u00fcller", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.346525, 48.886536]}, "recordid": "62c552f167f671f88569c1f2d6a44098fb514c51", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "21 rue Copreaux", "prix_salle": "1", "geoloc": [48.841494, 2.307117], "name": "Le drapeau de la fidelit\u00e9", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.307117, 48.841494]}, "recordid": "5120ea0b9d7387766072b90655166486928e25c8", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "125 rue Blomet", "prix_salle": "1", "geoloc": [48.839743, 2.296898], "name": "Le caf\u00e9 des amis", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.296898, 48.839743]}, "recordid": "865f62415adc5c34e3ca38a1748b7a324dfba209", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75004, "address": "10 rue Saint Martin", "prix_salle": "-", "geoloc": [48.857728, 2.349641], "name": "Le Caf\u00e9 Livres", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349641, 48.857728]}, "recordid": "7ef54a78802d49cafd2701458df2b0d0530d123b", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75007, "address": "46 avenue Bosquet", "prix_salle": "-", "geoloc": [48.856003, 2.30457], "name": "Le Bosquet", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.30457, 48.856003]}, "recordid": "d701a759e08a71f4bbb01f29473274b0152135d0", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "12 rue Armand Carrel", "prix_salle": "-", "geoloc": [48.889426, 2.332954], "name": "Le Chaumontois", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.332954, 48.889426]}, "recordid": "e12ff00a644c91ad910ddc63a770c190be93a393", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75013, "address": "34 avenue Pierre Mend\u00e8s-France", "prix_salle": "-", "geoloc": [48.838521, 2.370478], "name": "Le Kleemend's", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.370478, 48.838521]}, "recordid": "0f6cd1ee7751b00c9574efcfdcf66fa0e857d251", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "202 rue du faubourg st antoine", "prix_salle": "-", "geoloc": [48.849861, 2.385342], "name": "Caf\u00e9 Pierre", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.385342, 48.849861]}, "recordid": "f9de9d0fb5e92f047a6f1986a31f9dd4d38bcb36", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75008, "address": "61 rue de Ponthieu", "prix_salle": "-", "geoloc": [48.872202, 2.304624], "name": "Les Arcades", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.304624, 48.872202]}, "recordid": "67eaf58afc856077c0680601e453e75c0922c9c0", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75007, "address": "31 rue Saint-Dominique", "prix_salle": "-", "geoloc": [48.859031, 2.320315], "name": "Le Square", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.320315, 48.859031]}, "recordid": "678558317bc9ad46652e5b1643e70c2142a76e7e", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "75, avenue Ledru-Rollin", "prix_salle": "-", "geoloc": [48.850092, 2.37463], "name": "Assaporare Dix sur Dix", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.37463, 48.850092]}, "recordid": "667474321887d08a3cc636adf043ad354b65fa61", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "129 boulevard sebastopol", "prix_salle": "-", "geoloc": [48.86805, 2.353313], "name": "Au cerceau d'or", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.353313, 48.86805]}, "recordid": "c9ef52ba2fabe0286700329f18bbbbea9a10b474", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "21 ter boulevard Diderot", "prix_salle": "-", "geoloc": [48.845927, 2.373051], "name": "Aux cadrans", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.373051, 48.845927]}, "recordid": "ed5f98686856bf4ddd2b381b43ad229246741a90", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75016, "address": "17 rue Jean de la Fontaine", "prix_salle": "-", "geoloc": [48.851662, 2.273883], "name": "Caf\u00e9 antoine", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.273883, 48.851662]}, "recordid": "ab6d1e054e2e6ae7d6150013173f55e83c05ca23", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75008, "address": "rue de Lisbonne", "prix_salle": "-", "geoloc": [48.877642, 2.312823], "name": "Caf\u00e9 de la Mairie (du VIII)", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.312823, 48.877642]}, "recordid": "7de8a79b026ac63f453556612505b5bcd9229036", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "5 rue Claude Bernard", "prix_salle": "-", "geoloc": [48.838633, 2.349916], "name": "Caf\u00e9 Lea", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349916, 48.838633]}, "recordid": "fecd8900cf83027f74ceced9fc4ad80ac73b63a7", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "11 boulevard Saint-Germain", "prix_salle": "-", "geoloc": [48.849293, 2.354486], "name": "Cardinal Saint-Germain", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.354486, 48.849293]}, "recordid": "e4a078c30c98082896787f4e4b41a07554392529", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "52 rue Notre-Dame des Victoires", "prix_salle": "-", "geoloc": [48.869771, 2.342501], "name": "D\u00e9d\u00e9 la frite", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.342501, 48.869771]}, "recordid": "ccb2ba2f98043e8eefd5fda829dee1ea7f1d2c7a", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "36 rue du hameau", "prix_salle": "-", "geoloc": [48.834051, 2.287345], "name": "La Bauloise", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.287345, 48.834051]}, "recordid": "c9fe10abd15ede7ccaeb55c309898d30d7b19d0e", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75019, "address": "71 quai de Seine", "prix_salle": "-", "geoloc": [48.888165, 2.377387], "name": "Le Bellerive", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.377387, 48.888165]}, "recordid": "4e0b5c2d33d7c25fc54c51171f3d37e509959fc0", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75001, "address": "42 rue coquill\u00e8re", "prix_salle": "-", "geoloc": [48.864543, 2.340997], "name": "Le bistrot de Ma\u00eblle et Augustin", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.340997, 48.864543]}, "recordid": "52acab12469af291984e9a70962e08c72b058e10", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75009, "address": "14 rue Rougemont", "prix_salle": "-", "geoloc": [48.872103, 2.346161], "name": "Le Dellac", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.346161, 48.872103]}, "recordid": "4d1d627ecea2ffa279bb862f8ba495d95ca75350", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75004, "address": "1 rue Pecquay", "prix_salle": "-", "geoloc": [48.859645, 2.355598], "name": "Le Felteu", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.355598, 48.859645]}, "recordid": "2c1fa55460af282266d86fd003af4f929fdf4e7d", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75001, "address": "2 bis quai de la m\u00e9gisserie", "prix_salle": "-", "geoloc": [48.85763, 2.346101], "name": "Le Reynou", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.346101, 48.85763]}, "recordid": "d4ddd30ab3e721a317fc7ea89d5b9001255ce9f4", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "23 rue des abbesses", "prix_salle": "-", "geoloc": [48.884646, 2.337734], "name": "Le Saint Jean", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.337734, 48.884646]}, "recordid": "51b47cf167b7f32eeebb108330956694d75d4268", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "65 boulevard Pasteur", "prix_salle": "-", "geoloc": [48.841007, 2.31466], "name": "les montparnos", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.31466, 48.841007]}, "recordid": "2aaca891ffd0694c657a43889516ab72afdfba07", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75006, "address": "16 rue DE MEZIERES", "prix_salle": "-", "geoloc": [48.850323, 2.33039], "name": "L'antre d'eux", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.33039, 48.850323]}, "recordid": "4ff4337934c66f61e00d1d9551f7cdddba03e544", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "58 rue de Montorgueil", "prix_salle": "-", "geoloc": [48.864957, 2.346938], "name": "Drole d'endroit pour une rencontre", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.346938, 48.864957]}, "recordid": "3451657f880abe75d0c7e386fc698405556c53e8", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "104 rue caulaincourt", "prix_salle": "-", "geoloc": [48.889565, 2.339735], "name": "Le pari's caf\u00e9", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.339735, 48.889565]}, "recordid": "e8c34a537b673fcb26c76e02deca4f5a728929dc", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "60 rue saint-sabin", "prix_salle": "-", "geoloc": [48.859115, 2.368871], "name": "Le Poulailler", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.368871, 48.859115]}, "recordid": "325ea74ba83f716dde87c08cffd36f7df7722a49", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "33 Cour Saint Emilion", "prix_salle": "-", "geoloc": [48.833595, 2.38604], "name": "Chai 33", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.38604, 48.833595]}, "recordid": "528de8d5d8780bee83145637e315483d48f5ae3c", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "99 rue Jean-Pierre Timbaud", "prix_salle": "-", "geoloc": [48.868741, 2.379969], "name": "L'Assassin", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.379969, 48.868741]}, "recordid": "fac0483890ff8bdaeb3feddbdb032c5112f24678", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75020, "address": "1 rue d'Avron", "prix_salle": "-", "geoloc": [48.851463, 2.398691], "name": "l'Usine", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.398691, 48.851463]}, "recordid": "fee1e3eb103bbc98e19e45d34365da0f27166541", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "52 rue Liebniz", "prix_salle": "-", "geoloc": [48.896305, 2.332898], "name": "La Bricole", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.332898, 48.896305]}, "recordid": "4744e866c244c59eec43b3fe159542d2ef433065", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "place maubert", "prix_salle": "-", "geoloc": [48.850311, 2.34885], "name": "le ronsard", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.34885, 48.850311]}, "recordid": "49a390322b45246bc2c1e50fcd46815ad271bca0", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75003, "address": "82 rue des archives", "prix_salle": "-", "geoloc": [48.863038, 2.3604], "name": "Face Bar", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.3604, 48.863038]}, "recordid": "d96e16ebf2460bb2f6c34198918a071233725cbc", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75010, "address": "49 rue bichat", "prix_salle": "-", "geoloc": [48.872746, 2.366392], "name": "American Kitchen", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.366392, 48.872746]}, "recordid": "6b9395475cbbbbbacbaaeb070f71d31c2d183dc4", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75010, "address": "55 bis quai de valmy", "prix_salle": "-", "geoloc": [48.870598, 2.365413], "name": "La Marine", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.365413, 48.870598]}, "recordid": "d4d2f92d27f38de59e57744f434781e61283551c", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75017, "address": "21 avenue Brochant", "prix_salle": "-", "geoloc": [48.889101, 2.318001], "name": "Le Bloc", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.318001, 48.889101]}, "recordid": "e425882ee969d1e8bffe7234336ae40da88c8439", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75020, "address": "229 avenue Gambetta", "prix_salle": "-", "geoloc": [48.874697, 2.405421], "name": "La Recoleta au Manoir", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.405421, 48.874697]}, "recordid": "02de82cffb2918beafb740f4e924029d470b07a1", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "80 Rue Saint-Charles", "prix_salle": "-", "geoloc": [48.847344, 2.286078], "name": "Le Pareloup", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.286078, 48.847344]}, "recordid": "0227ca95f76bb6097ae0a0e6f455af2624d49ae3", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75014, "address": "3 rue de la Gait\u00e9", "prix_salle": "-", "geoloc": [48.840771, 2.324589], "name": "La Brasserie Gait\u00e9", "prix_terasse": "1", "date": "2013-08-22", "prix_comptoir": null, "geometry": {"type": "Point", "coordinates": [2.324589, 48.840771]}, "recordid": "e7c4cba08749c892a73db2715d06623d9e0c2f67", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75009, "address": "46 rue Victoire", "prix_salle": "-", "geoloc": [48.875232, 2.336036], "name": "Caf\u00e9 Zen", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.336036, 48.875232]}, "recordid": "5e9a6172f8b64cd098a5f2cf9b1d42567fe0a894", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75008, "address": "27 rue de Penthi\u00e8vre", "prix_salle": "1", "geoloc": [48.872677, 2.315276], "name": "O'Breizh", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.315276, 48.872677]}, "recordid": "ead3108add27ef41bb92517aca834f7d7f632816", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "23 rue saint augustin", "prix_salle": "1", "geoloc": [48.868838, 2.33605], "name": "Le Petit Choiseul", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.33605, 48.868838]}, "recordid": "de601277ca00567b62fa4f5277e4a17679faa753", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "7 rue Ep\u00e9e de Bois", "prix_salle": "1", "geoloc": [48.841526, 2.351012], "name": "Invitez vous chez nous", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.351012, 48.841526]}, "recordid": "1d2373bdec8a07306298e3ee54894ac295ee1d55", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "142 Rue Saint-Denis 75002 Paris", "prix_salle": "1", "geoloc": [48.86525, 2.350507], "name": "La Cordonnerie", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.350507, 48.86525]}, "recordid": "5c9bf60617a99ad75445b454f98e75e1a104021d", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "3, rue Baudelique", "prix_salle": "1", "geoloc": [48.892244, 2.346973], "name": "Le Supercoin", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.346973, 48.892244]}, "recordid": "68a4ee10f1fc4d2a659501e811d148420fa80e95", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "86 bis rue Riquet", "prix_salle": "1", "geoloc": [48.890043, 2.362241], "name": "Populettes", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.362241, 48.890043]}, "recordid": "8cc55d58d72621a7e91cf6b456731d2cb2863afc", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "49 rue des Cloys", "prix_salle": "1", "geoloc": [48.893017, 2.337776], "name": "Au bon coin", "prix_terasse": "1", "date": "2012-10-18", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.337776, 48.893017]}, "recordid": "0408f272c08c52e3cae035ffdeb8928698787ea9", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75013, "address": "69 rue Broca", "prix_salle": "1", "geoloc": [48.836919, 2.347003], "name": "Le Couvent", "prix_terasse": "-", "date": "2012-10-18", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.347003, 48.836919]}, "recordid": "729b2f228d3fd2db6f78bb624d451f59555e4a04", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "111 rue mouffetard", "prix_salle": "1", "geoloc": [48.840624, 2.349766], "name": "La Br\u00fblerie des Ternes", "prix_terasse": "-", "date": "2012-10-18", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349766, 48.840624]}, "recordid": "233dd2a17620cd5eae70fef11cc627748e3313d5", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75014, "address": "59 Boulevard Saint-Jacques", "prix_salle": "-", "geoloc": [48.832825, 2.336116], "name": "L'\u00c9cir", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.336116, 48.832825]}, "recordid": "4a44324a5a806801fd3e05af89cad7c0f1e69d1e", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "126, rue du Faubourg Saint Antoine", "prix_salle": "-", "geoloc": [48.850696, 2.378417], "name": "Le Chat bossu", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.378417, 48.850696]}, "recordid": "d1d02463f7c90d38cccffd898e15f51e65910baf", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75014, "address": "58 boulvevard Saint Jacques", "prix_salle": "-", "geoloc": [48.834157, 2.33381], "name": "Denfert caf\u00e9", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.33381, 48.834157]}, "recordid": "f78f406e5ccb95cb902ce618ed54eba1d4776a3c", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "95 rue Montmartre", "prix_salle": "-", "geoloc": [48.867948, 2.343582], "name": "Le Caf\u00e9 frapp\u00e9", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.343582, 48.867948]}, "recordid": "4dd2a924a7b2c5f061cecaba2f272548a8c83c6c", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75003, "address": "78 rue vieille du temple", "prix_salle": "-", "geoloc": [48.859772, 2.360558], "name": "La Perle", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.360558, 48.859772]}, "recordid": "476c54e643613ec36a9b1c533f32122fd873f3c3", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "1 rue Thouin", "prix_salle": "-", "geoloc": [48.845047, 2.349583], "name": "Le Descartes", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349583, 48.845047]}, "recordid": "e9cb0b0d6b6b512e9ecab889267ba342a4f0ea93", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75014, "address": "55 rue de la tombe Issoire", "prix_salle": "-", "geoloc": [48.830151, 2.334213], "name": "Le petit club", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.334213, 48.830151]}, "recordid": "c6ca1166fa7fd7050f5d1c626a1e17050116c91e", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "90 avenue Parmentier", "prix_salle": "-", "geoloc": [48.865707, 2.374382], "name": "Le Plein soleil", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.374382, 48.865707]}, "recordid": "95cf5fb735bd19826db70a3af4fe72fce647d4e5", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75008, "address": "146, boulevard Haussmann", "prix_salle": "-", "geoloc": [48.875322, 2.312329], "name": "Le Relais Haussmann", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.312329, 48.875322]}, "recordid": "6c5f68f47c916638342b77bd5edfc30bd7051303", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75007, "address": "88 rue Saint-Dominique", "prix_salle": "-", "geoloc": [48.859559, 2.30643], "name": "Le Malar", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.30643, 48.859559]}, "recordid": "c633cb01686aa5f3171bf59976dc9b7f23cbca54", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75020, "address": "47 rue Belgrand", "prix_salle": "-", "geoloc": [48.864628, 2.408038], "name": "Au panini de la place", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.408038, 48.864628]}, "recordid": "66dedd35fcbbbb24f328882d49098de7aa5f26ba", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75017, "address": "182 rue de Courcelles", "prix_salle": "-", "geoloc": [48.88435, 2.297978], "name": "Le Village", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.297978, 48.88435]}, "recordid": "1e07eaf8a93875906d0b18eb6a897c651943589a", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "41 rue de Charonne", "prix_salle": "-", "geoloc": [48.853381, 2.376706], "name": "Pause Caf\u00e9", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.376706, 48.853381]}, "recordid": "60d98c3236a70824df50e9aca83e7d7f13a310c5", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "14 rue Jean Mac\u00e9", "prix_salle": "-", "geoloc": [48.853253, 2.383415], "name": "Le Pure caf\u00e9", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.383415, 48.853253]}, "recordid": "66707fb2e707d2145fc2eb078a1b980a45921616", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "307 fg saint Antoine", "prix_salle": "-", "geoloc": [48.848873, 2.392859], "name": "Extra old caf\u00e9", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.392859, 48.848873]}, "recordid": "039ec7dcb219cfc434547b06938ba497afeb83b4", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75010, "address": "44 rue Vinaigriers", "prix_salle": "-", "geoloc": [48.873227, 2.360787], "name": "Chez Fafa", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.360787, 48.873227]}, "recordid": "1572d199f186bf86d7753fe71ac23477a7a8bd2c", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "3 rue Faidherbe", "prix_salle": "-", "geoloc": [48.850836, 2.384069], "name": "En attendant l'or", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.384069, 48.850836]}, "recordid": "de5789bb4a4ffbd244cded8dc555639dbe7d2279", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "30 rue des Petits-Champs", "prix_salle": "-", "geoloc": [48.866993, 2.336006], "name": "Br\u00fblerie San Jos\u00e9", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.336006, 48.866993]}, "recordid": "b736e5fa17396ee56a642212ccd0ab29c7f2bef1", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75001, "address": "2 place Martin Nadaud", "prix_salle": "-", "geoloc": [48.856434, 2.342683], "name": "Caf\u00e9 Martin", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.342683, 48.856434]}, "recordid": "25fbf857029c54d57909c158e3039349b77344ed", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75001, "address": "14 rue Turbigo, Paris", "prix_salle": "-", "geoloc": [48.863675, 2.348701], "name": "Etienne", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.348701, 48.863675]}, "recordid": "0190edd7b0766c6d3e43093deb41abe9446c1b22", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "184 bd Voltaire", "prix_salle": "-", "geoloc": [48.854584, 2.385193], "name": "L'ing\u00e9nu", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.385193, 48.854584]}, "recordid": "7fcef7475ec632d5c61c9c36c1d52a402ad2e9e8", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "8 rue L'Olive", "prix_salle": "-", "geoloc": [48.890605, 2.361349], "name": "L'Olive", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.361349, 48.890605]}, "recordid": "35286273f281c8c5082b3d3bd17f6bbf207426f9", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "18 rue Favart", "prix_salle": "-", "geoloc": [48.871396, 2.338321], "name": "Le Biz", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.338321, 48.871396]}, "recordid": "cf2f6b9e283aaeeca2214cc1fe57b45e45668e25", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "1 rue Louis le Grand", "prix_salle": "-", "geoloc": [48.868109, 2.331785], "name": "Le Cap Bourbon", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.331785, 48.868109]}, "recordid": "339030e95e0846b41a8e2b91045456c4e4a50043", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "9 Place du General Beuret", "prix_salle": "-", "geoloc": [48.84167, 2.303053], "name": "Le General Beuret", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.303053, 48.84167]}, "recordid": "12b37036d28d28b32ebe81756ad15eb68372947f", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "95 avenue Emile Zola", "prix_salle": "-", "geoloc": [48.846814, 2.289311], "name": "Le Germinal", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.289311, 48.846814]}, "recordid": "4e03831a64e886a28a7232e54f2812c1ced23c5a", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75001, "address": "202 rue Saint-Honor\u00e9", "prix_salle": "-", "geoloc": [48.862655, 2.337607], "name": "Le Ragueneau", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.337607, 48.862655]}, "recordid": "e303131b2600d4c2287749a36bf7193d2fa60bd7", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "72 rue lamarck", "prix_salle": "-", "geoloc": [48.889982, 2.338933], "name": "Le refuge", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.338933, 48.889982]}, "recordid": "f64310461736a769c6854fdefb99b9f2e7b230a9", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75010, "address": "13 rue du Faubourg Saint Denis", "prix_salle": "-", "geoloc": [48.870294, 2.352821], "name": "Le sully", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.352821, 48.870294]}, "recordid": "166be8588ff16e50838fa6a164d1e580497b795d", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "60 rue des bergers", "prix_salle": "-", "geoloc": [48.842128, 2.280374], "name": "Le bal du pirate", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.280374, 48.842128]}, "recordid": "93ff6e35406a074a6ba2667d2b286abf91132f6a", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "95 rue claude decaen", "prix_salle": "-", "geoloc": [48.838769, 2.39609], "name": "zic zinc", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.39609, 48.838769]}, "recordid": "8fdc739d64ff1f01973235301e3ec86791016759", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "35 rue de l'orillon", "prix_salle": "-", "geoloc": [48.870247, 2.376306], "name": "l'orillon bar", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.376306, 48.870247]}, "recordid": "6e6e9695ef04c3fdbd4cfa19f0cfb0c0f93f4933", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75020, "address": "116 Rue de M\u00e9nilmontant", "prix_salle": "-", "geoloc": [48.869848, 2.394247], "name": "Le Zazabar", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.394247, 48.869848]}, "recordid": "22edfe92a72ce1bb0eea972508b5caa7af2db2df", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "22 rue Linn\u00e9", "prix_salle": "-", "geoloc": [48.845458, 2.354796], "name": "L'In\u00e9vitable", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.354796, 48.845458]}, "recordid": "6893cb08e99319091de9ba80305f22e0ce4cc08d", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75013, "address": "77 rue Dunois", "prix_salle": "-", "geoloc": [48.83336, 2.365782], "name": "Le Dunois", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.365782, 48.83336]}, "recordid": "c3be1246dbc4ca5734d5bbd569436ba655105248", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75001, "address": "202 rue Saint Honor\u00e9", "prix_salle": "-", "geoloc": [48.862655, 2.337607], "name": "Ragueneau", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.337607, 48.862655]}, "recordid": "e946d9e8f8c5a130f98eca945efadfd9eec40dcb", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75013, "address": "48 rue du Dessous des Berges", "prix_salle": "1", "geoloc": [48.826608, 2.374571], "name": "Le Caminito", "prix_terasse": null, "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.374571, 48.826608]}, "recordid": "5d9ad8bcfbbc20adaec2aa7dcdb71326868c7686", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75010, "address": "55bis quai de Valmy", "prix_salle": "1", "geoloc": [48.870598, 2.365413], "name": "Epicerie Musicale", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.365413, 48.870598]}, "recordid": "8ffea133d93c608d337bc0129f4f9f3d5cad8dae", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "Le petit Bretonneau - \u00e0 l'int\u00e9rieur de l'H\u00f4pital", "prix_salle": "1", "geoloc": null, "name": "Le petit Bretonneau", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {}, "recordid": "dd1ffdbd55c2dc651201302b8015258f7d35fd35", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "104 rue amelot", "prix_salle": "1", "geoloc": [48.862575, 2.367427], "name": "Le Centenaire", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.367427, 48.862575]}, "recordid": "e47d25752c2c8bcab5efb0e3a41920ec7a8a766a", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "13 Rue du Pot de Fer", "prix_salle": "1", "geoloc": [48.842833, 2.348314], "name": "La Montagne Sans Genevi\u00e8ve", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": null, "geometry": {"type": "Point", "coordinates": [2.348314, 48.842833]}, "recordid": "314d170601f81e3a3f26d8801f0fbee39981c788", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75020, "address": "46 rue de Buzenval", "prix_salle": "1", "geoloc": [48.851325, 2.40171], "name": "Les P\u00e8res Populaires", "prix_terasse": "-", "date": "2012-10-18", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.40171, 48.851325]}, "recordid": "e75e8a3fe6212a0e576beec82f0128dd394e56fa", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75007, "address": "188 rue de Grenelle", "prix_salle": "-", "geoloc": [48.857658, 2.305613], "name": "Cafe de grenelle", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.305613, 48.857658]}, "recordid": "72e274046d671e68bc7754808feacfc95b91b6ed", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75009, "address": "73 rue de la Victoire", "prix_salle": "-", "geoloc": [48.875207, 2.332944], "name": "Le relais de la victoire", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.332944, 48.875207]}, "recordid": "01c21abdf35484f9ad184782979ecd078de84523", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75016, "address": "Route de la Muette \u00e0 Neuilly\nClub hippique du Jardin d\u2019Acclimatation", "prix_salle": "1", "geoloc": null, "name": "La chaumi\u00e8re gourmande", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": null, "geometry": {}, "recordid": "438ec18d35793d12eb6a137373c3fe4f3aa38a69", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "216, rue Marcadet", "prix_salle": "-", "geoloc": [48.891882, 2.33365], "name": "Le Brio", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.33365, 48.891882]}, "recordid": "49e3584ce3d6a6a236b4a0db688865bdd3483fec", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75017, "address": "22 rue des Dames", "prix_salle": "-", "geoloc": [48.884753, 2.324648], "name": "Caves populaires", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.324648, 48.884753]}, "recordid": "af81e5eca2b84ea706ac2d379edf65b0fb2f879a", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75014, "address": "12 avenue Jean Moulin", "prix_salle": "-", "geoloc": [48.827428, 2.325652], "name": "Caprice caf\u00e9", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.325652, 48.827428]}, "recordid": "88e07bdb723b49cd27c90403b5930bca1c93b458", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75013, "address": "7 rue Clisson", "prix_salle": "-", "geoloc": [48.832964, 2.369266], "name": "Tamm Bara", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.369266, 48.832964]}, "recordid": "baf59c98acafbe48ed9e91b64377da216a20cbcc", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75009, "address": "1 rue de Montholon", "prix_salle": "-", "geoloc": [48.876577, 2.348414], "name": "L'anjou", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.348414, 48.876577]}, "recordid": "e6f5949dca40548aad296208c61c498f639f648c", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75007, "address": "2 rue Robert Esnault Pelterie", "prix_salle": "-", "geoloc": [48.862599, 2.315086], "name": "Caf\u00e9 dans l'aerogare Air France Invalides", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.315086, 48.862599]}, "recordid": "6d175eb48c577fafdfc99df0ab55da468cf17164", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "10 rue d\"Ulm", "prix_salle": "-", "geoloc": [48.844854, 2.345413], "name": "Waikiki", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.345413, 48.844854]}, "recordid": "fb1e2bc2ae55d3d47da682c71093a12fa64fbd45", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75010, "address": "36 rue Beaurepaire", "prix_salle": "-", "geoloc": [48.871576, 2.364499], "name": "Chez Prune", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.364499, 48.871576]}, "recordid": "19399ede5b619761877822185bbb4c98b565974c", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75014, "address": "21 rue Boulard", "prix_salle": "-", "geoloc": [48.833863, 2.329046], "name": "Au Vin Des Rues", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.329046, 48.833863]}, "recordid": "87263873b6f8346b5777844be0122a307f29fcab", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "14 rue d'alleray", "prix_salle": "-", "geoloc": [48.838137, 2.301166], "name": "bistrot les timbr\u00e9s", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.301166, 48.838137]}, "recordid": "74b6d7113e5b14eb8e890663f061208bf4ff6728", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75008, "address": "9 rue de Miromesnil", "prix_salle": "-", "geoloc": [48.871799, 2.315985], "name": "Caf\u00e9 beauveau", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.315985, 48.871799]}, "recordid": "2759f5cdda4bcb88ca3ae2f7299b37b8e62596c8", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75001, "address": "9 rue des petits champs", "prix_salle": "-", "geoloc": [48.866259, 2.338739], "name": "Caf\u00e9 Pistache", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.338739, 48.866259]}, "recordid": "6d2675cdc912118d0376229be8e436feca9c8af7", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75020, "address": "13 Rue Jean-Baptiste Dumay", "prix_salle": "-", "geoloc": [48.874605, 2.387738], "name": "La Cagnotte", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.387738, 48.874605]}, "recordid": "f7085c754c0c97e418d7e5213753f74bd396fc27", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "172 rue de vaugirard", "prix_salle": "-", "geoloc": [48.842462, 2.310919], "name": "le 1 cinq", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.310919, 48.842462]}, "recordid": "17e917723fc99d6e5bd77eb9633ac2e789a9a6d9", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "28 bis boulevard Diderot", "prix_salle": "-", "geoloc": [48.84591, 2.375543], "name": "Le Killy Jen", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.375543, 48.84591]}, "recordid": "93132bd8b3ae67dfcc8cf8c1166e312ac4acb9b9", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "106 rue Lecourbe", "prix_salle": "-", "geoloc": [48.842868, 2.303173], "name": "Les Artisans", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.303173, 48.842868]}, "recordid": "c37ef573b4cb2d1e61795d6a9ef11de433dc9a99", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75001, "address": "83 avenue de Wagram", "prix_salle": "-", "geoloc": [48.865684, 2.334416], "name": "Peperoni", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.334416, 48.865684]}, "recordid": "9461f859ca009ced25555fa6af1e6867dda9223e", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "380 rue de vaugirard", "prix_salle": "-", "geoloc": [48.833146, 2.288834], "name": "le lutece", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.288834, 48.833146]}, "recordid": "ddd13990c1408700085366bd4ba313acd69a44ea", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "16 rue Ganneron", "prix_salle": "-", "geoloc": [48.886431, 2.327429], "name": "Brasiloja", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.327429, 48.886431]}, "recordid": "d679bb1642534278f4c0203d67be0bafd5306d81", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75004, "address": "16 rue de Rivoli", "prix_salle": "-", "geoloc": [48.855711, 2.359491], "name": "Rivolux", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.359491, 48.855711]}, "recordid": "bdd2b008cc765c7fe195c037b830cd2628420a2f", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "21 Bis Boulevard Diderot", "prix_salle": "-", "geoloc": [48.845898, 2.372766], "name": "L'europ\u00e9en", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.372766, 48.845898]}, "recordid": "693c0da7d4db24781ed161c01a661c36074a94fa", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75003, "address": "39 rue Notre Dame de Nazareth", "prix_salle": "-", "geoloc": [48.867465, 2.357791], "name": "NoMa", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.357791, 48.867465]}, "recordid": "60d8b670810cc95eb0439dd0c238f8205ea8ef76", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75020, "address": "1 Rue des Envierges", "prix_salle": "-", "geoloc": [48.871595, 2.385858], "name": "O'Paris", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.385858, 48.871595]}, "recordid": "297c040284a05efe35c69bb621505e6acfdcdda4", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75010, "address": "16 avenue Richerand", "prix_salle": "-", "geoloc": [48.872402, 2.366532], "name": "Caf\u00e9 Clochette", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.366532, 48.872402]}, "recordid": "a561a941f538e8a1d321bf8d98576d06be037962", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "40 Boulevard Beaumarchais", "prix_salle": "-", "geoloc": [48.856584, 2.368574], "name": "La cantoche de Paname", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.368574, 48.856584]}, "recordid": "5ba2aaec9f1de9d01e65be95215cab13c693cdf3", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75020, "address": "148 Boulevard de Charonne", "prix_salle": "-", "geoloc": [48.856496, 2.394874], "name": "Le Saint Ren\u00e9", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.394874, 48.856496]}, "recordid": "4d60b350d04d4b1bf4bfd4dd6cc59687dc792c74", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "196 rue du faubourg saint-antoine", "prix_salle": "1", "geoloc": [48.850055, 2.383908], "name": "La Libert\u00e9", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.383908, 48.850055]}, "recordid": "ee94e76326f8dcbe3500afec69f1a21eb1215ad0", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "16 rue des Petits Champs", "prix_salle": "1", "geoloc": [48.866737, 2.33716], "name": "Chez Rutabaga", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.33716, 48.866737]}, "recordid": "a420ea4608440b8dc8e0267fe8cc513daa950551", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75017, "address": "2 rue Lemercier", "prix_salle": "1", "geoloc": [48.885367, 2.325325], "name": "Le BB (Bouchon des Batignolles)", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.325325, 48.885367]}, "recordid": "20986cbfe11018bd0aba8150a49db1c435f7642d", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75009, "address": "10 rue Rossini", "prix_salle": "1", "geoloc": [48.873175, 2.339193], "name": "La Brocante", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.339193, 48.873175]}, "recordid": "2e10601c35669394d43936a771b18408be0338ba", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75014, "address": "3 rue Ga\u00eet\u00e9", "prix_salle": "1", "geoloc": [48.840771, 2.324589], "name": "Le Plomb du cantal", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.324589, 48.840771]}, "recordid": "6fb510614e00b065bf16a5af8e2c0eaf561a5654", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75017, "address": "22 rue des Dames", "prix_salle": "1", "geoloc": [48.884753, 2.324648], "name": "Les caves populaires", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.324648, 48.884753]}, "recordid": "d650c509a0aa8ed7b9c9b88861263f31463bbd0e", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75020, "address": "108 rue de M\u00e9nilmontant", "prix_salle": "-", "geoloc": [48.869519, 2.39339], "name": "Chez Luna", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.39339, 48.869519]}, "recordid": "736f4d996f1f8b7c3a0ce2abfeebfcce2a4bab13", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75019, "address": "1 rue du Plateau", "prix_salle": "-", "geoloc": [48.877903, 2.385365], "name": "Le bar Fleuri", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.385365, 48.877903]}, "recordid": "be55720646093788ec161c6cadc5ad8059f4b90b", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75017, "address": "101 rue des dames", "prix_salle": "-", "geoloc": [48.882939, 2.31809], "name": "Trois pi\u00e8ces cuisine", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.31809, 48.882939]}, "recordid": "7bbbfb755020a2c25cce0067601994ce5ee4193f", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "61 avenue de la Motte Picquet", "prix_salle": "-", "geoloc": [48.849497, 2.298855], "name": "Le Zinc", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.298855, 48.849497]}, "recordid": "e7c35a94454518de6de5bbecbc015fc37f7aea14", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75010, "address": "136 rue du Faubourg poissonni\u00e8re", "prix_salle": "-", "geoloc": [48.880669, 2.349964], "name": "La cantine de Zo\u00e9", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349964, 48.880669]}, "recordid": "0edc473b3432a869b8ed66b6c4c989766b699947", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75006, "address": "6/8 rue Stanislas", "prix_salle": "-", "geoloc": [48.844057, 2.328402], "name": "Les Vendangeurs", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.328402, 48.844057]}, "recordid": "e9766ea36f6293bf670ed938bff02b975d012973", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75006, "address": "3 carrefour de l'Od\u00e9on", "prix_salle": "-", "geoloc": [48.852053, 2.338779], "name": "L'avant comptoir", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.338779, 48.852053]}, "recordid": "fe843d2f43dcaac9129f5b36dc367558dfd3b3e4", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "1 rue Paul albert", "prix_salle": "1", "geoloc": [48.886504, 2.34498], "name": "Botak cafe", "prix_terasse": "1", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.34498, 48.886504]}, "recordid": "9e19c375e612f5fb803ec6a27881858619207812", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75010, "address": "67 rue du Ch\u00e2teau d'eau", "prix_salle": "-", "geoloc": [48.872722, 2.354594], "name": "le chateau d'eau", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.354594, 48.872722]}, "recordid": "05bb6a26ec5bfbba25da2d19a5f0e83d69800f38", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "58 rue du Fbg Saint-Antoine", "prix_salle": "-", "geoloc": [48.85192, 2.373229], "name": "Bistrot Saint-Antoine", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.373229, 48.85192]}, "recordid": "daa3908ddf69d378fec5b4548494727e1121adc4", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75004, "address": "11/13 boulevard Beaumarchais", "prix_salle": "-", "geoloc": [48.854685, 2.368487], "name": "Chez Oscar", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.368487, 48.854685]}, "recordid": "c73be0483480c59e6ab6bc3a906c8d9dd474887f", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75008, "address": "63 rue de Ponthieu", "prix_salle": "-", "geoloc": [48.87226, 2.304441], "name": "Le Fronton", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.304441, 48.87226]}, "recordid": "85a8200d3e3aed7724d3207ed8b1ee5ec50c1f90", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "48 avenue de la Motte Picquet", "prix_salle": "-", "geoloc": [48.851, 2.300378], "name": "Le Piquet", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.300378, 48.851]}, "recordid": "460e2adc95fd172f753b1b6ed296c2711639d49d", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "104 rue Mouffetard", "prix_salle": "-", "geoloc": [48.841089, 2.349565], "name": "Le Tournebride", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.349565, 48.841089]}, "recordid": "8a7a23ed68366f70ab939c877cbdce46f19d75c7", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75014, "address": "52 rue des plantes", "prix_salle": "-", "geoloc": [48.828704, 2.322074], "name": "maison du vin", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.322074, 48.828704]}, "recordid": "482507a8f0fe4960f94372b6fa12b16e7d4f2a93", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "11 Quai de la Tournelle", "prix_salle": "-", "geoloc": [48.849821, 2.355337], "name": "Caf\u00e9 rallye tournelles", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.355337, 48.849821]}, "recordid": "91d88e321a75b6a8c4dea816c399fda77c41f9d1", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75010, "address": "61 rue du ch\u00e2teau d'eau", "prix_salle": "-", "geoloc": [48.872498, 2.355136], "name": "Brasserie le Morvan", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.355136, 48.872498]}, "recordid": "6ec62058995948fc18d331f01a2d03acc0d9e0fa", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75019, "address": "6 rue M\u00e9lingue", "prix_salle": "1", "geoloc": [48.874879, 2.386064], "name": "Chez Miamophile", "prix_terasse": "1", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.386064, 48.874879]}, "recordid": "13924872737e4fc640a43da58937c3777c2ac753", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "18 rue de Crussol", "prix_salle": "-", "geoloc": [48.864269, 2.36858], "name": "Panem", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.36858, 48.864269]}, "recordid": "67bdf3a6989f80749a1ba33a17b1370de0a0e1cd", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75017, "address": "47 rue de Batignolles", "prix_salle": "-", "geoloc": [48.885662, 2.319591], "name": "Petits Freres des Pauvres", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.319591, 48.885662]}, "recordid": "e27fd00149514bbfad7dd7e8f9b0c677df2d3f25", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "198 rue de la Convention", "prix_salle": "-", "geoloc": [48.837212, 2.296046], "name": "Caf\u00e9 Dupont", "prix_terasse": "-", "date": "2012-05-11", "prix_comptoir": 0, "geometry": {"type": "Point", "coordinates": [2.296046, 48.837212]}, "recordid": "4d40e6d864dae81c152a05cb98e30933bde96aa1", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75008, "address": "28 rue de Ponthieu", "prix_salle": "1", "geoloc": [48.871002, 2.30879], "name": "L'Angle", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.30879, 48.871002]}, "recordid": "c40bd2d1f98b415e539c27cf68518d060ebab51e", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "19-23 rue L\u00e9on", "prix_salle": "1", "geoloc": [48.888023, 2.353467], "name": "Institut des Cultures d'Islam", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.353467, 48.888023]}, "recordid": "68d6d37b846e39bd8554e1f8f75974b486b0f27b", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75018, "address": "19 rue Pajol", "prix_salle": "1", "geoloc": [48.886044, 2.360781], "name": "Canopy Caf\u00e9 associatif", "prix_terasse": "-", "date": "2012-10-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.360781, 48.886044]}, "recordid": "ff73bafb514bb68eb925c81aee43c3a58ac3c70d", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75002, "address": "place de l'opera", "prix_salle": "-", "geoloc": [48.870287, 2.332491], "name": "L'Entracte", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.332491, 48.870287]}, "recordid": "0039cd8bceb5e281677a158f832a660789088071", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75003, "address": "15 rue du Parc Royal", "prix_salle": "-", "geoloc": [48.858709, 2.362701], "name": "Le S\u00e9vign\u00e9", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.362701, 48.858709]}, "recordid": "adcc8b4f78f05ba7b24b0593e1516dfb7b415f91", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75005, "address": "35 rue Claude Bernard", "prix_salle": "-", "geoloc": [48.839687, 2.347254], "name": "Le Caf\u00e9 d'avant", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.347254, 48.839687]}, "recordid": "b904fc48763938eee2169ba25aad2ffcc0dd6a9f", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75006, "address": "53 rue Notre-Dame des Champs", "prix_salle": "-", "geoloc": [48.844244, 2.330407], "name": "Le Lucernaire", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.330407, 48.844244]}, "recordid": "cc72af04314fd40e16ff611c799d378515043508", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75009, "address": "12 rue Blanche", "prix_salle": "-", "geoloc": [48.877599, 2.332111], "name": "Le Brigadier", "prix_terasse": "-", "date": "2012-10-09", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.332111, 48.877599]}, "recordid": "978d4bc68c9ebf81029d3e77274d2107777b8a75", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75013, "address": "26 rue du Docteur Magnan", "prix_salle": "-", "geoloc": [48.826494, 2.359987], "name": "L'\u00e2ge d'or", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.359987, 48.826494]}, "recordid": "40bffbdc0c9ed1cbce820fed875d7c21d8964640", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75017, "address": "Place de Clichy", "prix_salle": "-", "geoloc": [48.883717, 2.326861], "name": "Bagels & Coffee Corner", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.326861, 48.883717]}, "recordid": "262facde9b8c4568c9ba7fbce8f069ff8c76948d", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "10 boulevard Victor", "prix_salle": "-", "geoloc": [48.835843, 2.278501], "name": "Caf\u00e9 Victor", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.278501, 48.835843]}, "recordid": "e5817ec44ac5a7ea2e4a34b6a2e13d535156642b", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "54, avenue Daumesnil", "prix_salle": "-", "geoloc": [48.845337, 2.379024], "name": "L'empreinte", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.379024, 48.845337]}, "recordid": "b96ddd35cbbf5d93aaff79487afdf083b5ff0817", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75011, "address": "93, rue de la Roquette", "prix_salle": "-", "geoloc": [48.857312, 2.379055], "name": "L'horizon", "prix_terasse": "-", "date": "2012-03-07", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.379055, 48.857312]}, "recordid": "84c6b7335e7f82ac942c4f398723ec99076f148d", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "34 bis rue de Wattignies", "prix_salle": "-", "geoloc": [48.835878, 2.395723], "name": "Au pays de Vannes", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.395723, 48.835878]}, "recordid": "a17869dbb9d0d5b1e5ed7bb288053900b04ee944", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75007, "address": "36 rue de Varenne", "prix_salle": "-", "geoloc": [48.85413, 2.323539], "name": "Caf\u00e9 Varenne", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.323539, 48.85413]}, "recordid": "a26ec0d5fca47b8de77d862ad8a99b75bb520a09", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75004, "address": "125 Rue Saint-Antoine", "prix_salle": "-", "geoloc": [48.855161, 2.360218], "name": "l'El\u00e9phant du nil", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.360218, 48.855161]}, "recordid": "7b7ceefd1f9ed85041265c9577e0dc8bee01d45a", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "354 bis rue Vaugirard", "prix_salle": "-", "geoloc": [48.8357, 2.292961], "name": "Le Comptoir", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.292961, 48.8357]}, "recordid": "59d8fa304e535f4eb41f9746028034c9b30cbde4", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75015, "address": "358 rue de Vaugirard", "prix_salle": "-", "geoloc": [48.835451, 2.292515], "name": "Le Parc Vaugirard", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.292515, 48.835451]}, "recordid": "19f655206a8446959c8e796c2b3cb9001890f985", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75014, "address": "58 rue Daguerre", "prix_salle": "-", "geoloc": [48.834972, 2.327007], "name": "le Zango", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.327007, 48.834972]}, "recordid": "e1b54109015316a822747f788128f997a3478050", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75020, "address": "3 rue de Lagny", "prix_salle": "-", "geoloc": [48.848887, 2.399972], "name": "Melting Pot", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.399972, 48.848887]}, "recordid": "fd0de2cbf73e0a728cd73e4e2a9a4a9c646f76f2", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75017, "address": "174 avenue de Clichy", "prix_salle": "-", "geoloc": [48.892366, 2.317359], "name": "Pari's Caf\u00e9", "prix_terasse": "-", "date": "2012-06-27", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.317359, 48.892366]}, "recordid": "831446ae203f89de26d3300e625c20717e82d40a", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75012, "address": "157 rue Bercy 75012 Paris", "prix_salle": "-", "geoloc": [48.842146, 2.375986], "name": "L'entrep\u00f4t", "prix_terasse": "-", "date": "2014-02-01", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.375986, 48.842146]}, "recordid": "d8746118429eb118f38ecbee904636d9b33fa8ba", "city": "Paris", "country": "France"}
|
||||
{"zipcode": 75003, "address": "Place de la R\u00e9publique", "prix_salle": "-", "geoloc": [48.867092, 2.363288], "name": "Le caf\u00e9 Monde et M\u00e9dias", "prix_terasse": "-", "date": "2013-08-22", "prix_comptoir": 1, "geometry": {"type": "Point", "coordinates": [2.363288, 48.867092]}, "recordid": "af04c90f25e25daf7f5cbbab1bc740bac26541d4", "city": "Paris", "country": "France"}
|
||||
@ -1,29 +0,0 @@
|
||||
"""
|
||||
Extracts a list of parisian bars where you can buy a coffee for a reasonable price, and store them in a flat text file.
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph {
|
||||
rankdir = LR;
|
||||
stylesheet = "../_static/graphs.css";
|
||||
|
||||
BEGIN [shape="point"];
|
||||
BEGIN -> "ODS()" -> "transform" -> "FileWriter()";
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
import bonobo
|
||||
from bonobo.commands import get_default_services
|
||||
from bonobo.contrib.opendatasoft import OpenDataSoftAPI
|
||||
|
||||
filename = 'coffeeshops.txt'
|
||||
|
||||
graph = bonobo.Graph(
|
||||
OpenDataSoftAPI(dataset='liste-des-cafes-a-un-euro', netloc='opendata.paris.fr'),
|
||||
lambda row: '{nom_du_cafe}, {adresse}, {arrondissement} Paris, France'.format(**row),
|
||||
bonobo.FileWriter(path=filename),
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
bonobo.run(graph, services=get_default_services(__file__))
|
||||
@ -1,182 +1,183 @@
|
||||
Extérieur Quai, 5, rue d'Alsace, 75010 Paris, France
|
||||
Le Sully, 6 Bd henri IV, 75004 Paris, France
|
||||
O q de poule, 53 rue du ruisseau, 75018 Paris, France
|
||||
Le Pas Sage, 1 Passage du Grand Cerf, 75002 Paris, France
|
||||
La Renaissance, 112 Rue Championnet, 75018 Paris, France
|
||||
La Caravane, Rue de la Fontaine au Roi, 75011 Paris, France
|
||||
Le chantereine, 51 Rue Victoire, 75009 Paris, France
|
||||
Le Müller, 11 rue Feutrier, 75018 Paris, France
|
||||
Le drapeau de la fidelité, 21 rue Copreaux, 75015 Paris, France
|
||||
Le café des amis, 125 rue Blomet, 75015 Paris, France
|
||||
Le Café Livres, 10 rue Saint Martin, 75004 Paris, France
|
||||
Le Bosquet, 46 avenue Bosquet, 75007 Paris, France
|
||||
Le Chaumontois, 12 rue Armand Carrel, 75018 Paris, France
|
||||
Le Kleemend's, 34 avenue Pierre Mendès-France, 75013 Paris, France
|
||||
Café Pierre, 202 rue du faubourg st antoine, 75012 Paris, France
|
||||
Les Arcades, 61 rue de Ponthieu, 75008 Paris, France
|
||||
Le Square, 31 rue Saint-Dominique, 75007 Paris, France
|
||||
Assaporare Dix sur Dix, 75, avenue Ledru-Rollin, 75012 Paris, France
|
||||
Au cerceau d'or, 129 boulevard sebastopol, 75002 Paris, France
|
||||
Aux cadrans, 21 ter boulevard Diderot, 75012 Paris, France
|
||||
Café antoine, 17 rue Jean de la Fontaine, 75016 Paris, France
|
||||
Café de la Mairie (du VIII), rue de Lisbonne, 75008 Paris, France
|
||||
Café Lea, 5 rue Claude Bernard, 75005 Paris, France
|
||||
Cardinal Saint-Germain, 11 boulevard Saint-Germain, 75005 Paris, France
|
||||
Dédé la frite, 52 rue Notre-Dame des Victoires, 75002 Paris, France
|
||||
La Bauloise, 36 rue du hameau, 75015 Paris, France
|
||||
Le Bellerive, 71 quai de Seine, 75019 Paris, France
|
||||
Le bistrot de Maëlle et Augustin, 42 rue coquillère, 75001 Paris, France
|
||||
Le Dellac, 14 rue Rougemont, 75009 Paris, France
|
||||
Le Felteu, 1 rue Pecquay, 75004 Paris, France
|
||||
Le Reynou, 2 bis quai de la mégisserie, 75001 Paris, France
|
||||
Le Saint Jean, 23 rue des abbesses, 75018 Paris, France
|
||||
les montparnos, 65 boulevard Pasteur, 75015 Paris, France
|
||||
L'antre d'eux, 16 rue DE MEZIERES, 75006 Paris, France
|
||||
Drole d'endroit pour une rencontre, 58 rue de Montorgueil, 75002 Paris, France
|
||||
Le pari's café, 104 rue caulaincourt, 75018 Paris, France
|
||||
Le Poulailler, 60 rue saint-sabin, 75011 Paris, France
|
||||
Chai 33, 33 Cour Saint Emilion, 75012 Paris, France
|
||||
L'Assassin, 99 rue Jean-Pierre Timbaud, 75011 Paris, France
|
||||
l'Usine, 1 rue d'Avron, 75020 Paris, France
|
||||
La Bricole, 52 rue Liebniz, 75018 Paris, France
|
||||
le ronsard, place maubert, 75005 Paris, France
|
||||
Face Bar, 82 rue des archives, 75003 Paris, France
|
||||
American Kitchen, 49 rue bichat, 75010 Paris, France
|
||||
La Marine, 55 bis quai de valmy, 75010 Paris, France
|
||||
Le Bloc, 21 avenue Brochant, 75017 Paris, France
|
||||
La Recoleta au Manoir, 229 avenue Gambetta, 75020 Paris, France
|
||||
Le Pareloup, 80 Rue Saint-Charles, 75015 Paris, France
|
||||
La Brasserie Gaité, 3 rue de la Gaité, 75014 Paris, France
|
||||
Café Zen, 46 rue Victoire, 75009 Paris, France
|
||||
O'Breizh, 27 rue de Penthièvre, 75008 Paris, France
|
||||
Le Petit Choiseul, 23 rue saint augustin, 75002 Paris, France
|
||||
Invitez vous chez nous, 7 rue Epée de Bois, 75005 Paris, France
|
||||
La Cordonnerie, 142 Rue Saint-Denis 75002 Paris, 75002 Paris, France
|
||||
Le Supercoin, 3, rue Baudelique, 75018 Paris, France
|
||||
Populettes, 86 bis rue Riquet, 75018 Paris, France
|
||||
Au bon coin, 49 rue des Cloys, 75018 Paris, France
|
||||
Le Couvent, 69 rue Broca, 75013 Paris, France
|
||||
La Brûlerie des Ternes, 111 rue mouffetard, 75005 Paris, France
|
||||
L'Écir, 59 Boulevard Saint-Jacques, 75014 Paris, France
|
||||
Le Chat bossu, 126, rue du Faubourg Saint Antoine, 75012 Paris, France
|
||||
Denfert café, 58 boulvevard Saint Jacques, 75014 Paris, France
|
||||
Le Café frappé, 95 rue Montmartre, 75002 Paris, France
|
||||
La Perle, 78 rue vieille du temple, 75003 Paris, France
|
||||
Le Descartes, 1 rue Thouin, 75005 Paris, France
|
||||
Le petit club, 55 rue de la tombe Issoire, 75014 Paris, France
|
||||
Le Plein soleil, 90 avenue Parmentier, 75011 Paris, France
|
||||
Le Relais Haussmann, 146, boulevard Haussmann, 75008 Paris, France
|
||||
Le Malar, 88 rue Saint-Dominique, 75007 Paris, France
|
||||
Au panini de la place, 47 rue Belgrand, 75020 Paris, France
|
||||
Le Village, 182 rue de Courcelles, 75017 Paris, France
|
||||
Pause Café, 41 rue de Charonne, 75011 Paris, France
|
||||
Le Pure café, 14 rue Jean Macé, 75011 Paris, France
|
||||
Extra old café, 307 fg saint Antoine, 75011 Paris, France
|
||||
Chez Fafa, 44 rue Vinaigriers, 75010 Paris, France
|
||||
En attendant l'or, 3 rue Faidherbe, 75011 Paris, France
|
||||
Brûlerie San José, 30 rue des Petits-Champs, 75002 Paris, France
|
||||
Café Martin, 2 place Martin Nadaud, 75001 Paris, France
|
||||
Etienne, 14 rue Turbigo, Paris, 75001 Paris, France
|
||||
L'ingénu, 184 bd Voltaire, 75011 Paris, France
|
||||
L'Olive, 8 rue L'Olive, 75018 Paris, France
|
||||
Le Biz, 18 rue Favart, 75002 Paris, France
|
||||
Le Cap Bourbon, 1 rue Louis le Grand, 75002 Paris, France
|
||||
Le General Beuret, 9 Place du General Beuret, 75015 Paris, France
|
||||
Le Germinal, 95 avenue Emile Zola, 75015 Paris, France
|
||||
Le Ragueneau, 202 rue Saint-Honoré, 75001 Paris, France
|
||||
Le refuge, 72 rue lamarck, 75018 Paris, France
|
||||
Le sully, 13 rue du Faubourg Saint Denis, 75010 Paris, France
|
||||
Coffee Chope, 344Vrue Vaugirard, 75015 Paris, France
|
||||
Le bal du pirate, 60 rue des bergers, 75015 Paris, France
|
||||
zic zinc, 95 rue claude decaen, 75012 Paris, France
|
||||
l'orillon bar, 35 rue de l'orillon, 75011 Paris, France
|
||||
Le Zazabar, 116 Rue de Ménilmontant, 75020 Paris, France
|
||||
L'Inévitable, 22 rue Linné, 75005 Paris, France
|
||||
Le Dunois, 77 rue Dunois, 75013 Paris, France
|
||||
Ragueneau, 202 rue Saint Honoré, 75001 Paris, France
|
||||
Le Caminito, 48 rue du Dessous des Berges, 75013 Paris, France
|
||||
Epicerie Musicale, 55bis quai de Valmy, 75010 Paris, France
|
||||
Le petit Bretonneau, Le petit Bretonneau - à l'intérieur de l'Hôpital, 75018 Paris, France
|
||||
Le Centenaire, 104 rue amelot, 75011 Paris, France
|
||||
La Montagne Sans Geneviève, 13 Rue du Pot de Fer, 75005 Paris, France
|
||||
Les Pères Populaires, 46 rue de Buzenval, 75020 Paris, France
|
||||
Cafe de grenelle, 188 rue de Grenelle, 75007 Paris, France
|
||||
Le relais de la victoire, 73 rue de la Victoire, 75009 Paris, France
|
||||
La chaumière gourmande, Route de la Muette à Neuilly
|
||||
Club hippique du Jardin d’Acclimatation, 75016 Paris, France
|
||||
Le Brio, 216, rue Marcadet, 75018 Paris, France
|
||||
Caves populaires, 22 rue des Dames, 75017 Paris, France
|
||||
Caprice café, 12 avenue Jean Moulin, 75014 Paris, France
|
||||
Tamm Bara, 7 rue Clisson, 75013 Paris, France
|
||||
L'anjou, 1 rue de Montholon, 75009 Paris, France
|
||||
Café dans l'aerogare Air France Invalides, 2 rue Robert Esnault Pelterie, 75007 Paris, France
|
||||
Chez Prune, 36 rue Beaurepaire, 75010 Paris, France
|
||||
Au Vin Des Rues, 21 rue Boulard, 75014 Paris, France
|
||||
bistrot les timbrés, 14 rue d'alleray, 75015 Paris, France
|
||||
Café beauveau, 9 rue de Miromesnil, 75008 Paris, France
|
||||
Café Pistache, 9 rue des petits champs, 75001 Paris, France
|
||||
La Cagnotte, 13 Rue Jean-Baptiste Dumay, 75020 Paris, France
|
||||
le 1 cinq, 172 rue de vaugirard, 75015 Paris, France
|
||||
Le Killy Jen, 28 bis boulevard Diderot, 75012 Paris, France
|
||||
Les Artisans, 106 rue Lecourbe, 75015 Paris, France
|
||||
Peperoni, 83 avenue de Wagram, 75001 Paris, France
|
||||
le lutece, 380 rue de vaugirard, 75015 Paris, France
|
||||
Brasiloja, 16 rue Ganneron, 75018 Paris, France
|
||||
Rivolux, 16 rue de Rivoli, 75004 Paris, France
|
||||
L'européen, 21 Bis Boulevard Diderot, 75012 Paris, France
|
||||
NoMa, 39 rue Notre Dame de Nazareth, 75003 Paris, France
|
||||
O'Paris, 1 Rue des Envierges, 75020 Paris, France
|
||||
Café Clochette, 16 avenue Richerand, 75010 Paris, France
|
||||
La cantoche de Paname, 40 Boulevard Beaumarchais, 75011 Paris, France
|
||||
Le Saint René, 148 Boulevard de Charonne, 75020 Paris, France
|
||||
La Liberté, 196 rue du faubourg saint-antoine, 75012 Paris, France
|
||||
Chez Rutabaga, 16 rue des Petits Champs, 75002 Paris, France
|
||||
Le BB (Bouchon des Batignolles), 2 rue Lemercier, 75017 Paris, France
|
||||
La Brocante, 10 rue Rossini, 75009 Paris, France
|
||||
Le Plomb du cantal, 3 rue Gaîté, 75014 Paris, France
|
||||
Les caves populaires, 22 rue des Dames, 75017 Paris, France
|
||||
Chez Luna, 108 rue de Ménilmontant, 75020 Paris, France
|
||||
Le bar Fleuri, 1 rue du Plateau, 75019 Paris, France
|
||||
Trois pièces cuisine, 101 rue des dames, 75017 Paris, France
|
||||
Le Zinc, 61 avenue de la Motte Picquet, 75015 Paris, France
|
||||
La cantine de Zoé, 136 rue du Faubourg poissonnière, 75010 Paris, France
|
||||
Les Vendangeurs, 6/8 rue Stanislas, 75006 Paris, France
|
||||
L'avant comptoir, 3 carrefour de l'Odéon, 75006 Paris, France
|
||||
Botak cafe, 1 rue Paul albert, 75018 Paris, France
|
||||
le chateau d'eau, 67 rue du Château d'eau, 75010 Paris, France
|
||||
Bistrot Saint-Antoine, 58 rue du Fbg Saint-Antoine, 75012 Paris, France
|
||||
Chez Oscar, 11/13 boulevard Beaumarchais, 75004 Paris, France
|
||||
Le Fronton, 63 rue de Ponthieu, 75008 Paris, France
|
||||
Le Piquet, 48 avenue de la Motte Picquet, 75015 Paris, France
|
||||
Le Tournebride, 104 rue Mouffetard, 75005 Paris, France
|
||||
maison du vin, 52 rue des plantes, 75014 Paris, France
|
||||
L'entrepôt, 157 rue Bercy 75012 Paris, 75012 Paris, France
|
||||
Le café Monde et Médias, Place de la République, 75003 Paris, France
|
||||
Café rallye tournelles, 11 Quai de la Tournelle, 75005 Paris, France
|
||||
Brasserie le Morvan, 61 rue du château d'eau, 75010 Paris, France
|
||||
Chez Miamophile, 6 rue Mélingue, 75019 Paris, France
|
||||
Panem, 18 rue de Crussol, 75011 Paris, France
|
||||
Petits Freres des Pauvres, 47 rue de Batignolles, 75017 Paris, France
|
||||
Café Dupont, 198 rue de la Convention, 75015 Paris, France
|
||||
L'Angle, 28 rue de Ponthieu, 75008 Paris, France
|
||||
Institut des Cultures d'Islam, 19-23 rue Léon, 75018 Paris, France
|
||||
Canopy Café associatif, 19 rue Pajol, 75018 Paris, France
|
||||
L'Entracte, place de l'opera, 75002 Paris, France
|
||||
Le Sévigné, 15 rue du Parc Royal, 75003 Paris, France
|
||||
Le Café d'avant, 35 rue Claude Bernard, 75005 Paris, France
|
||||
Le Lucernaire, 53 rue Notre-Dame des Champs, 75006 Paris, France
|
||||
Le Brigadier, 12 rue Blanche, 75009 Paris, France
|
||||
L'âge d'or, 26 rue du Docteur Magnan, 75013 Paris, France
|
||||
Bagels & Coffee Corner, Place de Clichy, 75017 Paris, France
|
||||
Café Victor, 10 boulevard Victor, 75015 Paris, France
|
||||
L'empreinte, 54, avenue Daumesnil, 75012 Paris, France
|
||||
L'horizon, 93, rue de la Roquette, 75011 Paris, France
|
||||
Waikiki, 10 rue d"Ulm, 75005 Paris, France
|
||||
Au pays de Vannes, 34 bis rue de Wattignies, 75012 Paris, France
|
||||
Café Varenne, 36 rue de Varenne, 75007 Paris, France
|
||||
l'Eléphant du nil, 125 Rue Saint-Antoine, 75004 Paris, France
|
||||
Le Comptoir, 354 bis rue Vaugirard, 75015 Paris, France
|
||||
Le Parc Vaugirard, 358 rue de Vaugirard, 75015 Paris, France
|
||||
le Zango, 58 rue Daguerre, 75014 Paris, France
|
||||
Melting Pot, 3 rue de Lagny, 75020 Paris, France
|
||||
Pari's Café, 174 avenue de Clichy, 75017 Paris, France
|
||||
name,address,zipcode,city
|
||||
Coffee Chope,344Vrue Vaugirard,75015,Paris
|
||||
Extérieur Quai,"5, rue d'Alsace",75010,Paris
|
||||
Le Sully,6 Bd henri IV,75004,Paris
|
||||
O q de poule,53 rue du ruisseau,75018,Paris
|
||||
Le Pas Sage,1 Passage du Grand Cerf,75002,Paris
|
||||
La Renaissance,112 Rue Championnet,75018,Paris
|
||||
La Caravane,Rue de la Fontaine au Roi,75011,Paris
|
||||
Le chantereine,51 Rue Victoire,75009,Paris
|
||||
Le Müller,11 rue Feutrier,75018,Paris
|
||||
Le drapeau de la fidelité,21 rue Copreaux,75015,Paris
|
||||
Le café des amis,125 rue Blomet,75015,Paris
|
||||
Le Café Livres,10 rue Saint Martin,75004,Paris
|
||||
Le Bosquet,46 avenue Bosquet,75007,Paris
|
||||
Le Chaumontois,12 rue Armand Carrel,75018,Paris
|
||||
Le Kleemend's,34 avenue Pierre Mendès-France,75013,Paris
|
||||
Café Pierre,202 rue du faubourg st antoine,75012,Paris
|
||||
Les Arcades,61 rue de Ponthieu,75008,Paris
|
||||
Le Square,31 rue Saint-Dominique,75007,Paris
|
||||
Assaporare Dix sur Dix,"75, avenue Ledru-Rollin",75012,Paris
|
||||
Au cerceau d'or,129 boulevard sebastopol,75002,Paris
|
||||
Aux cadrans,21 ter boulevard Diderot,75012,Paris
|
||||
Café antoine,17 rue Jean de la Fontaine,75016,Paris
|
||||
Café de la Mairie (du VIII),rue de Lisbonne,75008,Paris
|
||||
Café Lea,5 rue Claude Bernard,75005,Paris
|
||||
Cardinal Saint-Germain,11 boulevard Saint-Germain,75005,Paris
|
||||
Dédé la frite,52 rue Notre-Dame des Victoires,75002,Paris
|
||||
La Bauloise,36 rue du hameau,75015,Paris
|
||||
Le Bellerive,71 quai de Seine,75019,Paris
|
||||
Le bistrot de Maëlle et Augustin,42 rue coquillère,75001,Paris
|
||||
Le Dellac,14 rue Rougemont,75009,Paris
|
||||
Le Felteu,1 rue Pecquay,75004,Paris
|
||||
Le Reynou,2 bis quai de la mégisserie,75001,Paris
|
||||
Le Saint Jean,23 rue des abbesses,75018,Paris
|
||||
les montparnos,65 boulevard Pasteur,75015,Paris
|
||||
Le Supercoin,"3, rue Baudelique",75018,Paris
|
||||
Populettes,86 bis rue Riquet,75018,Paris
|
||||
Au bon coin,49 rue des Cloys,75018,Paris
|
||||
Le Couvent,69 rue Broca,75013,Paris
|
||||
La Brûlerie des Ternes,111 rue mouffetard,75005,Paris
|
||||
L'Écir,59 Boulevard Saint-Jacques,75014,Paris
|
||||
Le Chat bossu,"126, rue du Faubourg Saint Antoine",75012,Paris
|
||||
Denfert café,58 boulvevard Saint Jacques,75014,Paris
|
||||
Le Café frappé,95 rue Montmartre,75002,Paris
|
||||
La Perle,78 rue vieille du temple,75003,Paris
|
||||
Le Descartes,1 rue Thouin,75005,Paris
|
||||
Le petit club,55 rue de la tombe Issoire,75014,Paris
|
||||
Le Plein soleil,90 avenue Parmentier,75011,Paris
|
||||
Le Relais Haussmann,"146, boulevard Haussmann",75008,Paris
|
||||
Le Malar,88 rue Saint-Dominique,75007,Paris
|
||||
Au panini de la place,47 rue Belgrand,75020,Paris
|
||||
Le Village,182 rue de Courcelles,75017,Paris
|
||||
Pause Café,41 rue de Charonne,75011,Paris
|
||||
Le Pure café,14 rue Jean Macé,75011,Paris
|
||||
Extra old café,307 fg saint Antoine,75011,Paris
|
||||
Chez Fafa,44 rue Vinaigriers,75010,Paris
|
||||
En attendant l'or,3 rue Faidherbe,75011,Paris
|
||||
Brûlerie San José,30 rue des Petits-Champs,75002,Paris
|
||||
Café Martin,2 place Martin Nadaud,75001,Paris
|
||||
Etienne,"14 rue Turbigo, Paris",75001,Paris
|
||||
L'ingénu,184 bd Voltaire,75011,Paris
|
||||
L'Olive,8 rue L'Olive,75018,Paris
|
||||
Le Biz,18 rue Favart,75002,Paris
|
||||
Le Cap Bourbon,1 rue Louis le Grand,75002,Paris
|
||||
Le General Beuret,9 Place du General Beuret,75015,Paris
|
||||
Le Germinal,95 avenue Emile Zola,75015,Paris
|
||||
Le Ragueneau,202 rue Saint-Honoré,75001,Paris
|
||||
Le refuge,72 rue lamarck,75018,Paris
|
||||
Le sully,13 rue du Faubourg Saint Denis,75010,Paris
|
||||
L'antre d'eux,16 rue DE MEZIERES,75006,Paris
|
||||
Drole d'endroit pour une rencontre,58 rue de Montorgueil,75002,Paris
|
||||
Le pari's café,104 rue caulaincourt,75018,Paris
|
||||
Le Poulailler,60 rue saint-sabin,75011,Paris
|
||||
Chai 33,33 Cour Saint Emilion,75012,Paris
|
||||
L'Assassin,99 rue Jean-Pierre Timbaud,75011,Paris
|
||||
l'Usine,1 rue d'Avron,75020,Paris
|
||||
La Bricole,52 rue Liebniz,75018,Paris
|
||||
le ronsard,place maubert,75005,Paris
|
||||
Face Bar,82 rue des archives,75003,Paris
|
||||
American Kitchen,49 rue bichat,75010,Paris
|
||||
La Marine,55 bis quai de valmy,75010,Paris
|
||||
Le Bloc,21 avenue Brochant,75017,Paris
|
||||
La Recoleta au Manoir,229 avenue Gambetta,75020,Paris
|
||||
Le Pareloup,80 Rue Saint-Charles,75015,Paris
|
||||
La Brasserie Gaité,3 rue de la Gaité,75014,Paris
|
||||
Café Zen,46 rue Victoire,75009,Paris
|
||||
O'Breizh,27 rue de Penthièvre,75008,Paris
|
||||
Le Petit Choiseul,23 rue saint augustin,75002,Paris
|
||||
Invitez vous chez nous,7 rue Epée de Bois,75005,Paris
|
||||
La Cordonnerie,142 Rue Saint-Denis 75002 Paris,75002,Paris
|
||||
Le bal du pirate,60 rue des bergers,75015,Paris
|
||||
zic zinc,95 rue claude decaen,75012,Paris
|
||||
l'orillon bar,35 rue de l'orillon,75011,Paris
|
||||
Le Zazabar,116 Rue de Ménilmontant,75020,Paris
|
||||
L'Inévitable,22 rue Linné,75005,Paris
|
||||
Le Dunois,77 rue Dunois,75013,Paris
|
||||
Ragueneau,202 rue Saint Honoré,75001,Paris
|
||||
Le Caminito,48 rue du Dessous des Berges,75013,Paris
|
||||
Epicerie Musicale,55bis quai de Valmy,75010,Paris
|
||||
Le petit Bretonneau,Le petit Bretonneau - à l'intérieur de l'Hôpital,75018,Paris
|
||||
Le Centenaire,104 rue amelot,75011,Paris
|
||||
La Montagne Sans Geneviève,13 Rue du Pot de Fer,75005,Paris
|
||||
Les Pères Populaires,46 rue de Buzenval,75020,Paris
|
||||
Cafe de grenelle,188 rue de Grenelle,75007,Paris
|
||||
Le relais de la victoire,73 rue de la Victoire,75009,Paris
|
||||
La chaumière gourmande,"Route de la Muette à Neuilly
|
||||
Club hippique du Jardin d’Acclimatation",75016,Paris
|
||||
Le Brio,"216, rue Marcadet",75018,Paris
|
||||
Caves populaires,22 rue des Dames,75017,Paris
|
||||
Caprice café,12 avenue Jean Moulin,75014,Paris
|
||||
Tamm Bara,7 rue Clisson,75013,Paris
|
||||
L'anjou,1 rue de Montholon,75009,Paris
|
||||
Café dans l'aerogare Air France Invalides,2 rue Robert Esnault Pelterie,75007,Paris
|
||||
Waikiki,"10 rue d""Ulm",75005,Paris
|
||||
Chez Prune,36 rue Beaurepaire,75010,Paris
|
||||
Au Vin Des Rues,21 rue Boulard,75014,Paris
|
||||
bistrot les timbrés,14 rue d'alleray,75015,Paris
|
||||
Café beauveau,9 rue de Miromesnil,75008,Paris
|
||||
Café Pistache,9 rue des petits champs,75001,Paris
|
||||
La Cagnotte,13 Rue Jean-Baptiste Dumay,75020,Paris
|
||||
le 1 cinq,172 rue de vaugirard,75015,Paris
|
||||
Le Killy Jen,28 bis boulevard Diderot,75012,Paris
|
||||
Les Artisans,106 rue Lecourbe,75015,Paris
|
||||
Peperoni,83 avenue de Wagram,75001,Paris
|
||||
le lutece,380 rue de vaugirard,75015,Paris
|
||||
Brasiloja,16 rue Ganneron,75018,Paris
|
||||
Rivolux,16 rue de Rivoli,75004,Paris
|
||||
L'européen,21 Bis Boulevard Diderot,75012,Paris
|
||||
NoMa,39 rue Notre Dame de Nazareth,75003,Paris
|
||||
O'Paris,1 Rue des Envierges,75020,Paris
|
||||
Café Clochette,16 avenue Richerand,75010,Paris
|
||||
La cantoche de Paname,40 Boulevard Beaumarchais,75011,Paris
|
||||
Le Saint René,148 Boulevard de Charonne,75020,Paris
|
||||
La Liberté,196 rue du faubourg saint-antoine,75012,Paris
|
||||
Chez Rutabaga,16 rue des Petits Champs,75002,Paris
|
||||
Le BB (Bouchon des Batignolles),2 rue Lemercier,75017,Paris
|
||||
La Brocante,10 rue Rossini,75009,Paris
|
||||
Le Plomb du cantal,3 rue Gaîté,75014,Paris
|
||||
Les caves populaires,22 rue des Dames,75017,Paris
|
||||
Chez Luna,108 rue de Ménilmontant,75020,Paris
|
||||
Le bar Fleuri,1 rue du Plateau,75019,Paris
|
||||
Trois pièces cuisine,101 rue des dames,75017,Paris
|
||||
Le Zinc,61 avenue de la Motte Picquet,75015,Paris
|
||||
La cantine de Zoé,136 rue du Faubourg poissonnière,75010,Paris
|
||||
Les Vendangeurs,6/8 rue Stanislas,75006,Paris
|
||||
L'avant comptoir,3 carrefour de l'Odéon,75006,Paris
|
||||
Botak cafe,1 rue Paul albert,75018,Paris
|
||||
le chateau d'eau,67 rue du Château d'eau,75010,Paris
|
||||
Bistrot Saint-Antoine,58 rue du Fbg Saint-Antoine,75012,Paris
|
||||
Chez Oscar,11/13 boulevard Beaumarchais,75004,Paris
|
||||
Le Fronton,63 rue de Ponthieu,75008,Paris
|
||||
Le Piquet,48 avenue de la Motte Picquet,75015,Paris
|
||||
Le Tournebride,104 rue Mouffetard,75005,Paris
|
||||
maison du vin,52 rue des plantes,75014,Paris
|
||||
L'entrepôt,157 rue Bercy 75012 Paris,75012,Paris
|
||||
Le café Monde et Médias,Place de la République,75003,Paris
|
||||
Café rallye tournelles,11 Quai de la Tournelle,75005,Paris
|
||||
Brasserie le Morvan,61 rue du château d'eau,75010,Paris
|
||||
Chez Miamophile,6 rue Mélingue,75019,Paris
|
||||
Panem,18 rue de Crussol,75011,Paris
|
||||
Petits Freres des Pauvres,47 rue de Batignolles,75017,Paris
|
||||
Café Dupont,198 rue de la Convention,75015,Paris
|
||||
L'Angle,28 rue de Ponthieu,75008,Paris
|
||||
Institut des Cultures d'Islam,19-23 rue Léon,75018,Paris
|
||||
Canopy Café associatif,19 rue Pajol,75018,Paris
|
||||
L'Entracte,place de l'opera,75002,Paris
|
||||
Le Sévigné,15 rue du Parc Royal,75003,Paris
|
||||
Le Café d'avant,35 rue Claude Bernard,75005,Paris
|
||||
Le Lucernaire,53 rue Notre-Dame des Champs,75006,Paris
|
||||
Le Brigadier,12 rue Blanche,75009,Paris
|
||||
L'âge d'or,26 rue du Docteur Magnan,75013,Paris
|
||||
Bagels & Coffee Corner,Place de Clichy,75017,Paris
|
||||
Café Victor,10 boulevard Victor,75015,Paris
|
||||
L'empreinte,"54, avenue Daumesnil",75012,Paris
|
||||
L'horizon,"93, rue de la Roquette",75011,Paris
|
||||
Au pays de Vannes,34 bis rue de Wattignies,75012,Paris
|
||||
Café Varenne,36 rue de Varenne,75007,Paris
|
||||
l'Eléphant du nil,125 Rue Saint-Antoine,75004,Paris
|
||||
Le Comptoir,354 bis rue Vaugirard,75015,Paris
|
||||
Le Parc Vaugirard,358 rue de Vaugirard,75015,Paris
|
||||
le Zango,58 rue Daguerre,75014,Paris
|
||||
Melting Pot,3 rue de Lagny,75020,Paris
|
||||
Pari's Café,174 avenue de Clichy,75017,Paris
|
||||
|
||||
@ -2,4 +2,7 @@ from bonobo import get_examples_path, open_fs
|
||||
|
||||
|
||||
def get_services():
|
||||
return {'fs': open_fs(get_examples_path())}
|
||||
return {
|
||||
'fs': open_fs(get_examples_path()),
|
||||
'fs.output': open_fs(),
|
||||
}
|
||||
|
||||
@ -1,10 +1,36 @@
|
||||
import bonobo
|
||||
from bonobo.commands import get_default_services
|
||||
from bonobo.examples.files._services import get_services
|
||||
|
||||
|
||||
def get_graph(*, _limit=None, _print=False):
|
||||
return bonobo.Graph(
|
||||
bonobo.CsvReader('datasets/coffeeshops.txt'),
|
||||
*((bonobo.Limit(_limit), ) if _limit else ()),
|
||||
*((bonobo.PrettyPrinter(), ) if _print else ()),
|
||||
bonobo.CsvWriter('coffeeshops.csv', fs='fs.output')
|
||||
)
|
||||
|
||||
graph = bonobo.Graph(
|
||||
bonobo.CsvReader('datasets/coffeeshops.txt', headers=('item', )),
|
||||
bonobo.PrettyPrinter(),
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
bonobo.run(graph, services=get_default_services(__file__))
|
||||
parser = bonobo.get_argument_parser()
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
'-l',
|
||||
type=int,
|
||||
default=None,
|
||||
help='If set, limits the number of processed lines.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--print',
|
||||
'-p',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='If set, pretty prints before writing to output file.'
|
||||
)
|
||||
|
||||
with bonobo.parse_args(parser) as options:
|
||||
bonobo.run(
|
||||
get_graph(_limit=options['limit'], _print=options['print']),
|
||||
services=get_services()
|
||||
)
|
||||
|
||||
@ -1,17 +1,50 @@
|
||||
import bonobo
|
||||
from bonobo import Bag
|
||||
from bonobo.commands import get_default_services
|
||||
from bonobo.examples.files._services import get_services
|
||||
|
||||
|
||||
def get_fields(**row):
|
||||
return Bag(**row['fields'])
|
||||
def get_graph(*, _limit=None, _print=False):
|
||||
graph = bonobo.Graph()
|
||||
|
||||
trunk = graph.add_chain(
|
||||
bonobo.JsonReader('datasets/theaters.json'),
|
||||
*((bonobo.Limit(_limit), ) if _limit else ()),
|
||||
)
|
||||
|
||||
if _print:
|
||||
graph.add_chain(bonobo.PrettyPrinter(), _input=trunk.output)
|
||||
|
||||
graph.add_chain(
|
||||
bonobo.JsonWriter('theaters.json', fs='fs.output'),
|
||||
_input=trunk.output
|
||||
)
|
||||
graph.add_chain(
|
||||
bonobo.LdjsonWriter('theaters.ldjson', fs='fs.output'),
|
||||
_input=trunk.output
|
||||
)
|
||||
|
||||
return graph
|
||||
|
||||
graph = bonobo.Graph(
|
||||
bonobo.JsonReader('datasets/theaters.json'),
|
||||
get_fields,
|
||||
bonobo.PrettyPrinter(),
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
bonobo.run(graph, services=get_default_services(__file__))
|
||||
parser = bonobo.get_argument_parser()
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
'-l',
|
||||
type=int,
|
||||
default=None,
|
||||
help='If set, limits the number of processed lines.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--print',
|
||||
'-p',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='If set, pretty prints before writing to output file.'
|
||||
)
|
||||
|
||||
with bonobo.parse_args(parser) as options:
|
||||
bonobo.run(
|
||||
get_graph(_limit=options['limit'], _print=options['print']),
|
||||
services=get_services()
|
||||
)
|
||||
|
||||
@ -27,33 +27,51 @@ messages categorized as spam, and (3) prints the output.
|
||||
|
||||
'''
|
||||
|
||||
import bonobo
|
||||
from bonobo.commands import get_default_services
|
||||
from fs.tarfs import TarFS
|
||||
|
||||
import bonobo
|
||||
from bonobo import examples
|
||||
|
||||
def cleanse_sms(**row):
|
||||
if row['category'] == 'spam':
|
||||
row['sms_clean'] = '**MARKED AS SPAM** ' + row['sms'][0:50] + (
|
||||
'...' if len(row['sms']) > 50 else ''
|
||||
|
||||
def cleanse_sms(category, sms):
|
||||
if category == 'spam':
|
||||
sms_clean = '**MARKED AS SPAM** ' + sms[0:50] + (
|
||||
'...' if len(sms) > 50 else ''
|
||||
)
|
||||
elif category == 'ham':
|
||||
sms_clean = sms
|
||||
else:
|
||||
row['sms_clean'] = row['sms']
|
||||
raise ValueError('Unknown category {!r}.'.format(category))
|
||||
|
||||
return row['sms_clean']
|
||||
return category, sms, sms_clean
|
||||
|
||||
|
||||
graph = bonobo.Graph(
|
||||
# spam.pkl is within the gzipped tarball
|
||||
bonobo.PickleReader('spam.pkl'),
|
||||
cleanse_sms,
|
||||
bonobo.PrettyPrinter(),
|
||||
)
|
||||
def get_graph(*, _limit=(), _print=()):
|
||||
graph = bonobo.Graph()
|
||||
|
||||
graph.add_chain(
|
||||
# spam.pkl is within the gzipped tarball
|
||||
bonobo.PickleReader('spam.pkl'),
|
||||
*_limit,
|
||||
cleanse_sms,
|
||||
*_print,
|
||||
)
|
||||
|
||||
return graph
|
||||
|
||||
|
||||
def get_services():
|
||||
return {'fs': TarFS(bonobo.get_examples_path('datasets/spam.tgz'))}
|
||||
from ._services import get_services
|
||||
return {
|
||||
**get_services(),
|
||||
'fs': TarFS(bonobo.get_examples_path('datasets/spam.tgz'))
|
||||
}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bonobo.run(graph, services=get_default_services(__file__))
|
||||
parser = examples.get_argument_parser()
|
||||
with bonobo.parse_args(parser) as options:
|
||||
bonobo.run(
|
||||
get_graph(**examples.get_graph_options(options)),
|
||||
services=get_services()
|
||||
)
|
||||
|
||||
@ -1,19 +1,29 @@
|
||||
import bonobo
|
||||
from bonobo.commands import get_default_services
|
||||
from bonobo import examples
|
||||
from bonobo.examples.files._services import get_services
|
||||
|
||||
|
||||
def skip_comments(line):
|
||||
line = line.strip()
|
||||
if not line.startswith('#'):
|
||||
yield line
|
||||
|
||||
|
||||
graph = bonobo.Graph(
|
||||
bonobo.FileReader('datasets/passwd.txt'),
|
||||
skip_comments,
|
||||
lambda s: s.split(':'),
|
||||
lambda l: l[0],
|
||||
print,
|
||||
)
|
||||
def get_graph(*, _limit=(), _print=()):
|
||||
return bonobo.Graph(
|
||||
bonobo.FileReader('datasets/passwd.txt'),
|
||||
skip_comments,
|
||||
*_limit,
|
||||
lambda s: s.split(':')[0],
|
||||
*_print,
|
||||
bonobo.FileWriter('usernames.txt', fs='fs.output'),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bonobo.run(graph, services=get_default_services(__file__))
|
||||
parser = examples.get_argument_parser()
|
||||
with bonobo.parse_args(parser) as options:
|
||||
bonobo.run(
|
||||
get_graph(**examples.get_graph_options(options)),
|
||||
services=get_services()
|
||||
)
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
from bonobo import get_examples_path, open_fs
|
||||
|
||||
|
||||
def get_services():
|
||||
return {'fs': open_fs(get_examples_path())}
|
||||
@ -1,41 +0,0 @@
|
||||
"""
|
||||
Example on how to use :class:`bonobo.Bag` instances to pass flexible args/kwargs to the next callable.
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph {
|
||||
rankdir = LR;
|
||||
stylesheet = "../_static/graphs.css";
|
||||
|
||||
BEGIN [shape="point"];
|
||||
BEGIN -> "extract()" -> "transform(...)" -> "load(...)";
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
from random import randint
|
||||
|
||||
from bonobo import Bag, Graph
|
||||
|
||||
|
||||
def extract():
|
||||
yield Bag(topic='foo')
|
||||
yield Bag(topic='bar')
|
||||
yield Bag(topic='baz')
|
||||
|
||||
|
||||
def transform(topic: str):
|
||||
return Bag.inherit(title=topic.title(), rand=randint(10, 99))
|
||||
|
||||
|
||||
def load(topic: str, title: str, rand: int):
|
||||
print('{} ({}) wait={}'.format(title, topic, rand))
|
||||
|
||||
|
||||
graph = Graph()
|
||||
graph.add_chain(extract, transform, load)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from bonobo import run
|
||||
|
||||
run(graph)
|
||||
@ -1,21 +0,0 @@
|
||||
"""
|
||||
Simple example of :func:`bonobo.count` usage.
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph {
|
||||
rankdir = LR;
|
||||
stylesheet = "../_static/graphs.css";
|
||||
|
||||
BEGIN [shape="point"];
|
||||
BEGIN -> "range()" -> "count" -> "print";
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
import bonobo
|
||||
|
||||
graph = bonobo.Graph(range(42), bonobo.count, print)
|
||||
|
||||
if __name__ == '__main__':
|
||||
bonobo.run(graph)
|
||||
@ -1,43 +0,0 @@
|
||||
"""
|
||||
Example on how to use symple python dictionaries to communicate between transformations.
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph {
|
||||
rankdir = LR;
|
||||
stylesheet = "../_static/graphs.css";
|
||||
|
||||
BEGIN [shape="point"];
|
||||
BEGIN -> "extract()" -> "transform(row: dict)" -> "load(row: dict)";
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
from random import randint
|
||||
|
||||
from bonobo import Graph
|
||||
|
||||
|
||||
def extract():
|
||||
yield {'topic': 'foo'}
|
||||
yield {'topic': 'bar'}
|
||||
yield {'topic': 'baz'}
|
||||
|
||||
|
||||
def transform(row: dict):
|
||||
return {
|
||||
'topic': row['topic'].title(),
|
||||
'randint': randint(10, 99),
|
||||
}
|
||||
|
||||
|
||||
def load(row: dict):
|
||||
print(row)
|
||||
|
||||
|
||||
graph = Graph(extract, transform, load)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from bonobo import run
|
||||
|
||||
run(graph)
|
||||
@ -1,24 +0,0 @@
|
||||
import bonobo
|
||||
|
||||
from bonobo import Filter
|
||||
|
||||
|
||||
class OddOnlyFilter(Filter):
|
||||
def filter(self, i):
|
||||
return i % 2
|
||||
|
||||
|
||||
@Filter
|
||||
def multiples_of_three(i):
|
||||
return not (i % 3)
|
||||
|
||||
|
||||
graph = bonobo.Graph(
|
||||
lambda: tuple(range(50)),
|
||||
OddOnlyFilter(),
|
||||
multiples_of_three,
|
||||
print,
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
bonobo.run(graph)
|
||||
@ -1,19 +0,0 @@
|
||||
import bonobo
|
||||
import time
|
||||
|
||||
from bonobo.constants import NOT_MODIFIED
|
||||
|
||||
|
||||
def pause(*args, **kwargs):
|
||||
time.sleep(0.1)
|
||||
return NOT_MODIFIED
|
||||
|
||||
|
||||
graph = bonobo.Graph(
|
||||
lambda: tuple(range(20)),
|
||||
pause,
|
||||
print,
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
bonobo.run(graph)
|
||||
@ -1,39 +0,0 @@
|
||||
"""
|
||||
Example on how to use symple python strings to communicate between transformations.
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph {
|
||||
rankdir = LR;
|
||||
stylesheet = "../_static/graphs.css";
|
||||
|
||||
BEGIN [shape="point"];
|
||||
BEGIN -> "extract()" -> "transform(s: str)" -> "load(s: str)";
|
||||
}
|
||||
|
||||
"""
|
||||
from random import randint
|
||||
|
||||
from bonobo import Graph
|
||||
|
||||
|
||||
def extract():
|
||||
yield 'foo'
|
||||
yield 'bar'
|
||||
yield 'baz'
|
||||
|
||||
|
||||
def transform(s: str):
|
||||
return '{} ({})'.format(s.title(), randint(10, 99))
|
||||
|
||||
|
||||
def load(s: str):
|
||||
print(s)
|
||||
|
||||
|
||||
graph = Graph(extract, transform, load)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from bonobo import run
|
||||
|
||||
run(graph)
|
||||
@ -13,7 +13,8 @@ class MyJsonWriter(bonobo.JsonWriter):
|
||||
|
||||
def write(self, fs, file, lineno, **row):
|
||||
return bonobo.FileWriter.write(
|
||||
self, fs, file, lineno, json.dumps(row)[1:-1]
|
||||
self, fs, file, lineno,
|
||||
json.dumps(row)[1:-1]
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
from . import bags, dicts, strings
|
||||
|
||||
__all__ = [
|
||||
'bags',
|
||||
'dicts',
|
||||
'strings',
|
||||
]
|
||||
@ -1,41 +0,0 @@
|
||||
"""
|
||||
Example on how to use :class:`bonobo.Bag` instances to pass flexible args/kwargs to the next callable.
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph {
|
||||
rankdir = LR;
|
||||
stylesheet = "../_static/graphs.css";
|
||||
|
||||
BEGIN [shape="point"];
|
||||
BEGIN -> "extract()" -> "transform(...)" -> "load(...)";
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
from random import randint
|
||||
|
||||
from bonobo import Bag, Graph
|
||||
|
||||
|
||||
def extract():
|
||||
yield Bag(topic='foo')
|
||||
yield Bag(topic='bar')
|
||||
yield Bag(topic='baz')
|
||||
|
||||
|
||||
def transform(topic: str):
|
||||
return Bag.inherit(title=topic.title(), rand=randint(10, 99))
|
||||
|
||||
|
||||
def load(topic: str, title: str, rand: int):
|
||||
print('{} ({}) wait={}'.format(title, topic, rand))
|
||||
|
||||
|
||||
graph = Graph()
|
||||
graph.add_chain(extract, transform, load)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from bonobo import run
|
||||
|
||||
run(graph)
|
||||
@ -1,43 +0,0 @@
|
||||
"""
|
||||
Example on how to use symple python dictionaries to communicate between transformations.
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph {
|
||||
rankdir = LR;
|
||||
stylesheet = "../_static/graphs.css";
|
||||
|
||||
BEGIN [shape="point"];
|
||||
BEGIN -> "extract()" -> "transform(row: dict)" -> "load(row: dict)";
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
from random import randint
|
||||
|
||||
from bonobo import Graph
|
||||
|
||||
|
||||
def extract():
|
||||
yield {'topic': 'foo'}
|
||||
yield {'topic': 'bar'}
|
||||
yield {'topic': 'baz'}
|
||||
|
||||
|
||||
def transform(row: dict):
|
||||
return {
|
||||
'topic': row['topic'].title(),
|
||||
'randint': randint(10, 99),
|
||||
}
|
||||
|
||||
|
||||
def load(row: dict):
|
||||
print(row)
|
||||
|
||||
|
||||
graph = Graph(extract, transform, load)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from bonobo import run
|
||||
|
||||
run(graph)
|
||||
@ -23,11 +23,11 @@ def extract():
|
||||
yield 'baz'
|
||||
|
||||
|
||||
def transform(s: str):
|
||||
def transform(s):
|
||||
return '{} ({})'.format(s.title(), randint(10, 99))
|
||||
|
||||
|
||||
def load(s: str):
|
||||
def load(s):
|
||||
print(s)
|
||||
|
||||
|
||||
|
||||
@ -3,9 +3,11 @@ import sys
|
||||
from contextlib import contextmanager
|
||||
from logging import ERROR
|
||||
|
||||
from bonobo.util.objects import Wrapper, get_name
|
||||
from mondrian import term
|
||||
|
||||
from bonobo.util import deprecated
|
||||
from bonobo.util.objects import Wrapper, get_name
|
||||
|
||||
|
||||
@contextmanager
|
||||
def recoverable(error_handler):
|
||||
@ -107,6 +109,13 @@ class Lifecycle:
|
||||
|
||||
self._killed = True
|
||||
|
||||
@deprecated
|
||||
def handle_error(self, exctype, exc, tb, *, level=logging.ERROR):
|
||||
return self.error((exctype, exc, tb), level=level)
|
||||
|
||||
def error(self, exc_info, *, level=logging.ERROR):
|
||||
logging.getLogger(__name__).log(level, repr(self), exc_info=exc_info)
|
||||
|
||||
def fatal(self, exc_info, *, level=logging.CRITICAL):
|
||||
logging.getLogger(__name__).log(level, repr(self), exc_info=exc_info)
|
||||
self._defunct = True
|
||||
|
||||
@ -41,8 +41,8 @@ class GraphExecutionContext:
|
||||
outputs = self.graph.outputs_of(i)
|
||||
if len(outputs):
|
||||
node_context.outputs = [self[j].input for j in outputs]
|
||||
node_context.input.on_begin = partial(node_context.send, BEGIN, _control=True)
|
||||
node_context.input.on_end = partial(node_context.send, END, _control=True)
|
||||
node_context.input.on_begin = partial(node_context._send, BEGIN, _control=True)
|
||||
node_context.input.on_end = partial(node_context._send, END, _control=True)
|
||||
node_context.input.on_finalize = partial(node_context.stop)
|
||||
|
||||
def __getitem__(self, item):
|
||||
|
||||
@ -1,25 +1,36 @@
|
||||
import logging
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from queue import Empty
|
||||
from time import sleep
|
||||
from types import GeneratorType
|
||||
|
||||
from bonobo.config import create_container
|
||||
from bonobo.config.processors import ContextCurrifier
|
||||
from bonobo.constants import NOT_MODIFIED, BEGIN, END, TICK_PERIOD
|
||||
from bonobo.errors import InactiveReadableError, UnrecoverableError
|
||||
from bonobo.constants import NOT_MODIFIED, BEGIN, END, TICK_PERIOD, Token
|
||||
from bonobo.errors import InactiveReadableError, UnrecoverableError, UnrecoverableTypeError
|
||||
from bonobo.execution.contexts.base import BaseContext
|
||||
from bonobo.structs.bags import Bag
|
||||
from bonobo.structs.inputs import Input
|
||||
from bonobo.structs.tokens import Token
|
||||
from bonobo.util import get_name, iserrorbag, isloopbackbag, isbag, istuple, isconfigurabletype
|
||||
from bonobo.util import get_name, istuple, isconfigurabletype, ensure_tuple
|
||||
from bonobo.util.bags import BagType
|
||||
from bonobo.util.statistics import WithStatistics
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
UnboundArguments = namedtuple('UnboundArguments', ['args', 'kwargs'])
|
||||
|
||||
|
||||
class NodeExecutionContext(BaseContext, WithStatistics):
|
||||
def __init__(self, wrapped, *, parent=None, services=None, _input=None, _outputs=None):
|
||||
"""
|
||||
Node execution context has the responsibility fo storing the state of a transformation during its execution.
|
||||
|
||||
:param wrapped: wrapped transformation
|
||||
:param parent: parent context, most probably a graph context
|
||||
:param services: dict-like collection of services
|
||||
:param _input: input queue (optional)
|
||||
:param _outputs: output queues (optional)
|
||||
"""
|
||||
BaseContext.__init__(self, wrapped, parent=parent)
|
||||
WithStatistics.__init__(self, 'in', 'out', 'err', 'warn')
|
||||
|
||||
@ -37,6 +48,10 @@ class NodeExecutionContext(BaseContext, WithStatistics):
|
||||
self.input = _input or Input()
|
||||
self.outputs = _outputs or []
|
||||
|
||||
# Types
|
||||
self._input_type, self._input_length = None, None
|
||||
self._output_type = None
|
||||
|
||||
# Stack: context decorators for the execution
|
||||
self._stack = None
|
||||
|
||||
@ -48,22 +63,40 @@ class NodeExecutionContext(BaseContext, WithStatistics):
|
||||
return '<{}({}{}){}>'.format(type_name, self.status, name, self.get_statistics_as_string(prefix=' '))
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Starts this context, a.k.a the phase where you setup everything which will be necessary during the whole
|
||||
lifetime of a transformation.
|
||||
|
||||
The "ContextCurrifier" is in charge of setting up a decorating stack, that includes both services and context
|
||||
processors, and will call the actual node callable with additional parameters.
|
||||
|
||||
"""
|
||||
super().start()
|
||||
|
||||
try:
|
||||
self._stack = ContextCurrifier(self.wrapped, *self._get_initial_context())
|
||||
initial = self._get_initial_context()
|
||||
self._stack = ContextCurrifier(self.wrapped, *initial.args, **initial.kwargs)
|
||||
if isconfigurabletype(self.wrapped):
|
||||
# Not normal to have a partially configured object here, so let's warn the user instead of having get into
|
||||
# the hard trouble of understanding that by himself.
|
||||
raise TypeError(
|
||||
'The Configurable should be fully instanciated by now, unfortunately I got a PartiallyConfigured object...'
|
||||
'Configurables should be instanciated before execution starts.\nGot {!r}.\n'.format(self.wrapped)
|
||||
)
|
||||
self._stack.setup(self)
|
||||
except Exception:
|
||||
return self.fatal(sys.exc_info())
|
||||
# Set the logging level to the lowest possible, to avoid double log.
|
||||
self.fatal(sys.exc_info(), level=0)
|
||||
|
||||
# We raise again, so the error is not ignored out of execution loops.
|
||||
raise
|
||||
|
||||
def loop(self):
|
||||
"""
|
||||
The actual infinite loop for this transformation.
|
||||
|
||||
"""
|
||||
logger.debug('Node loop starts for {!r}.'.format(self))
|
||||
|
||||
while self.should_loop:
|
||||
try:
|
||||
self.step()
|
||||
@ -72,29 +105,31 @@ class NodeExecutionContext(BaseContext, WithStatistics):
|
||||
except Empty:
|
||||
sleep(TICK_PERIOD) # XXX: How do we determine this constant?
|
||||
continue
|
||||
except UnrecoverableError:
|
||||
self.handle_error(*sys.exc_info())
|
||||
self.input.shutdown()
|
||||
break
|
||||
except (
|
||||
NotImplementedError,
|
||||
UnrecoverableError,
|
||||
):
|
||||
self.fatal(sys.exc_info()) # exit loop
|
||||
except Exception: # pylint: disable=broad-except
|
||||
self.handle_error(*sys.exc_info())
|
||||
self.error(sys.exc_info()) # does not exit loop
|
||||
except BaseException:
|
||||
self.handle_error(*sys.exc_info())
|
||||
break
|
||||
self.fatal(sys.exc_info()) # exit loop
|
||||
|
||||
logger.debug('Node loop ends for {!r}.'.format(self))
|
||||
|
||||
def step(self):
|
||||
"""Runs a transformation callable with given args/kwargs and flush the result into the right
|
||||
output channel."""
|
||||
"""
|
||||
A single step in the loop.
|
||||
|
||||
# Pull data
|
||||
input_bag = self.get()
|
||||
Basically gets an input bag, send it to the node, interpret the results.
|
||||
|
||||
"""
|
||||
|
||||
# Pull and check data
|
||||
input_bag = self._get()
|
||||
|
||||
# Sent through the stack
|
||||
try:
|
||||
results = input_bag.apply(self._stack)
|
||||
except Exception:
|
||||
return self.handle_error(*sys.exc_info())
|
||||
results = self._stack(input_bag)
|
||||
|
||||
# self._exec_time += timer.duration
|
||||
# Put data onto output channels
|
||||
@ -109,32 +144,85 @@ class NodeExecutionContext(BaseContext, WithStatistics):
|
||||
except StopIteration:
|
||||
# That's not an error, we're just done.
|
||||
break
|
||||
except Exception:
|
||||
# Let's kill this loop, won't be able to generate next.
|
||||
self.handle_error(*sys.exc_info())
|
||||
break
|
||||
else:
|
||||
self.send(_resolve(input_bag, result))
|
||||
# Push data (in case of an iterator)
|
||||
self._send(self._cast(input_bag, result))
|
||||
elif results:
|
||||
self.send(_resolve(input_bag, results))
|
||||
# Push data (returned value)
|
||||
self._send(self._cast(input_bag, results))
|
||||
else:
|
||||
# case with no result, an execution went through anyway, use for stats.
|
||||
# self._exec_count += 1
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Cleanup the context, after the loop ended.
|
||||
|
||||
"""
|
||||
if self._stack:
|
||||
self._stack.teardown()
|
||||
try:
|
||||
self._stack.teardown()
|
||||
except:
|
||||
self.fatal(sys.exc_info())
|
||||
|
||||
super().stop()
|
||||
|
||||
def handle_error(self, exctype, exc, tb, *, level=logging.ERROR):
|
||||
self.increment('err')
|
||||
logging.getLogger(__name__).log(level, repr(self), exc_info=(exctype, exc, tb))
|
||||
def send(self, *_output, _input=None):
|
||||
return self._send(self._cast(_input, _output))
|
||||
|
||||
def fatal(self, exc_info, *, level=logging.CRITICAL):
|
||||
super().fatal(exc_info, level=level)
|
||||
self.input.shutdown()
|
||||
### Input type and fields
|
||||
@property
|
||||
def input_type(self):
|
||||
return self._input_type
|
||||
|
||||
def set_input_type(self, input_type):
|
||||
if self._input_type is not None:
|
||||
raise RuntimeError('Cannot override input type, already have %r.', self._input_type)
|
||||
|
||||
if type(input_type) is not type:
|
||||
raise UnrecoverableTypeError('Input types must be regular python types.')
|
||||
|
||||
if not issubclass(input_type, tuple):
|
||||
raise UnrecoverableTypeError('Input types must be subclasses of tuple (and act as tuples).')
|
||||
|
||||
self._input_type = input_type
|
||||
|
||||
def get_input_fields(self):
|
||||
return self._input_type._fields if self._input_type and hasattr(self._input_type, '_fields') else None
|
||||
|
||||
def set_input_fields(self, fields, typename='Bag'):
|
||||
self.set_input_type(BagType(typename, fields))
|
||||
|
||||
### Output type and fields
|
||||
@property
|
||||
def output_type(self):
|
||||
return self._output_type
|
||||
|
||||
def set_output_type(self, output_type):
|
||||
if self._output_type is not None:
|
||||
raise RuntimeError('Cannot override output type, already have %r.', self._output_type)
|
||||
|
||||
if type(output_type) is not type:
|
||||
raise UnrecoverableTypeError('Output types must be regular python types.')
|
||||
|
||||
if not issubclass(output_type, tuple):
|
||||
raise UnrecoverableTypeError('Output types must be subclasses of tuple (and act as tuples).')
|
||||
|
||||
self._output_type = output_type
|
||||
|
||||
def get_output_fields(self):
|
||||
return self._output_type._fields if self._output_type and hasattr(self._output_type, '_fields') else None
|
||||
|
||||
def set_output_fields(self, fields, typename='Bag'):
|
||||
self.set_output_type(BagType(typename, fields))
|
||||
|
||||
### Attributes
|
||||
def setdefault(self, attr, value):
|
||||
try:
|
||||
getattr(self, attr)
|
||||
except AttributeError:
|
||||
setattr(self, attr, value)
|
||||
|
||||
def write(self, *messages):
|
||||
"""
|
||||
@ -143,14 +231,83 @@ class NodeExecutionContext(BaseContext, WithStatistics):
|
||||
:param mixed value: message
|
||||
"""
|
||||
for message in messages:
|
||||
self.input.put(message if isinstance(message, (Bag, Token)) else Bag(message))
|
||||
if isinstance(message, Token):
|
||||
self.input.put(message)
|
||||
elif self._input_type:
|
||||
self.input.put(ensure_tuple(message, cls=self._input_type))
|
||||
else:
|
||||
self.input.put(ensure_tuple(message))
|
||||
|
||||
def write_sync(self, *messages):
|
||||
self.write(BEGIN, *messages, END)
|
||||
for _ in messages:
|
||||
self.step()
|
||||
|
||||
def send(self, value, _control=False):
|
||||
def error(self, exc_info, *, level=logging.ERROR):
|
||||
self.increment('err')
|
||||
super().error(exc_info, level=level)
|
||||
|
||||
def fatal(self, exc_info, *, level=logging.CRITICAL):
|
||||
self.increment('err')
|
||||
super().fatal(exc_info, level=level)
|
||||
self.input.shutdown()
|
||||
|
||||
def get_service(self, name):
|
||||
if self.parent:
|
||||
return self.parent.services.get(name)
|
||||
return self.services.get(name)
|
||||
|
||||
def _get(self):
|
||||
"""
|
||||
Read from the input queue.
|
||||
|
||||
If Queue raises (like Timeout or Empty), stat won't be changed.
|
||||
|
||||
"""
|
||||
input_bag = self.input.get()
|
||||
|
||||
# Store or check input type
|
||||
if self._input_type is None:
|
||||
self._input_type = type(input_bag)
|
||||
elif type(input_bag) is not self._input_type:
|
||||
raise UnrecoverableTypeError(
|
||||
'Input type changed between calls to {!r}.\nGot {!r} which is not of type {!r}.'.format(
|
||||
self.wrapped, input_bag, self._input_type
|
||||
)
|
||||
)
|
||||
|
||||
# Store or check input length, which is a soft fallback in case we're just using tuples
|
||||
if self._input_length is None:
|
||||
self._input_length = len(input_bag)
|
||||
elif len(input_bag) != self._input_length:
|
||||
raise UnrecoverableTypeError(
|
||||
'Input length changed between calls to {!r}.\nExpected {} but got {}: {!r}.'.format(
|
||||
self.wrapped, self._input_length, len(input_bag), input_bag
|
||||
)
|
||||
)
|
||||
|
||||
self.increment('in') # XXX should that go before type check ?
|
||||
|
||||
return input_bag
|
||||
|
||||
def _cast(self, _input, _output):
|
||||
"""
|
||||
Transforms a pair of input/output into what is the real output.
|
||||
|
||||
:param _input: Bag
|
||||
:param _output: mixed
|
||||
:return: Bag
|
||||
"""
|
||||
|
||||
if _output is NOT_MODIFIED:
|
||||
if self._output_type is None:
|
||||
return _input
|
||||
else:
|
||||
return self._output_type(*_input)
|
||||
|
||||
return ensure_tuple(_output, cls=(self.output_type or tuple))
|
||||
|
||||
def _send(self, value, _control=False):
|
||||
"""
|
||||
Sends a message to all of this context's outputs.
|
||||
|
||||
@ -161,29 +318,15 @@ class NodeExecutionContext(BaseContext, WithStatistics):
|
||||
if not _control:
|
||||
self.increment('out')
|
||||
|
||||
if iserrorbag(value):
|
||||
value.apply(self.handle_error)
|
||||
elif isloopbackbag(value):
|
||||
self.input.put(value)
|
||||
else:
|
||||
for output in self.outputs:
|
||||
output.put(value)
|
||||
|
||||
def get(self):
|
||||
"""
|
||||
Get from the queue first, then increment stats, so if Queue raise Timeout or Empty, stat won't be changed.
|
||||
|
||||
"""
|
||||
row = self.input.get() # XXX TIMEOUT ???
|
||||
self.increment('in')
|
||||
return row
|
||||
for output in self.outputs:
|
||||
output.put(value)
|
||||
|
||||
def _get_initial_context(self):
|
||||
if self.parent:
|
||||
return self.parent.services.args_for(self.wrapped)
|
||||
return UnboundArguments((), self.parent.services.kwargs_for(self.wrapped))
|
||||
if self.services:
|
||||
return self.services.args_for(self.wrapped)
|
||||
return ()
|
||||
return UnboundArguments((), self.services.kwargs_for(self.wrapped))
|
||||
return UnboundArguments((), {})
|
||||
|
||||
|
||||
def isflag(param):
|
||||
@ -210,23 +353,3 @@ def split_tokens(output):
|
||||
i += 1
|
||||
|
||||
return output[:i], output[i:]
|
||||
|
||||
|
||||
def _resolve(input_bag, output):
|
||||
"""
|
||||
This function is key to how bonobo works (and internal, too). It transforms a pair of input/output into what is the
|
||||
real output.
|
||||
|
||||
:param input_bag: Bag
|
||||
:param output: mixed
|
||||
:return: Bag
|
||||
"""
|
||||
if isbag(output):
|
||||
return output
|
||||
|
||||
tokens, output = split_tokens(output)
|
||||
|
||||
if len(tokens) == 1 and tokens[0] is NOT_MODIFIED:
|
||||
return input_bag
|
||||
|
||||
return output if isbag(output) else Bag(output)
|
||||
|
||||
@ -3,10 +3,8 @@ import logging
|
||||
import sys
|
||||
from concurrent.futures import Executor, ProcessPoolExecutor, ThreadPoolExecutor
|
||||
|
||||
from bonobo.structs.bags import Bag
|
||||
from bonobo.constants import BEGIN, END
|
||||
from bonobo.execution.strategies.base import Strategy
|
||||
from bonobo.util import get_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -24,7 +22,7 @@ class ExecutorStrategy(Strategy):
|
||||
|
||||
def execute(self, graph, **kwargs):
|
||||
context = self.create_graph_execution_context(graph, **kwargs)
|
||||
context.write(BEGIN, Bag(), END)
|
||||
context.write(BEGIN, (), END)
|
||||
|
||||
futures = []
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
from bonobo.constants import BEGIN, END
|
||||
from bonobo.execution.strategies.base import Strategy
|
||||
from bonobo.structs.bags import Bag
|
||||
|
||||
|
||||
class NaiveStrategy(Strategy):
|
||||
@ -8,7 +7,7 @@ class NaiveStrategy(Strategy):
|
||||
|
||||
def execute(self, graph, **kwargs):
|
||||
context = self.create_graph_execution_context(graph, **kwargs)
|
||||
context.write(BEGIN, Bag(), END)
|
||||
context.write(BEGIN, (), END)
|
||||
|
||||
# start
|
||||
context.start()
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
import functools
|
||||
import itertools
|
||||
import operator
|
||||
import pprint
|
||||
from functools import reduce
|
||||
|
||||
from bonobo.util import ensure_tuple
|
||||
from mondrian import term
|
||||
|
||||
from bonobo import settings
|
||||
from bonobo.config import Configurable, Option
|
||||
from bonobo.config.processors import ContextProcessor
|
||||
from bonobo.constants import NOT_MODIFIED, ARGNAMES
|
||||
from bonobo.structs.bags import Bag
|
||||
from bonobo.config import Configurable, Option, Method, use_raw_input, use_context, use_no_input
|
||||
from bonobo.config.functools import transformation_factory
|
||||
from bonobo.config.processors import ContextProcessor, use_context_processor
|
||||
from bonobo.constants import NOT_MODIFIED
|
||||
from bonobo.util.objects import ValueHolder
|
||||
from bonobo.util.term import CLEAR_EOL
|
||||
|
||||
@ -14,11 +20,9 @@ __all__ = [
|
||||
'Limit',
|
||||
'PrettyPrinter',
|
||||
'Tee',
|
||||
'Update',
|
||||
'arg0_to_kwargs',
|
||||
'SetFields',
|
||||
'count',
|
||||
'identity',
|
||||
'kwargs_to_arg0',
|
||||
'noop',
|
||||
]
|
||||
|
||||
@ -35,6 +39,8 @@ class Limit(Configurable):
|
||||
|
||||
Number of rows to let go through.
|
||||
|
||||
TODO: simplify into a closure building factory?
|
||||
|
||||
"""
|
||||
limit = Option(positional=True, default=10)
|
||||
|
||||
@ -42,7 +48,7 @@ class Limit(Configurable):
|
||||
def counter(self, context):
|
||||
yield ValueHolder(0)
|
||||
|
||||
def call(self, counter, *args, **kwargs):
|
||||
def __call__(self, counter, *args, **kwargs):
|
||||
counter += 1
|
||||
if counter <= self.limit:
|
||||
yield NOT_MODIFIED
|
||||
@ -60,17 +66,6 @@ def Tee(f):
|
||||
return wrapped
|
||||
|
||||
|
||||
def count(counter, *args, **kwargs):
|
||||
counter += 1
|
||||
|
||||
|
||||
@ContextProcessor.decorate(count)
|
||||
def _count_counter(self, context):
|
||||
counter = ValueHolder(0)
|
||||
yield counter
|
||||
context.send(Bag(counter._value))
|
||||
|
||||
|
||||
def _shorten(s, w):
|
||||
if w and len(s) > w:
|
||||
s = s[0:w - 3] + '...'
|
||||
@ -80,87 +75,73 @@ def _shorten(s, w):
|
||||
class PrettyPrinter(Configurable):
|
||||
max_width = Option(
|
||||
int,
|
||||
default=term.get_size()[0],
|
||||
required=False,
|
||||
__doc__='''
|
||||
If set, truncates the output values longer than this to this width.
|
||||
'''
|
||||
)
|
||||
|
||||
def call(self, *args, **kwargs):
|
||||
formater = self._format_quiet if settings.QUIET.get() else self._format_console
|
||||
argnames = kwargs.get(ARGNAMES, None)
|
||||
filter = Method(
|
||||
default=
|
||||
(lambda self, index, key, value: (value is not None) and (not isinstance(key, str) or not key.startswith('_'))),
|
||||
__doc__='''
|
||||
A filter that determine what to print.
|
||||
|
||||
Default is to ignore any key starting with an underscore and none values.
|
||||
'''
|
||||
)
|
||||
|
||||
for i, (item, value) in enumerate(
|
||||
itertools.chain(enumerate(args), filter(lambda x: not x[0].startswith('_'), kwargs.items()))
|
||||
):
|
||||
print(formater(i, item, value, argnames=argnames))
|
||||
@ContextProcessor
|
||||
def context(self, context):
|
||||
yield context
|
||||
|
||||
def _format_quiet(self, i, item, value, *, argnames=None):
|
||||
def __call__(self, context, *args, **kwargs):
|
||||
quiet = settings.QUIET.get()
|
||||
formater = self._format_quiet if quiet else self._format_console
|
||||
|
||||
if not quiet:
|
||||
print('\u250e' + '\u2500' * (self.max_width - 1))
|
||||
|
||||
for index, (key, value) in enumerate(itertools.chain(enumerate(args), kwargs.items())):
|
||||
if self.filter(index, key, value):
|
||||
print(formater(index, key, value, fields=context.get_input_fields()))
|
||||
|
||||
if not quiet:
|
||||
print('\u2516' + '\u2500' * (self.max_width - 1))
|
||||
|
||||
return NOT_MODIFIED
|
||||
|
||||
def _format_quiet(self, index, key, value, *, fields=None):
|
||||
# XXX should we implement argnames here ?
|
||||
return ' '.join(((' ' if i else '-'), str(item), ':', str(value).strip()))
|
||||
return ' '.join(((' ' if index else '-'), str(key), ':', str(value).strip()))
|
||||
|
||||
def _format_console(self, i, item, value, *, argnames=None):
|
||||
argnames = argnames or []
|
||||
if not isinstance(item, str):
|
||||
if len(argnames) >= item:
|
||||
item = '{} / {}'.format(item, argnames[item])
|
||||
def _format_console(self, index, key, value, *, fields=None):
|
||||
fields = fields or []
|
||||
if not isinstance(key, str):
|
||||
if len(fields) >= key and str(key) != str(fields[key]):
|
||||
key = '{}{}'.format(fields[key], term.lightblack('[{}]'.format(key)))
|
||||
else:
|
||||
item = str(i)
|
||||
key = str(index)
|
||||
|
||||
return ' '.join(
|
||||
(
|
||||
(' ' if i else '•'), item, '=', _shorten(str(value).strip(),
|
||||
self.max_width).replace('\n', '\n' + CLEAR_EOL), CLEAR_EOL
|
||||
)
|
||||
)
|
||||
prefix = '\u2503 {} = '.format(key)
|
||||
prefix_length = len(prefix)
|
||||
|
||||
def indent(text, prefix):
|
||||
for i, line in enumerate(text.splitlines()):
|
||||
yield (prefix if i else '') + line + CLEAR_EOL + '\n'
|
||||
|
||||
repr_of_value = ''.join(
|
||||
indent(pprint.pformat(value, width=self.max_width - prefix_length), '\u2503' + ' ' * (len(prefix) - 1))
|
||||
).strip()
|
||||
return '{}{}{}'.format(prefix, repr_of_value.replace('\n', CLEAR_EOL + '\n'), CLEAR_EOL)
|
||||
|
||||
|
||||
def noop(*args, **kwargs): # pylint: disable=unused-argument
|
||||
from bonobo.constants import NOT_MODIFIED
|
||||
@use_no_input
|
||||
def noop(*args, **kwargs):
|
||||
return NOT_MODIFIED
|
||||
|
||||
|
||||
def arg0_to_kwargs(row):
|
||||
"""
|
||||
Transform items in a stream from "arg0" format (each call only has one positional argument, which is a dict-like
|
||||
object) to "kwargs" format (each call only has keyword arguments that represent a row).
|
||||
|
||||
:param row:
|
||||
:return: bonobo.Bag
|
||||
"""
|
||||
return Bag(**row)
|
||||
|
||||
|
||||
def kwargs_to_arg0(**row):
|
||||
"""
|
||||
Transform items in a stream from "kwargs" format (each call only has keyword arguments that represent a row) to
|
||||
"arg0" format (each call only has one positional argument, which is a dict-like object) .
|
||||
|
||||
:param **row:
|
||||
:return: bonobo.Bag
|
||||
"""
|
||||
return Bag(row)
|
||||
|
||||
|
||||
def Update(*consts, **kwconsts):
|
||||
"""
|
||||
Transformation factory to update a stream with constant values, by appending to args and updating kwargs.
|
||||
|
||||
:param consts: what to append to the input stream args
|
||||
:param kwconsts: what to use to update input stream kwargs
|
||||
:return: function
|
||||
|
||||
"""
|
||||
|
||||
def update(*args, **kwargs):
|
||||
nonlocal consts, kwconsts
|
||||
return (*args, *consts, {**kwargs, **kwconsts})
|
||||
|
||||
update.__name__ = 'Update({})'.format(Bag.format_args(*consts, **kwconsts))
|
||||
|
||||
return update
|
||||
|
||||
|
||||
class FixedWindow(Configurable):
|
||||
"""
|
||||
Transformation factory to create fixed windows of inputs, as lists.
|
||||
@ -176,10 +157,112 @@ class FixedWindow(Configurable):
|
||||
def buffer(self, context):
|
||||
buffer = yield ValueHolder([])
|
||||
if len(buffer):
|
||||
context.send(Bag(buffer.get()))
|
||||
last_value = buffer.get()
|
||||
last_value += [None] * (self.length - len(last_value))
|
||||
context.send(*last_value)
|
||||
|
||||
def call(self, buffer, x):
|
||||
buffer.append(x)
|
||||
@use_raw_input
|
||||
def __call__(self, buffer, bag):
|
||||
buffer.append(bag)
|
||||
if len(buffer) >= self.length:
|
||||
yield buffer.get()
|
||||
yield tuple(buffer.get())
|
||||
buffer.set([])
|
||||
|
||||
|
||||
@transformation_factory
|
||||
def SetFields(fields):
|
||||
@use_context
|
||||
@use_no_input
|
||||
def _SetFields(context):
|
||||
nonlocal fields
|
||||
if not context.output_type:
|
||||
context.set_output_fields(fields)
|
||||
return NOT_MODIFIED
|
||||
|
||||
return _SetFields
|
||||
|
||||
|
||||
@transformation_factory
|
||||
def UnpackItems(*items, fields=None, defaults=None):
|
||||
"""
|
||||
>>> UnpackItems(0)
|
||||
|
||||
:param items:
|
||||
:param fields:
|
||||
:param defaults:
|
||||
:return: callable
|
||||
"""
|
||||
defaults = defaults or {}
|
||||
|
||||
@use_context
|
||||
@use_raw_input
|
||||
def _UnpackItems(context, bag):
|
||||
nonlocal fields, items, defaults
|
||||
|
||||
if fields is None:
|
||||
fields = ()
|
||||
for item in items:
|
||||
fields += tuple(bag[item].keys())
|
||||
context.set_output_fields(fields)
|
||||
|
||||
values = ()
|
||||
for item in items:
|
||||
values += tuple(bag[item].get(field, defaults.get(field)) for field in fields)
|
||||
|
||||
return values
|
||||
|
||||
return _UnpackItems
|
||||
|
||||
|
||||
@transformation_factory
|
||||
def Rename(**translations):
|
||||
# XXX todo handle duplicated
|
||||
|
||||
fields = None
|
||||
translations = {v: k for k, v in translations.items()}
|
||||
|
||||
@use_context
|
||||
@use_raw_input
|
||||
def _Rename(context, bag):
|
||||
nonlocal fields, translations
|
||||
|
||||
if not fields:
|
||||
fields = tuple(translations.get(field, field) for field in context.get_input_fields())
|
||||
context.set_output_fields(fields)
|
||||
|
||||
return NOT_MODIFIED
|
||||
|
||||
return _Rename
|
||||
|
||||
|
||||
@transformation_factory
|
||||
def Format(**formats):
|
||||
fields, newfields = None, None
|
||||
|
||||
@use_context
|
||||
@use_raw_input
|
||||
def _Format(context, bag):
|
||||
nonlocal fields, newfields, formats
|
||||
|
||||
if not context.output_type:
|
||||
fields = context.input_type._fields
|
||||
newfields = tuple(field for field in formats if not field in fields)
|
||||
context.set_output_fields(fields + newfields)
|
||||
|
||||
return tuple(
|
||||
formats[field].format(**bag._asdict()) if field in formats else bag.get(field)
|
||||
for field in fields + newfields
|
||||
)
|
||||
|
||||
return _Format
|
||||
|
||||
|
||||
def _count(self, context):
|
||||
counter = yield ValueHolder(0)
|
||||
context.send(counter.get())
|
||||
|
||||
|
||||
@use_no_input
|
||||
@use_context_processor(_count)
|
||||
def count(counter):
|
||||
counter += 1
|
||||
|
||||
@ -21,6 +21,6 @@ class Filter(Configurable):
|
||||
|
||||
filter = Method()
|
||||
|
||||
def call(self, *args, **kwargs):
|
||||
def __call__(self, *args, **kwargs):
|
||||
if self.filter(*args, **kwargs):
|
||||
return NOT_MODIFIED
|
||||
|
||||
@ -5,10 +5,11 @@ class FileHandler(Configurable):
|
||||
"""Abstract component factory for file-related components.
|
||||
|
||||
Args:
|
||||
fs (str): service name to use for filesystem.
|
||||
path (str): which path to use within the provided filesystem.
|
||||
eol (str): which character to use to separate lines.
|
||||
mode (str): which mode to use when opening the file.
|
||||
fs (str): service name to use for filesystem.
|
||||
encoding (str): which encoding to use when opening the file.
|
||||
"""
|
||||
|
||||
path = Option(str, required=True, positional=True) # type: str
|
||||
@ -19,7 +20,7 @@ class FileHandler(Configurable):
|
||||
fs = Service('fs') # type: str
|
||||
|
||||
@ContextProcessor
|
||||
def file(self, context, fs):
|
||||
def file(self, context, *, fs):
|
||||
with self.open(fs) as file:
|
||||
yield file
|
||||
|
||||
@ -28,22 +29,8 @@ class FileHandler(Configurable):
|
||||
|
||||
|
||||
class Reader:
|
||||
"""Abstract component factory for readers.
|
||||
"""
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
yield from self.read(*args, **kwargs)
|
||||
|
||||
def read(self, *args, **kwargs):
|
||||
raise NotImplementedError('Abstract.')
|
||||
pass
|
||||
|
||||
|
||||
class Writer:
|
||||
"""Abstract component factory for writers.
|
||||
"""
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.write(*args, **kwargs)
|
||||
|
||||
def write(self, *args, **kwargs):
|
||||
raise NotImplementedError('Abstract.')
|
||||
pass
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import csv
|
||||
import warnings
|
||||
|
||||
from bonobo.config import Option, ContextProcessor
|
||||
from bonobo.config.options import RemovedOption, Method
|
||||
from bonobo.constants import NOT_MODIFIED, ARGNAMES
|
||||
from bonobo.config import Option, use_raw_input, use_context
|
||||
from bonobo.config.options import Method, RenamedOption
|
||||
from bonobo.constants import NOT_MODIFIED
|
||||
from bonobo.nodes.io.base import FileHandler
|
||||
from bonobo.nodes.io.file import FileReader, FileWriter
|
||||
from bonobo.structs.bags import Bag
|
||||
from bonobo.util import ensure_tuple
|
||||
from bonobo.util.bags import BagType
|
||||
|
||||
|
||||
class CsvHandler(FileHandler):
|
||||
@ -21,17 +20,38 @@ class CsvHandler(FileHandler):
|
||||
|
||||
The CSV quote character.
|
||||
|
||||
.. attribute:: headers
|
||||
.. attribute:: fields
|
||||
|
||||
The list of column names, if the CSV does not contain it as its first line.
|
||||
|
||||
"""
|
||||
delimiter = Option(str, default=';')
|
||||
quotechar = Option(str, default='"')
|
||||
headers = Option(ensure_tuple, required=False)
|
||||
ioformat = RemovedOption(positional=False, value='kwargs')
|
||||
|
||||
# Dialect related options
|
||||
delimiter = Option(str, default=csv.excel.delimiter, required=False)
|
||||
quotechar = Option(str, default=csv.excel.quotechar, required=False)
|
||||
escapechar = Option(str, default=csv.excel.escapechar, required=False)
|
||||
doublequote = Option(str, default=csv.excel.doublequote, required=False)
|
||||
skipinitialspace = Option(str, default=csv.excel.skipinitialspace, required=False)
|
||||
lineterminator = Option(str, default=csv.excel.lineterminator, required=False)
|
||||
quoting = Option(str, default=csv.excel.quoting, required=False)
|
||||
|
||||
# Fields (renamed from headers)
|
||||
headers = RenamedOption('fields')
|
||||
fields = Option(ensure_tuple, required=False)
|
||||
|
||||
def get_dialect_kwargs(self):
|
||||
return {
|
||||
'delimiter': self.delimiter,
|
||||
'quotechar': self.quotechar,
|
||||
'escapechar': self.escapechar,
|
||||
'doublequote': self.doublequote,
|
||||
'skipinitialspace': self.skipinitialspace,
|
||||
'lineterminator': self.lineterminator,
|
||||
'quoting': self.quoting,
|
||||
}
|
||||
|
||||
|
||||
@use_context
|
||||
class CsvReader(FileReader, CsvHandler):
|
||||
"""
|
||||
Reads a CSV and yield the values as dicts.
|
||||
@ -45,6 +65,7 @@ class CsvReader(FileReader, CsvHandler):
|
||||
skip = Option(int, default=0)
|
||||
|
||||
@Method(
|
||||
positional=False,
|
||||
__doc__='''
|
||||
Builds the CSV reader, a.k.a an object we can iterate, each iteration giving one line of fields, as an
|
||||
iterable.
|
||||
@ -53,20 +74,37 @@ class CsvReader(FileReader, CsvHandler):
|
||||
'''
|
||||
)
|
||||
def reader_factory(self, file):
|
||||
return csv.reader(file, delimiter=self.delimiter, quotechar=self.quotechar)
|
||||
return csv.reader(file, **self.get_dialect_kwargs())
|
||||
|
||||
def read(self, fs, file):
|
||||
def read(self, file, context, *, fs):
|
||||
context.setdefault('skipped', 0)
|
||||
reader = self.reader_factory(file)
|
||||
headers = self.headers or next(reader)
|
||||
skip = self.skip
|
||||
|
||||
if not context.output_type:
|
||||
context.set_output_fields(self.fields or next(reader))
|
||||
|
||||
for row in reader:
|
||||
yield Bag(*row, **{ARGNAMES: headers})
|
||||
if context.skipped < skip:
|
||||
context.skipped += 1
|
||||
continue
|
||||
yield tuple(row)
|
||||
|
||||
__call__ = read
|
||||
|
||||
|
||||
def get_values(args, *, fields):
|
||||
print(fields, args)
|
||||
|
||||
return
|
||||
if context.input_type and context.input_type is tuple:
|
||||
context.writer(bag[0:len(context.fields)])
|
||||
else:
|
||||
context.writer([bag.get(field) if type(field) is str else bag[field] for field in context.fields])
|
||||
|
||||
|
||||
@use_context
|
||||
class CsvWriter(FileWriter, CsvHandler):
|
||||
@ContextProcessor
|
||||
def context(self, context, *args):
|
||||
yield context
|
||||
|
||||
@Method(
|
||||
__doc__='''
|
||||
Builds the CSV writer, a.k.a an object we can pass a field collection to be written as one line in the
|
||||
@ -76,34 +114,31 @@ class CsvWriter(FileWriter, CsvHandler):
|
||||
'''
|
||||
)
|
||||
def writer_factory(self, file):
|
||||
return csv.writer(file, delimiter=self.delimiter, quotechar=self.quotechar, lineterminator=self.eol).writerow
|
||||
return csv.writer(file, **self.get_dialect_kwargs()).writerow
|
||||
|
||||
def write(self, fs, file, lineno, context, *args, _argnames=None):
|
||||
try:
|
||||
writer = context.writer
|
||||
except AttributeError:
|
||||
def write(self, file, context, *values, fs):
|
||||
context.setdefault('lineno', 0)
|
||||
fields = context.get_input_fields()
|
||||
|
||||
if not context.lineno:
|
||||
context.writer = self.writer_factory(file)
|
||||
writer = context.writer
|
||||
context.headers = self.headers or _argnames
|
||||
|
||||
if context.headers and not lineno:
|
||||
writer(context.headers)
|
||||
if fields:
|
||||
context.writer(fields)
|
||||
context.lineno += 1
|
||||
|
||||
lineno += 1
|
||||
|
||||
if context.headers:
|
||||
try:
|
||||
row = [args[i] for i, header in enumerate(context.headers) if header]
|
||||
except IndexError:
|
||||
warnings.warn(
|
||||
'At line #{}, expected {} fields but only got {}. Padding with empty strings.'.format(
|
||||
lineno, len(context.headers), len(args)
|
||||
if fields:
|
||||
if len(values) != len(fields):
|
||||
raise ValueError(
|
||||
'Values length differs from input fields length. Expected: {}. Got: {}. Values: {!r}.'.format(
|
||||
len(fields), len(values), values
|
||||
)
|
||||
)
|
||||
row = [(args[i] if i < len(args) else '') for i, header in enumerate(context.headers) if header]
|
||||
context.writer(values)
|
||||
else:
|
||||
row = args
|
||||
|
||||
writer(row)
|
||||
for arg in values:
|
||||
context.writer(ensure_tuple(arg))
|
||||
|
||||
return NOT_MODIFIED
|
||||
|
||||
__call__ = write
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
from bonobo.config import Option
|
||||
from bonobo.config.processors import ContextProcessor
|
||||
from bonobo.config import Option, ContextProcessor, use_context
|
||||
from bonobo.constants import NOT_MODIFIED
|
||||
from bonobo.errors import UnrecoverableError
|
||||
from bonobo.nodes.io.base import FileHandler, Reader, Writer
|
||||
from bonobo.util.objects import ValueHolder
|
||||
from bonobo.util import ensure_tuple
|
||||
|
||||
|
||||
class FileReader(Reader, FileHandler):
|
||||
@ -14,7 +14,44 @@ class FileReader(Reader, FileHandler):
|
||||
|
||||
mode = Option(str, default='r')
|
||||
|
||||
def read(self, fs, file):
|
||||
output_fields = Option(
|
||||
ensure_tuple,
|
||||
required=False,
|
||||
__doc__='''
|
||||
Specify the field names of output lines.
|
||||
Mutually exclusive with "output_type".
|
||||
'''
|
||||
)
|
||||
output_type = Option(
|
||||
required=False,
|
||||
__doc__='''
|
||||
Specify the type of output lines.
|
||||
Mutually exclusive with "output_fields".
|
||||
'''
|
||||
)
|
||||
|
||||
@ContextProcessor
|
||||
def output(self, context, *args, **kwargs):
|
||||
"""
|
||||
Allow all readers to use eventually use output_fields XOR output_type options.
|
||||
|
||||
"""
|
||||
|
||||
output_fields = self.output_fields
|
||||
output_type = self.output_type
|
||||
|
||||
if output_fields and output_type:
|
||||
raise UnrecoverableError('Cannot specify both output_fields and output_type option.')
|
||||
|
||||
if self.output_type:
|
||||
context.set_output_type(self.output_type)
|
||||
|
||||
if self.output_fields:
|
||||
context.set_output_fields(self.output_fields)
|
||||
|
||||
yield
|
||||
|
||||
def read(self, file, *, fs):
|
||||
"""
|
||||
Write a row on the next line of given file.
|
||||
Prefix is used for newlines.
|
||||
@ -22,7 +59,10 @@ class FileReader(Reader, FileHandler):
|
||||
for line in file:
|
||||
yield line.rstrip(self.eol)
|
||||
|
||||
__call__ = read
|
||||
|
||||
|
||||
@use_context
|
||||
class FileWriter(Writer, FileHandler):
|
||||
"""Component factory for file or file-like writers.
|
||||
|
||||
@ -32,18 +72,16 @@ class FileWriter(Writer, FileHandler):
|
||||
|
||||
mode = Option(str, default='w+')
|
||||
|
||||
@ContextProcessor
|
||||
def lineno(self, context, fs, file):
|
||||
lineno = ValueHolder(0)
|
||||
yield lineno
|
||||
|
||||
def write(self, fs, file, lineno, line):
|
||||
def write(self, file, context, line, *, fs):
|
||||
"""
|
||||
Write a row on the next line of opened file in context.
|
||||
"""
|
||||
self._write_line(file, (self.eol if lineno.value else '') + line)
|
||||
lineno += 1
|
||||
context.setdefault('lineno', 0)
|
||||
self._write_line(file, (self.eol if context.lineno else '') + line)
|
||||
context.lineno += 1
|
||||
return NOT_MODIFIED
|
||||
|
||||
def _write_line(self, file, line):
|
||||
return file.write(line)
|
||||
|
||||
__call__ = write
|
||||
|
||||
@ -1,78 +1,86 @@
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
from bonobo.config.options import RemovedOption
|
||||
from bonobo.config.processors import ContextProcessor
|
||||
from bonobo.config import Method
|
||||
from bonobo.config.processors import ContextProcessor, use_context
|
||||
from bonobo.constants import NOT_MODIFIED
|
||||
from bonobo.nodes.io.base import FileHandler
|
||||
from bonobo.nodes.io.file import FileReader, FileWriter
|
||||
from bonobo.structs.bags import Bag
|
||||
|
||||
|
||||
class JsonHandler(FileHandler):
|
||||
eol = ',\n'
|
||||
prefix, suffix = '[', ']'
|
||||
ioformat = RemovedOption(positional=False, value='kwargs')
|
||||
|
||||
|
||||
class JsonReader(FileReader, JsonHandler):
|
||||
loader = staticmethod(json.load)
|
||||
|
||||
def read(self, fs, file):
|
||||
for line in self.loader(file):
|
||||
yield line
|
||||
class LdjsonHandler(FileHandler):
|
||||
eol = '\n'
|
||||
prefix, suffix = '', ''
|
||||
|
||||
|
||||
class JsonDictItemsReader(JsonReader):
|
||||
def read(self, fs, file):
|
||||
for line in self.loader(file).items():
|
||||
yield Bag(*line)
|
||||
class JsonReader(JsonHandler, FileReader):
|
||||
@Method(positional=False)
|
||||
def loader(self, file):
|
||||
return json.loads(file)
|
||||
|
||||
def read(self, file, *, fs):
|
||||
yield from self.loader(file.read())
|
||||
|
||||
__call__ = read
|
||||
|
||||
|
||||
class JsonWriter(FileWriter, JsonHandler):
|
||||
class LdjsonReader(LdjsonHandler, JsonReader):
|
||||
"""
|
||||
Read a stream of line-delimited JSON objects (one object per line).
|
||||
|
||||
Not to be mistaken with JSON-LD (where LD stands for linked data).
|
||||
|
||||
"""
|
||||
|
||||
def read(self, file, *, fs):
|
||||
yield from map(self.loader, file)
|
||||
|
||||
__call__ = read
|
||||
|
||||
|
||||
@use_context
|
||||
class JsonWriter(JsonHandler, FileWriter):
|
||||
@ContextProcessor
|
||||
def envelope(self, context, fs, file, lineno):
|
||||
def envelope(self, context, file, *, fs):
|
||||
file.write(self.prefix)
|
||||
yield
|
||||
file.write(self.suffix)
|
||||
|
||||
def write(self, fs, file, lineno, arg0=None, **kwargs):
|
||||
def write(self, file, context, *args, fs):
|
||||
"""
|
||||
Write a json row on the next line of file pointed by ctx.file.
|
||||
|
||||
:param ctx:
|
||||
:param row:
|
||||
"""
|
||||
row = _getrow(arg0, kwargs)
|
||||
self._write_line(file, (self.eol if lineno.value else '') + json.dumps(row))
|
||||
lineno += 1
|
||||
context.setdefault('lineno', 0)
|
||||
fields = context.get_input_fields()
|
||||
|
||||
if fields:
|
||||
prefix = self.eol if context.lineno else ''
|
||||
self._write_line(file, prefix + json.dumps(OrderedDict(zip(fields, args))))
|
||||
context.lineno += 1
|
||||
else:
|
||||
for arg in args:
|
||||
prefix = self.eol if context.lineno else ''
|
||||
self._write_line(file, prefix + json.dumps(arg))
|
||||
context.lineno += 1
|
||||
|
||||
return NOT_MODIFIED
|
||||
|
||||
|
||||
class LdjsonReader(FileReader):
|
||||
"""Read a stream of JSON objects, one object per line."""
|
||||
loader = staticmethod(json.loads)
|
||||
|
||||
def read(self, fs, file):
|
||||
for line in file:
|
||||
yield self.loader(line)
|
||||
__call__ = write
|
||||
|
||||
|
||||
class LdjsonWriter(FileWriter):
|
||||
"""Write a stream of JSON objects, one object per line."""
|
||||
@use_context
|
||||
class LdjsonWriter(LdjsonHandler, JsonWriter):
|
||||
"""
|
||||
Write a stream of Line-delimited JSON objects (one object per line).
|
||||
|
||||
def write(self, fs, file, lineno, arg0=None, **kwargs):
|
||||
row = _getrow(arg0, kwargs)
|
||||
file.write(json.dumps(row) + '\n')
|
||||
lineno += 1 # class-level variable
|
||||
return NOT_MODIFIED
|
||||
Not to be mistaken with JSON-LD (where LD stands for linked data).
|
||||
|
||||
|
||||
def _getrow(arg0, kwargs):
|
||||
if len(kwargs):
|
||||
assert arg0 is None, 'Got both positional and keyword arguments, I recommend using keyword arguments.'
|
||||
return kwargs
|
||||
|
||||
if arg0 is not None:
|
||||
return arg0
|
||||
|
||||
return kwargs
|
||||
"""
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import pickle
|
||||
|
||||
from bonobo.config import Option
|
||||
from bonobo.config.processors import ContextProcessor
|
||||
from bonobo.config import Option, use_context
|
||||
from bonobo.constants import NOT_MODIFIED
|
||||
from bonobo.nodes.io.base import FileHandler
|
||||
from bonobo.nodes.io.file import FileReader, FileWriter
|
||||
from bonobo.util.objects import ValueHolder
|
||||
|
||||
|
||||
class PickleHandler(FileHandler):
|
||||
@ -17,9 +15,10 @@ class PickleHandler(FileHandler):
|
||||
|
||||
"""
|
||||
|
||||
item_names = Option(tuple, required=False)
|
||||
fields = Option(tuple, required=False)
|
||||
|
||||
|
||||
@use_context
|
||||
class PickleReader(FileReader, PickleHandler):
|
||||
"""
|
||||
Reads a Python pickle object and yields the items in dicts.
|
||||
@ -27,11 +26,7 @@ class PickleReader(FileReader, PickleHandler):
|
||||
|
||||
mode = Option(str, default='rb')
|
||||
|
||||
@ContextProcessor
|
||||
def pickle_headers(self, context, fs, file):
|
||||
yield ValueHolder(self.item_names)
|
||||
|
||||
def read(self, fs, file, pickle_headers):
|
||||
def read(self, file, context, *, fs):
|
||||
data = pickle.load(file)
|
||||
|
||||
# if the data is not iterable, then wrap the object in a list so it may be iterated
|
||||
@ -45,28 +40,31 @@ class PickleReader(FileReader, PickleHandler):
|
||||
except TypeError:
|
||||
iterator = iter([data])
|
||||
|
||||
if not pickle_headers.get():
|
||||
pickle_headers.set(next(iterator))
|
||||
if not context.output_type:
|
||||
context.set_output_fields(self.fields or next(iterator))
|
||||
fields = context.get_output_fields()
|
||||
fields_length = len(fields)
|
||||
|
||||
item_count = len(pickle_headers.value)
|
||||
for row in iterator:
|
||||
if len(row) != fields_length:
|
||||
raise ValueError('Received an object with {} items, expected {}.'.format(len(row), fields_length))
|
||||
|
||||
for i in iterator:
|
||||
if len(i) != item_count:
|
||||
raise ValueError('Received an object with %d items, expecting %d.' % (
|
||||
len(i),
|
||||
item_count,
|
||||
))
|
||||
yield tuple(row.values() if is_dict else row)
|
||||
|
||||
yield dict(zip(i)) if is_dict else dict(zip(pickle_headers.value, i))
|
||||
__call__ = read
|
||||
|
||||
|
||||
@use_context
|
||||
class PickleWriter(FileWriter, PickleHandler):
|
||||
mode = Option(str, default='wb')
|
||||
|
||||
def write(self, fs, file, lineno, item):
|
||||
def write(self, file, context, item, *, fs):
|
||||
"""
|
||||
Write a pickled item to the opened file.
|
||||
"""
|
||||
context.setdefault('lineno', 0)
|
||||
file.write(pickle.dumps(item))
|
||||
lineno += 1
|
||||
context.lineno += 1
|
||||
return NOT_MODIFIED
|
||||
|
||||
__call__ = write
|
||||
|
||||
@ -47,6 +47,6 @@ class RateLimited(Configurable):
|
||||
bucket.stop()
|
||||
bucket.join()
|
||||
|
||||
def call(self, bucket, *args, **kwargs):
|
||||
def __call__(self, bucket, *args, **kwargs):
|
||||
bucket.wait()
|
||||
return self.handler(*args, **kwargs)
|
||||
|
||||
@ -95,30 +95,26 @@ class ConsoleOutputPlugin(Plugin):
|
||||
|
||||
liveliness_color = alive_color if node.alive else dead_color
|
||||
liveliness_prefix = ' {}{}{} '.format(liveliness_color, node.status, Style.RESET_ALL)
|
||||
_line = ''.join(
|
||||
(
|
||||
liveliness_prefix,
|
||||
node.name,
|
||||
name_suffix,
|
||||
' ',
|
||||
node.get_statistics_as_string(),
|
||||
' ',
|
||||
node.get_flags_as_string(),
|
||||
Style.RESET_ALL,
|
||||
' ',
|
||||
)
|
||||
)
|
||||
_line = ''.join((
|
||||
liveliness_prefix,
|
||||
node.name,
|
||||
name_suffix,
|
||||
' ',
|
||||
node.get_statistics_as_string(),
|
||||
' ',
|
||||
node.get_flags_as_string(),
|
||||
Style.RESET_ALL,
|
||||
' ',
|
||||
))
|
||||
print(prefix + _line + CLEAR_EOL, file=self._stderr)
|
||||
|
||||
if append:
|
||||
# todo handle multiline
|
||||
print(
|
||||
''.join(
|
||||
(
|
||||
' `-> ', ' '.join('{}{}{}: {}'.format(Style.BRIGHT, k, Style.RESET_ALL, v) for k, v in append),
|
||||
CLEAR_EOL
|
||||
)
|
||||
),
|
||||
''.join((
|
||||
' `-> ', ' '.join('{}{}{}: {}'.format(Style.BRIGHT, k, Style.RESET_ALL, v) for k, v in append),
|
||||
CLEAR_EOL
|
||||
)),
|
||||
file=self._stderr
|
||||
)
|
||||
t_cnt += 1
|
||||
@ -132,10 +128,9 @@ class ConsoleOutputPlugin(Plugin):
|
||||
if self.counter % 10 and self._append_cache:
|
||||
append = self._append_cache
|
||||
else:
|
||||
self._append_cache = append = (
|
||||
('Memory', '{0:.2f} Mb'.format(memory_usage())),
|
||||
# ('Total time', '{0} s'.format(execution_time(harness))),
|
||||
)
|
||||
self._append_cache = append = (('Memory', '{0:.2f} Mb'.format(memory_usage())),
|
||||
# ('Total time', '{0} s'.format(execution_time(harness))),
|
||||
)
|
||||
else:
|
||||
append = ()
|
||||
self.write(context, prefix=self.prefix, append=append, rewind=rewind)
|
||||
|
||||
6
bonobo/plugins/sentry.py
Normal file
6
bonobo/plugins/sentry.py
Normal file
@ -0,0 +1,6 @@
|
||||
from bonobo.plugins import Plugin
|
||||
from raven import Client
|
||||
|
||||
|
||||
class SentryPlugin(Plugin):
|
||||
pass
|
||||
@ -1,10 +1,5 @@
|
||||
from bonobo.structs.bags import Bag, ErrorBag
|
||||
from bonobo.structs.graphs import Graph
|
||||
from bonobo.structs.tokens import Token
|
||||
|
||||
__all__ = [
|
||||
'Bag',
|
||||
'ErrorBag',
|
||||
'Graph',
|
||||
'Token',
|
||||
]
|
||||
|
||||
@ -1,193 +0,0 @@
|
||||
import itertools
|
||||
|
||||
from bonobo.constants import INHERIT_INPUT, LOOPBACK
|
||||
from bonobo.structs.tokens import Token
|
||||
|
||||
__all__ = [
|
||||
'Bag',
|
||||
'ErrorBag',
|
||||
]
|
||||
|
||||
|
||||
class Bag:
|
||||
"""
|
||||
Bags are simple datastructures that holds arguments and keyword arguments together, that may be applied to a
|
||||
callable.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from bonobo import Bag
|
||||
>>> def myfunc(foo, *, bar):
|
||||
... print(foo, bar)
|
||||
...
|
||||
>>> bag = Bag('foo', bar='baz')
|
||||
>>> bag.apply(myfunc)
|
||||
foo baz
|
||||
|
||||
A bag can inherit another bag, allowing to override only a few arguments without touching the parent.
|
||||
|
||||
Example:
|
||||
|
||||
>>> bag2 = Bag(bar='notbaz', _parent=bag)
|
||||
>>> bag2.apply(myfunc)
|
||||
foo notbaz
|
||||
|
||||
"""
|
||||
|
||||
default_flags = ()
|
||||
|
||||
@staticmethod
|
||||
def format_args(*args, **kwargs):
|
||||
return ', '.join(itertools.chain(map(repr, args), ('{}={!r}'.format(k, v) for k, v in kwargs.items())))
|
||||
|
||||
def __new__(cls, *args, _flags=None, _parent=None, **kwargs):
|
||||
# Handle the special case where we call Bag's constructor with only one bag or token as argument.
|
||||
if len(args) == 1 and len(kwargs) == 0:
|
||||
if isinstance(args[0], Bag):
|
||||
raise ValueError('Bag cannot be instanciated with a bag (for now ...).')
|
||||
|
||||
if isinstance(args[0], Token):
|
||||
return args[0]
|
||||
|
||||
# Otherwise, type will handle that for us.
|
||||
return super().__new__(cls)
|
||||
|
||||
def __init__(self, *args, _flags=None, _parent=None, _argnames=None, **kwargs):
|
||||
self._flags = type(self).default_flags + (_flags or ())
|
||||
self._argnames = _argnames
|
||||
self._parent = _parent
|
||||
|
||||
if len(args) == 1 and len(kwargs) == 0:
|
||||
# If we only have one argument, that may be because we're using the shorthand syntax.
|
||||
mixed = args[0]
|
||||
|
||||
if isinstance(mixed, Bag):
|
||||
# Just duplicate the bag.
|
||||
self._args = mixed.args
|
||||
self._kwargs = mixed.kwargs
|
||||
elif isinstance(mixed, tuple):
|
||||
if not len(mixed):
|
||||
# Empty bag.
|
||||
self._args = ()
|
||||
self._kwargs = {}
|
||||
elif isinstance(mixed[-1], dict):
|
||||
# Args + Kwargs
|
||||
self._args = mixed[:-1]
|
||||
self._kwargs = mixed[-1]
|
||||
else:
|
||||
# Args only
|
||||
self._args = mixed
|
||||
self._kwargs = {}
|
||||
elif isinstance(mixed, dict):
|
||||
# Kwargs only
|
||||
self._args = ()
|
||||
self._kwargs = mixed
|
||||
else:
|
||||
self._args = args
|
||||
self._kwargs = {}
|
||||
|
||||
else:
|
||||
# Otherwise, lets get args/kwargs from the constructor.
|
||||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
|
||||
def __repr__(self):
|
||||
return 'Bag({})'.format(Bag.format_args(*self.args, **self.kwargs))
|
||||
|
||||
@property
|
||||
def args(self):
|
||||
if self._parent is None:
|
||||
return self._args
|
||||
return (
|
||||
*self._parent.args,
|
||||
*self._args,
|
||||
)
|
||||
|
||||
@property
|
||||
def kwargs(self):
|
||||
if self._parent is None:
|
||||
return self._kwargs
|
||||
return {
|
||||
**self._parent.kwargs,
|
||||
**self._kwargs,
|
||||
}
|
||||
|
||||
@property
|
||||
def flags(self):
|
||||
return self._flags
|
||||
|
||||
@property
|
||||
def specials(self):
|
||||
return {k: self.__dict__[k] for k in ('_argnames', ) if k in self.__dict__ and self.__dict__[k]}
|
||||
|
||||
def apply(self, func_or_iter, *args, **kwargs):
|
||||
if callable(func_or_iter):
|
||||
return func_or_iter(*args, *self.args, **kwargs, **self.kwargs, **self.specials)
|
||||
|
||||
if len(args) == 0 and len(kwargs) == 0:
|
||||
try:
|
||||
iter(func_or_iter)
|
||||
|
||||
def generator():
|
||||
yield from func_or_iter
|
||||
|
||||
return generator()
|
||||
except TypeError as exc:
|
||||
raise TypeError('Could not apply bag to {}.'.format(func_or_iter)) from exc
|
||||
|
||||
raise TypeError('Could not apply bag to {}.'.format(func_or_iter))
|
||||
|
||||
def get(self):
|
||||
"""
|
||||
Get a 2 element tuple of this bag's args and kwargs.
|
||||
|
||||
:return: tuple
|
||||
"""
|
||||
return self.args, self.kwargs
|
||||
|
||||
def extend(self, *args, **kwargs):
|
||||
return type(self)(*args, _parent=self, **kwargs)
|
||||
|
||||
def set_parent(self, parent):
|
||||
self._parent = parent
|
||||
|
||||
@classmethod
|
||||
def inherit(cls, *args, **kwargs):
|
||||
return cls(*args, _flags=(INHERIT_INPUT, ), **kwargs)
|
||||
|
||||
def __eq__(self, other):
|
||||
# XXX there are overlapping cases, but this is very handy for now. Let's think about it later.
|
||||
|
||||
# bag
|
||||
if isinstance(other, Bag) and other.args == self.args and other.kwargs == self.kwargs:
|
||||
return True
|
||||
|
||||
# tuple
|
||||
if isinstance(other, tuple):
|
||||
# self == ()
|
||||
if not len(other):
|
||||
return not self.args and not self.kwargs
|
||||
|
||||
if isinstance(other[-1], dict):
|
||||
# self == (*args, {**kwargs}) ?
|
||||
return other[:-1] == self.args and other[-1] == self.kwargs
|
||||
|
||||
# self == (*args) ?
|
||||
return other == self.args and not self.kwargs
|
||||
|
||||
# dict (aka kwargs)
|
||||
if isinstance(other, dict) and not self.args and other == self.kwargs:
|
||||
return True
|
||||
|
||||
return len(self.args) == 1 and not self.kwargs and self.args[0] == other
|
||||
|
||||
def args_as_dict(self):
|
||||
return dict(zip(self._argnames, self.args))
|
||||
|
||||
|
||||
class LoopbackBag(Bag):
|
||||
default_flags = (LOOPBACK, )
|
||||
|
||||
|
||||
class ErrorBag(Bag):
|
||||
pass
|
||||
@ -1,12 +1,16 @@
|
||||
import html
|
||||
import json
|
||||
from collections import namedtuple
|
||||
from copy import copy
|
||||
|
||||
from graphviz import ExecutableNotFound
|
||||
from graphviz.dot import Digraph
|
||||
|
||||
from bonobo.constants import BEGIN
|
||||
from bonobo.util import get_name
|
||||
|
||||
GraphRange = namedtuple('GraphRange', ['graph', 'input', 'output'])
|
||||
|
||||
|
||||
class Graph:
|
||||
"""
|
||||
@ -51,15 +55,19 @@ class Graph:
|
||||
if len(nodes):
|
||||
_input = self._resolve_index(_input)
|
||||
_output = self._resolve_index(_output)
|
||||
_first = None
|
||||
_last = None
|
||||
|
||||
for i, node in enumerate(nodes):
|
||||
_next = self.add_node(node)
|
||||
_last = self.add_node(node)
|
||||
if not i and _name:
|
||||
if _name in self.named:
|
||||
raise KeyError('Duplicate name {!r} in graph.'.format(_name))
|
||||
self.named[_name] = _next
|
||||
self.outputs_of(_input, create=True).add(_next)
|
||||
_input = _next
|
||||
self.named[_name] = _last
|
||||
if not _first:
|
||||
_first = _last
|
||||
self.outputs_of(_input, create=True).add(_last)
|
||||
_input = _last
|
||||
|
||||
if _output is not None:
|
||||
self.outputs_of(_input, create=True).add(_output)
|
||||
@ -67,7 +75,8 @@ class Graph:
|
||||
if hasattr(self, '_topologcally_sorted_indexes_cache'):
|
||||
del self._topologcally_sorted_indexes_cache
|
||||
|
||||
return self
|
||||
return GraphRange(self, _first, _last)
|
||||
return GraphRange(self, None, None)
|
||||
|
||||
def copy(self):
|
||||
g = Graph()
|
||||
@ -135,11 +144,11 @@ class Graph:
|
||||
def _repr_dot_(self):
|
||||
return str(self.graphviz)
|
||||
|
||||
def _repr_svg_(self):
|
||||
return self.graphviz._repr_svg_()
|
||||
|
||||
def _repr_html_(self):
|
||||
return '<div>{}</div><pre>{}</pre>'.format(self.graphviz._repr_svg_(), html.escape(repr(self)))
|
||||
try:
|
||||
return '<div>{}</div><pre>{}</pre>'.format(self.graphviz._repr_svg_(), html.escape(repr(self)))
|
||||
except (ExecutableNotFound, FileNotFoundError) as exc:
|
||||
return '<strong>{}</strong>: {}'.format(type(exc).__name__, str(exc))
|
||||
|
||||
def _resolve_index(self, mixed):
|
||||
""" Find the index based on various strategies for a node, probably an input or output of chain. Supported inputs are indexes, node values or names.
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
class Token:
|
||||
"""Factory for signal oriented queue messages or other token types."""
|
||||
|
||||
def __init__(self, name):
|
||||
self.__name__ = name
|
||||
|
||||
def __repr__(self):
|
||||
return '<{}>'.format(self.__name__)
|
||||
@ -2,13 +2,10 @@ from bonobo.util.collections import ensure_tuple, sortedlist, tuplize
|
||||
from bonobo.util.compat import deprecated, deprecated_alias
|
||||
from bonobo.util.inspect import (
|
||||
inspect_node,
|
||||
isbag,
|
||||
isconfigurable,
|
||||
isconfigurabletype,
|
||||
iscontextprocessor,
|
||||
isdict,
|
||||
iserrorbag,
|
||||
isloopbackbag,
|
||||
ismethod,
|
||||
isoption,
|
||||
istuple,
|
||||
@ -25,13 +22,10 @@ __all__ = [
|
||||
'get_attribute_or_create',
|
||||
'get_name',
|
||||
'inspect_node',
|
||||
'isbag',
|
||||
'isconfigurable',
|
||||
'isconfigurabletype',
|
||||
'iscontextprocessor',
|
||||
'isdict',
|
||||
'iserrorbag',
|
||||
'isloopbackbag',
|
||||
'ismethod',
|
||||
'isoption',
|
||||
'istype',
|
||||
|
||||
187
bonobo/util/bags.py
Normal file
187
bonobo/util/bags.py
Normal file
@ -0,0 +1,187 @@
|
||||
import functools
|
||||
import re
|
||||
import sys
|
||||
from keyword import iskeyword
|
||||
|
||||
from slugify import slugify
|
||||
|
||||
_class_template = '''\
|
||||
from builtins import property as _property, tuple as _tuple
|
||||
from operator import itemgetter as _itemgetter
|
||||
from collections import OrderedDict
|
||||
|
||||
class {typename}(tuple):
|
||||
'{typename}({arg_list})'
|
||||
|
||||
__slots__ = ()
|
||||
_attrs = {attrs!r}
|
||||
_fields = {fields!r}
|
||||
|
||||
def __new__(_cls, {arg_list}):
|
||||
"""
|
||||
Create new instance of {typename}({arg_list})
|
||||
|
||||
"""
|
||||
return _tuple.__new__(_cls, ({arg_list}))
|
||||
|
||||
def __getnewargs__(self):
|
||||
"""
|
||||
Return self as a plain tuple.
|
||||
Used by copy and pickle.
|
||||
|
||||
"""
|
||||
return tuple(self)
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Return a nicely formatted representation string
|
||||
|
||||
"""
|
||||
return self.__class__.__name__ + '({repr_fmt})' % self
|
||||
|
||||
def get(self, field, default=None):
|
||||
try:
|
||||
index = self._fields.index(field)
|
||||
except ValueError:
|
||||
return default
|
||||
return self[index]
|
||||
|
||||
@classmethod
|
||||
def _make(cls, iterable, new=tuple.__new__, len=len):
|
||||
'Make a new {typename} object from a sequence or iterable'
|
||||
result = new(cls, iterable)
|
||||
if len(result) != {num_fields:d}:
|
||||
raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result))
|
||||
return result
|
||||
|
||||
def _replace(_self, **kwds):
|
||||
'Return a new {typename} object replacing specified fields with new values'
|
||||
result = _self._make(map(kwds.pop, {fields!r}, _self))
|
||||
if kwds:
|
||||
raise ValueError('Got unexpected field names: %r' % list(kwds))
|
||||
return result
|
||||
|
||||
def _asdict(self):
|
||||
"""
|
||||
Return a new OrderedDict which maps field names to their values.
|
||||
|
||||
"""
|
||||
return OrderedDict(zip(self._fields, self))
|
||||
|
||||
{field_defs}
|
||||
'''
|
||||
|
||||
_field_template = '''\
|
||||
{name} = _property(_itemgetter({index:d}), doc={doc!r})
|
||||
'''.strip('\n')
|
||||
|
||||
_reserved = frozenset(
|
||||
['_', '_cls', '_attrs', '_fields', 'get', '_asdict', '_replace', '_make', 'self', '_self', 'tuple'] + dir(tuple)
|
||||
)
|
||||
|
||||
_multiple_underscores_pattern = re.compile('__+')
|
||||
_slugify_allowed_chars_pattern = re.compile(r'[^a-z0-9_]+', flags=re.IGNORECASE)
|
||||
|
||||
|
||||
def _uniquify(f):
|
||||
seen = set(_reserved)
|
||||
|
||||
@functools.wraps(f)
|
||||
def _uniquified(x):
|
||||
nonlocal f, seen
|
||||
x = str(x)
|
||||
v = v0 = _multiple_underscores_pattern.sub('_', f(x))
|
||||
i = 0
|
||||
# if last character is not "allowed", let's start appending indexes right from the first iteration
|
||||
if len(x) and _slugify_allowed_chars_pattern.match(x[-1]):
|
||||
v = '{}{}'.format(v0, i)
|
||||
while v in seen:
|
||||
v = '{}{}'.format(v0, i)
|
||||
i += 1
|
||||
seen.add(v)
|
||||
return v
|
||||
|
||||
return _uniquified
|
||||
|
||||
|
||||
def _make_valid_attr_name(x):
|
||||
if iskeyword(x):
|
||||
x = '_' + x
|
||||
if x.isidentifier():
|
||||
return x
|
||||
x = slugify(x, separator='_', regex_pattern=_slugify_allowed_chars_pattern)
|
||||
if x.isidentifier():
|
||||
return x
|
||||
x = '_' + x
|
||||
if x.isidentifier():
|
||||
return x
|
||||
raise ValueError(x)
|
||||
|
||||
|
||||
def BagType(typename, fields, *, verbose=False, module=None):
|
||||
# Validate the field names. At the user's option, either generate an error
|
||||
# message or automatically replace the field name with a valid name.
|
||||
|
||||
attrs = tuple(map(_uniquify(_make_valid_attr_name), fields))
|
||||
if type(fields) is str:
|
||||
raise TypeError('BagType does not support providing fields as a string.')
|
||||
fields = list(map(str, fields))
|
||||
typename = str(typename)
|
||||
|
||||
for i, name in enumerate([typename] + fields):
|
||||
if type(name) is not str:
|
||||
raise TypeError('Type names and field names must be strings, got {name!r}'.format(name=name))
|
||||
if not i:
|
||||
if not name.isidentifier():
|
||||
raise ValueError('Type names must be valid identifiers: {name!r}'.format(name=name))
|
||||
if iskeyword(name):
|
||||
raise ValueError('Type names cannot be a keyword: {name!r}'.format(name=name))
|
||||
|
||||
seen = set()
|
||||
for name in fields:
|
||||
if name in seen:
|
||||
raise ValueError('Encountered duplicate field name: {name!r}'.format(name=name))
|
||||
seen.add(name)
|
||||
|
||||
# Fill-in the class template
|
||||
class_definition = _class_template.format(
|
||||
typename=typename,
|
||||
fields=tuple(fields),
|
||||
attrs=attrs,
|
||||
num_fields=len(fields),
|
||||
arg_list=repr(attrs).replace("'", "")[1:-1],
|
||||
repr_fmt=', '.join(('%r' if isinstance(fields[index], int) else '{name}=%r').format(name=name)
|
||||
for index, name in enumerate(attrs)),
|
||||
field_defs='\n'.join(
|
||||
_field_template.format(
|
||||
index=index,
|
||||
name=name,
|
||||
doc='Alias for ' +
|
||||
('field #{}'.format(index) if isinstance(fields[index], int) else repr(fields[index]))
|
||||
) for index, name in enumerate(attrs)
|
||||
)
|
||||
)
|
||||
|
||||
# Execute the template string in a temporary namespace and support
|
||||
# tracing utilities by setting a value for frame.f_globals['__name__']
|
||||
namespace = dict(__name__='namedtuple_%s' % typename)
|
||||
exec(class_definition, namespace)
|
||||
result = namespace[typename]
|
||||
result._source = class_definition
|
||||
if verbose:
|
||||
print(result._source)
|
||||
|
||||
# For pickling to work, the __module__ variable needs to be set to the frame
|
||||
# where the named tuple is created. Bypass this step in environments where
|
||||
# sys._getframe is not defined (Jython for example) or sys._getframe is not
|
||||
# defined for arguments greater than 0 (IronPython), or where the user has
|
||||
# specified a particular module.
|
||||
if module is None:
|
||||
try:
|
||||
module = sys._getframe(1).f_globals.get('__name__', '__main__')
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
if module is not None:
|
||||
result.__module__ = module
|
||||
|
||||
return result
|
||||
@ -7,7 +7,7 @@ class sortedlist(list):
|
||||
bisect.insort(self, x)
|
||||
|
||||
|
||||
def ensure_tuple(tuple_or_mixed):
|
||||
def ensure_tuple(tuple_or_mixed, *, cls=tuple):
|
||||
"""
|
||||
If it's not a tuple, let's make a tuple of one item.
|
||||
Otherwise, not changed.
|
||||
@ -16,11 +16,17 @@ def ensure_tuple(tuple_or_mixed):
|
||||
:return: tuple
|
||||
|
||||
"""
|
||||
if tuple_or_mixed is None:
|
||||
return ()
|
||||
if isinstance(tuple_or_mixed, tuple):
|
||||
|
||||
if isinstance(tuple_or_mixed, cls):
|
||||
return tuple_or_mixed
|
||||
return (tuple_or_mixed, )
|
||||
|
||||
if tuple_or_mixed is None:
|
||||
return tuple.__new__(cls, ())
|
||||
|
||||
if isinstance(tuple_or_mixed, tuple):
|
||||
return tuple.__new__(cls, tuple_or_mixed)
|
||||
|
||||
return tuple.__new__(cls, (tuple_or_mixed, ))
|
||||
|
||||
|
||||
def tuplize(generator):
|
||||
|
||||
@ -12,16 +12,20 @@ def isconfigurable(mixed):
|
||||
return isinstance(mixed, Configurable)
|
||||
|
||||
|
||||
def isconfigurabletype(mixed):
|
||||
def isconfigurabletype(mixed, *, strict=False):
|
||||
"""
|
||||
Check if the given argument is an instance of :class:`bonobo.config.ConfigurableMeta`, meaning it has all the
|
||||
plumbery necessary to build :class:`bonobo.config.Configurable`-like instances.
|
||||
|
||||
:param mixed:
|
||||
:param strict: should we consider partially configured objects?
|
||||
:return: bool
|
||||
"""
|
||||
from bonobo.config.configurables import ConfigurableMeta
|
||||
return isinstance(mixed, ConfigurableMeta)
|
||||
from bonobo.config.configurables import ConfigurableMeta, PartiallyConfigured
|
||||
return isinstance(mixed, (ConfigurableMeta, ) if strict else (
|
||||
ConfigurableMeta,
|
||||
PartiallyConfigured,
|
||||
))
|
||||
|
||||
|
||||
def isoption(mixed):
|
||||
@ -88,39 +92,6 @@ def istuple(mixed):
|
||||
return isinstance(mixed, tuple)
|
||||
|
||||
|
||||
def isbag(mixed):
|
||||
"""
|
||||
Check if the given argument is an instance of a :class:`bonobo.Bag`.
|
||||
|
||||
:param mixed:
|
||||
:return: bool
|
||||
"""
|
||||
from bonobo.structs.bags import Bag
|
||||
return isinstance(mixed, Bag)
|
||||
|
||||
|
||||
def iserrorbag(mixed):
|
||||
"""
|
||||
Check if the given argument is an instance of an :class:`bonobo.ErrorBag`.
|
||||
|
||||
:param mixed:
|
||||
:return: bool
|
||||
"""
|
||||
from bonobo.structs.bags import ErrorBag
|
||||
return isinstance(mixed, ErrorBag)
|
||||
|
||||
|
||||
def isloopbackbag(mixed):
|
||||
"""
|
||||
Check if the given argument is an instance of a :class:`bonobo.Bag`, marked for loopback behaviour.
|
||||
|
||||
:param mixed:
|
||||
:return: bool
|
||||
"""
|
||||
from bonobo.constants import LOOPBACK
|
||||
return isbag(mixed) and LOOPBACK in mixed.flags
|
||||
|
||||
|
||||
ConfigurableInspection = namedtuple(
|
||||
'ConfigurableInspection', [
|
||||
'type',
|
||||
@ -149,7 +120,7 @@ def inspect_node(mixed, *, _partial=None):
|
||||
|
||||
:raise: TypeError
|
||||
"""
|
||||
if isconfigurabletype(mixed):
|
||||
if isconfigurabletype(mixed, strict=True):
|
||||
inst, typ = None, mixed
|
||||
elif isconfigurable(mixed):
|
||||
inst, typ = mixed, type(mixed)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import contextlib
|
||||
import functools
|
||||
import io
|
||||
import os
|
||||
@ -8,8 +9,9 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from bonobo import open_fs, Token, __main__, get_examples_path, Bag
|
||||
from bonobo import open_fs, __main__, get_examples_path
|
||||
from bonobo.commands import entrypoint
|
||||
from bonobo.constants import Token
|
||||
from bonobo.execution.contexts.graph import GraphExecutionContext
|
||||
from bonobo.execution.contexts.node import NodeExecutionContext
|
||||
|
||||
@ -24,9 +26,9 @@ def optional_contextmanager(cm, *, ignore=False):
|
||||
|
||||
|
||||
class FilesystemTester:
|
||||
def __init__(self, extension='txt', mode='w'):
|
||||
def __init__(self, extension='txt', mode='w', *, input_data=''):
|
||||
self.extension = extension
|
||||
self.input_data = ''
|
||||
self.input_data = input_data
|
||||
self.mode = mode
|
||||
|
||||
def get_services_for_reader(self, tmpdir):
|
||||
@ -58,7 +60,7 @@ class BufferingContext:
|
||||
return self.buffer
|
||||
|
||||
def get_buffer_args_as_dicts(self):
|
||||
return list(map(lambda x: x.args_as_dict() if isinstance(x, Bag) else dict(x), self.buffer))
|
||||
return [row._asdict() if hasattr(row, '_asdict') else dict(row) for row in self.buffer]
|
||||
|
||||
|
||||
class BufferingNodeExecutionContext(BufferingContext, NodeExecutionContext):
|
||||
@ -141,3 +143,121 @@ class EnvironmentTestCase():
|
||||
|
||||
assert err == ''
|
||||
return dict(map(lambda line: line.split(' ', 1), filter(None, out.split('\n'))))
|
||||
|
||||
|
||||
class StaticNodeTest:
|
||||
node = None
|
||||
services = {}
|
||||
|
||||
NodeExecutionContextType = BufferingNodeExecutionContext
|
||||
|
||||
@contextlib.contextmanager
|
||||
def execute(self, *args, **kwargs):
|
||||
with self.NodeExecutionContextType(type(self).node, services=self.services) as context:
|
||||
yield context
|
||||
|
||||
def call(self, *args, **kwargs):
|
||||
return type(self).node(*args, **kwargs)
|
||||
|
||||
|
||||
class ConfigurableNodeTest:
|
||||
NodeType = None
|
||||
NodeExecutionContextType = BufferingNodeExecutionContext
|
||||
|
||||
services = {}
|
||||
|
||||
@staticmethod
|
||||
def incontext(*create_args, **create_kwargs):
|
||||
def decorator(method):
|
||||
@functools.wraps(method)
|
||||
def _incontext(self, *args, **kwargs):
|
||||
nonlocal create_args, create_kwargs
|
||||
with self.execute(*create_args, **create_kwargs) as context:
|
||||
return method(self, context, *args, **kwargs)
|
||||
|
||||
return _incontext
|
||||
|
||||
return decorator
|
||||
|
||||
def create(self, *args, **kwargs):
|
||||
return self.NodeType(*self.get_create_args(*args), **self.get_create_kwargs(**kwargs))
|
||||
|
||||
@contextlib.contextmanager
|
||||
def execute(self, *args, **kwargs):
|
||||
with self.NodeExecutionContextType(self.create(*args, **kwargs), services=self.services) as context:
|
||||
yield context
|
||||
|
||||
def get_create_args(self, *args):
|
||||
return args
|
||||
|
||||
def get_create_kwargs(self, **kwargs):
|
||||
return kwargs
|
||||
|
||||
def get_filesystem_tester(self):
|
||||
return FilesystemTester(self.extension, input_data=self.input_data)
|
||||
|
||||
|
||||
class ReaderTest(ConfigurableNodeTest):
|
||||
""" Helper class to test reader transformations. """
|
||||
|
||||
ReaderNodeType = None
|
||||
|
||||
extension = 'txt'
|
||||
input_data = ''
|
||||
|
||||
@property
|
||||
def NodeType(self):
|
||||
return self.ReaderNodeType
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reader_test_fixture(self, tmpdir):
|
||||
fs_tester = self.get_filesystem_tester()
|
||||
self.fs, self.filename, self.services = fs_tester.get_services_for_reader(tmpdir)
|
||||
self.tmpdir = tmpdir
|
||||
|
||||
def get_create_args(self, *args):
|
||||
return (self.filename, ) + args
|
||||
|
||||
def test_customizable_output_type_transform_not_a_type(self):
|
||||
context = self.NodeExecutionContextType(
|
||||
self.create(*self.get_create_args(), output_type=str.upper, **self.get_create_kwargs()),
|
||||
services=self.services
|
||||
)
|
||||
with pytest.raises(TypeError):
|
||||
context.start()
|
||||
|
||||
def test_customizable_output_type_transform_not_a_tuple(self):
|
||||
context = self.NodeExecutionContextType(
|
||||
self.create(
|
||||
*self.get_create_args(), output_type=type('UpperString', (str, ), {}), **self.get_create_kwargs()
|
||||
),
|
||||
services=self.services
|
||||
)
|
||||
with pytest.raises(TypeError):
|
||||
context.start()
|
||||
|
||||
|
||||
class WriterTest(ConfigurableNodeTest):
|
||||
""" Helper class to test writer transformations. """
|
||||
|
||||
WriterNodeType = None
|
||||
|
||||
extension = 'txt'
|
||||
input_data = ''
|
||||
|
||||
@property
|
||||
def NodeType(self):
|
||||
return self.WriterNodeType
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _writer_test_fixture(self, tmpdir):
|
||||
fs_tester = self.get_filesystem_tester()
|
||||
self.fs, self.filename, self.services = fs_tester.get_services_for_writer(tmpdir)
|
||||
self.tmpdir = tmpdir
|
||||
|
||||
def get_create_args(self, *args):
|
||||
return (self.filename, ) + args
|
||||
|
||||
def readlines(self):
|
||||
with self.fs.open(self.filename) as fp:
|
||||
return tuple(map(str.strip, fp.readlines()))
|
||||
|
||||
@ -41,7 +41,7 @@ instances.
|
||||
class JoinDatabaseCategories(Configurable):
|
||||
database = Service('orders_database')
|
||||
|
||||
def call(self, database, row):
|
||||
def __call__(self, database, row):
|
||||
return {
|
||||
**row,
|
||||
'category': database.get_category_name_for_sku(row['sku'])
|
||||
|
||||
@ -206,7 +206,7 @@ can be used as a graph node, then use camelcase names:
|
||||
# configurable
|
||||
class ChangeCase(Configurable):
|
||||
modifier = Option(default='upper')
|
||||
def call(self, s: str) -> str:
|
||||
def __call__(self, s: str) -> str:
|
||||
return getattr(s, self.modifier)()
|
||||
|
||||
# transformation factory
|
||||
|
||||
@ -30,7 +30,7 @@ Configurables allows to use the following features:
|
||||
class PrefixIt(Configurable):
|
||||
prefix = Option(str, positional=True, default='>>>')
|
||||
|
||||
def call(self, row):
|
||||
def __call__(self, row):
|
||||
return self.prefix + ' ' + row
|
||||
|
||||
prefixer = PrefixIt('$')
|
||||
@ -48,7 +48,7 @@ Configurables allows to use the following features:
|
||||
url = Option(default='https://jsonplaceholder.typicode.com/users')
|
||||
http = Service('http.client')
|
||||
|
||||
def call(self, http):
|
||||
def __call__(self, http):
|
||||
resp = http.get(self.url)
|
||||
|
||||
for row in resp.json():
|
||||
@ -68,7 +68,7 @@ Configurables allows to use the following features:
|
||||
class Applier(Configurable):
|
||||
apply = Method()
|
||||
|
||||
def call(self, row):
|
||||
def __call__(self, row):
|
||||
return self.apply(row)
|
||||
|
||||
@Applier
|
||||
@ -114,7 +114,7 @@ Let's see how to use it, starting from the previous service example:
|
||||
url = Option(default='https://jsonplaceholder.typicode.com/users')
|
||||
http = Service('http.client')
|
||||
|
||||
def call(self, http):
|
||||
def __call__(self, http):
|
||||
resp = http.get(self.url)
|
||||
|
||||
for row in resp.json():
|
||||
|
||||
@ -30,7 +30,7 @@ Configurables allows to use the following features:
|
||||
class PrefixIt(Configurable):
|
||||
prefix = Option(str, positional=True, default='>>>')
|
||||
|
||||
def call(self, row):
|
||||
def __call__(self, row):
|
||||
return self.prefix + ' ' + row
|
||||
|
||||
prefixer = PrefixIt('$')
|
||||
@ -48,7 +48,7 @@ Configurables allows to use the following features:
|
||||
url = Option(default='https://jsonplaceholder.typicode.com/users')
|
||||
http = Service('http.client')
|
||||
|
||||
def call(self, http):
|
||||
def __call__(self, http):
|
||||
resp = http.get(self.url)
|
||||
|
||||
for row in resp.json():
|
||||
@ -68,7 +68,7 @@ Configurables allows to use the following features:
|
||||
class Applier(Configurable):
|
||||
apply = Method()
|
||||
|
||||
def call(self, row):
|
||||
def __call__(self, row):
|
||||
return self.apply(row)
|
||||
|
||||
@Applier
|
||||
@ -114,7 +114,7 @@ Let's see how to use it, starting from the previous service example:
|
||||
url = Option(default='https://jsonplaceholder.typicode.com/users')
|
||||
http = Service('http.client')
|
||||
|
||||
def call(self, http):
|
||||
def __call__(self, http):
|
||||
resp = http.get(self.url)
|
||||
|
||||
for row in resp.json():
|
||||
|
||||
@ -9,12 +9,12 @@ idna==2.6
|
||||
imagesize==0.7.1
|
||||
jinja2==2.10
|
||||
markupsafe==1.0
|
||||
py==1.4.34
|
||||
py==1.5.2
|
||||
pygments==2.2.0
|
||||
pytest-cov==2.5.1
|
||||
pytest-sugar==0.9.0
|
||||
pytest-timeout==1.2.0
|
||||
pytest==3.2.3
|
||||
pytest==3.2.5
|
||||
pytz==2017.3
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
@ -23,4 +23,4 @@ sphinx==1.6.5
|
||||
sphinxcontrib-websupport==1.0.1
|
||||
termcolor==1.1.0
|
||||
urllib3==1.22
|
||||
yapf==0.19.0
|
||||
yapf==0.20.0
|
||||
|
||||
@ -6,7 +6,7 @@ chardet==3.0.4
|
||||
colorama==0.3.9
|
||||
docker-pycreds==0.2.1
|
||||
docker==2.3.0
|
||||
fs==2.0.16
|
||||
fs==2.0.17
|
||||
idna==2.6
|
||||
packaging==16.8
|
||||
pbr==3.1.1
|
||||
|
||||
@ -32,7 +32,7 @@ pyzmq==17.0.0b3
|
||||
qtconsole==4.3.1
|
||||
simplegeneric==0.8.1
|
||||
six==1.11.0
|
||||
terminado==0.6
|
||||
terminado==0.7
|
||||
testpath==0.3.1
|
||||
tornado==4.5.2
|
||||
traitlets==4.3.2
|
||||
|
||||
@ -4,7 +4,7 @@ bonobo-sqlalchemy==0.5.1
|
||||
certifi==2017.11.5
|
||||
chardet==3.0.4
|
||||
colorama==0.3.9
|
||||
fs==2.0.16
|
||||
fs==2.0.17
|
||||
idna==2.6
|
||||
packaging==16.8
|
||||
pbr==3.1.1
|
||||
|
||||
@ -3,19 +3,21 @@ appdirs==1.4.3
|
||||
certifi==2017.11.5
|
||||
chardet==3.0.4
|
||||
colorama==0.3.9
|
||||
fs==2.0.16
|
||||
fs==2.0.17
|
||||
graphviz==0.8.1
|
||||
idna==2.6
|
||||
jinja2==2.10
|
||||
markupsafe==1.0
|
||||
mondrian==0.4.0
|
||||
mondrian==0.5.1
|
||||
packaging==16.8
|
||||
pbr==3.1.1
|
||||
psutil==5.4.1
|
||||
pyparsing==2.2.0
|
||||
python-slugify==1.2.4
|
||||
pytz==2017.3
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
stevedore==1.27.1
|
||||
unidecode==0.4.21
|
||||
urllib3==1.22
|
||||
whistle==1.0.0
|
||||
|
||||
18
setup.py
18
setup.py
@ -43,14 +43,12 @@ else:
|
||||
setup(
|
||||
author='Romain Dorgueil',
|
||||
author_email='romain@dorgueil.net',
|
||||
data_files=[
|
||||
(
|
||||
'share/jupyter/nbextensions/bonobo-jupyter', [
|
||||
'bonobo/contrib/jupyter/static/extension.js', 'bonobo/contrib/jupyter/static/index.js',
|
||||
'bonobo/contrib/jupyter/static/index.js.map'
|
||||
]
|
||||
)
|
||||
],
|
||||
data_files=[(
|
||||
'share/jupyter/nbextensions/bonobo-jupyter', [
|
||||
'bonobo/contrib/jupyter/static/extension.js', 'bonobo/contrib/jupyter/static/index.js',
|
||||
'bonobo/contrib/jupyter/static/index.js.map'
|
||||
]
|
||||
)],
|
||||
description=('Bonobo, a simple, modern and atomic extract-transform-load toolkit for '
|
||||
'python 3.5+.'),
|
||||
license='Apache License, Version 2.0',
|
||||
@ -62,8 +60,8 @@ setup(
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'colorama (>= 0.3)', 'fs (>= 2.0, < 2.1)', 'graphviz (>= 0.8, < 0.9)', 'jinja2 (>= 2.9, < 3)',
|
||||
'mondrian (>= 0.4, < 0.5)', 'packaging (>= 16, < 17)', 'psutil (>= 5.4, < 6)', 'requests (>= 2, < 3)',
|
||||
'stevedore (>= 1.27, < 1.28)', 'whistle (>= 1.0, < 1.1)'
|
||||
'mondrian (>= 0.5, < 0.6)', 'packaging (>= 16, < 17)', 'psutil (>= 5.4, < 6)', 'python-slugify (>= 1.2, < 1.3)',
|
||||
'requests (>= 2, < 3)', 'stevedore (>= 1.27, < 1.28)', 'whistle (>= 1.0, < 1.1)'
|
||||
],
|
||||
extras_require={
|
||||
'dev': [
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import pprint
|
||||
|
||||
import pytest
|
||||
|
||||
from bonobo.config.configurables import Configurable
|
||||
|
||||
@ -7,7 +7,7 @@ class MethodBasedConfigurable(Configurable):
|
||||
foo = Option(positional=True)
|
||||
bar = Option()
|
||||
|
||||
def call(self, *args, **kwargs):
|
||||
def __call__(self, *args, **kwargs):
|
||||
self.handler(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ class Bobby(Configurable):
|
||||
def think(self, context):
|
||||
yield 'different'
|
||||
|
||||
def call(self, think, *args, **kwargs):
|
||||
def __call__(self, think, *args, **kwargs):
|
||||
self.handler('1', *args, **kwargs)
|
||||
self.handler2('2', *args, **kwargs)
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from operator import attrgetter
|
||||
|
||||
from bonobo.config import Configurable
|
||||
from bonobo.config.processors import ContextProcessor, resolve_processors, ContextCurrifier
|
||||
from bonobo.config.processors import ContextProcessor, resolve_processors, ContextCurrifier, use_context_processor
|
||||
|
||||
|
||||
class CP1(Configurable):
|
||||
@ -59,5 +59,16 @@ def test_setup_teardown():
|
||||
o = CP1()
|
||||
stack = ContextCurrifier(o)
|
||||
stack.setup()
|
||||
assert o(*stack.context) == ('this is A', 'THIS IS b')
|
||||
assert o(*stack.args) == ('this is A', 'THIS IS b')
|
||||
stack.teardown()
|
||||
|
||||
|
||||
def test_processors_on_func():
|
||||
def cp(context):
|
||||
yield context
|
||||
|
||||
@use_context_processor(cp)
|
||||
def node(context):
|
||||
pass
|
||||
|
||||
assert get_all_processors_names(node) == ['cp']
|
||||
|
||||
@ -3,9 +3,9 @@ import time
|
||||
|
||||
import pytest
|
||||
|
||||
from bonobo.util import get_name
|
||||
from bonobo.config import Configurable, Container, Exclusive, Service, requires
|
||||
from bonobo.config import Configurable, Container, Exclusive, Service, use
|
||||
from bonobo.config.services import validate_service_name, create_container
|
||||
from bonobo.util import get_name
|
||||
|
||||
|
||||
class PrinterInterface():
|
||||
@ -30,7 +30,7 @@ SERVICES = Container(
|
||||
class MyServiceDependantConfigurable(Configurable):
|
||||
printer = Service(PrinterInterface, )
|
||||
|
||||
def __call__(self, printer: PrinterInterface, *args):
|
||||
def __call__(self, *args, printer: PrinterInterface):
|
||||
return printer.print(*args)
|
||||
|
||||
|
||||
@ -51,15 +51,15 @@ def test_service_name_validator():
|
||||
def test_service_dependency():
|
||||
o = MyServiceDependantConfigurable(printer='printer0')
|
||||
|
||||
assert o(SERVICES.get('printer0'), 'foo', 'bar') == '0;foo;bar'
|
||||
assert o(SERVICES.get('printer1'), 'bar', 'baz') == '1;bar;baz'
|
||||
assert o(*SERVICES.args_for(o), 'foo', 'bar') == '0;foo;bar'
|
||||
assert o('foo', 'bar', printer=SERVICES.get('printer0')) == '0;foo;bar'
|
||||
assert o('bar', 'baz', printer=SERVICES.get('printer1')) == '1;bar;baz'
|
||||
assert o('foo', 'bar', **SERVICES.kwargs_for(o)) == '0;foo;bar'
|
||||
|
||||
|
||||
def test_service_dependency_unavailable():
|
||||
o = MyServiceDependantConfigurable(printer='printer2')
|
||||
with pytest.raises(KeyError):
|
||||
SERVICES.args_for(o)
|
||||
SERVICES.kwargs_for(o)
|
||||
|
||||
|
||||
class VCR:
|
||||
@ -100,13 +100,13 @@ def test_requires():
|
||||
|
||||
services = Container(output=vcr.append)
|
||||
|
||||
@requires('output')
|
||||
@use('output')
|
||||
def append(out, x):
|
||||
out(x)
|
||||
|
||||
svcargs = services.args_for(append)
|
||||
svcargs = services.kwargs_for(append)
|
||||
assert len(svcargs) == 1
|
||||
assert svcargs[0] == vcr.append
|
||||
assert svcargs['output'] == vcr.append
|
||||
|
||||
|
||||
@pytest.mark.parametrize('services', [None, {}])
|
||||
|
||||
@ -2,7 +2,8 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from bonobo import Bag, Graph
|
||||
from bonobo import Graph
|
||||
from bonobo.constants import EMPTY
|
||||
from bonobo.execution.contexts.node import NodeExecutionContext
|
||||
from bonobo.execution.strategies import NaiveStrategy
|
||||
from bonobo.util.testing import BufferingNodeExecutionContext, BufferingGraphExecutionContext
|
||||
@ -13,23 +14,23 @@ def test_node_string():
|
||||
return 'foo'
|
||||
|
||||
with BufferingNodeExecutionContext(f) as context:
|
||||
context.write_sync(Bag())
|
||||
context.write_sync(EMPTY)
|
||||
output = context.get_buffer()
|
||||
|
||||
assert len(output) == 1
|
||||
assert output[0] == 'foo'
|
||||
assert output[0] == ('foo', )
|
||||
|
||||
def g():
|
||||
yield 'foo'
|
||||
yield 'bar'
|
||||
|
||||
with BufferingNodeExecutionContext(g) as context:
|
||||
context.write_sync(Bag())
|
||||
context.write_sync(EMPTY)
|
||||
output = context.get_buffer()
|
||||
|
||||
assert len(output) == 2
|
||||
assert output[0] == 'foo'
|
||||
assert output[1] == 'bar'
|
||||
assert output[0] == ('foo', )
|
||||
assert output[1] == ('bar', )
|
||||
|
||||
|
||||
def test_node_bytes():
|
||||
@ -37,23 +38,23 @@ def test_node_bytes():
|
||||
return b'foo'
|
||||
|
||||
with BufferingNodeExecutionContext(f) as context:
|
||||
context.write_sync(Bag())
|
||||
context.write_sync(EMPTY)
|
||||
|
||||
output = context.get_buffer()
|
||||
assert len(output) == 1
|
||||
assert output[0] == b'foo'
|
||||
assert output[0] == (b'foo', )
|
||||
|
||||
def g():
|
||||
yield b'foo'
|
||||
yield b'bar'
|
||||
|
||||
with BufferingNodeExecutionContext(g) as context:
|
||||
context.write_sync(Bag())
|
||||
context.write_sync(EMPTY)
|
||||
output = context.get_buffer()
|
||||
|
||||
assert len(output) == 2
|
||||
assert output[0] == b'foo'
|
||||
assert output[1] == b'bar'
|
||||
assert output[0] == (b'foo', )
|
||||
assert output[1] == (b'bar', )
|
||||
|
||||
|
||||
def test_node_dict():
|
||||
@ -61,40 +62,38 @@ def test_node_dict():
|
||||
return {'id': 1, 'name': 'foo'}
|
||||
|
||||
with BufferingNodeExecutionContext(f) as context:
|
||||
context.write_sync(Bag())
|
||||
context.write_sync(EMPTY)
|
||||
output = context.get_buffer()
|
||||
|
||||
assert len(output) == 1
|
||||
assert output[0] == {'id': 1, 'name': 'foo'}
|
||||
assert output[0] == ({'id': 1, 'name': 'foo'}, )
|
||||
|
||||
def g():
|
||||
yield {'id': 1, 'name': 'foo'}
|
||||
yield {'id': 2, 'name': 'bar'}
|
||||
|
||||
with BufferingNodeExecutionContext(g) as context:
|
||||
context.write_sync(Bag())
|
||||
context.write_sync(EMPTY)
|
||||
output = context.get_buffer()
|
||||
|
||||
assert len(output) == 2
|
||||
assert output[0] == {'id': 1, 'name': 'foo'}
|
||||
assert output[1] == {'id': 2, 'name': 'bar'}
|
||||
assert output[0] == ({'id': 1, 'name': 'foo'}, )
|
||||
assert output[1] == ({'id': 2, 'name': 'bar'}, )
|
||||
|
||||
|
||||
def test_node_dict_chained():
|
||||
strategy = NaiveStrategy(GraphExecutionContextType=BufferingGraphExecutionContext)
|
||||
|
||||
def uppercase_name(**kwargs):
|
||||
return {**kwargs, 'name': kwargs['name'].upper()}
|
||||
|
||||
def f():
|
||||
return {'id': 1, 'name': 'foo'}
|
||||
|
||||
def uppercase_name(values):
|
||||
return {**values, 'name': values['name'].upper()}
|
||||
|
||||
graph = Graph(f, uppercase_name)
|
||||
context = strategy.execute(graph)
|
||||
output = context.get_buffer()
|
||||
|
||||
assert len(output) == 1
|
||||
assert output[0] == {'id': 1, 'name': 'FOO'}
|
||||
assert output[0] == ({'id': 1, 'name': 'FOO'}, )
|
||||
|
||||
def g():
|
||||
yield {'id': 1, 'name': 'foo'}
|
||||
@ -105,8 +104,8 @@ def test_node_dict_chained():
|
||||
output = context.get_buffer()
|
||||
|
||||
assert len(output) == 2
|
||||
assert output[0] == {'id': 1, 'name': 'FOO'}
|
||||
assert output[1] == {'id': 2, 'name': 'BAR'}
|
||||
assert output[0] == ({'id': 1, 'name': 'FOO'}, )
|
||||
assert output[1] == ({'id': 2, 'name': 'BAR'}, )
|
||||
|
||||
|
||||
def test_node_tuple():
|
||||
@ -114,7 +113,7 @@ def test_node_tuple():
|
||||
return 'foo', 'bar'
|
||||
|
||||
with BufferingNodeExecutionContext(f) as context:
|
||||
context.write_sync(Bag())
|
||||
context.write_sync(EMPTY)
|
||||
output = context.get_buffer()
|
||||
|
||||
assert len(output) == 1
|
||||
@ -125,7 +124,7 @@ def test_node_tuple():
|
||||
yield 'foo', 'baz'
|
||||
|
||||
with BufferingNodeExecutionContext(g) as context:
|
||||
context.write_sync(Bag())
|
||||
context.write_sync(EMPTY)
|
||||
output = context.get_buffer()
|
||||
|
||||
assert len(output) == 2
|
||||
@ -167,7 +166,7 @@ def test_node_tuple_dict():
|
||||
return 'foo', 'bar', {'id': 1}
|
||||
|
||||
with BufferingNodeExecutionContext(f) as context:
|
||||
context.write_sync(Bag())
|
||||
context.write_sync(EMPTY)
|
||||
output = context.get_buffer()
|
||||
|
||||
assert len(output) == 1
|
||||
@ -178,7 +177,7 @@ def test_node_tuple_dict():
|
||||
yield 'foo', 'baz', {'id': 2}
|
||||
|
||||
with BufferingNodeExecutionContext(g) as context:
|
||||
context.write_sync(Bag())
|
||||
context.write_sync(EMPTY)
|
||||
output = context.get_buffer()
|
||||
|
||||
assert len(output) == 2
|
||||
|
||||
@ -21,19 +21,9 @@ class ResponseMock:
|
||||
|
||||
def test_read_from_opendatasoft_api():
|
||||
extract = OpenDataSoftAPI(dataset='test-a-set')
|
||||
with patch(
|
||||
'requests.get', return_value=ResponseMock([
|
||||
{
|
||||
'fields': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
},
|
||||
{
|
||||
'fields': {
|
||||
'foo': 'zab'
|
||||
}
|
||||
},
|
||||
])
|
||||
):
|
||||
with patch('requests.get', return_value=ResponseMock([
|
||||
{'fields': {'foo': 'bar'}},
|
||||
{'fields': {'foo': 'zab'}},
|
||||
])):
|
||||
for line in extract('http://example.com/', ValueHolder(0)):
|
||||
assert 'foo' in line
|
||||
|
||||
@ -9,13 +9,7 @@ def useless(*args, **kwargs):
|
||||
def test_not_modified():
|
||||
input_messages = [
|
||||
('foo', 'bar'),
|
||||
{
|
||||
'foo': 'bar'
|
||||
},
|
||||
('foo', {
|
||||
'bar': 'baz'
|
||||
}),
|
||||
(),
|
||||
('foo', 'baz'),
|
||||
]
|
||||
|
||||
with BufferingNodeExecutionContext(useless) as context:
|
||||
|
||||
@ -1,62 +1,152 @@
|
||||
from collections import namedtuple
|
||||
from unittest import TestCase
|
||||
|
||||
import pytest
|
||||
|
||||
from bonobo import CsvReader, CsvWriter, settings
|
||||
from bonobo.execution.contexts.node import NodeExecutionContext
|
||||
from bonobo.util.testing import FilesystemTester, BufferingNodeExecutionContext
|
||||
from bonobo import CsvReader, CsvWriter
|
||||
from bonobo.constants import EMPTY
|
||||
from bonobo.util.testing import FilesystemTester, BufferingNodeExecutionContext, WriterTest, ConfigurableNodeTest, ReaderTest
|
||||
|
||||
csv_tester = FilesystemTester('csv')
|
||||
csv_tester.input_data = 'a,b,c\na foo,b foo,c foo\na bar,b bar,c bar'
|
||||
|
||||
defaults = {'lineterminator': '\n'}
|
||||
|
||||
def test_write_csv_ioformat_arg0(tmpdir):
|
||||
fs, filename, services = csv_tester.get_services_for_writer(tmpdir)
|
||||
with pytest.raises(ValueError):
|
||||
CsvWriter(path=filename, ioformat=settings.IOFORMAT_ARG0)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
CsvReader(path=filename, delimiter=',', ioformat=settings.IOFORMAT_ARG0),
|
||||
|
||||
|
||||
def test_write_csv_to_file_no_headers(tmpdir):
|
||||
fs, filename, services = csv_tester.get_services_for_writer(tmpdir)
|
||||
|
||||
with NodeExecutionContext(CsvWriter(filename), services=services) as context:
|
||||
context.write_sync(('bar', ), ('baz', 'boo'))
|
||||
|
||||
with fs.open(filename) as fp:
|
||||
assert fp.read() == 'bar\nbaz;boo\n'
|
||||
|
||||
|
||||
def test_write_csv_to_file_with_headers(tmpdir):
|
||||
fs, filename, services = csv_tester.get_services_for_writer(tmpdir)
|
||||
|
||||
with NodeExecutionContext(CsvWriter(filename, headers='foo'), services=services) as context:
|
||||
context.write_sync(('bar', ), ('baz', 'boo'))
|
||||
|
||||
with fs.open(filename) as fp:
|
||||
assert fp.read() == 'foo\nbar\nbaz\n'
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
getattr(context, 'file')
|
||||
incontext = ConfigurableNodeTest.incontext
|
||||
|
||||
|
||||
def test_read_csv_from_file_kwargs(tmpdir):
|
||||
fs, filename, services = csv_tester.get_services_for_reader(tmpdir)
|
||||
|
||||
with BufferingNodeExecutionContext(
|
||||
CsvReader(path=filename, delimiter=','),
|
||||
services=services,
|
||||
) as context:
|
||||
context.write_sync(())
|
||||
with BufferingNodeExecutionContext(CsvReader(filename, **defaults), services=services) as context:
|
||||
context.write_sync(EMPTY)
|
||||
|
||||
assert context.get_buffer_args_as_dicts() == [
|
||||
{
|
||||
'a': 'a foo',
|
||||
'b': 'b foo',
|
||||
'c': 'c foo',
|
||||
}, {
|
||||
'a': 'a bar',
|
||||
'b': 'b bar',
|
||||
'c': 'c bar',
|
||||
}
|
||||
]
|
||||
assert context.get_buffer_args_as_dicts() == [{
|
||||
'a': 'a foo',
|
||||
'b': 'b foo',
|
||||
'c': 'c foo',
|
||||
}, {
|
||||
'a': 'a bar',
|
||||
'b': 'b bar',
|
||||
'c': 'c bar',
|
||||
}]
|
||||
|
||||
|
||||
###
|
||||
# CSV Readers / Writers
|
||||
###
|
||||
|
||||
|
||||
class Csv:
|
||||
extension = 'csv'
|
||||
ReaderNodeType = CsvReader
|
||||
WriterNodeType = CsvWriter
|
||||
|
||||
|
||||
L1, L2, L3, L4 = ('a', 'hey'), ('b', 'bee'), ('c', 'see'), ('d', 'dee')
|
||||
LL = ('i', 'have', 'more', 'values')
|
||||
|
||||
|
||||
class CsvReaderTest(Csv, ReaderTest, TestCase):
|
||||
input_data = '\n'.join((
|
||||
'id,name',
|
||||
'1,John Doe',
|
||||
'2,Jane Doe',
|
||||
',DPR',
|
||||
'42,Elon Musk',
|
||||
))
|
||||
|
||||
def check_output(self, context, *, prepend=None):
|
||||
out = context.get_buffer()
|
||||
assert out == (prepend or list()) + [
|
||||
('1', 'John Doe'),
|
||||
('2', 'Jane Doe'),
|
||||
('', 'DPR'),
|
||||
('42', 'Elon Musk'),
|
||||
]
|
||||
|
||||
@incontext()
|
||||
def test_nofields(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
self.check_output(context)
|
||||
assert context.get_output_fields() == ('id', 'name')
|
||||
|
||||
@incontext(output_type=tuple)
|
||||
def test_output_type(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
self.check_output(context, prepend=[('id', 'name')])
|
||||
|
||||
@incontext(
|
||||
output_fields=(
|
||||
'x',
|
||||
'y',
|
||||
), skip=1
|
||||
)
|
||||
def test_output_fields(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
self.check_output(context)
|
||||
assert context.get_output_fields() == ('x', 'y')
|
||||
|
||||
|
||||
class CsvWriterTest(Csv, WriterTest, TestCase):
|
||||
@incontext()
|
||||
def test_fields(self, context):
|
||||
context.set_input_fields(['foo', 'bar'])
|
||||
context.write_sync(('a', 'b'), ('c', 'd'))
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == (
|
||||
'foo,bar',
|
||||
'a,b',
|
||||
'c,d',
|
||||
)
|
||||
|
||||
@incontext()
|
||||
def test_fields_from_type(self, context):
|
||||
context.set_input_type(namedtuple('Point', 'x y'))
|
||||
context.write_sync((1, 2), (3, 4))
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == ('x,y', '1,2', '3,4')
|
||||
|
||||
@incontext()
|
||||
def test_nofields_multiple_args(self, context):
|
||||
# multiple args are iterated onto and flattened in output
|
||||
context.write_sync((L1, L2), (L3, L4))
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == (
|
||||
'a,hey',
|
||||
'b,bee',
|
||||
'c,see',
|
||||
'd,dee',
|
||||
)
|
||||
|
||||
@incontext()
|
||||
def test_nofields_multiple_args_length_mismatch(self, context):
|
||||
# if length of input vary, then we get a TypeError (unrecoverable)
|
||||
with pytest.raises(TypeError):
|
||||
context.write_sync((L1, L2), (L3, ))
|
||||
|
||||
@incontext()
|
||||
def test_nofields_single_arg(self, context):
|
||||
# single args are just dumped, shapes can vary.
|
||||
context.write_sync((L1, ), (LL, ), (L3, ))
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == (
|
||||
'a,hey',
|
||||
'i,have,more,values',
|
||||
'c,see',
|
||||
)
|
||||
|
||||
@incontext()
|
||||
def test_nofields_empty_args(self, context):
|
||||
# empty calls are ignored
|
||||
context.write_sync(EMPTY, EMPTY, EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == ()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from bonobo import Bag, FileReader, FileWriter
|
||||
from bonobo.constants import BEGIN, END
|
||||
from bonobo import FileReader, FileWriter
|
||||
from bonobo.constants import EMPTY
|
||||
from bonobo.execution.contexts.node import NodeExecutionContext
|
||||
from bonobo.util.testing import BufferingNodeExecutionContext, FilesystemTester
|
||||
|
||||
@ -30,9 +30,7 @@ def test_file_writer_in_context(tmpdir, lines, output):
|
||||
fs, filename, services = txt_tester.get_services_for_writer(tmpdir)
|
||||
|
||||
with NodeExecutionContext(FileWriter(path=filename), services=services) as context:
|
||||
context.write(BEGIN, *map(Bag, lines), END)
|
||||
for _ in range(len(lines)):
|
||||
context.step()
|
||||
context.write_sync(*lines)
|
||||
|
||||
with fs.open(filename) as fp:
|
||||
assert fp.read() == output
|
||||
@ -42,9 +40,9 @@ def test_file_reader(tmpdir):
|
||||
fs, filename, services = txt_tester.get_services_for_reader(tmpdir)
|
||||
|
||||
with BufferingNodeExecutionContext(FileReader(path=filename), services=services) as context:
|
||||
context.write_sync(Bag())
|
||||
output = context.get_buffer()
|
||||
context.write_sync(EMPTY)
|
||||
|
||||
output = context.get_buffer()
|
||||
assert len(output) == 2
|
||||
assert output[0] == 'Hello'
|
||||
assert output[1] == 'World'
|
||||
assert output[0] == ('Hello', )
|
||||
assert output[1] == ('World', )
|
||||
|
||||
@ -1,66 +1,300 @@
|
||||
import json
|
||||
from collections import OrderedDict, namedtuple
|
||||
from unittest import TestCase
|
||||
|
||||
import pytest
|
||||
|
||||
from bonobo import JsonReader, JsonWriter, settings
|
||||
from bonobo import JsonReader, JsonWriter
|
||||
from bonobo import LdjsonReader, LdjsonWriter
|
||||
from bonobo.execution.contexts.node import NodeExecutionContext
|
||||
from bonobo.util.testing import FilesystemTester, BufferingNodeExecutionContext
|
||||
from bonobo.constants import EMPTY
|
||||
from bonobo.util.testing import WriterTest, ReaderTest, ConfigurableNodeTest
|
||||
|
||||
json_tester = FilesystemTester('json')
|
||||
json_tester.input_data = '''[{"x": "foo"},{"x": "bar"}]'''
|
||||
FOOBAR = {'foo': 'bar'}
|
||||
OD_ABC = OrderedDict((('a', 'A'), ('b', 'B'), ('c', 'C')))
|
||||
FOOBAZ = {'foo': 'baz'}
|
||||
|
||||
incontext = ConfigurableNodeTest.incontext
|
||||
|
||||
###
|
||||
# Standard JSON Readers / Writers
|
||||
###
|
||||
|
||||
|
||||
def test_write_json_ioformat_arg0(tmpdir):
|
||||
fs, filename, services = json_tester.get_services_for_writer(tmpdir)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
JsonWriter(filename, ioformat=settings.IOFORMAT_ARG0)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
JsonReader(filename, ioformat=settings.IOFORMAT_ARG0),
|
||||
class Json:
|
||||
extension = 'json'
|
||||
ReaderNodeType = JsonReader
|
||||
WriterNodeType = JsonWriter
|
||||
|
||||
|
||||
@pytest.mark.parametrize('add_kwargs', (
|
||||
{},
|
||||
{
|
||||
'ioformat': settings.IOFORMAT_KWARGS,
|
||||
},
|
||||
))
|
||||
def test_write_json_kwargs(tmpdir, add_kwargs):
|
||||
fs, filename, services = json_tester.get_services_for_writer(tmpdir)
|
||||
class JsonReaderDictsTest(Json, ReaderTest, TestCase):
|
||||
input_data = '[{"foo": "bar"},\n{"baz": "boz"}]'
|
||||
|
||||
with NodeExecutionContext(JsonWriter(filename, **add_kwargs), services=services) as context:
|
||||
context.write_sync({'foo': 'bar'})
|
||||
@incontext()
|
||||
def test_nofields(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
|
||||
with fs.open(filename) as fp:
|
||||
assert fp.read() == '[{"foo": "bar"}]'
|
||||
assert context.get_buffer() == [
|
||||
({
|
||||
"foo": "bar"
|
||||
}, ),
|
||||
({
|
||||
"baz": "boz"
|
||||
}, ),
|
||||
]
|
||||
|
||||
|
||||
stream_json_tester = FilesystemTester('json')
|
||||
stream_json_tester.input_data = '''{"foo": "bar"}\n{"baz": "boz"}'''
|
||||
class JsonReaderListsTest(Json, ReaderTest, TestCase):
|
||||
input_data = '[[1,2,3],\n[4,5,6]]'
|
||||
|
||||
@incontext()
|
||||
def test_nofields(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert context.get_buffer() == [
|
||||
([1, 2, 3], ),
|
||||
([4, 5, 6], ),
|
||||
]
|
||||
|
||||
@incontext(output_type=tuple)
|
||||
def test_output_type(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert context.get_buffer() == [
|
||||
([1, 2, 3], ),
|
||||
([4, 5, 6], ),
|
||||
]
|
||||
|
||||
|
||||
def test_read_stream_json(tmpdir):
|
||||
fs, filename, services = stream_json_tester.get_services_for_reader(tmpdir)
|
||||
with BufferingNodeExecutionContext(LdjsonReader(filename), services=services) as context:
|
||||
context.write_sync(tuple())
|
||||
actual = context.get_buffer()
|
||||
class JsonReaderStringsTest(Json, ReaderTest, TestCase):
|
||||
input_data = '[' + ',\n'.join(map(json.dumps, ('foo', 'bar', 'baz'))) + ']'
|
||||
|
||||
expected = [{"foo": "bar"}, {"baz": "boz"}]
|
||||
assert expected == actual
|
||||
@incontext()
|
||||
def test_nofields(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert context.get_buffer() == [
|
||||
('foo', ),
|
||||
('bar', ),
|
||||
('baz', ),
|
||||
]
|
||||
|
||||
@incontext(output_type=tuple)
|
||||
def test_output_type(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert context.get_buffer() == [
|
||||
('foo', ),
|
||||
('bar', ),
|
||||
('baz', ),
|
||||
]
|
||||
|
||||
|
||||
def test_write_stream_json(tmpdir):
|
||||
fs, filename, services = stream_json_tester.get_services_for_reader(tmpdir)
|
||||
class JsonWriterTest(Json, WriterTest, TestCase):
|
||||
@incontext()
|
||||
def test_fields(self, context):
|
||||
context.set_input_fields(['foo', 'bar'])
|
||||
context.write_sync(('a', 'b'), ('c', 'd'))
|
||||
context.stop()
|
||||
|
||||
with BufferingNodeExecutionContext(LdjsonWriter(filename), services=services) as context:
|
||||
context.write_sync(
|
||||
{
|
||||
'foo': 'bar'
|
||||
},
|
||||
{'baz': 'boz'},
|
||||
assert self.readlines() == (
|
||||
'[{"foo": "a", "bar": "b"},',
|
||||
'{"foo": "c", "bar": "d"}]',
|
||||
)
|
||||
|
||||
expected = '''{"foo": "bar"}\n{"baz": "boz"}\n'''
|
||||
with fs.open(filename) as fin:
|
||||
actual = fin.read()
|
||||
assert expected == actual
|
||||
@incontext()
|
||||
def test_fields_from_type(self, context):
|
||||
context.set_input_type(namedtuple('Point', 'x y'))
|
||||
context.write_sync((1, 2), (3, 4))
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == (
|
||||
'[{"x": 1, "y": 2},',
|
||||
'{"x": 3, "y": 4}]',
|
||||
)
|
||||
|
||||
@incontext()
|
||||
def test_nofields_multiple_args(self, context):
|
||||
# multiple args are iterated onto and flattened in output
|
||||
context.write_sync((FOOBAR, FOOBAR), (OD_ABC, FOOBAR), (FOOBAZ, FOOBAR))
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == (
|
||||
'[{"foo": "bar"},',
|
||||
'{"foo": "bar"},',
|
||||
'{"a": "A", "b": "B", "c": "C"},',
|
||||
'{"foo": "bar"},',
|
||||
'{"foo": "baz"},',
|
||||
'{"foo": "bar"}]',
|
||||
)
|
||||
|
||||
@incontext()
|
||||
def test_nofields_multiple_args_length_mismatch(self, context):
|
||||
# if length of input vary, then we get a TypeError (unrecoverable)
|
||||
with pytest.raises(TypeError):
|
||||
context.write_sync((FOOBAR, FOOBAR), (OD_ABC))
|
||||
|
||||
@incontext()
|
||||
def test_nofields_single_arg(self, context):
|
||||
# single args are just dumped, shapes can vary.
|
||||
context.write_sync(FOOBAR, OD_ABC, FOOBAZ)
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == (
|
||||
'[{"foo": "bar"},',
|
||||
'{"a": "A", "b": "B", "c": "C"},',
|
||||
'{"foo": "baz"}]',
|
||||
)
|
||||
|
||||
@incontext()
|
||||
def test_nofields_empty_args(self, context):
|
||||
# empty calls are ignored
|
||||
context.write_sync(EMPTY, EMPTY, EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == ('[]', )
|
||||
|
||||
|
||||
###
|
||||
# Line Delimiter JSON Readers / Writers
|
||||
###
|
||||
|
||||
|
||||
class Ldjson:
|
||||
extension = 'ldjson'
|
||||
ReaderNodeType = LdjsonReader
|
||||
WriterNodeType = LdjsonWriter
|
||||
|
||||
|
||||
class LdjsonReaderDictsTest(Ldjson, ReaderTest, TestCase):
|
||||
input_data = '{"foo": "bar"}\n{"baz": "boz"}'
|
||||
|
||||
@incontext()
|
||||
def test_nofields(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert context.get_buffer() == [
|
||||
({
|
||||
"foo": "bar"
|
||||
}, ),
|
||||
({
|
||||
"baz": "boz"
|
||||
}, ),
|
||||
]
|
||||
|
||||
|
||||
class LdjsonReaderListsTest(Ldjson, ReaderTest, TestCase):
|
||||
input_data = '[1,2,3]\n[4,5,6]'
|
||||
|
||||
@incontext()
|
||||
def test_nofields(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert context.get_buffer() == [
|
||||
([1, 2, 3], ),
|
||||
([4, 5, 6], ),
|
||||
]
|
||||
|
||||
@incontext(output_type=tuple)
|
||||
def test_output_type(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert context.get_buffer() == [
|
||||
([1, 2, 3], ),
|
||||
([4, 5, 6], ),
|
||||
]
|
||||
|
||||
|
||||
class LdjsonReaderStringsTest(Ldjson, ReaderTest, TestCase):
|
||||
input_data = '\n'.join(map(json.dumps, ('foo', 'bar', 'baz')))
|
||||
|
||||
@incontext()
|
||||
def test_nofields(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert context.get_buffer() == [
|
||||
('foo', ),
|
||||
('bar', ),
|
||||
('baz', ),
|
||||
]
|
||||
|
||||
@incontext(output_type=tuple)
|
||||
def test_output_type(self, context):
|
||||
context.write_sync(EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert context.get_buffer() == [
|
||||
('foo', ),
|
||||
('bar', ),
|
||||
('baz', ),
|
||||
]
|
||||
|
||||
|
||||
class LdjsonWriterTest(Ldjson, WriterTest, TestCase):
|
||||
@incontext()
|
||||
def test_fields(self, context):
|
||||
context.set_input_fields(['foo', 'bar'])
|
||||
context.write_sync(('a', 'b'), ('c', 'd'))
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == ('{"foo": "a", "bar": "b"}', '{"foo": "c", "bar": "d"}')
|
||||
|
||||
@incontext()
|
||||
def test_fields_from_type(self, context):
|
||||
context.set_input_type(namedtuple('Point', 'x y'))
|
||||
context.write_sync((1, 2), (3, 4))
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == (
|
||||
'{"x": 1, "y": 2}',
|
||||
'{"x": 3, "y": 4}',
|
||||
)
|
||||
|
||||
@incontext()
|
||||
def test_nofields_multiple_args(self, context):
|
||||
# multiple args are iterated onto and flattened in output
|
||||
context.write_sync((FOOBAR, FOOBAR), (OD_ABC, FOOBAR), (FOOBAZ, FOOBAR))
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == (
|
||||
'{"foo": "bar"}',
|
||||
'{"foo": "bar"}',
|
||||
'{"a": "A", "b": "B", "c": "C"}',
|
||||
'{"foo": "bar"}',
|
||||
'{"foo": "baz"}',
|
||||
'{"foo": "bar"}',
|
||||
)
|
||||
|
||||
@incontext()
|
||||
def test_nofields_multiple_args_length_mismatch(self, context):
|
||||
# if length of input vary, then we get a TypeError (unrecoverable)
|
||||
with pytest.raises(TypeError):
|
||||
context.write_sync((FOOBAR, FOOBAR), (OD_ABC))
|
||||
|
||||
@incontext()
|
||||
def test_nofields_single_arg(self, context):
|
||||
# single args are just dumped, shapes can vary.
|
||||
context.write_sync(FOOBAR, OD_ABC, FOOBAZ)
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == (
|
||||
'{"foo": "bar"}',
|
||||
'{"a": "A", "b": "B", "c": "C"}',
|
||||
'{"foo": "baz"}',
|
||||
)
|
||||
|
||||
@incontext()
|
||||
def test_nofields_empty_args(self, context):
|
||||
# empty calls are ignored
|
||||
context.write_sync(EMPTY, EMPTY, EMPTY)
|
||||
context.stop()
|
||||
|
||||
assert self.readlines() == ()
|
||||
|
||||
@ -2,7 +2,8 @@ import pickle
|
||||
|
||||
import pytest
|
||||
|
||||
from bonobo import Bag, PickleReader, PickleWriter
|
||||
from bonobo import PickleReader, PickleWriter
|
||||
from bonobo.constants import EMPTY
|
||||
from bonobo.execution.contexts.node import NodeExecutionContext
|
||||
from bonobo.util.testing import BufferingNodeExecutionContext, FilesystemTester
|
||||
|
||||
@ -14,7 +15,7 @@ def test_write_pickled_dict_to_file(tmpdir):
|
||||
fs, filename, services = pickle_tester.get_services_for_writer(tmpdir)
|
||||
|
||||
with NodeExecutionContext(PickleWriter(filename), services=services) as context:
|
||||
context.write_sync(Bag(({'foo': 'bar'}, {})), Bag(({'foo': 'baz', 'ignore': 'this'}, {})))
|
||||
context.write_sync({'foo': 'bar'}, {'foo': 'baz', 'ignore': 'this'})
|
||||
|
||||
with fs.open(filename, 'rb') as fp:
|
||||
assert pickle.loads(fp.read()) == {'foo': 'bar'}
|
||||
@ -27,17 +28,11 @@ def test_read_pickled_list_from_file(tmpdir):
|
||||
fs, filename, services = pickle_tester.get_services_for_reader(tmpdir)
|
||||
|
||||
with BufferingNodeExecutionContext(PickleReader(filename), services=services) as context:
|
||||
context.write_sync(())
|
||||
output = context.get_buffer()
|
||||
context.write_sync(EMPTY)
|
||||
|
||||
assert len(output) == 2
|
||||
assert output[0] == {
|
||||
'a': 'a foo',
|
||||
'b': 'b foo',
|
||||
'c': 'c foo',
|
||||
}
|
||||
assert output[1] == {
|
||||
'a': 'a bar',
|
||||
'b': 'b bar',
|
||||
'c': 'c bar',
|
||||
}
|
||||
output = context.get_buffer()
|
||||
assert context.get_output_fields() == ('a', 'b', 'c')
|
||||
assert output == [
|
||||
('a foo', 'b foo', 'c foo'),
|
||||
('a bar', 'b bar', 'c bar'),
|
||||
]
|
||||
|
||||
@ -1,64 +1,79 @@
|
||||
from operator import methodcaller
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
import bonobo
|
||||
from bonobo.config.processors import ContextCurrifier
|
||||
from bonobo.constants import NOT_MODIFIED
|
||||
from bonobo.util.testing import BufferingNodeExecutionContext
|
||||
from bonobo.constants import NOT_MODIFIED, EMPTY
|
||||
from bonobo.util import ensure_tuple, ValueHolder
|
||||
from bonobo.util.testing import BufferingNodeExecutionContext, StaticNodeTest, ConfigurableNodeTest
|
||||
|
||||
|
||||
def test_count():
|
||||
with pytest.raises(TypeError):
|
||||
bonobo.count()
|
||||
class CountTest(StaticNodeTest, TestCase):
|
||||
node = bonobo.count
|
||||
|
||||
context = MagicMock()
|
||||
def test_counter_required(self):
|
||||
with pytest.raises(TypeError):
|
||||
self.call()
|
||||
|
||||
with ContextCurrifier(bonobo.count).as_contextmanager(context) as stack:
|
||||
for i in range(42):
|
||||
stack()
|
||||
def test_manual_call(self):
|
||||
counter = ValueHolder(0)
|
||||
for i in range(3):
|
||||
self.call(counter)
|
||||
assert counter == 3
|
||||
|
||||
assert len(context.method_calls) == 1
|
||||
bag = context.send.call_args[0][0]
|
||||
assert isinstance(bag, bonobo.Bag)
|
||||
assert 0 == len(bag.kwargs)
|
||||
assert 1 == len(bag.args)
|
||||
assert bag.args[0] == 42
|
||||
def test_execution(self):
|
||||
with self.execute() as context:
|
||||
context.write_sync(*([EMPTY] * 42))
|
||||
assert context.get_buffer() == [(42, )]
|
||||
|
||||
|
||||
def test_identity():
|
||||
assert bonobo.identity(42) == 42
|
||||
class IdentityTest(StaticNodeTest, TestCase):
|
||||
node = bonobo.identity
|
||||
|
||||
def test_basic_call(self):
|
||||
assert self.call(42) == 42
|
||||
|
||||
def test_execution(self):
|
||||
object_list = [object() for _ in range(42)]
|
||||
with self.execute() as context:
|
||||
context.write_sync(*object_list)
|
||||
assert context.get_buffer() == list(map(ensure_tuple, object_list))
|
||||
|
||||
|
||||
def test_limit():
|
||||
context, results = MagicMock(), []
|
||||
class LimitTest(ConfigurableNodeTest, TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.NodeType = bonobo.Limit
|
||||
|
||||
with ContextCurrifier(bonobo.Limit(2)).as_contextmanager(context) as stack:
|
||||
for i in range(42):
|
||||
results += list(stack())
|
||||
def test_execution_default(self):
|
||||
object_list = [object() for _ in range(42)]
|
||||
with self.execute() as context:
|
||||
context.write_sync(*object_list)
|
||||
|
||||
assert results == [NOT_MODIFIED] * 2
|
||||
assert context.get_buffer() == list(map(ensure_tuple, object_list[:10]))
|
||||
|
||||
def test_execution_custom(self):
|
||||
object_list = [object() for _ in range(42)]
|
||||
with self.execute(21) as context:
|
||||
context.write_sync(*object_list)
|
||||
|
||||
def test_limit_not_there():
|
||||
context, results = MagicMock(), []
|
||||
assert context.get_buffer() == list(map(ensure_tuple, object_list[:21]))
|
||||
|
||||
with ContextCurrifier(bonobo.Limit(42)).as_contextmanager(context) as stack:
|
||||
for i in range(10):
|
||||
results += list(stack())
|
||||
def test_manual(self):
|
||||
limit = self.NodeType(5)
|
||||
buffer = []
|
||||
for x in range(10):
|
||||
buffer += list(limit(x))
|
||||
assert len(buffer) == 5
|
||||
|
||||
assert results == [NOT_MODIFIED] * 10
|
||||
|
||||
|
||||
def test_limit_default():
|
||||
context, results = MagicMock(), []
|
||||
|
||||
with ContextCurrifier(bonobo.Limit()).as_contextmanager(context) as stack:
|
||||
for i in range(20):
|
||||
results += list(stack())
|
||||
|
||||
assert results == [NOT_MODIFIED] * 10
|
||||
def test_underflow(self):
|
||||
limit = self.NodeType(10)
|
||||
buffer = []
|
||||
for x in range(5):
|
||||
buffer += list(limit(x))
|
||||
assert len(buffer) == 5
|
||||
|
||||
|
||||
def test_tee():
|
||||
@ -76,36 +91,28 @@ def test_noop():
|
||||
assert bonobo.noop(1, 2, 3, 4, foo='bar') == NOT_MODIFIED
|
||||
|
||||
|
||||
def test_update():
|
||||
with BufferingNodeExecutionContext(bonobo.Update('a', k=True)) as context:
|
||||
context.write_sync('a', ('a', {'b': 1}), ('b', {'k': False}))
|
||||
assert context.get_buffer() == [
|
||||
bonobo.Bag('a', 'a', k=True),
|
||||
bonobo.Bag('a', 'a', b=1, k=True),
|
||||
bonobo.Bag('b', 'a', k=True),
|
||||
]
|
||||
assert context.name == "Update('a', k=True)"
|
||||
|
||||
|
||||
def test_fixedwindow():
|
||||
with BufferingNodeExecutionContext(bonobo.FixedWindow(2)) as context:
|
||||
context.write_sync(*range(10))
|
||||
assert context.get_buffer() == [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
|
||||
assert context.get_buffer() == [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
|
||||
|
||||
with BufferingNodeExecutionContext(bonobo.FixedWindow(2)) as context:
|
||||
context.write_sync(*range(9))
|
||||
assert context.get_buffer() == [[0, 1], [2, 3], [4, 5], [6, 7], [8]]
|
||||
assert context.get_buffer() == [(0, 1), (2, 3), (4, 5), (6, 7), (
|
||||
8,
|
||||
None,
|
||||
)]
|
||||
|
||||
with BufferingNodeExecutionContext(bonobo.FixedWindow(1)) as context:
|
||||
context.write_sync(*range(3))
|
||||
assert context.get_buffer() == [[0], [1], [2]]
|
||||
assert context.get_buffer() == [(0, ), (1, ), (2, )]
|
||||
|
||||
|
||||
def test_methodcaller():
|
||||
with BufferingNodeExecutionContext(methodcaller('swapcase')) as context:
|
||||
context.write_sync('aaa', 'bBb', 'CcC')
|
||||
assert context.get_buffer() == ['AAA', 'BbB', 'cCc']
|
||||
assert context.get_buffer() == list(map(ensure_tuple, ['AAA', 'BbB', 'cCc']))
|
||||
|
||||
with BufferingNodeExecutionContext(methodcaller('zfill', 5)) as context:
|
||||
context.write_sync('a', 'bb', 'ccc')
|
||||
assert context.get_buffer() == ['0000a', '000bb', '00ccc']
|
||||
assert context.get_buffer() == list(map(ensure_tuple, ['0000a', '000bb', '00ccc']))
|
||||
|
||||
@ -1,170 +0,0 @@
|
||||
import pickle
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from bonobo import Bag
|
||||
from bonobo.constants import INHERIT_INPUT, BEGIN
|
||||
from bonobo.structs import Token
|
||||
|
||||
args = (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
kwargs = dict(acme='corp')
|
||||
|
||||
|
||||
def test_basic():
|
||||
my_callable1 = Mock()
|
||||
my_callable2 = Mock()
|
||||
bag = Bag(*args, **kwargs)
|
||||
|
||||
assert not my_callable1.called
|
||||
result1 = bag.apply(my_callable1)
|
||||
assert my_callable1.called and result1 is my_callable1.return_value
|
||||
|
||||
assert not my_callable2.called
|
||||
result2 = bag.apply(my_callable2)
|
||||
assert my_callable2.called and result2 is my_callable2.return_value
|
||||
|
||||
assert result1 is not result2
|
||||
|
||||
my_callable1.assert_called_once_with(*args, **kwargs)
|
||||
my_callable2.assert_called_once_with(*args, **kwargs)
|
||||
|
||||
|
||||
def test_constructor_empty():
|
||||
a, b = Bag(), Bag()
|
||||
assert a == b
|
||||
assert a.args is ()
|
||||
assert a.kwargs == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(('arg_in', 'arg_out'), (
|
||||
((), ()),
|
||||
({}, ()),
|
||||
(('a', 'b', 'c'), None),
|
||||
))
|
||||
def test_constructor_shorthand(arg_in, arg_out):
|
||||
if arg_out is None:
|
||||
arg_out = arg_in
|
||||
assert Bag(arg_in) == arg_out
|
||||
|
||||
|
||||
def test_constructor_kwargs_only():
|
||||
assert Bag(foo='bar') == {'foo': 'bar'}
|
||||
|
||||
|
||||
def test_constructor_identity():
|
||||
assert Bag(BEGIN) is BEGIN
|
||||
|
||||
|
||||
def test_inherit():
|
||||
bag = Bag('a', a=1)
|
||||
bag2 = Bag.inherit('b', b=2, _parent=bag)
|
||||
bag3 = bag.extend('c', c=3)
|
||||
bag4 = Bag('d', d=4)
|
||||
|
||||
assert bag.args == ('a', )
|
||||
assert bag.kwargs == {'a': 1}
|
||||
assert bag.flags is ()
|
||||
|
||||
assert bag2.args == (
|
||||
'a',
|
||||
'b',
|
||||
)
|
||||
assert bag2.kwargs == {'a': 1, 'b': 2}
|
||||
assert INHERIT_INPUT in bag2.flags
|
||||
|
||||
assert bag3.args == (
|
||||
'a',
|
||||
'c',
|
||||
)
|
||||
assert bag3.kwargs == {'a': 1, 'c': 3}
|
||||
assert bag3.flags is ()
|
||||
|
||||
assert bag4.args == ('d', )
|
||||
assert bag4.kwargs == {'d': 4}
|
||||
assert bag4.flags is ()
|
||||
|
||||
bag4.set_parent(bag)
|
||||
assert bag4.args == (
|
||||
'a',
|
||||
'd',
|
||||
)
|
||||
assert bag4.kwargs == {'a': 1, 'd': 4}
|
||||
assert bag4.flags is ()
|
||||
|
||||
bag4.set_parent(bag3)
|
||||
assert bag4.args == (
|
||||
'a',
|
||||
'c',
|
||||
'd',
|
||||
)
|
||||
assert bag4.kwargs == {'a': 1, 'c': 3, 'd': 4}
|
||||
assert bag4.flags is ()
|
||||
|
||||
|
||||
def test_pickle():
|
||||
bag1 = Bag('a', a=1)
|
||||
bag2 = Bag.inherit('b', b=2, _parent=bag1)
|
||||
bag3 = bag1.extend('c', c=3)
|
||||
bag4 = Bag('d', d=4)
|
||||
|
||||
# XXX todo this probably won't work with inheriting bags if parent is not there anymore? maybe that's not true
|
||||
# because the parent may be in the serialization output but we need to verify this assertion.
|
||||
|
||||
for bag in bag1, bag2, bag3, bag4:
|
||||
pickled = pickle.dumps(bag)
|
||||
unpickled = pickle.loads(pickled)
|
||||
assert unpickled == bag
|
||||
|
||||
|
||||
def test_eq_operator_bag():
|
||||
assert Bag('foo') == Bag('foo')
|
||||
assert Bag('foo') != Bag('bar')
|
||||
assert Bag('foo') is not Bag('foo')
|
||||
assert Bag('foo') != Token('foo')
|
||||
assert Token('foo') != Bag('foo')
|
||||
|
||||
|
||||
def test_eq_operator_tuple_mixed():
|
||||
assert Bag('foo', bar='baz') == ('foo', {'bar': 'baz'})
|
||||
assert Bag('foo') == ('foo', {})
|
||||
assert Bag() == ({}, )
|
||||
|
||||
|
||||
def test_eq_operator_tuple_not_mixed():
|
||||
assert Bag('foo', 'bar') == ('foo', 'bar')
|
||||
assert Bag('foo') == ('foo', )
|
||||
assert Bag() == ()
|
||||
|
||||
|
||||
def test_eq_operator_dict():
|
||||
assert Bag(foo='bar') == {'foo': 'bar'}
|
||||
assert Bag(
|
||||
foo='bar', corp='acme'
|
||||
) == {
|
||||
'foo': 'bar',
|
||||
'corp': 'acme',
|
||||
}
|
||||
assert Bag(
|
||||
foo='bar', corp='acme'
|
||||
) == {
|
||||
'corp': 'acme',
|
||||
'foo': 'bar',
|
||||
}
|
||||
assert Bag() == {}
|
||||
|
||||
|
||||
def test_repr():
|
||||
bag = Bag('a', a=1)
|
||||
assert repr(bag) == "Bag('a', a=1)"
|
||||
|
||||
|
||||
def test_iterator():
|
||||
bag = Bag()
|
||||
assert list(bag.apply([1, 2, 3])) == [1, 2, 3]
|
||||
assert list(bag.apply((1, 2, 3))) == [1, 2, 3]
|
||||
assert list(bag.apply(range(5))) == [0, 1, 2, 3, 4]
|
||||
assert list(bag.apply('azerty')) == ['a', 'z', 'e', 'r', 't', 'y']
|
||||
@ -1,4 +1,4 @@
|
||||
from bonobo.structs import Token
|
||||
from bonobo.constants import Token
|
||||
|
||||
|
||||
def test_token_repr():
|
||||
|
||||
@ -1,29 +1,28 @@
|
||||
from bonobo.config.processors import ContextProcessor
|
||||
from bonobo.config.processors import use_context_processor
|
||||
from bonobo.constants import BEGIN, END
|
||||
from bonobo.execution.contexts.graph import GraphExecutionContext
|
||||
from bonobo.execution.strategies import NaiveStrategy
|
||||
from bonobo.structs import Bag, Graph
|
||||
from bonobo.structs import Graph
|
||||
|
||||
|
||||
def generate_integers():
|
||||
yield from range(10)
|
||||
|
||||
|
||||
def square(i: int) -> int:
|
||||
def square(i):
|
||||
return i**2
|
||||
|
||||
|
||||
def push_result(results, i: int):
|
||||
results.append(i)
|
||||
|
||||
|
||||
@ContextProcessor.decorate(push_result)
|
||||
def results(f, context):
|
||||
results = []
|
||||
yield results
|
||||
results = yield list()
|
||||
context.parent.results = results
|
||||
|
||||
|
||||
@use_context_processor(results)
|
||||
def push_result(results, i):
|
||||
results.append(i)
|
||||
|
||||
|
||||
chain = (generate_integers, square, push_result)
|
||||
|
||||
|
||||
@ -62,7 +61,7 @@ def test_simple_execution_context():
|
||||
assert not context.started
|
||||
assert not context.stopped
|
||||
|
||||
context.write(BEGIN, Bag(), END)
|
||||
context.write(BEGIN, (), END)
|
||||
|
||||
assert not context.alive
|
||||
assert not context.started
|
||||
|
||||
278
tests/util/test_bags.py
Normal file
278
tests/util/test_bags.py
Normal file
@ -0,0 +1,278 @@
|
||||
"""Those tests are mostly a copy paste of cpython unit tests for namedtuple, with a few differences to reflect the
|
||||
implementation details that differs. It ensures that we caught the same edge cases as they did."""
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import pickle
|
||||
import string
|
||||
import sys
|
||||
import unittest
|
||||
from collections import OrderedDict
|
||||
from random import choice
|
||||
|
||||
from bonobo.util.bags import BagType
|
||||
|
||||
################################################################################
|
||||
### Named Tuples
|
||||
################################################################################
|
||||
|
||||
TBag = BagType('TBag', ('x', 'y', 'z')) # type used for pickle tests
|
||||
|
||||
|
||||
class TestBagType(unittest.TestCase):
|
||||
def _create(self, *fields, typename='abc'):
|
||||
bt = BagType(typename, fields)
|
||||
assert bt._fields == fields
|
||||
assert len(bt._fields) == len(bt._attrs)
|
||||
return bt
|
||||
|
||||
def test_factory(self):
|
||||
Point = BagType('Point', ('x', 'y'))
|
||||
self.assertEqual(Point.__name__, 'Point')
|
||||
self.assertEqual(Point.__slots__, ())
|
||||
self.assertEqual(Point.__module__, __name__)
|
||||
self.assertEqual(Point.__getitem__, tuple.__getitem__)
|
||||
assert Point._fields == ('x', 'y')
|
||||
assert Point._attrs == ('x', 'y')
|
||||
|
||||
self.assertRaises(ValueError, BagType, 'abc%', ('efg', 'ghi')) # type has non-alpha char
|
||||
self.assertRaises(ValueError, BagType, 'class', ('efg', 'ghi')) # type has keyword
|
||||
self.assertRaises(ValueError, BagType, '9abc', ('efg', 'ghi')) # type starts with digit
|
||||
|
||||
assert self._create('efg', 'g%hi')._attrs == ('efg', 'g_hi')
|
||||
assert self._create('abc', 'class')._attrs == ('abc', '_class')
|
||||
assert self._create('8efg', '9ghi')._attrs == ('_8efg', '_9ghi')
|
||||
assert self._create('_efg', 'ghi')._attrs == ('_efg', 'ghi')
|
||||
|
||||
self.assertRaises(ValueError, BagType, 'abc', ('efg', 'efg', 'ghi')) # duplicate field
|
||||
|
||||
self._create('x1', 'y2', typename='Point0') # Verify that numbers are allowed in names
|
||||
self._create('a', 'b', 'c', typename='_') # Test leading underscores in a typename
|
||||
|
||||
bt = self._create('a!', 'a?')
|
||||
assert bt._attrs == ('a0', 'a1')
|
||||
x = bt('foo', 'bar')
|
||||
assert x.get('a!') == 'foo'
|
||||
assert x.a0 == 'foo'
|
||||
assert x.get('a?') == 'bar'
|
||||
assert x.a1 == 'bar'
|
||||
|
||||
# check unicode output
|
||||
bt = self._create('the', 'quick', 'brown', 'fox')
|
||||
assert "u'" not in repr(bt._fields)
|
||||
|
||||
self.assertRaises(TypeError, Point._make, [11]) # catch too few args
|
||||
self.assertRaises(TypeError, Point._make, [11, 22, 33]) # catch too many args
|
||||
|
||||
@unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above")
|
||||
def test_factory_doc_attr(self):
|
||||
Point = BagType('Point', ('x', 'y'))
|
||||
self.assertEqual(Point.__doc__, 'Point(x, y)')
|
||||
|
||||
@unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above")
|
||||
def test_doc_writable(self):
|
||||
Point = BagType('Point', ('x', 'y'))
|
||||
self.assertEqual(Point.x.__doc__, "Alias for 'x'")
|
||||
Point.x.__doc__ = 'docstring for Point.x'
|
||||
self.assertEqual(Point.x.__doc__, 'docstring for Point.x')
|
||||
|
||||
def test_name_fixer(self):
|
||||
for spec, renamed in [
|
||||
[('efg', 'g%hi'), ('efg', 'g_hi')], # field with non-alpha char
|
||||
[('abc', 'class'), ('abc', '_class')], # field has keyword
|
||||
[('8efg', '9ghi'), ('_8efg', '_9ghi')], # field starts with digit
|
||||
[('abc', '_efg'), ('abc', '_efg')], # field with leading underscore
|
||||
[('abc', '', 'x'), ('abc', '_0', 'x')], # fieldname is a space
|
||||
[('&', '¨', '*'), ('_0', '_1', '_2')], # Duplicate attrs, in theory
|
||||
]:
|
||||
assert self._create(*spec)._attrs == renamed
|
||||
|
||||
def test_module_parameter(self):
|
||||
NT = BagType('NT', ['x', 'y'], module=collections)
|
||||
self.assertEqual(NT.__module__, collections)
|
||||
|
||||
def test_instance(self):
|
||||
Point = self._create('x', 'y', typename='Point')
|
||||
p = Point(11, 22)
|
||||
self.assertEqual(p, Point(x=11, y=22))
|
||||
self.assertEqual(p, Point(11, y=22))
|
||||
self.assertEqual(p, Point(y=22, x=11))
|
||||
self.assertEqual(p, Point(*(11, 22)))
|
||||
self.assertEqual(p, Point(**dict(x=11, y=22)))
|
||||
self.assertRaises(TypeError, Point, 1) # too few args
|
||||
self.assertRaises(TypeError, Point, 1, 2, 3) # too many args
|
||||
self.assertRaises(TypeError, eval, 'Point(XXX=1, y=2)', locals()) # wrong keyword argument
|
||||
self.assertRaises(TypeError, eval, 'Point(x=1)', locals()) # missing keyword argument
|
||||
self.assertEqual(repr(p), 'Point(x=11, y=22)')
|
||||
self.assertNotIn('__weakref__', dir(p))
|
||||
self.assertEqual(p, Point._make([11, 22])) # test _make classmethod
|
||||
self.assertEqual(p._fields, ('x', 'y')) # test _fields attribute
|
||||
self.assertEqual(p._replace(x=1), (1, 22)) # test _replace method
|
||||
self.assertEqual(p._asdict(), dict(x=11, y=22)) # test _asdict method
|
||||
|
||||
try:
|
||||
p._replace(x=1, error=2)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self._fail('Did not detect an incorrect fieldname')
|
||||
|
||||
p = Point(x=11, y=22)
|
||||
self.assertEqual(repr(p), 'Point(x=11, y=22)')
|
||||
|
||||
def test_tupleness(self):
|
||||
Point = BagType('Point', ('x', 'y'))
|
||||
p = Point(11, 22)
|
||||
|
||||
self.assertIsInstance(p, tuple)
|
||||
self.assertEqual(p, (11, 22)) # matches a real tuple
|
||||
self.assertEqual(tuple(p), (11, 22)) # coercable to a real tuple
|
||||
self.assertEqual(list(p), [11, 22]) # coercable to a list
|
||||
self.assertEqual(max(p), 22) # iterable
|
||||
self.assertEqual(max(*p), 22) # star-able
|
||||
x, y = p
|
||||
self.assertEqual(p, (x, y)) # unpacks like a tuple
|
||||
self.assertEqual((p[0], p[1]), (11, 22)) # indexable like a tuple
|
||||
self.assertRaises(IndexError, p.__getitem__, 3)
|
||||
|
||||
self.assertEqual(p.x, x)
|
||||
self.assertEqual(p.y, y)
|
||||
self.assertRaises(AttributeError, eval, 'p.z', locals())
|
||||
|
||||
def test_odd_sizes(self):
|
||||
Zero = BagType('Zero', ())
|
||||
self.assertEqual(Zero(), ())
|
||||
self.assertEqual(Zero._make([]), ())
|
||||
self.assertEqual(repr(Zero()), 'Zero()')
|
||||
self.assertEqual(Zero()._asdict(), {})
|
||||
self.assertEqual(Zero()._fields, ())
|
||||
|
||||
Dot = BagType('Dot', ('d', ))
|
||||
self.assertEqual(Dot(1), (1, ))
|
||||
self.assertEqual(Dot._make([1]), (1, ))
|
||||
self.assertEqual(Dot(1).d, 1)
|
||||
self.assertEqual(repr(Dot(1)), 'Dot(d=1)')
|
||||
self.assertEqual(Dot(1)._asdict(), {'d': 1})
|
||||
self.assertEqual(Dot(1)._replace(d=999), (999, ))
|
||||
self.assertEqual(Dot(1)._fields, ('d', ))
|
||||
|
||||
n = 5000 if sys.version_info >= (3, 7) else 254
|
||||
names = list(set(''.join([choice(string.ascii_letters) for j in range(10)]) for i in range(n)))
|
||||
n = len(names)
|
||||
Big = BagType('Big', names)
|
||||
b = Big(*range(n))
|
||||
self.assertEqual(b, tuple(range(n)))
|
||||
self.assertEqual(Big._make(range(n)), tuple(range(n)))
|
||||
for pos, name in enumerate(names):
|
||||
self.assertEqual(getattr(b, name), pos)
|
||||
repr(b) # make sure repr() doesn't blow-up
|
||||
d = b._asdict()
|
||||
d_expected = dict(zip(names, range(n)))
|
||||
self.assertEqual(d, d_expected)
|
||||
b2 = b._replace(**dict([(names[1], 999), (names[-5], 42)]))
|
||||
b2_expected = list(range(n))
|
||||
b2_expected[1] = 999
|
||||
b2_expected[-5] = 42
|
||||
self.assertEqual(b2, tuple(b2_expected))
|
||||
self.assertEqual(b._fields, tuple(names))
|
||||
|
||||
def test_pickle(self):
|
||||
p = TBag(x=10, y=20, z=30)
|
||||
for module in (pickle, ):
|
||||
loads = getattr(module, 'loads')
|
||||
dumps = getattr(module, 'dumps')
|
||||
for protocol in range(-1, module.HIGHEST_PROTOCOL + 1):
|
||||
q = loads(dumps(p, protocol))
|
||||
self.assertEqual(p, q)
|
||||
self.assertEqual(p._fields, q._fields)
|
||||
self.assertNotIn(b'OrderedDict', dumps(p, protocol))
|
||||
|
||||
def test_copy(self):
|
||||
p = TBag(x=10, y=20, z=30)
|
||||
for copier in copy.copy, copy.deepcopy:
|
||||
q = copier(p)
|
||||
self.assertEqual(p, q)
|
||||
self.assertEqual(p._fields, q._fields)
|
||||
|
||||
def test_name_conflicts(self):
|
||||
# Some names like "self", "cls", "tuple", "itemgetter", and "property"
|
||||
# failed when used as field names. Test to make sure these now work.
|
||||
T = BagType('T', ('itemgetter', 'property', 'self', 'cls', 'tuple'))
|
||||
t = T(1, 2, 3, 4, 5)
|
||||
self.assertEqual(t, (1, 2, 3, 4, 5))
|
||||
newt = t._replace(itemgetter=10, property=20, self=30, cls=40, tuple=50)
|
||||
self.assertEqual(newt, (10, 20, 30, 40, 50))
|
||||
|
||||
# Broader test of all interesting names taken from the code, old
|
||||
# template, and an example
|
||||
words = {
|
||||
'Alias', 'At', 'AttributeError', 'Build', 'Bypass', 'Create', 'Encountered', 'Expected', 'Field', 'For',
|
||||
'Got', 'Helper', 'IronPython', 'Jython', 'KeyError', 'Make', 'Modify', 'Note', 'OrderedDict', 'Point',
|
||||
'Return', 'Returns', 'Type', 'TypeError', 'Used', 'Validate', 'ValueError', 'Variables', 'a', 'accessible',
|
||||
'add', 'added', 'all', 'also', 'an', 'arg_list', 'args', 'arguments', 'automatically', 'be', 'build',
|
||||
'builtins', 'but', 'by', 'cannot', 'class_namespace', 'classmethod', 'cls', 'collections', 'convert',
|
||||
'copy', 'created', 'creation', 'd', 'debugging', 'defined', 'dict', 'dictionary', 'doc', 'docstring',
|
||||
'docstrings', 'duplicate', 'effect', 'either', 'enumerate', 'environments', 'error', 'example', 'exec', 'f',
|
||||
'f_globals', 'field', 'field_names', 'fields', 'formatted', 'frame', 'function', 'functions', 'generate',
|
||||
'getter', 'got', 'greater', 'has', 'help', 'identifiers', 'indexable', 'instance', 'instantiate',
|
||||
'interning', 'introspection', 'isidentifier', 'isinstance', 'itemgetter', 'iterable', 'join', 'keyword',
|
||||
'keywords', 'kwds', 'len', 'like', 'list', 'map', 'maps', 'message', 'metadata', 'method', 'methods',
|
||||
'module', 'module_name', 'must', 'name', 'named', 'namedtuple', 'namedtuple_', 'names', 'namespace',
|
||||
'needs', 'new', 'nicely', 'num_fields', 'number', 'object', 'of', 'operator', 'option', 'p', 'particular',
|
||||
'pickle', 'pickling', 'plain', 'pop', 'positional', 'property', 'r', 'regular', 'rename', 'replace',
|
||||
'replacing', 'repr', 'repr_fmt', 'representation', 'result', 'reuse_itemgetter', 's', 'seen', 'sequence',
|
||||
'set', 'side', 'specified', 'split', 'start', 'startswith', 'step', 'str', 'string', 'strings', 'subclass',
|
||||
'sys', 'targets', 'than', 'the', 'their', 'this', 'to', 'tuple_new', 'type', 'typename', 'underscore',
|
||||
'unexpected', 'unpack', 'up', 'use', 'used', 'user', 'valid', 'values', 'variable', 'verbose', 'where',
|
||||
'which', 'work', 'x', 'y', 'z', 'zip'
|
||||
}
|
||||
sorted_words = tuple(sorted(words))
|
||||
T = BagType('T', sorted_words)
|
||||
# test __new__
|
||||
values = tuple(range(len(words)))
|
||||
t = T(*values)
|
||||
self.assertEqual(t, values)
|
||||
t = T(**dict(zip(T._attrs, values)))
|
||||
self.assertEqual(t, values)
|
||||
# test _make
|
||||
t = T._make(values)
|
||||
self.assertEqual(t, values)
|
||||
# exercise __repr__
|
||||
repr(t)
|
||||
# test _asdict
|
||||
self.assertEqual(t._asdict(), dict(zip(T._fields, values)))
|
||||
# test _replace
|
||||
t = T._make(values)
|
||||
newvalues = tuple(v * 10 for v in values)
|
||||
newt = t._replace(**dict(zip(T._fields, newvalues)))
|
||||
self.assertEqual(newt, newvalues)
|
||||
# test _fields
|
||||
self.assertEqual(T._attrs, sorted_words)
|
||||
# test __getnewargs__
|
||||
self.assertEqual(t.__getnewargs__(), values)
|
||||
|
||||
def test_repr(self):
|
||||
A = BagType('A', ('x', ))
|
||||
self.assertEqual(repr(A(1)), 'A(x=1)')
|
||||
|
||||
# repr should show the name of the subclass
|
||||
class B(A):
|
||||
pass
|
||||
|
||||
self.assertEqual(repr(B(1)), 'B(x=1)')
|
||||
|
||||
def test_namedtuple_subclass_issue_24931(self):
|
||||
class Point(BagType('_Point', ['x', 'y'])):
|
||||
pass
|
||||
|
||||
a = Point(3, 4)
|
||||
self.assertEqual(a._asdict(), OrderedDict([('x', 3), ('y', 4)]))
|
||||
|
||||
a.w = 5
|
||||
self.assertEqual(a.__dict__, {'w': 5})
|
||||
|
||||
def test_annoying_attribute_names(self):
|
||||
self._create(
|
||||
'__slots__', '__getattr__', '_attrs', '_fields', '__new__', '__getnewargs__', '__repr__', '_make', 'get',
|
||||
'_replace', '_asdict', '_cls', 'self', 'tuple'
|
||||
)
|
||||
Reference in New Issue
Block a user