Ejemplo n.º 1
0
    def add_listener(self, listener=None, **kws):
        """为工作流添加监听器,该方法有两种使用方式。

           直接使用: 直接传递一个监听器对象或者一个AbstractListener子类的类型对象
                    当传递监听器对象时,此对象会在每次进行execute时重复使用。
                    当传递类型对象时,每次进行execute都会依照此类型生成一个新的对象
                    类型对象的构造函数参数除self以外必须为空。
           包装函数: 只传递感兴趣的生命周期
                    workflow.add_listener(start=on_start, finish=on_finish)
        """
        if listener:
            if (not isinstance(listener, AbstractListener)
                    and not issubclass(listener, AbstractListener)):
                raise InvalidArgumentException(
                    u"监听器必须是AbstractListener对象或者是其子类类型对象")
            self._listeners.append(listener)
            return
        if kws:
            event_funcs = []
            for event_name in kws:
                if not event_name.startswith("on_"):
                    event_name = "on_" + event_name
                if event_name not in AbstractListener.EVENTS:
                    raise InvalidArgumentException(u"不被支持的事件:" + event_name)
                event_funcs += (event_name, kws[event_name])
            self._listeners.append(AbstractListener.wrap_function(event_funcs))
            return
Ejemplo n.º 2
0
def load_module(module_path, module_name=None, entry_point=None):
    """根据不同形式的module_path来对模块进行加载
       :param module_path
              如果以冒号开头,那么加载指定entry_point注册的模块
              如果以.py结尾,那么按照文件进行加载。
              其它情况按照当前PYTHONPATH中的模块名进行加载
       :return 如果加载成功,那么返回加载的模块对象
               如果加载失败,那么返回None
    """

    if module_path[0] == ":":
        # 加载entry_point模块
        if not entry_point:
            raise InvalidArgumentException(u"必须指定entry_point参数")
        module_path = module_path[1:]
        for ep in pkg_resources.iter_entry_points(entry_point):
            if ep.name == module_path:
                return ep.load()
        return None
    elif module_path.endswith(".py"):
        # 加载文件模块
        if module_name is None:
            module_name = os.path.split(module_path)[1].split(".")[0]
        if not os.path.exists(module_path):
            raise InvalidArgumentException(u"找不到模块文件 '{}'".format(module_path))
        return imp.load_source()
    else:
        # 按模块名称加载模块
        return __import__(module_path, fromlist=[""])
Ejemplo n.º 3
0
    def __init__(self,
                 name,
                 plugin=None,
                 caller=None,
                 args=None,
                 thread_num=10,
                 task_num_per_thread=None,
                 pool_type=ThreadPoolExecutor,
                 sub_join=None,
                 result_join=_expand_sub_results,
                 error_action="stop",
                 error_handler=None,
                 error_default_value=None,
                 goto=None):
        """
        :param name 工作单元名称
        :param plugin 插件名称
        :param caller 执行逻辑
        :param args 参数
        :param thread_num 线程数目,默认开启10个线程
        :param task_num_per_thread 每线程任务数,主要用于不可预知总数的生成器参数
        :param pool_type 池对象类型
        :param sub_join 针对每组线程的join逻辑,接受一个Context对象和一个结果列表作为参数
        :param result_join 对最终的结果进行处理,接受一个Context对象和各个Task的结果列表
        :param error_action 错误处理动作,如果是stop,那么会终止整个工作流的执行
                            如果是continue,那么会忽略错误继续执行
        :param error_handler 错误处理器,如果错误处理动作为continue,那么不会调用上层workflow
                             的错误监听器,而是会调用此处的错误处理器
        :param error_default_value 如果是error_action为continue,那么会以该默认值作为错误操作默认结果
        :param goto 要执行的下一个工作单元
        """
        Job.__init__(self, name, plugin, caller, args, goto)
        self._thread_num = thread_num
        self._task_num_per_thread = task_num_per_thread
        self._pool_type, self._sub_join = pool_type, sub_join
        self._result_join, self._error_action = result_join, error_action
        self._error_handler = error_handler
        self._error_default_value = error_default_value

        if self._error_action != "stop" and self._error_action != "continue":
            raise InvalidArgumentException(u"错误处理动作只允许stop或者continue类型")

        try:
            len(args)
        except TypeError:
            if self._task_num_per_thread is None:
                raise InvalidArgumentException(
                    u"args参数为无法预知长度的类型,无法自动分配任务,"
                    u"请使用task_num_per_thread参数来为每个线程分配任务数目")
        else:
            # 根据参数总数目计算每个线程应该分配的任务数
            if self._task_num_per_thread is None:
                args_len = len(self._args)
                if args_len % self._thread_num == 0:
                    self._task_num_per_thread = args_len / self._thread_num
                else:
                    self._task_num_per_thread = args_len / self._thread_num + 1

        self._error_break = False  # 错误中断标记
