Skip to content

Commit

Permalink
Merge branch 'release/1.0.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
smertiens committed Apr 6, 2019
2 parents 9fff6b7 + cdcb497 commit bc6d10c
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 47 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Expand Up @@ -10,4 +10,6 @@ fileformat.txt
dist
build
demo_*
build_*.sh
build_*.sh
test/htmlcov
test/.coverage
4 changes: 3 additions & 1 deletion .travis.yml
Expand Up @@ -11,11 +11,13 @@ python:

install:
- pip install Pillow
- pip install coverage
#- pip install Pyside2 - no installable package for python 3.4
- pip install pytest
- pip install pytest-cov
#- pip install pytest-qt - ui tests not working atm
- export PYTHONPATH=$PYTHONPATH:$(pwd)/src

# command to run tests
script:
- pytest
- pytest --cov=atraxiflow
8 changes: 6 additions & 2 deletions README.md
Expand Up @@ -2,6 +2,8 @@
The flexible python workflow tool

[![Build Status](https://travis-ci.org/smertiens/AtraxiFlow.svg?branch=master)](https://travis-ci.org/smertiens/AtraxiFlow)
[![Documentation Status](https://readthedocs.org/projects/atraxiflow/badge/?version=latest)](https://atraxiflow.readthedocs.io/en/latest/?badge=latest)
[![PyPI version](https://badge.fury.io/py/atraxi-flow.svg)](https://badge.fury.io/py/atraxi-flow)

* Create easy-to-read automation scripts in minutes - work with files, folders, images or anything else
* Add your own logic as AtraxiFlow node and share it with others
Expand All @@ -11,8 +13,8 @@ built with Qt5
**Learn**

* See what you can do and check out the [examples](https://github.com/smertiens/AtraxiExamples)
* Get started with the user manual [user manual](https://docs.atraxi-flow.com/manual/index.html)
* Learn how to write your own nodes in minutes with the [developer manual](https://docs.atraxi-flow.com/dev/index.html)
* Get started with the [user manual](https://atraxiflow.readthedocs.io/en/latest/manual)
* Learn how to write your own nodes in minutes with the [developer manual](https://atraxiflow.readthedocs.io/en/latest/dev)

**Install**
```
Expand All @@ -26,6 +28,8 @@ pip install atraxi-flow

**Latest Changes**

_1.0.2:_ Fixes in DateTimeProcessor and improved file date/time comparison in FileFilterNode

_1.0.1:_ ShellExecNode: new options "echo_command" and "echo_output"

_1.0.0:_ First production release
Expand Down
2 changes: 1 addition & 1 deletion doc/source/manual/nodes/filesystem.rst
Expand Up @@ -84,7 +84,7 @@ This node outputs a list of :ref:`fsres`, containing the matched files and fold
* - filesize
- Filter by filesize. You can use the exact number in bytes or a conveniance format (1K, 1M, 1G, 1T)
* - date_created
- Filter by the files created date. You can enter the dates as string ("12.01.2017") or use "today", "yesterday"
- Filter by the files creation date. You can enter the dates as string ("12.01.2017" / "01/12/2017") or use "today", "yesterday", "tomorrow"
* - date_modified
- Filter by the files last modified date
* - type
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name='atraxi-flow',
version='1.0.1',
version='1.0.2',
packages=setuptools.find_packages('src'),
package_dir={'':'src'},
classifiers=[
Expand Down
2 changes: 2 additions & 0 deletions src/atraxiflow/__init__.py
Expand Up @@ -8,6 +8,8 @@

import os, platform, logging, sys

AXF_VERSION = '1.0.1'

### Initialize environment ###

axf_user_dir = os.path.expanduser('~')
Expand Down
75 changes: 69 additions & 6 deletions src/atraxiflow/core/data.py
Expand Up @@ -9,17 +9,80 @@
import re
from datetime import datetime, timedelta

from atraxiflow.core.exceptions import ValueException


class DatetimeProcessor:
def processString(str):
if "today" == str:

RANGE_DATE = 'RANGE_DATE'
RANGE_DATETIME_SHORT = 'RANGE_DATETIME_SHORT'
RANGE_DATETIME_LONG = 'RANGE_DATETIME_LONG'

def __init__(self):
self._range = None

def get_logger(self):
'''
Returns the classes logger
:return: Logger
'''
return logging.getLogger(DatetimeProcessor.__module__)

def get_range(self):
return self._range

def process_string(self, date_str):
if 'today' == date_str:
self._range = self.RANGE_DATE
return datetime.now()
elif "yesterday" == str:
elif 'yesterday' == date_str:
self._range = self.RANGE_DATE
return datetime.now() - timedelta(days=1)
elif 'tomorrow' == date_str:
self._range = self.RANGE_DATE
return datetime.now() + timedelta(days=1)
else:
return datetime.strptime(str)
fmt = ''
# Determine datetime format
# European date format (dd.mm.YY / YYYY)
res = re.match(r'^\d{2}.\d{2}.\d{2}(\d{2})*( \d{2}:\d{2}(:\d{2})*)*$', date_str)
if res:
self._range = self.RANGE_DATE

if res.groups()[0] == None:
fmt = '%d.%m.%y'
else:
fmt = '%d.%m.%Y'

if res.groups()[1] is not None:
self._range = self.RANGE_DATETIME_SHORT
fmt += ' %H:%M'

if res.groups()[2] is not None:
self._range = self.RANGE_DATETIME_LONG
fmt += ':%S'

# American date format (mmm/dd/YY / YYYY)
res = re.match(r'^\d{2}/\d{2}/\d{2}(\d{2})*( \d{2}:\d{2}(:\d{2})*)*$', date_str)
if res:
self._range = self.RANGE_DATE
if res.groups()[0] == None:
fmt = '%m/%d/%y'
else:
fmt = '%m/%d/%Y'

if res.groups()[1] is not None:
self._range = self.RANGE_DATETIME_SHORT
fmt += ' %H:%M'

if res.groups()[2] is not None:
self._range = self.RANGE_DATETIME_LONG
fmt += ':%S'

if fmt == '':
self.get_logger().error('Unrecognized date/time format: {0}. Using current date/time.'.format(date_str))
self._range = self.RANGE_DATETIME_LONG
return datetime.now()

return datetime.strptime(date_str, fmt)


class StringValueProcessor:
Expand Down
7 changes: 4 additions & 3 deletions src/atraxiflow/core/filesystem.py
Expand Up @@ -6,6 +6,7 @@
#

import os
from datetime import datetime


class FSObject:
Expand Down Expand Up @@ -55,10 +56,10 @@ def getFilesize(self):
return os.path.getsize(self.path)

def getLastModified(self):
return os.path.getmtime(self.path)
return datetime.fromtimestamp(os.path.getmtime(self.path))

def getLastAccessed(self):
return os.path.getatime(self.path)
return datetime.fromtimestamp(os.path.getatime(self.path))

def getCreated(self):
return os.path.getctime(self.path)
return datetime.fromtimestamp(os.path.getctime(self.path))
16 changes: 10 additions & 6 deletions src/atraxiflow/core/graphics.py
Expand Up @@ -24,14 +24,18 @@ def check_environment():
"In case you have PIL installed, you will need to uninstall it first before installing Pillow.")
return False

import PIL
version = PIL.__version__.split('.')
msg = "You need at least Pillow version 5.4 to use the ImageNodes. You can install/update Pillow using pip."

if int(version[0]) < MAJ_VER_REQUIRED:
logging.error(msg)
return False
if (int(version[1]) < MIN_VER_REQUIRED):
try:
import PIL
version = PIL.__version__.split('.')

if int(version[0]) < MAJ_VER_REQUIRED or (
int(version[0]) == MAJ_VER_REQUIRED and int(version[1]) < MIN_VER_REQUIRED):
logging.error(msg)
return False

except ImportError:
logging.error(msg)
return False

Expand Down
12 changes: 6 additions & 6 deletions src/atraxiflow/nodes/common.py
Expand Up @@ -19,7 +19,7 @@ class ShellExecNode(ProcessorNode):
def __init__(self, name="", props=None):
self._known_properties = {
'cmd': {
'label': 'Commmand',
'label': 'Command',
'type': "string",
'required': True,
'hint': 'Command to execute'
Expand All @@ -40,13 +40,13 @@ def __init__(self, name="", props=None):
},
'echo_command': {
'label': 'Echo command',
'type': "boolean",
'type': "bool",
'required': False,
'default': False
},
'echo_output': {
'label': 'Echo output',
'type': "boolean",
'type': "bool",
'required': False,
'default': False
}
Expand Down Expand Up @@ -141,7 +141,7 @@ def __init__(self, name="", props=None):
'type': "resource_query",
'required': False,
'hint': 'Resource query to be output',
"default": None
"default": ''
}
}
self._listeners = {}
Expand All @@ -158,7 +158,7 @@ def run(self, stream):
if self.get_property('msg') is not None:
print(self.parse_string(stream, self.get_property('msg')))

if self.get_property('res') is not None:
if self.get_property('res') != '':

resources = stream.get_resources(self.get_property('res'))

Expand All @@ -183,7 +183,7 @@ def __init__(self, name="", props=None):
'type': 'number',
'required': False,
'hint': 'Delay time in seconds',
'default': '5'
'default': 5
}
}

Expand Down
59 changes: 48 additions & 11 deletions src/atraxiflow/nodes/filesystem.py
Expand Up @@ -41,12 +41,12 @@ def __init__(self, name="", props=None):
'filter': {
'label': "List of filters",
'type': "list",
'list_item': [
'creator:list_item_fields': [
{
'name': 'prop',
'label': 'Property',
'type': 'combobox',
'value': ['filesize', 'date_created', 'date_modified', 'type', 'filename', 'filedir']
'value': ['file_size', 'date_created', 'date_modified', 'type', 'filename', 'filedir']
},
{
'name': 'comp',
Expand All @@ -60,7 +60,7 @@ def __init__(self, name="", props=None):
'type': 'text'
}
],
'list_item_formatter': self.format_list_item,
'creator:list_item_formatter': self.format_list_item,
'required': True,
'hint': 'Filters all or given filesystem resources'
},
Expand All @@ -82,10 +82,20 @@ def __init__(self, name="", props=None):
self._out = []

def format_list_item(self, format, data):
if format == 'list':
return '{0} {1} {2}'.format(data['prop'], data['comp'], data['value'])
if format == 'list_item':
return '{0} {1} "{2}"'.format(data['prop'], data['comp'], data['value'])
elif format == 'store':
return data
elif format == 'node_value':
# TODO: Iterate node values
if isinstance(data, list):
out = []
for row in data:
out.append([row['prop'], row['comp'], row['value']])

return out
else:
return [data['prop'], data['comp'], data['value']]

def get_output(self):
return self._out
Expand All @@ -102,13 +112,14 @@ def _filesize_value_to_number(self, str_size):
return int(matches.group(1))
else:
f = 1
if matches.group(2) == "K":
ext = matches.group(2).upper()
if ext == "K":
f = 1024
elif matches.group(2) == "M":
elif ext == "M":
f = 1024 * 1024
elif matches.group(2) == "G":
elif ext == "G":
f = 1024 * 1024 * 1024
elif matches.group(2) == "T":
elif ext == "T":
f = 1024 * 1024 * 1024 * 1024
else:
self._stream.get_logger().log("Invalid filesize format suffix: %s".format(matches.group(2)))
Expand All @@ -123,15 +134,41 @@ def _matches_filter(self, fso, filter):
left_val = None
right_val = None

dtp = DatetimeProcessor()

if filter[0] == "file_size":
left_val = fso.getFilesize()
right_val = self._filesize_value_to_number(filter[2])
elif filter[0] == "date_created":
left_val = fso.getCreated()
right_val = DatetimeProcessor.processString(filter[2])
right_val = dtp.process_string(filter[2])

if dtp.get_range() == DatetimeProcessor.RANGE_DATE:
left_val.replace(hour=0, minute=0, second=0, microsecond=0)
right_val.replace(hour=0, minute=0, second=0, microsecond=0)
elif dtp.get_range() == DatetimeProcessor.RANGE_DATETIME_SHORT:
left_val.replace(second=0, microsecond=0)
right_val.replace(second=0, microsecond=0)
elif dtp.get_range() == DatetimeProcessor.RANGE_DATETIME_LONG:
left_val.replace(microsecond=0)
right_val.replace(microsecond=0)

elif filter[0] == "date_modified":
left_val = fso.getLastModified()
right_val = DatetimeProcessor.processString(filter[2])
right_val = dtp.process_string(filter[2])

if dtp.get_range() == DatetimeProcessor.RANGE_DATE:
left_val = left_val.replace(hour=0, minute=0, second=0, microsecond=0)
right_val = right_val.replace(hour=0, minute=0, second=0, microsecond=0)
elif dtp.get_range() == DatetimeProcessor.RANGE_DATETIME_SHORT:
left_val = left_val.replace(second=0, microsecond=0)
right_val = right_val.replace(second=0, microsecond=0)
elif dtp.get_range() == DatetimeProcessor.RANGE_DATETIME_LONG:
left_val = left_val.replace(microsecond=0)
right_val = right_val.replace(microsecond=0)

print(left_val, right_val)

elif filter[0] == "type":
left_val = 'folder' if fso.isFolder() else 'file'
right_val = filter[2]
Expand Down

0 comments on commit bc6d10c

Please sign in to comment.