-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from pinzon/package-setup
Package setup
- Loading branch information
Showing
12 changed files
with
439 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
name: Publish to PyPI | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
publish: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v3 | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v4 | ||
with: | ||
python-version: '3.x' | ||
|
||
- name: Install dependencies | ||
run: | | ||
pip install -e .[test] | ||
- name: Run tests | ||
run: pytest | ||
|
||
- name: Install build tools | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install build twine | ||
- name: Build project | ||
run: python -m build | ||
|
||
- name: Publish to PyPI | ||
env: | ||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} | ||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} | ||
run: twine upload dist/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
name: Run Tests | ||
|
||
on: | ||
pull_request: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v3 | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v4 | ||
with: | ||
python-version: '3.12' | ||
|
||
- name: Install dependencies | ||
run: | | ||
pip install -e .[test] | ||
- name: Run tests | ||
run: pytest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,6 @@ | |
__pycache__ | ||
.idea | ||
.vscode | ||
aws_json_term_matcher.egg-info | ||
.ruff_cache | ||
build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
VENV_BIN ?= python3 -m venv | ||
VENV_DIR ?= .venv | ||
PIP_CMD ?= pip3 | ||
|
||
ifeq ($(OS), Windows_NT) | ||
VENV_ACTIVATE = $(VENV_DIR)/Scripts/activate | ||
else | ||
VENV_ACTIVATE = $(VENV_DIR)/bin/activate | ||
endif | ||
|
||
VENV_RUN = . $(VENV_ACTIVATE) | ||
|
||
|
||
$(VENV_ACTIVATE): pyproject.toml | ||
test -d $(VENV_DIR) || $(VENV_BIN) $(VENV_DIR) | ||
$(VENV_RUN); $(PIP_CMD) install --upgrade pip setuptools wheel plux | ||
touch $(VENV_ACTIVATE) | ||
|
||
|
||
venv: $(VENV_ACTIVATE) ## Create a new (empty) virtual environment | ||
|
||
format: venv ## Run ruff and black to format the whole codebase | ||
($(VENV_RUN); python -m ruff check --output-format=full --fix .; python -m black .) | ||
|
||
lint: venv ## Run code linter to check code style and check if formatter would make changes | ||
($(VENV_RUN); python -m ruff check --show-source . && python -m black --check .) | ||
|
||
install: venv | ||
$(VENV_RUN); $(PIP_CMD) install -e . | ||
|
||
test: venv ## Run tests | ||
($(VENV_RUN); python -m pytest -v --cov=plux --cov-report=term-missing --cov-report=xml tests) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
class ParsingError(Exception): | ||
def __init__(self, message): | ||
super().__init__(message) | ||
|
||
|
||
class MatchingError(Exception): | ||
def __init__(self, message): | ||
super().__init__(message) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,33 @@ | ||
start: "{" expression "}" | ||
|
||
expression: "(" expression ")" -> paren | ||
expression: "(" expression ")" | ||
| expression "&&" expression -> and_op | ||
| expression "||" expression -> or_op | ||
| BOOL | ||
| comparison | ||
|
||
BOOL: "true" | "false" | ||
%ignore /\s+/ | ||
comparison: entity COMPARATOR value | ||
COMPARATOR: "=" | "!=" | ">" | ">=" | "<" | "<=" | ||
|
||
entity: "$" selection | ||
selection: (attribute_access | index_access)+ | ||
attribute_access: "." NAME | "[" ESCAPED_STRING "]" | ||
index_access: "[" INT "]" | ||
|
||
value: ESCAPED_STRING | NUMBER | SCIENTIFIC | IP | WILDCARD_IP | ||
|
||
|
||
NUMBER: INT | FLOAT | ||
SCIENTIFIC: SIGNED_INT "e" SIGNED_INT | SIGNED_FLOAT "e" SIGNED_INT | ||
IP: INT "." INT "." INT "." INT | ||
WILDCARD_IP: INT "." "*" | INT "." INT "." "*" | INT "." INT "." INT "." "*" | ||
|
||
NAME: /[a-zA-Z_][a-zA-Z0-9_-]*/ | ||
INT: /[0-9]+/ | ||
FLOAT: /[0-9]+\.[0-9]+/ | ||
SIGNED_INT: INT | "+"INT | "-"INT | ||
SIGNED_FLOAT: FLOAT | "+"FLOAT | "-"FLOAT | ||
|
||
|
||
%import common.WS | ||
%import common.ESCAPED_STRING | ||
%ignore WS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
import os | ||
|
||
from lark import Lark, Transformer, v_args, Tree, Token | ||
from lark.exceptions import UnexpectedCharacters, UnexpectedInput, UnexpectedToken | ||
|
||
from aws_json_term_matcher.exceptions import ParsingError, MatchingError | ||
|
||
|
||
class IpRange: | ||
def __init__(self, ip_range: str): | ||
self.range = ip_range | ||
|
||
def ip_is_in_range(self, ip: str | None) -> bool: | ||
if ip is None: | ||
return False | ||
if len(ip) == 0: | ||
return False | ||
|
||
ip_parts = ip.split(".") | ||
range_parts = self.range.split(".") | ||
|
||
# Compare each part of the IP to the range | ||
for i in range(len(range_parts)): | ||
if range_parts[i] == "*": | ||
continue # Wildcard matches any value | ||
if i >= len(ip_parts) or ip_parts[i] != range_parts[i]: | ||
return False | ||
return True | ||
|
||
|
||
# Transformer to evaluate the parsed filter | ||
@v_args(inline=True) | ||
class FilterEvaluator(Transformer): | ||
def __init__(self, data): | ||
self.data = data | ||
|
||
def start(self, expr): | ||
if isinstance(expr, bool): | ||
return expr | ||
elif hasattr(expr, "children") and len(expr.children) > 0: | ||
child = expr.children[0] | ||
if isinstance(child, bool): | ||
return child | ||
|
||
if hasattr(child, "children") and len(child.children) > 0: | ||
return child.children[0] | ||
|
||
return None # Fallback if the structure isn't as expected | ||
|
||
def and_op(self, left, right): | ||
value_left = left.children[0] | ||
value_right = right.children[0] | ||
return value_left and value_right | ||
|
||
def or_op(self, left: Tree, right: Tree): | ||
value_left = left.children[0] | ||
value_right = right.children[0] | ||
return value_left or value_right | ||
|
||
def comparison(self, entity, comparator, value): | ||
entity_value = self.resolve_entity(entity) | ||
result = self.compare(entity_value, comparator, value) | ||
return result | ||
|
||
def resolve_entity(self, entity: Tree): | ||
# Extract the entity from the dictionary based on selection rules | ||
# This would resolve $.attribute or $[index] kind of paths in the dictionary | ||
keys = [] | ||
# in this case the three only is composed of branch with just one branch | ||
# entity -> selection -> attribute access -> "NAME" | ||
|
||
def _resolve(node): | ||
if node.data == "attribute_access": | ||
# Handles attributes like $.attributeName or $["attributeName"] | ||
child = node.children[0] | ||
if child.type == "NAME": | ||
keys.append(child.value) # Regular attribute | ||
elif child.type == "ESCAPED_STRING": | ||
keys.append( | ||
child.value.strip('"') | ||
) # Attribute accessed like ["attr"] | ||
|
||
elif node.data == "index_access": | ||
index = node.children[0].value | ||
keys.append(index) | ||
|
||
elif node.data == "selection": | ||
# Keep recursing through the selection (attributes or indices) | ||
for child in node.children: | ||
_resolve(child) | ||
elif node.data == "entity": | ||
for child in node.children: | ||
_resolve(child) | ||
|
||
# Start traversing the entity tree to build the keys | ||
_resolve(entity) | ||
|
||
value = self.data | ||
try: | ||
for key in keys: | ||
if key.isdigit(): | ||
value = value[int(key)] | ||
else: | ||
value = value.get(key, None) | ||
return value | ||
except IndexError: | ||
return None | ||
|
||
def compare(self, entity_value, comparator, value): | ||
comparator_value = comparator.value | ||
|
||
if isinstance(value, IpRange): | ||
return value.ip_is_in_range(entity_value) | ||
|
||
if comparator_value == "=": | ||
return entity_value == value | ||
elif comparator_value == "!=": | ||
return entity_value != value | ||
elif comparator_value == ">": | ||
return entity_value > value | ||
elif comparator_value == ">=": | ||
return entity_value >= value | ||
elif comparator_value == "<": | ||
return entity_value < value | ||
elif comparator_value == "<=": | ||
return entity_value <= value | ||
return False | ||
|
||
def value(self, value: Token): | ||
# Returns the value as-is (for STRING, NUMBER, etc.) | ||
if value.type in ["SCIENTIFIC", "NUMBER"]: | ||
return float(value.value) | ||
|
||
if value.type == "WILDCARD_IP": | ||
return IpRange(value.value) | ||
|
||
return value.value.strip("\"'") | ||
|
||
|
||
def load_parser(): | ||
grammar_path = os.path.join(os.path.dirname(__file__), "grammar.lark") | ||
|
||
with open(grammar_path, "r") as grammar_file: | ||
grammar = grammar_file.read() | ||
return Lark(grammar, start="start", parser="lalr") | ||
|
||
|
||
def parse_filter(expression): | ||
parser = load_parser() | ||
try: | ||
return parser.parse(expression) | ||
except (UnexpectedCharacters, UnexpectedInput, UnexpectedToken) as e: | ||
raise ParsingError(str(e)) | ||
|
||
|
||
def match(obj: dict, filter: str): | ||
tree = parse_filter(filter) | ||
evaluator = FilterEvaluator(obj) | ||
|
||
try: | ||
return evaluator.transform(tree) | ||
except Exception as e: | ||
raise MatchingError(str(e)) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[project] | ||
name = "aws-json-term-matcher" | ||
version = "0.1.0" | ||
authors = [ | ||
{ name = 'Cristopher Pinzon', email = '[email protected]' } | ||
] | ||
description = "The core library and runtime of LocalStack" | ||
requires-python = ">=3.8" | ||
dependencies=[ | ||
"lark" | ||
] | ||
|
||
[project.optional-dependencies] | ||
dev = [ | ||
"pytest", | ||
"ruff", | ||
"black" | ||
] | ||
|
||
test = [ | ||
"pytest" | ||
] |
Oops, something went wrong.