Ejemplo n.º 4
0
    def _get_runtime_args(self, context, template_args):
        """获取运行时参数
           Job的参数分为两部分,一部分是在workflow声明时指定的参数
           一部分是在运行时通过context指定的参数

           参数支持两种形式,一种是参数列表,一种是字典关键字的形式

           如果是列表,那么将会用context中的列表取代self._args中的列表
           如果是字典,那么将会执行update操作,context中的项将覆盖_args中的项
           如果self._args为None,那么直接使用context中的参数
           如果self._args与context类型不一致,那么抛出异常

           最终将args中的字符串变量名用Context真实值进行替换
        """

        context_args = context.args(self.name)
        if isinstance(context_args, types.FunctionType):
            context_args = context_args(context)

        if isinstance(context_args, types.StringTypes):
            if context_args.startswith("$"):
                context_args = context[context_args[1:]]
            else:
                context_args = context[context_args]

        args_schema = None
        if not context_args:
            args_schema = template_args
        elif not template_args:
            args_schema = context_args
        elif isinstance(template_args, SequenceCollectionType):
            if not isinstance(context_args, SequenceCollectionType):
                raise InvalidArgumentException(
                    u"Job '{}'的初始参数类型跟上下文指定的参数类型不一致".format(self.name))
            args_schema = template_args
            if context_args:
                args_schema = context_args
        elif isinstance(template_args, types.DictType):
            if not isinstance(context_args, types.DictType):
                raise InvalidArgumentException(
                    u"Job '{}'的初始参数类型跟上下文指定的参数类型不一致".format(self.name))
            args_schema = template_args
            if context_args:
                args_schema = dict(template_args)
                args_schema.update(context_args)
        else:
            raise InvalidArgumentException(u"只能接受列表、元组、字典类型的参数对象")

        # 替换为真正的变量
        if not args_schema:
            return None
        elif isinstance(args_schema, SequenceCollectionType):
            return [parse_context_var(context, arg) for arg in args_schema]
        elif isinstance(args_schema, types.DictType):
            return {
                arg_name: parse_context_var(context, args_schema[arg_name])
                for arg_name in args_schema
            }
Ejemplo n.º 5
0
    def execute(self, context):
        fork_pool = context.get("_fork.pool", None)
        count_down_latch = context["_fork.count_down_latch"]
        fork_result = context["_fork.result"]

        try:
            count_down_latch. await ()  # 等待Fork线程执行完毕
            result = None
            if self._join is not None:
                result = self._join(context, fork_result)
            else:
                result = []
                for fork_end in fork_result:
                    if fork_end.status == End.STATUS_OK:
                        result.append(fork_end.result)
                    elif fork_end.status == End.STATUS_BAD_REQUEST:
                        raise InvalidArgumentException(fork_end.msg)
                    elif fork_end.status == End.STATUS_ERROR_HAPPENED:
                        raise fork_end.exc_value
            context["{}.result".format(self._name)] = result
            return result
        finally:
            # 清理上下文变量
            if fork_pool is not None:
                fork_pool.shutdown()
                del context["_fork.pool"]
            del context["_fork.count_down_latch"]
            del context["_fork.result"]
Ejemplo n.º 6
0
 def __init__(self,
              name,
              plugin=None,
              caller=None,
              args=None,
              max_items=10,
              timeout=None,
              filter=None,
              immediately=False,
              give_back_handler=None,
              goto=None):
     """
     :param name 工作单元名称
     :param plugin 使用插件名称
     :param caller 执行逻辑
     :param args 参数
     :param max_items 最大条目
     :param timeout 超时时间
     :param filter 过滤器,接受一个context参数和一个条目,返回True or False
     :param immediately 如果超时,是否立即结束工作,
                        如果为True,那么会造成正在处理的数据丢失,但是会准时停止。
                        如果为False,那么会等待当前任务完成再继续,
                        但如果目前正在遭遇IO阻塞之类的情况,会继续阻塞很长时间。
     :param give_back_handler 用于处理immediately为True时丢失的数据,比如重新归还到队列等等。
     :param goto 下一步要执行的工作单元
     """
     Job.__init__(self, name, plugin, caller, args, goto)
     self._max_items, self._timeout = max_items, timeout
     self._filter = filter
     self._immediately = immediately
     self._give_back_handler = give_back_handler
     if self._timeout is not None and self._timeout < 0:
         raise InvalidArgumentException(u"timeout参数必须是大于等于0的整数,单位是秒")
