Contributing¶
The Materials Consortia is very open to contributions to this package.
This may be anything from simple feedback and raising new issues to creating new PRs.
We have below recommendations for setting up an environment in which one may develop the package further.
Getting Started with Filter Parsing and Transforming¶
Example use:
from optimade.filterparser import Parser
p = Parser(version=(0,9,7))
tree = p.parse("nelements<3")
print(tree)
Tree(start, [Tree(expression, [Tree(term, [Tree(atom, [Tree(comparison, [Token(VALUE, 'nelements'), Token(OPERATOR, '<'), Token(VALUE, '3')])])])])])
print(tree.pretty())
start
expression
term
atom
comparison
nelements
<
3
tree = p.parse('_mp_bandgap > 5.0 AND _cod_molecular_weight < 350')
print(tree.pretty())
start
expression
term
term
atom
comparison
_mp_bandgap
>
5.0
AND
atom
comparison
_cod_molecular_weight
<
350
# Assumes graphviz installed on system (e.g. `conda install -c anaconda graphviz`) and `pip install pydot`
from lark.tree import pydot__tree_to_png
pydot__tree_to_png(tree, "exampletree.png")
Flow for Parsing User-Supplied Filter and Converting to Backend Query¶
optimade.filterparser.Parser
will take user input to generate a lark.Tree
and feed that to a lark.Transformer
.
E.g., optimade.filtertransformers.mongo.MongoTransformer
will turn the tree into something useful for your MondoDB backend:
# Example: Converting to MongoDB Query Syntax
from optimade.filtertransformers.mongo import MongoTransformer
transformer = MongoTransformer()
tree = p.parse('_mp_bandgap > 5.0 AND _cod_molecular_weight < 350')
query = transformer.transform(tree)
print(query)
{'$and': [{'_mp_bandgap': {'$gt': 5.0}}, {'_cod_molecular_weight': {'$lt': 350.0}}]}
There is also a basic JSON transformer (optimade.filtertransformers.json.JSONTransformer
) you can use as a simple example for developing your own transformer.
You can also use the JSON output it produces as an easy-to-parse input for a "transformer" in your programming language of choice.
class JSONTransformer(Transformer):
def __init__(self, compact=False):
self.compact = compact
super().__init__()
def __default__(self, data, children):
items = []
for c in children:
if isinstance(c, Token):
token_repr = {
"@module": "lark.lexer",
"@class": "Token",
"type_": c.type,
"value": c.value,
}
if self.compact:
del token_repr["@module"]
del token_repr["@class"]
items.append(token_repr)
elif isinstance(c, dict):
items.append(c)
else:
raise ValueError(f"Unknown type {type(c)} for tree child {c}")
tree_repr = {
"@module": "lark",
"@class": "Tree",
"data": data,
"children": items,
}
if self.compact:
del tree_repr["@module"]
del tree_repr["@class"]
return tree_repr
Developing New Filter Transformers¶
If you would like to add a new transformer, please add:
- A module (.py file) in the
optimade/filtertransformers
folder. - Any additional Python requirements must be optional and provided as a separate "
extra_requires
" entry insetup.py
. - Tests in
optimade/filtertransformers/tests
that are skipped if the required packages fail to import.
For examples, please check out existing filter transformers.