Exemple #1
0
class Factory:
    """Interface to build units for an army.
    Army side is provided at instanciation.
    Each call providing unit type and position will use loaded stats
    to add a new unit to the underlying army.
    """
    side: int
    units: List[Unit] = dfield(default_factory=list)

    _registery = {}

    @classmethod
    @tools(log="registered {name}")
    def register(cls, name, prototype):
        """Register prototype for given unit type name."""
        cls._registery[name] = prototype

    @property
    def army(self):
        """Provide army built from created units."""
        return Army(self.units)

    def __call__(self, name, coords, strategy, is_centurion=False):
        try:
            base = self._registery[name]
        except KeyError:
            tools(log=f"Unregistered name: {name}")()
            base = UnitBase(0, 0, 0, 0)
        field = UnitField(self.side, coords, is_centurion)
        strat = Strategy(*strategy)
        self.units.append(Unit(base, field, strat))
        return self.units[-1]
Exemple #2
0
class ProfileSearch(SumoSearch):
    """Search over User Profiles."""

    group_ids: list[int] = dfield(default_factory=list)

    def get_index(self):
        return ProfileDocument.Index.read_alias

    def get_fields(self):
        return ["username", "name"]

    def get_highlight_fields_options(self):
        return []

    def get_filter(self):
        return DSLQ(
            "boosting",
            positive=self.build_query(),
            negative=DSLQ(
                "bool",
                must_not=DSLQ("terms", group_ids=self.group_ids),
            ),
            negative_boost=0.5,
        )

    def make_result(self, hit):
        return {
            "type": "user",
            "avatar": getattr(hit, "avatar", None),
            "username": hit.username,
            "name": getattr(hit, "name", ""),
            "user_id": hit.meta.id,
        }
Exemple #3
0
class CompoundSearch(SumoSearch):
    """Combine a number of SumoSearch classes into one search."""

    _children: list[SumoSearch] = dfield(default_factory=list, init=False)
    _parse_query: bool = True

    @property
    def parse_query(self):
        return self._parse_query

    @parse_query.setter
    def parse_query(self, value):
        """Set value of parse_query across all children."""
        self._parse_query = value
        for child in self._children:
            child.parse_query = value

    def add(self, child):
        """Add a SumoSearch instance to search over. Chainable."""
        self._children.append(child)

    def _from_children(self, name):
        """
        Get an attribute from all children.

        Will flatten lists.
        """
        value = []

        for child in self._children:
            attr = getattr(child, name)()
            if isinstance(attr, list):
                # if the attribute's value is itself a list, unpack it
                value = [*value, *attr]
            else:
                value.append(attr)
        return value

    def get_index(self):
        return ",".join(self._from_children("get_index"))

    def get_fields(self):
        return self._from_children("get_fields")

    def get_highlight_fields_options(self):
        return self._from_children("get_highlight_fields_options")

    def get_filter(self):
        # `should` with `minimum_should_match=1` acts like an OR filter
        return DSLQ("bool", should=self._from_children("get_filter"), minimum_should_match=1)

    def make_result(self, hit):
        index = hit.meta.index
        for child in self._children:
            if same_base_index(index, child.get_index()):
                return child.make_result(hit)
Exemple #4
0
class SumoSearch(SumoSearchInterface):
    """Base class for search classes.

    Implements the run() function, which will perform the search.

    Child classes should define values for the various abstract properties this
    class inherits, relevant to the documents the child class is searching over.
    """

    total: int = dfield(default=0, init=False)
    hits: list[AttrDict] = dfield(default_factory=list, init=False)
    results: list[dict] = dfield(default_factory=list, init=False)
    last_key: Union[int, slice, None] = dfield(default=None, init=False)

    query: str = ""
    default_operator: str = "AND"

    def __len__(self):
        return self.total

    @overload
    def __getitem__(self, key: int) -> dict:
        ...

    @overload
    def __getitem__(self, key: slice) -> list[dict]:
        ...

    def __getitem__(self, key):
        if self.last_key is None or self.last_key != key:
            self.run(key=key)
        if isinstance(key, int):
            # if key is an int, then self.results will be a list containing a single result
            # return the result, rather than a 1-length list
            return self.results[0]
        return self.results

    def build_query(self):
        """Build a query to search over a specific set of documents."""
        return DSLQ(
            "simple_query_string",
            query=self.query,
            default_operator=self.default_operator,
            fields=self.get_fields(),
            # everything apart from WHITESPACE as that interferes with char mappings
            # and synonyms with whitespace in them by breaking up the phrase into tokens,
            # before they have a chance to go through the filter:
            flags="AND|ESCAPE|FUZZY|NEAR|NOT|OR|PHRASE|PRECEDENCE|PREFIX|SLOP",
        )

    def run(self,
            key: Union[int, slice] = slice(0,
                                           settings.SEARCH_RESULTS_PER_PAGE)):
        """Perform search, placing the results in `self.results`, and the total
        number of results (across all pages) in `self.total`. Chainable."""

        search = DSLSearch(
            using=es7_client(),
            index=self.get_index()).params(**settings.ES7_SEARCH_PARAMS)

        # add the search class' filter
        search = search.query(self.get_filter())
        # add highlights for the search class' highlight_fields
        for highlight_field, options in self.get_highlight_fields_options():
            search = search.highlight(highlight_field, **options)
        # slice search
        search = search[key]

        # perform search
        self.hits = search.execute().hits
        self.last_key = key

        self.total = self.hits.total.value
        self.results = [self.make_result(hit) for hit in self.hits]

        return self
Exemple #5
0
class PROXIDataset:
    accession: str = dfield(default=None)
    title: str = dfield(default=None)
    summary: str = dfield(default=None)
    instruments: List[str] = dfield(default_factory=list)
    identifiers: List[str] = dfield(default_factory=list)
    species: List[str] = dfield(default_factory=list)
    modifications: List[str] = dfield(default_factory=list)
    contacts: List[str] = dfield(default_factory=list)
    publications: List[str] = dfield(default_factory=list)
    keywords: List[str] = dfield(default_factory=list)
    dataset_link: str = dfield(default=None)
    dataset_files: List[PROXIDataFile] = dfield(default_factory=list)
    links: List[str] = dfield(default_factory=list)

    def to_dict(self):
        d = dataclasses.asdict(self)
        d['datasetLinks'] = d.pop("dataset_links", None)
        # TODO: Make this consistent whether or not it is a dict or an instance
        d['dataFiles'] = [
            PROXIDataFile(f['uri'], FileType(**f['file_type'])).to_dict()
            for f in d.pop('dataset_files', [])
        ]
        return d

    @classmethod
    def from_dict(cls, d):
        d = d.copy()
        d['dataset_links'] = d.pop('datasetLinks', None)
        d['dataset_files'] = [
            PROXIDataFile.from_dict(f) for f in d.pop('datasetFiles', [])
        ]
        return cls(**d)

    @classmethod
    def new(cls, dataset_id):
        self = cls()
        self.accession = dataset_id
        self.title = dataset_id
        self.summary = dataset_id
        return self