Python中过滤对象的用户界面

Python中过滤对象的用户界面,python,api,design-patterns,Python,Api,Design Patterns,在我的申请中,我有一个如下定义/概述的职业类别。此作业类的实例表示特定的作业运行。作业可以有多个检查点,每个检查点可以有多个命令 Job - JobName - [JobCheckpoint] - StartTime - EndTime - Status - ... JobCheckpoint - JobCheckpointName - [JobCommand] - StartTime - EndTime - Status - ... JobCommand - Jo

在我的申请中,我有一个如下定义/概述的职业类别。此作业类的实例表示特定的作业运行。作业可以有多个检查点,每个检查点可以有多个命令

Job
 - JobName
 - [JobCheckpoint]
 - StartTime
 - EndTime
 - Status
 - ...

JobCheckpoint
 - JobCheckpointName
 - [JobCommand]
 - StartTime
 - EndTime
 - Status
 - ...

JobCommand
 - JobCommandName
 - [Command]
 - StartTime
 - EndTime
 - Status 
 - ...
在任何一天,都有大约10万个不同的作业在运行。作业信息保存在文件系统中。我想用Python设计一个用于查询这些作业对象的用户界面。例如,用户应该能够查询

  • 在x和y间隔之间运行的所有作业
  • 运行命令x的所有作业
  • 所有作业都处于失败状态
  • 所有作业都处于失败和终止状态
  • 特定作业的所有检查点/命令
  • 还有更多
  • 为了解决这个问题,我考虑在用户界面中提供以下方法

     get_jobs(Filter)
    
    我不知道如何用Python设计这个过滤器类

  • 以支持对作业对象的所有此类查询
  • 并让用户保持对API的简单/直观的使用

  • 这里的线索真的很感谢

    这些都是部分主观问题。但是,我将尽我目前所知和所提问题中可用的信息,尝试回答其中一些问题

    过滤器类的外观如何

    例如,这可能取决于存储机制。它是作为一组Python对象存储在内存中,还是首先从SQL数据库或NoSQL数据库中取出

    如果它是从SQL数据库中获取的,则可以利用SQL的过滤机制。它毕竟是一种(结构化)查询语言

    在这种情况下,您的过滤器类将类似于将字段值转换为一组SQL运算符/条件

    如果是一堆Python对象,没有用于查询数据的数据库机制,那么您可能需要考虑自己的查询/过滤方法

    筛选器类可能正在使用条件类和运算符类。也许您有一个作为抽象类的操作符类,并有“粘合”操作符将条件粘合在一起(和/或)。以及另一种用于比较域对象属性和值的运算符

    对于后者,即使您没有为其设计“过滤语言”,您也可以从API查询格式中获得一些灵感,这里为Flask Untivent指定了:

    当然,如果您正在设计一个查询接口,例如RESTAPI,Flask Untivent的查询格式可以给您一些如何处理查询的灵感

    返回的域对象列表正确吗?还是应该返回dict列表

    返回域对象列表的优点是能够使用继承。这至少是一个可能的优势

    某些类别的草图:

    from abc import ABCMeta, abstractmethod
    from typing import List
    
    class DomainObjectOperatorGlue(metaclass=ABCMeta):    
        @abstractmethod
        def operate(self, haystack: List['DomainObject'], criteria: 
            List['DomainObject']) -> List['DomainObject']:
            pass
    
    class DomainObjectFieldGlueOperator(metaclass=ABCMeta):
        @abstractmethod
        def operate(self, conditions: List[bool]) -> bool:
            pass
    
    class DomainObjectFieldGlueOperatorAnd(DomainObjectFieldGlueOperator):
        def operate(self, conditions: List[bool]) -> bool:
            # If all conditions are True then return True here,
            # otherwise return False.
            # (...)
            pass
    
    class DomainObjectFieldGlueOperatorOr(DomainObjectFieldGlueOperator):
        def operate(self, conditions: List[bool]) -> bool:
            # If only one (or more) of the conditions are True then return True
            # otherwise, if none are True, return False.
            # (...)
            pass
    
    
    class DomainObjectOperatorAnd(DomainObjectOperatorGlue):
        def __init__(self):
            pass
    
        def operate(self, haystack: 'JobsCollection', criteria: 
    List['DomainObject']) -> List['DomainObject']:
            """
            Returns list of haystackelements or empty list.
            Includes haystackelement if all (search) 'criteria' elements 
    (DomainObjects) are met for haystackelement (DomainObject).
            """
            result = []
            for haystackelement in haystack.jobs:
                # AND operator wants all criteria to be True for haystackelement (Job)
            # to be included in returned search results.
            criteria_all_true_for_haystackelement = True
            for criterium in criteria:
                if haystackelement.excludes(criterium):
                    criteria_all_true_for_haystackelement = False
                    break
            if criteria_all_true_for_haystackelement:
                result.append(haystackelement)
        return result
    
    class DomainObjectOperatorOr(DomainObjectOperatorGlue):
        def __init__(self):
            pass
    
    def operate(self, haystack: List['DomainObject'], criteria: List['DomainObject']) -> List['DomainObject']:
        """
        Returns list of haystackelements or empty list.
        Includes haystackelement if all (search) 'criteria' elements (DomainObjects) are met for haystackelement (DomainObject).
        """
        result = []
        for haystackelement in haystack:
            # OR operator wants at least ONE criterium to be True for haystackelement
            # to be included in returned search results.
            at_least_one_criterium_true_for_haystackelement = False
            for criterium in criteria:
                if haystackelement.matches(criterium):
                    at_least_one_criterium_true_for_haystackelement = True
                    break
            if at_least_one_criterium_true_for_haystackelement:
                result.append(haystackelement)
        return result
    
    class DomainObjectFilter(metaclass=ABCMeta):
        def __init__(self, criteria: List['DomainObject'], criteria_glue: 
    DomainObjectOperatorGlue):
            self.criteria = criteria
            self.criteria_glue = criteria_glue
    
        @abstractmethod
        def apply(self, haystack: 'JobsCollection') -> List['DomainObject']:
            """
           Applies filter to given 'haystack' (list of jobs with sub-objects in there);
        returns filtered list of DomainObjects or empty list if none found
        according to criteria (and criteria glue).
            """
            return self.criteria_glue.operate(haystack, self.criteria)
    
    class DomainObject(metaclass=ABCMeta):
        def __init__(self):
            pass
    
        @abstractmethod
        def matches(self, domain_object: 'DomainObject') -> bool:
            """ Returns True if this DomainObject matches specified DomainObject,
        False otherwise.
         """
        pass
    
    def excludes(self, domain_object: 'DomainObject') -> bool:
        """
        Convenience method; the inverse of includes-method.
        """
        return not self.matches(domain_object)
    
    
    class Job(DomainObject):
        def __init__(self, name, start, end, status, job_checkpoints: 
    List['JobCheckpoint']):
            self.name = name
            self.start = start
            self.end = end
            self.status = status
            self.job_checkpoints = job_checkpoints
    
        def matches(self, domain_object: 'DomainObject', field_glue: 
    DomainObjectFieldGlueOperator) -> bool:
            """
            Returns True if this DomainObject includes specified DomainObject,
         False otherwise.
             """
            if domain_object is Job:
                # See if specified fields in search criteria (domain_object/Job) matches this job.
                # Determine here which fields user did not leave empty,
                # and guess for sensible search criteria.
                # Return True if it's  a match, False otherwise.
                condition_results = []
                if domain_object.name != None:
                    condition_results.append(domain_object.name in self.name)
                if domain_object.start != None or domain_object.end != None:
                    if domain_object.start == None:
                        # ...Use broadest start time for criteria here...
                        # time_range_condition = ...
                        condition_results.append(time_range_condition)                 
                    elif domain_object.end == None:
                        # ...Use broadest end time for criteria here...
                        # time_range_condition = ...
                        condition_results.append(time_range_condition)                 
                    else:
                        # Both start and end time specified; use specified time range.
                    # time_range_condition = ...
                    condition_results.append(time_range_condition)
                # Then evaluate condition_results;
                # e.g. return True if all condition_results are True here,
                # false otherwise depending on implementation of field_glue class:
                return field_glue.operate(condition_results)
        elif domain_object is JobCheckpoint:
            # Determine here which fields user did not leave empty,
            # and guess for sensible search criteria.
            # Return True if it's  a match, False otherwise.
            # First establish if parent of JobCheckpoint is 'self' (this job)
            # if so, then check if search criteria for JobCheckpoint match,
            # glue fields with something like:
            return field_glue.operate(condition_results)
        elif domain_object is JobCommand:
            # (...)
            if domain_object.parent_job == self:
                # see if conditions pan out
                return field_glue.operate(condition_results)
    
    class JobCheckpoint(DomainObject):
        def __init__(self, name, start, end, status, job_commands: List['JobCommand'], parent_job: Job):
           self.name = name
            self.start = start
            self.end = end
            self.status = status
           self.job_commands = job_commands
            # For easier reference;
            # e.g. when search criteria matches this JobCheckpoint
            # then Job associated to it can be found
            # more easily.
            self.parent_job = parent_job
    
    class JobCommand(DomainObject):
        def __init__(self, name, start, end, status, parent_checkpoint: JobCheckpoint, parent_job: Job):
            self.name = name
            self.start = start
            self.end = end
            self.status = status
            # For easier reference;
            # e.g. when search criteria matches this JobCommand
            # then Job or JobCheckpoint associated to it can be found
            # more easily.
            self.parent_checkpoint = parent_checkpoint
            self.parent_job = parent_job
    
    class JobsCollection(DomainObject):
        def __init__(self, jobs: List['Job']):
             self.jobs = jobs
    
        def get_jobs(self, filter: DomainObjectFilter) -> List[Job]:
            return filter.apply(self)
    
        def get_commands(self, job: Job) -> List[JobCommand]:
            """
            Returns all commands for specified job (search criteria).
            """
            result = []
            for some_job in self.jobs:
                if job.matches(some_job):
                    for job_checkpoint in job.job_checkpoints:
                        result.extend(job_checkpoint.job_commands)
             return result
    
        def get_checkpoints(self, job: Job) -> List[JobCheckpoint]:
            """
            Returns all checkpoints for specified job (search criteria).
            """
            result = []
            for some_job in self.jobs:
                if job.matches(some_job):
                    result.extend(job.job_checkpoints)
            return result
    

    dict
    由键和值组成。如果值是
    Job
    s,那么键应该是什么?如果您不知道,也许一个
    列表
    会更好。很抱歉造成混淆,我更新了qn。问题是基本上返回列表是正确的设计还是返回列表是正确的设计?因为您返回的项目有一组定义的属性(如您列出的,例如作业有名称、开始时间等),特定的域对象通常是更好的选择。整个数据是作为Python对象存储在内存中还是存储在某种数据库中?在文件系统中,我从文件系统中读取它,并构造一个域对象。它计划允许将输出限制到某些属性(例如,仅列出运行命令x的所有作业的开始时间)?如果是这样的话,一个字典(或者一个
    collections.namedtuple()
    效率会更高。否则,如果无论如何都要提供有关该对象的完整信息,域对象就是正确的。例如,返回的作业对象可以提供方法按需加载其检查点以提高效率。dict无法做到这一点。存储是一个文件系统。我想这是有道理的“筛选器类可能使用条件类和运算符类。可能有一个运算符类作为抽象类,并有“粘合”运算符将条件粘合在一起(和/或)。还有另一种运算符将域对象的属性与值进行比较。“是否有任何粗略的类框架?我为此添加了一些粗略的类框架。感谢详细的类框架。这对我来说很有意义。如果您提供了它的示例使用示例,那么跟踪代码就很容易了。”。