Ejemplo n.º 7
0
 def __init__(self,
              name,
              sub_jobs,
              pool=None,
              pool_type=ThreadPoolExecutor,
              join=None,
              error_action="stop",
              error_handler=None,
              error_default_value=None,
              goto=None):
     """
     :param name 并行单元名称
     :param sub_jobs 子任务列表
     :param pool 指定池对象,若为None,那么会依据pool_type构建一个新的pool
     :param pool_type 池类型,如果pool为None,根据该类型来构建新的pool
     :param join 对最终结果进行处理,接受context对象和每个任务的结果列表作为参数
     :param error_action 错误处理动作,如果是stop,那么会终止整个工作流,
                         如果是continue,那么会忽略该错误继续工作流
     :param error_handler 错误处理器,当使用continue时,不会触发全局的error listener
                          可在此指定单独的error_handler进行处理
     :param error_default_value 当处于continue的错误处理模式时,出错任务的默认值
     :param goto 下一步要执行的单元
     """
     if self._error_action != "stop" and self._error_action != "continue":
         raise InvalidArgumentException(u"错误处理动作只允许stop或者continue类型")
Ejemplo n.º 8
0
 def _validate_type(self, value):
     if not self._type:
         return
     if not isinstance(value, self._type):
         raise InvalidArgumentException(
             u"参数 '{name}' 的类型不正确,只允许以下类型:{types}".format(name=self._name,
                                                          types=self._type))
Ejemplo n.º 9
0
 def _check_req(self, req):
     if not isinstance(req, Req):
         if isinstance(req, types.StringTypes):
             req = Req("get", req)
         else:
             raise InvalidArgumentException(u"req_list参数中包含不合法的类型")
     return req
Ejemplo n.º 10
0
 def _validate_regex(self, value):
     if not self._regex:
         return
     value = str(value)
     if not re.search(self._regex, value):
         raise InvalidArgumentException(
             u"参数 '{name}' 不符合正则表达式'{regex}'".format(name=self._name,
                                                     regex=self._regex))
Ejemplo n.º 11
0
 def _get_read_file_queue(self, context):
     if not os.path.exists(self._filepath):
         raise InvalidArgumentException(u"目标文件 '{}' 不存在".format(
             self._filepath))
     read_file_queue = []
     if self._pointer is None or not os.path.exists(self._pointer):
         read_file_queue.append((self._filepath, 0))
         return read_file_queue
     saved_pos, saved_inode = self._read_pointer()
     # 比较inode是否一致
     if os.stat(self._filepath).st_ino == saved_inode:
         read_file_queue.append((self._filepath, saved_pos))
     else:
         if self._change_file_logic is None:
             raise InvalidArgumentException(
                 u"文件已经切换,但是未指定change_file_logic文件切换逻辑")
         old_file = self._change_file_logic(context)
         read_file_queue.append((old_file, saved_pos))
         read_file_queue.append((self._filepath, 0))
     return read_file_queue
Ejemplo n.º 12
0
    def __init__(self, event_funcs):
        super(_WrappedFunctionListener, self).__init__()

        if len(event_funcs) % 2 != 0:
            raise InvalidArgumentException(
                (u"event_funcs元数不正确,"
                 u"必须为(事件名1, 函数1, 事件名2, 函数2, ...)的形式"))
        for event_name, func in itertools.izip(event_funcs[::2],
                                               event_funcs[1::2]):
            if not event_name.startswith("on_"):
                event_name = "on_" + event_name

            if event_name not in AbstractListener.EVENTS:
                raise InvalidArgumentException(u"未知的事件:{}".format(event_name))

            def event_handler_factory(func):
                def event_handler(ctx, f=func):
                    return f(ctx)

                return event_handler

            setattr(self, event_name, event_handler_factory(func))
