Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,10 @@ instance/

# Virtualenv
venv/
venv36/
venv37/
venv38/
.venv/

# Datasets
datasets/
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- [Status](#status)
- [Overview](#overview)
- [Installation](#installation)
- [Verify Local Environment](#verify-local-environment)
- [Windows](#windows)
- [macOS](#macos)
- [About pip](#about-pip)
Expand All @@ -20,6 +21,8 @@ This project is design to be completed on [Pluralsight](https://pluralsight.com)

## Installation

### Verify Local Environment

### Windows

Open a command prompt or powershell and run the following commands, replacing 'project-root' with the path to the root folder of the project.
Expand All @@ -37,25 +40,29 @@ Open a terminal and run the following commands, replacing 'project-root' with th

```bash
> cd 'project-root'
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install -r requirements.txt
> python3 -m venv venv
> source venv/bin/activate
> pip install -r requirements.txt
```

*Note: If you've installed Python 3 using a method other than Homebrew, you might need to type `python` in the second command instead of `python3`.*

### About pip

Versions pip updates frequently, but versions greater than 10.x.x should work with this project.
`pip` updates frequently, but versions greater than 10.x.x should work with this project.

## Verify Setup

In order to verify that everything is setup correctly, run the following command from the project root.

```bash
> pytest
pytest
```

You should see that all the tests are failing. This is good! We’ll be fixing these tests once we jump into the build step. Every time you want to check your work locally you can type that command, and it will report the status of every task in the project.
You should see that all the tests are failing. This is good! We’ll be fixing these tests once we jump into the build step.

Every time you want to check your work locally you can type that command, and it will report the status of every task in the project.

## Previewing Your Work

You can preview your work by opening a terminal, changing to the project root, activating the virtual environment, and executing the appropriate python script. For example `python sensor/sensor.py`.
182 changes: 182 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import ast
import json

import parso
import pytest

from collections import OrderedDict
from types import GeneratorType as generator
from itertools import chain
from pathlib import Path

from objectpath import Tree
from mongoquery import Query

from tests.nodes import convert_node, flatten
from tests.template import Template


class Parser:
def __init__(self, file_name, nodes):

sensor = Path.cwd() / "sensor"
# ext = sensor / "extensions"

self.data = {
"success": True,
"full_path": "",
"message": "",
"start_pos": 0,
"nodes": nodes,
}

if file_name is not None:
path = lambda root, fn: root / "{}.py".format(fn)
# if file_name == "menu" or file_name == "stats":
# full_path = path(ext, file_name)
if file_name == "sensor":
full_path = path(Path.cwd(), file_name)
else:
full_path = path(sensor, file_name)

grammar = parso.load_grammar()
module = grammar.parse(path=full_path)
self.data["success"] = len(grammar.iter_errors(module)) == 0

if self.data["success"]:
self.data["message"] = "Syntax: valid"
if file_name is not None:
self.data["nodes"] = convert_node(ast.parse(full_path.read_text()))
self.data["code"] = full_path.read_text()

else:
self.data["message"] = grammar.iter_errors(module)[0].message
self.data["start_pos"] = grammar.iter_errors(module)[0].start_pos[0]

@property
def nodes(self):
return self.data["nodes"]

n = nodes

@property
def success(self):
return self.data["success"]

@property
def code(self):
return self.data["code"]

def query(self, pattern):
nodes = Template(pattern).process(self.code)
if isinstance(nodes, list) and len(nodes) == 1:
nodes = nodes[0]

return Parser(None, nodes)

def query_raw(self, pattern):
nodes = Template(pattern).process(self.code, True)
if isinstance(nodes, list) and len(nodes) == 1:
nodes = [flatten(convert_node(node)) for node in nodes[0].body]
return Parser(None, nodes)

def last_line(self):
return flatten(self.nodes["body"][-1])

@property
def message(self):
return "{} on or around line {} in `{}`.".format(
self.data["message"], self.data["start_pos"], self.data["full_path"]
)

def match(self, template):
return Parser(None, list(filter(Query(template).match, self.nodes)))

def execute(self, expr):
result = Tree(self.nodes).execute(expr)
if isinstance(result, (generator, chain, map)):
process = list(result)
return (
Parser(None, process[0]) if len(process) == 1 else Parser(None, process)
)
else:
return Parser(None, result)

ex = execute

def exists(self):
return bool(self.nodes)

def calls(self):
nodes = self.execute("$.body[@.type is 'Expr' and @.value.type is 'Call']").n
node_list = [nodes] if isinstance(nodes, dict) else nodes

return Parser(None, [flatten(node) for node in node_list])

def assign_(self):
return Parser(None, [flatten(self.execute("$.body[@.type is 'Assign']").n)])

def assigns(self):
return Parser(
None,
[flatten(node) for node in self.execute("$.body[@.type is 'Assign']").n],
)

def globals(self, name):
return name in self.execute("$.body[@.type is 'Global'].names").n

def defines(self, name):
return self.execute(
"$.body[@.type is 'FunctionDef' and @.name is '{}'].(name, args, body, decorator_list)".format(
name
)
)

def class_(self, name):
return self.execute(
"$.body[@.type is 'ClassDef' and @.name is '{}'].(name, args, body)".format(
name
)
)

def decorators(self):
return Parser(None, [flatten(self.execute("$.decorator_list").n)])

def returns(self, name):
return name == self.execute("$.body[@.type is 'Return'].value.id").n

def returns_call(self):
return Parser(None, [flatten(self.execute("$.body[@.type is 'Return']").n)])

def method(self, name):
return self.execute(
"$..body[@.type is 'FunctionDef' and @.name is '{}']".format(name)
)

def has_arg(self, name, pos=0):
nodes = self.execute("$.args.args.arg").n
return nodes[pos] if isinstance(nodes, list) else nodes

def imports(self, name):
return name in self.execute("$.body[@.type is 'Import'].names..name").n

def for_(self):
for_body = self.execute("$.body[@.type is 'For'].body").n
iterators = self.execute("$.body[@.type is 'For'].(target, iter)").n
return Parser(None, [flatten(for_body), flatten(iterators)])

def from_imports(self, mod, alias):
nodes = self.execute(
"$.body[@.type is 'ImportFrom' and @.module is '{}'].names..name".format(
mod
)
).n
return alias in (nodes if isinstance(nodes, list) else [nodes])


@pytest.fixture
def parse():
def _parse(file_name):
return Parser(file_name, {})

return _parse
2 changes: 1 addition & 1 deletion datasets/SENSOR_ROOM1.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
id,date,time,temperature,humidity,energy_usage,particulate,room
id,date,time,temperature,humidity,energy_usage,particulate,area
1,5/7/2020,0:01,60,0.8,0xffe,6.2E+00,1
2,5/7/2020,0:01,60,0.8,0xff3,6.3E+00,1
3,5/7/2020,0:04,60,0.8,0xfef,6.5E+00,1
Expand Down
2 changes: 1 addition & 1 deletion datasets/SENSOR_ROOM2.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
id,date,time,temperature,humidity,energy_usage,particulate,room
id,date,time,temperature,humidity,energy_usage,particulate,area
1,5/7/2020,0:01,50,0.8,0xffc,8.1E+00,2
2,5/7/2020,0:02,50,0.53,0xffb,8.1E+00,2
3,5/7/2020,0:02,50,0.82,0xff3,8.5E+00,2
Expand Down
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[pytest]
addopts = --tb=short -p no:warnings
addopts = -rN --tb=short -p no:warnings
16 changes: 7 additions & 9 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
docutils==0.16
Markdown==3.1.1
parso==0.6.0
pytest==5.3.5
Markdown==3.2.1
mongoquery==1.3.6
objectpath==0.6.1
parso==0.6.2
pytest==5.4.1
pytest-json-report==1.2.1
pytest-metadata==1.8.0
pytest-sugar==0.9.2
python-dateutil==2.7.5
py==1.7.0
pyparsing==2.3.0
more-itertools==4.3.0
PyYAML==5.3.1
typer==0.1.1
21 changes: 21 additions & 0 deletions sensor/load_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Module 1: Load data from files
import os
import glob
import csv

def load_sensor_data():
sensor_data = []

sensor_files = glob.glob(os.path.join(os.getcwd(), 'datasets', '*.csv'))

# Loop over list of files
for sensor_file in sensor_files:
with open(sensor_file ) as data_file:
# Create a csv.DictReader
data_reader = csv.DictReader(data_file, delimiter=',')
# Loop over each row dictionary
for row in data_reader:
# Create a list of dictionaries
sensor_data.append(row)

return sensor_data
1 change: 0 additions & 1 deletion sensor/load_info.py

This file was deleted.

6 changes: 6 additions & 0 deletions sensor/sensor_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Runner script for all modules
from load_data import load_sensor_data # module 2

data = load_sensor_data()
# print(f"Loaded records {len(data)}")
print("Loaded records: [{}]".format(len(data)))
1 change: 0 additions & 1 deletion sensor/sensor_data.py

This file was deleted.

Loading