diff --git a/.style.yapf b/.style.yapf
index eaa5a7a..1da51e8 100644
--- a/.style.yapf
+++ b/.style.yapf
@@ -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
diff --git a/Makefile b/Makefile
index 445b863..d4c3556 100644
--- a/Makefile
+++ b/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
diff --git a/Projectfile b/Projectfile
index be2e0c8..9b879c9 100644
--- a/Projectfile
+++ b/Projectfile
@@ -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',
diff --git a/RELEASE-0.6.rst b/RELEASE-0.6.rst
new file mode 100644
index 0000000..805a4e3
--- /dev/null
+++ b/RELEASE-0.6.rst
@@ -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.
+
+
diff --git a/bonobo/__init__.py b/bonobo/__init__.py
index 0ac9bc3..32baf98 100644
--- a/bonobo/__init__.py
+++ b/bonobo/__init__.py
@@ -15,6 +15,23 @@ from bonobo._api import __all__
from bonobo._version import __version__
__all__ = ['__version__'] + __all__
+__logo__ = ''
__version__ = __version__
+
+def _repr_html_():
+ """This allows to easily display a version snippet in Jupyter."""
+ from bonobo.util.pkgs import bonobo_packages
+ from bonobo.commands.version import get_versions
+
+ return (
+ '
'
+ '
{}
'
+ '
{}
'
+ '
'
+ ).format(
+ __logo__, ' '.join(get_versions(all=True))
+ )
+
+
del sys
diff --git a/bonobo/_api.py b/bonobo/_api.py
index 7b8edc9..2f5522c 100644
--- a/bonobo/_api.py
+++ b/bonobo/_api.py
@@ -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,
)
diff --git a/bonobo/_version.py b/bonobo/_version.py
index 2724bac..1986ae4 100644
--- a/bonobo/_version.py
+++ b/bonobo/_version.py
@@ -1 +1 @@
-__version__ = '0.6.dev0'
+__version__ = '0.6.0a0'
diff --git a/bonobo/commands/convert.py b/bonobo/commands/convert.py
index 198dce0..f490213 100644
--- a/bonobo/commands/convert.py
+++ b/bonobo/commands/convert.py
@@ -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 []))
diff --git a/bonobo/commands/version.py b/bonobo/commands/version.py
index 3e3239a..5ca2311 100644
--- a/bonobo/commands/version.py
+++ b/bonobo/commands/version.py
@@ -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')
diff --git a/bonobo/config/__init__.py b/bonobo/config/__init__.py
index a86e8ba..6ba99e8 100644
--- a/bonobo/config/__init__.py
+++ b/bonobo/config/__init__.py
@@ -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',
]
diff --git a/bonobo/config/configurables.py b/bonobo/config/configurables.py
index 85ecdde..3e6f154 100644
--- a/bonobo/config/configurables.py
+++ b/bonobo/config/configurables.py
@@ -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.')
diff --git a/bonobo/config/functools.py b/bonobo/config/functools.py
new file mode 100644
index 0000000..506306b
--- /dev/null
+++ b/bonobo/config/functools.py
@@ -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
diff --git a/bonobo/config/options.py b/bonobo/config/options.py
index 51702c3..85b50e7 100644
--- a/bonobo/config/options.py
+++ b/bonobo/config/options.py
@@ -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
diff --git a/bonobo/config/processors.py b/bonobo/config/processors.py
index 3740192..c8a2ddf 100644
--- a/bonobo/config/processors.py
+++ b/bonobo/config/processors.py
@@ -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)
diff --git a/bonobo/config/services.py b/bonobo/config/services.py
index acf4dfd..282d88f 100644
--- a/bonobo/config/services.py
+++ b/bonobo/config/services.py
@@ -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__
diff --git a/bonobo/constants.py b/bonobo/constants.py
index 7f20fcd..fceb8f9 100644
--- a/bonobo/constants.py
+++ b/bonobo/constants.py
@@ -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'
diff --git a/bonobo/contrib/google/__init__.py b/bonobo/contrib/google/__init__.py
index e8e66b5..74d4612 100644
--- a/bonobo/contrib/google/__init__.py
+++ b/bonobo/contrib/google/__init__.py
@@ -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)
diff --git a/bonobo/errors.py b/bonobo/errors.py
index 08b97d4..53d0a5d 100644
--- a/bonobo/errors.py
+++ b/bonobo/errors.py
@@ -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
diff --git a/bonobo/examples/__init__.py b/bonobo/examples/__init__.py
index e69de29..ec68fc5 100644
--- a/bonobo/examples/__init__.py
+++ b/bonobo/examples/__init__.py
@@ -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 (),
+ }
diff --git a/bonobo/examples/nodes/__init__.py b/bonobo/examples/coffeeshops.csv
similarity index 100%
rename from bonobo/examples/nodes/__init__.py
rename to bonobo/examples/coffeeshops.csv
diff --git a/bonobo/examples/datasets/__main__.py b/bonobo/examples/datasets/__main__.py
new file mode 100644
index 0000000..37444ac
--- /dev/null
+++ b/bonobo/examples/datasets/__main__.py
@@ -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())
diff --git a/bonobo/examples/datasets/_services.py b/bonobo/examples/datasets/_services.py
deleted file mode 100644
index 36d3b18..0000000
--- a/bonobo/examples/datasets/_services.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from os.path import dirname
-
-import bonobo
-
-
-def get_services():
- return {'fs': bonobo.open_fs(dirname(__file__))}
diff --git a/bonobo/examples/datasets/coffeeshops.csv b/bonobo/examples/datasets/coffeeshops.csv
new file mode 100644
index 0000000..c4c10e3
--- /dev/null
+++ b/bonobo/examples/datasets/coffeeshops.csv
@@ -0,0 +1 @@
+"['name', 'address', 'zipcode', 'city']"
diff --git a/bonobo/examples/datasets/coffeeshops.json b/bonobo/examples/datasets/coffeeshops.json
index 391b5e8..8544018 100644
--- a/bonobo/examples/datasets/coffeeshops.json
+++ b/bonobo/examples/datasets/coffeeshops.json
@@ -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"}
\ No newline at end of file
+[{"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"}]
\ No newline at end of file
diff --git a/bonobo/examples/datasets/coffeeshops.ldjson b/bonobo/examples/datasets/coffeeshops.ldjson
new file mode 100644
index 0000000..30a5316
--- /dev/null
+++ b/bonobo/examples/datasets/coffeeshops.ldjson
@@ -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"}
\ No newline at end of file
diff --git a/bonobo/examples/datasets/coffeeshops.py b/bonobo/examples/datasets/coffeeshops.py
deleted file mode 100644
index 80f2d8d..0000000
--- a/bonobo/examples/datasets/coffeeshops.py
+++ /dev/null
@@ -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__))
diff --git a/bonobo/examples/datasets/coffeeshops.txt b/bonobo/examples/datasets/coffeeshops.txt
index 9e3c181..8382078 100644
--- a/bonobo/examples/datasets/coffeeshops.txt
+++ b/bonobo/examples/datasets/coffeeshops.txt
@@ -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
\ No newline at end of file
+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
diff --git a/bonobo/examples/files/_services.py b/bonobo/examples/files/_services.py
index 337bf6b..825e39d 100644
--- a/bonobo/examples/files/_services.py
+++ b/bonobo/examples/files/_services.py
@@ -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(),
+ }
diff --git a/bonobo/examples/files/csv_handlers.py b/bonobo/examples/files/csv_handlers.py
index 555bc67..acc6189 100644
--- a/bonobo/examples/files/csv_handlers.py
+++ b/bonobo/examples/files/csv_handlers.py
@@ -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()
+ )
diff --git a/bonobo/examples/files/json_handlers.py b/bonobo/examples/files/json_handlers.py
index f1818cd..819a8fd 100644
--- a/bonobo/examples/files/json_handlers.py
+++ b/bonobo/examples/files/json_handlers.py
@@ -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()
+ )
diff --git a/bonobo/examples/files/pickle_handlers.py b/bonobo/examples/files/pickle_handlers.py
index ed2ecd4..a04c4ea 100644
--- a/bonobo/examples/files/pickle_handlers.py
+++ b/bonobo/examples/files/pickle_handlers.py
@@ -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()
+ )
diff --git a/bonobo/examples/files/text_handlers.py b/bonobo/examples/files/text_handlers.py
index abbae1a..2e91227 100644
--- a/bonobo/examples/files/text_handlers.py
+++ b/bonobo/examples/files/text_handlers.py
@@ -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()
+ )
diff --git a/bonobo/examples/nodes/_services.py b/bonobo/examples/nodes/_services.py
deleted file mode 100644
index 337bf6b..0000000
--- a/bonobo/examples/nodes/_services.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from bonobo import get_examples_path, open_fs
-
-
-def get_services():
- return {'fs': open_fs(get_examples_path())}
diff --git a/bonobo/examples/nodes/bags.py b/bonobo/examples/nodes/bags.py
deleted file mode 100644
index 2bfe5de..0000000
--- a/bonobo/examples/nodes/bags.py
+++ /dev/null
@@ -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)
diff --git a/bonobo/examples/nodes/count.py b/bonobo/examples/nodes/count.py
deleted file mode 100644
index ea440a0..0000000
--- a/bonobo/examples/nodes/count.py
+++ /dev/null
@@ -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)
diff --git a/bonobo/examples/nodes/dicts.py b/bonobo/examples/nodes/dicts.py
deleted file mode 100644
index fde4b08..0000000
--- a/bonobo/examples/nodes/dicts.py
+++ /dev/null
@@ -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)
diff --git a/bonobo/examples/nodes/filter.py b/bonobo/examples/nodes/filter.py
deleted file mode 100644
index 4f7219a..0000000
--- a/bonobo/examples/nodes/filter.py
+++ /dev/null
@@ -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)
diff --git a/bonobo/examples/nodes/slow.py b/bonobo/examples/nodes/slow.py
deleted file mode 100644
index ecaaf44..0000000
--- a/bonobo/examples/nodes/slow.py
+++ /dev/null
@@ -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)
diff --git a/bonobo/examples/nodes/strings.py b/bonobo/examples/nodes/strings.py
deleted file mode 100644
index 1903151..0000000
--- a/bonobo/examples/nodes/strings.py
+++ /dev/null
@@ -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)
diff --git a/bonobo/examples/tutorials/tut02e03_writeasmap.py b/bonobo/examples/tutorials/tut02e03_writeasmap.py
index c7c7711..afc251e 100644
--- a/bonobo/examples/tutorials/tut02e03_writeasmap.py
+++ b/bonobo/examples/tutorials/tut02e03_writeasmap.py
@@ -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]
)
diff --git a/bonobo/examples/types/__init__.py b/bonobo/examples/types/__init__.py
index a2c0ceb..e69de29 100644
--- a/bonobo/examples/types/__init__.py
+++ b/bonobo/examples/types/__init__.py
@@ -1,7 +0,0 @@
-from . import bags, dicts, strings
-
-__all__ = [
- 'bags',
- 'dicts',
- 'strings',
-]
\ No newline at end of file
diff --git a/bonobo/examples/types/bags.py b/bonobo/examples/types/bags.py
deleted file mode 100644
index 2bfe5de..0000000
--- a/bonobo/examples/types/bags.py
+++ /dev/null
@@ -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)
diff --git a/bonobo/examples/types/dicts.py b/bonobo/examples/types/dicts.py
deleted file mode 100644
index fde4b08..0000000
--- a/bonobo/examples/types/dicts.py
+++ /dev/null
@@ -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)
diff --git a/bonobo/examples/types/strings.py b/bonobo/examples/types/strings.py
index 2fa765f..6675a77 100644
--- a/bonobo/examples/types/strings.py
+++ b/bonobo/examples/types/strings.py
@@ -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)
diff --git a/bonobo/execution/contexts/base.py b/bonobo/execution/contexts/base.py
index 847633b..953f13c 100644
--- a/bonobo/execution/contexts/base.py
+++ b/bonobo/execution/contexts/base.py
@@ -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
diff --git a/bonobo/execution/contexts/graph.py b/bonobo/execution/contexts/graph.py
index 55dbf7e..a6559a3 100644
--- a/bonobo/execution/contexts/graph.py
+++ b/bonobo/execution/contexts/graph.py
@@ -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):
diff --git a/bonobo/execution/contexts/node.py b/bonobo/execution/contexts/node.py
index 3cb3521..194cf36 100644
--- a/bonobo/execution/contexts/node.py
+++ b/bonobo/execution/contexts/node.py
@@ -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)
diff --git a/bonobo/execution/strategies/executor.py b/bonobo/execution/strategies/executor.py
index d7a0017..fc72226 100644
--- a/bonobo/execution/strategies/executor.py
+++ b/bonobo/execution/strategies/executor.py
@@ -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 = []
diff --git a/bonobo/execution/strategies/naive.py b/bonobo/execution/strategies/naive.py
index bd581ff..01b0416 100644
--- a/bonobo/execution/strategies/naive.py
+++ b/bonobo/execution/strategies/naive.py
@@ -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()
diff --git a/bonobo/nodes/basics.py b/bonobo/nodes/basics.py
index 3a53d2d..9710ef7 100644
--- a/bonobo/nodes/basics.py
+++ b/bonobo/nodes/basics.py
@@ -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
diff --git a/bonobo/nodes/filter.py b/bonobo/nodes/filter.py
index 2ec0130..0e0026f 100644
--- a/bonobo/nodes/filter.py
+++ b/bonobo/nodes/filter.py
@@ -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
diff --git a/bonobo/nodes/io/base.py b/bonobo/nodes/io/base.py
index af9e609..325d081 100644
--- a/bonobo/nodes/io/base.py
+++ b/bonobo/nodes/io/base.py
@@ -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
diff --git a/bonobo/nodes/io/csv.py b/bonobo/nodes/io/csv.py
index 188fd80..ed0a738 100644
--- a/bonobo/nodes/io/csv.py
+++ b/bonobo/nodes/io/csv.py
@@ -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
diff --git a/bonobo/nodes/io/file.py b/bonobo/nodes/io/file.py
index e49d6de..a8b2c2e 100644
--- a/bonobo/nodes/io/file.py
+++ b/bonobo/nodes/io/file.py
@@ -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
diff --git a/bonobo/nodes/io/json.py b/bonobo/nodes/io/json.py
index 6c2b4b2..86d3262 100644
--- a/bonobo/nodes/io/json.py
+++ b/bonobo/nodes/io/json.py
@@ -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
+ """
diff --git a/bonobo/nodes/io/pickle.py b/bonobo/nodes/io/pickle.py
index bc02ce8..da96a6d 100644
--- a/bonobo/nodes/io/pickle.py
+++ b/bonobo/nodes/io/pickle.py
@@ -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
diff --git a/bonobo/nodes/io/xml.py b/bonobo/nodes/io/xml.py
deleted file mode 100644
index e69de29..0000000
diff --git a/bonobo/nodes/throttle.py b/bonobo/nodes/throttle.py
index 58f5c09..04e5cf3 100644
--- a/bonobo/nodes/throttle.py
+++ b/bonobo/nodes/throttle.py
@@ -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)
diff --git a/bonobo/plugins/console.py b/bonobo/plugins/console.py
index 584244c..69a044c 100644
--- a/bonobo/plugins/console.py
+++ b/bonobo/plugins/console.py
@@ -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)
diff --git a/bonobo/plugins/sentry.py b/bonobo/plugins/sentry.py
new file mode 100644
index 0000000..44799da
--- /dev/null
+++ b/bonobo/plugins/sentry.py
@@ -0,0 +1,6 @@
+from bonobo.plugins import Plugin
+from raven import Client
+
+
+class SentryPlugin(Plugin):
+ pass
diff --git a/bonobo/structs/__init__.py b/bonobo/structs/__init__.py
index 6c0d9ab..ba640c9 100644
--- a/bonobo/structs/__init__.py
+++ b/bonobo/structs/__init__.py
@@ -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',
]
diff --git a/bonobo/structs/bags.py b/bonobo/structs/bags.py
deleted file mode 100644
index 0738790..0000000
--- a/bonobo/structs/bags.py
+++ /dev/null
@@ -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
diff --git a/bonobo/structs/graphs.py b/bonobo/structs/graphs.py
index f11cd31..39de1fe 100644
--- a/bonobo/structs/graphs.py
+++ b/bonobo/structs/graphs.py
@@ -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 '