Ejemplo n.º 13
0
def parse_time_unit(time_unit):
    """将以下单位描述转换为秒
       1d -> 24 * 60 * 60
       1h -> 1 * 60 * 60
       1m -> 1 * 60
       1s -> 1
    """
    match_result = time_unit_regex.search(time_unit)
    if not match_result:
        raise InvalidArgumentException(u"参数 '{}' 不符合时间单位格式".format(time_unit))
    time_value, unit = (int(match_result.group(1)),
                        match_result.group(2).lower())
    return time_value * time_units[unit]
Ejemplo n.º 14
0
 def _validate_min_max(self, value):
     if self._min is not None:
         if isinstance(value, numbers.Number):
             if self._min > value:
                 raise InvalidArgumentException(
                     u"参数 '{name}' 的值不能小于{min}".format(name=self._name,
                                                       min=self._min))
         else:
             if self._min > len(value):
                 raise InvalidArgumentException(
                     u"参数 '{name}' 的长度不能小于{min}".format(name=self._name,
                                                        min=self._min))
     if self._max is not None:
         if isinstance(value, numbers.Number):
             if self._max < value:
                 raise InvalidArgumentException(
                     u"参数 '{name}' 的值不能大于{max}".format(name=self._name,
                                                       max=self._max))
         else:
             if self._max < len(value):
                 raise InvalidArgumentException(
                     u"参数 '{name}' 的长度不能大于{max}".format(name=self._name,
                                                        max=self._max))
Ejemplo n.º 15
0
 def __init__(self, attachment_file, mime_type, attachment_filename=None):
     """
     :param attachment_file  作为附件的文件对象,可以是file对象或者StringIO对象,
                             如果是字符串,那么将作为文件路径进行加载
     :param mime_type  附件的mime type,比如application/octet-stream
     :param attachment_filename  附件所使用的文件名
     """
     if attachment_filename is None:
         if isinstance(attachment_file, types.StringTypes):
             self._attachment_filename = os.path.split(
                 attachment_file)[1]
         elif isinstance(attachment_file, types.FileType):
             self._attachment_filename = os.path.split(
                 attachment_file.name)[1]
         else:
             raise InvalidArgumentException(
                 u"必须制定attachement_filename参数作为附件文件名")
Ejemplo n.º 16
0
    def execute(self,
                context,
                way,
                left,
                right,
                on,
                fields,
                name,
                titles=None,
                variable=None):
        """
        :param context 上下文对象
        :param way join方式,允许inner、left和right三种join方式
        :param on  join条件,left_column = right_column,多个条件用逗号隔开
        :param fields 结果字段
        :param name 结果表格名称
        :param titles 结果表格标题
        :param variable 用于存储的上下文变量
        """

        if way not in ("inner", "left", "right"):
            raise InvalidArgumentException(
                u"不合法的join方式'{}',只支持inner、left、right三种join方式".format(way))

        conditions = self._parse_conditions(on)

        if isinstance(left, types.StringTypes):
            left = context[left]
        if isinstance(right, types.StringTypes):
            right = context[right]

        if way == "inner":
            result = self._inner_join(left, right, fields, conditions)
        elif way == "left":
            result = self._side_join("left", left, right, fields, conditions)
        elif way == "right":
            result = self._side_join("right", right, left, fields, conditions)

        if titles is None:
            titles = tuple(Title(f.split(".")[1]) for f in fields)

        table = ListTable(name, titles, result)
        if variable:
            context[variable] = table
        else:
            return table
Ejemplo n.º 17
0
    def validate(self, value):
        """执行验证
           :param value 要验证的值
        """
        if self._required and self._is_empty(value):
            raise InvalidArgumentException(u"参数 '{}' 的值是必须的,不能为空".format(
                self._name))

        # 如果非必须并且为空,那么接下来的验证就不必运行了
        if self._is_empty(value):
            return

        # 检查类型
        self._validate_type(value)

        # 检查大小、长度
        self._validate_min_max(value)

        # 检查正则
        self._validate_regex(value)

        # 检查逻辑
        self._validate_logic(value)
Ejemplo n.º 18
0
    def __call__(self, context):
        read_file_queue = self._get_read_file_queue(context)
        result = []
        for read_file_info in read_file_queue:
            filepath, pos = read_file_info
            record_matcher = self._record_matcher
            if isinstance(self._record_matcher, types.StringTypes):
                if self._record_matcher == "line":

                    def record_matcher(ctx):
                        if ctx.record_filter is None or \
                                ctx.record_filter(ctx.current_line):
                            if ctx.record_handler is None:
                                ctx.records.append(ctx.current_line)
                            else:
                                ctx.records.append(
                                    ctx.record_handler(ctx.current_line))
                            ctx.record_pos = ctx.current_pos
                else:
                    raise InvalidArgumentException(
                        u"record_matcher参数的值必须是函数或者是\"line\"")
            TextR.TextRecordContext(filepath=filepath,
                                    pos=pos,
                                    record_matcher=record_matcher,
                                    record_handler=self._record_handler,
                                    record_filter=self._record_filter,
                                    pointer=self._pointer,
                                    max_line=self._max_line,
                                    records=result).read()

        if self._result_wrapper is not None:
            result = self._result_wrapper(result)

        if self._variable:
            context[self._variable] = result

        return result
Ejemplo n.º 19
0
    def __call__(self, context):
        if (self._style == "line" and self._path
                and not self._path.startswith(HTTP_SCHEMA)):
            with open(self._path, "w") as f:
                for row in self._object:
                    row = self._handle_record(row)
                    f.write(ujson.dumps(row) + "\n")
            return

        # json文本
        json_text = ""

        if isinstance(self._object, types.FunctionType):
            self._object = self._object(context)
        elif isinstance(self._object, types.StringTypes):
            self._object = context[self._object]

        if self._style == "object":
            json_text = ujson.dumps(self._object)

        result = []
        for row in self._object:
            row = self._handle_record(row, result.append)

        # 数组格式直接dump
        if self._style == "array":
            json_text = ujson.dumps(result)

        # line格式
        if self._style == "line":
            string_buffer = []
            for row in self._object:
                row = self._handle_record(row)
                string_buffer.append(ujson.dumps(row))
            json_text = "\n".join(string_buffer)

        if self._path is None:
            if self._variable:
                context[self._variable] = json_text
                return
            else:
                raise InvalidArgumentException(
                    u"当path为None时,必须指定一个有效的variable")

        if self._path.startswith(HTTP_SCHEMA):
            if self._http_method.lower() == "post":
                if self._http_field:
                    requests.post(self._path,
                                  data={self._http_field: json_text})
                elif self._style == "line":
                    requests.post(self._path, data=json_text)
                else:
                    requests.post(self._path, json=json_text)
            elif self._http_method.lower() == "put":
                requests.put(self._path, json=json_text)
        else:
            with open(self._path, "w") as f:
                f.write(json_text)

        if self._variable:
            context[self._variable] = json_text
Ejemplo n.º 20
0
    def execute(self, context, server, receivers, mail=None,
                sender=None, subject=None, content=None,
                encoding="utf-8", attachments=None):
        """
        :param server SMTP服务名称
        :param receiver 收件人列表,可以是字符串组成的邮箱列表,也可以是对象列表
        :param mail  用来动态表述邮件内容的邮件类
        :param sender 发件人,可以传递字符串或函数,函数接受上下文以及收件人对象作为参数
        :param subject 标题,可传递字符串或函数,函数参数同上
        :param content 正文,可以是字符串或函数,函数参数同上
        :param encoding 编码,可以是字符串或函数,函数参数同上
        :param attachments 附件列表,可以是Attachment对象列表或者函数,函数参数同上
        """

        # 组装Mail对象列表
        mail_list = []
        if mail is not None:
            if not issubclass(mail, Mail):
                raise InvalidArgumentException(
                    u"mail参数必须是girlfriend.plugin.mail.Mail类型的子类")
        else:
            mail = partial(_Mail, sender=sender, subject=subject,
                           content=content, encoding=encoding,
                           attachments=attachments)

        if isinstance(receivers, types.StringTypes):
            mail_list = [mail(context=context, receiver=receivers)]
        else:
            mail_list = [mail(context=context, receiver=receiver)
                         for receiver in receivers]

        # 创建SMTP连接
        smtp_config = _smtp_manager.get_smtp_config("smtp_" + server)
        if smtp_config.ssl:
            smtp_server = smtplib.SMTP_SSL(
                host=smtp_config.host,
                port=smtp_config.port
            )
        else:
            smtp_server = smtplib.SMTP(
                host=smtp_config.host,
                port=smtp_config.port
            )
        smtp_server.login(smtp_config.account, smtp_config.password)

        try:
            for mail in mail_list:
                msg = MIMEMultipart()
                msg['From'] = mail.sender
                msg['To'] = mail.receiver_email
                msg['Subject'] = Header(mail.subject, mail.encoding)
                msg.attach(MIMEText(mail.content, "html", mail.encoding))
                attachments = mail.attachments
                if attachments:
                    for attachment in attachments:
                        msg.attach(attachment.build_mime_object())
                smtp_server.sendmail(
                    mail.sender,
                    [email_address.strip()
                     for email_address in mail.receiver_email.split(",")],
                    msg.as_string())
        finally:
            smtp_server.quit()
Ejemplo n.º 21
0
    def __init__(self,
                 workflow_list,
                 config=None,
                 plugin_mgr=plugin_manager,
                 context_factory=Context,
                 logger=None,
                 parrent_context=None,
                 thread_id=None):
        """
        :param workflow_list 工作单元列表
        :param config 配置数据
        :param plugin_mgr 插件管理器,默认是plugin自带的entry_points管理器
        :param context_factory 上下文工厂,必须具有config, args, plugin_mgr, parent
                               这四个约定的参数
        :param logger 日志对象
        """

        self._workflow_list = workflow_list
        self._config = config or Config()
        self._plugin_manager = plugin_mgr

        self._units = {}  # 以名称为Key的工作流单元引用字典
        for idx, unit in enumerate(self._workflow_list):
            if unit.name in self._units:
                raise WorkflowUnitExistedException(unit.name)
            self._units[unit.name] = unit
            if unit.unittype == "job" or unit.unittype == "join":
                # 如果未指定goto,那么goto的默认值是下一个节点
                if unit.goto is None:
                    if idx < len(self._workflow_list) - 1:
                        unit.goto = self._workflow_list[idx + 1].name
                    else:
                        unit.goto = "end"
            elif unit.unittype == "fork":
                # 自动设置起始节点
                if unit.start_point is None:
                    if idx < len(self._workflow_list) - 1:
                        unit.start_point = self._workflow_list[idx + 1].name
                    else:
                        raise InvalidArgumentException(
                            u"Fork单元 '{}' 必须指定一个有效的start_point参数")
                # 设置下一步运行的goto节点,如果未指定,则设置最近的join
                if unit.goto is None:
                    for i, next_unit in enumerate(self._workflow_list[idx +
                                                                      1:],
                                                  start=idx + 1):
                        if next_unit.unittype == "join":
                            unit.goto = next_unit.name
                            break
                    else:
                        raise InvalidArgumentException(
                            u"Fork单元 '{}' 必须使用一个有效的goto跳转到Join".format(
                                unit.name))
                # 自动设置结束节点
                if unit.end_point is None:
                    for i, next_unit in enumerate(self._workflow_list[idx +
                                                                      1:],
                                                  start=idx + 1):
                        if (next_unit.unittype == "join"
                                and next_unit.name == unit.goto):
                            # join unit前一个元素
                            unit.end_point = self._workflow_list[i - 1].name
                            break
                    else:
                        raise InvalidArgumentException(
                            u"Fork单元 '{}' 必须指定一个有效的end_point参数".format(
                                unit.name))

        self._context_factory = context_factory
        self._listeners = []

        # 创建logger
        if logger is None:
            self._logger = create_logger("girlfriend", (stdout_handler(), ))
        else:
            self._logger = logger

        self._parrent_context = parrent_context
        self._thread_id = thread_id
Ejemplo n.º 22
0
 def __init__(self, count):
     if count <= 0:
         raise InvalidArgumentException(u"count参数必须为正整数")
     self._count = count
     self._awaiting_count = count
     self._condition = threading.Condition()
Ejemplo n.º 23
0
 def add_four(ctx, num):
     if num < 0:
         raise InvalidArgumentException("num must more than zero!")
     return num + 4
Ejemplo n.º 24
0
 def parse(cls, statement):
     match_result = _JoinCondition.PATTERN.search(statement)
     if not match_result:
         raise InvalidArgumentException(u"表达式'{}'格式不合法".format(statement))
     return cls(match_result.group(1), match_result.group(2))
Ejemplo n.º 25
0
 def _validate_logic(self, value):
     if self._logic is None:
         return
     msg = self._logic(value)
     if msg:
         raise InvalidArgumentException(msg)