Skip to content

Commit d0e3ef2

Browse files
author
ilievss
committed
created the app structure and added a command that mocks a project attachments folder
1 parent 64e7efd commit d0e3ef2

6 files changed

Lines changed: 181 additions & 123 deletions

File tree

.gitignore

Lines changed: 5 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,11 @@
1+
# Intellij IDEA
2+
.idea
3+
14
# Byte-compiled / optimized / DLL files
25
__pycache__/
3-
*.py[cod]
4-
*$py.class
5-
6-
# C extensions
7-
*.so
8-
9-
# Distribution / packaging
10-
.Python
11-
build/
12-
develop-eggs/
13-
dist/
14-
downloads/
15-
eggs/
16-
.eggs/
17-
lib/
18-
lib64/
19-
parts/
20-
sdist/
21-
var/
22-
wheels/
23-
pip-wheel-metadata/
24-
share/python-wheels/
25-
*.egg-info/
26-
.installed.cfg
27-
*.egg
28-
MANIFEST
29-
30-
# PyInstaller
31-
# Usually these files are written by a python script from a template
32-
# before PyInstaller builds the exe, so as to inject date/other infos into it.
33-
*.manifest
34-
*.spec
35-
36-
# Installer logs
37-
pip-log.txt
38-
pip-delete-this-directory.txt
39-
40-
# Unit test / coverage reports
41-
htmlcov/
42-
.tox/
43-
.nox/
44-
.coverage
45-
.coverage.*
46-
.cache
47-
nosetests.xml
48-
coverage.xml
49-
*.cover
50-
*.py,cover
51-
.hypothesis/
52-
.pytest_cache/
53-
54-
# Translations
55-
*.mo
56-
*.pot
57-
58-
# Django stuff:
59-
*.log
60-
local_settings.py
61-
db.sqlite3
62-
db.sqlite3-journal
63-
64-
# Flask stuff:
65-
instance/
66-
.webassets-cache
67-
68-
# Scrapy stuff:
69-
.scrapy
70-
71-
# Sphinx documentation
72-
docs/_build/
73-
74-
# PyBuilder
75-
target/
76-
77-
# Jupyter Notebook
78-
.ipynb_checkpoints
79-
80-
# IPython
81-
profile_default/
82-
ipython_config.py
83-
84-
# pyenv
85-
.python-version
86-
87-
# pipenv
88-
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89-
# However, in case of collaboration, if having platform-specific dependencies or dependencies
90-
# having no cross-platform support, pipenv may install dependencies that don't work, or not
91-
# install all needed dependencies.
92-
#Pipfile.lock
93-
94-
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
95-
__pypackages__/
96-
97-
# Celery stuff
98-
celerybeat-schedule
99-
celerybeat.pid
100-
101-
# SageMath parsed files
102-
*.sage.py
1036

1047
# Environments
105-
.env
106-
.venv
107-
env/
1088
venv/
109-
ENV/
110-
env.bak/
111-
venv.bak/
112-
113-
# Spyder project settings
114-
.spyderproject
115-
.spyproject
116-
117-
# Rope project settings
118-
.ropeproject
119-
120-
# mkdocs documentation
121-
/site
122-
123-
# mypy
124-
.mypy_cache/
125-
.dmypy.json
126-
dmypy.json
1279

128-
# Pyre type checker
129-
.pyre/
10+
# logging
11+
log.txt

app.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import argparse
2+
3+
from argparse import ArgumentDefaultsHelpFormatter
4+
5+
import logger
6+
7+
from command.export.issues.attachments import AttachmentsMockCommandHandler
8+
9+
10+
class Application:
11+
12+
def run(self):
13+
# top level parser
14+
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
15+
subparsers = parser.add_subparsers(title='subcommands', description='Choose a command')
16+
17+
# export subcommand
18+
export_parser = subparsers.add_parser('export', help='Use a subcommand')
19+
export_subparser = export_parser.add_subparsers()
20+
21+
# issues subcommand
22+
issues_parser = export_subparser.add_parser('issues')
23+
issues_subparser = issues_parser.add_subparsers()
24+
25+
# attachments subcommand
26+
attachment_parser = issues_subparser.add_parser('attachments')
27+
attachment_subparsers = attachment_parser.add_subparsers()
28+
29+
# mock subcommand
30+
mock_parser = attachment_subparsers.add_parser('mock', formatter_class=ArgumentDefaultsHelpFormatter)
31+
mock_parser.add_argument('--project_key', help='the project key', default='ABC')
32+
mock_parser.add_argument('--num_issues', help='the number of issue folders to create', default=10)
33+
mock_parser.add_argument('--num_attachments_per_issue',
34+
help='the number of attachment folders to create in each issue folder',
35+
default=2)
36+
mock_parser.add_argument('--fileattachment_table_csv_export_file_name',
37+
help='the name of the mocked csv file with the fileattachment table export',
38+
default='fileattachment.csv')
39+
mock_parser.add_argument('--dates_hours_diff',
40+
help='the hour difference acting as a timezone for the dates in the attachment '
41+
'file names and csv export. Values is between -24 and +24. Examples: -1, +3, 0',
42+
default=0,
43+
type=int)
44+
mock_parser.add_argument('path', help='The path where the mock directory will be created')
45+
mock_parser.set_defaults(exec=AttachmentsMockCommandHandler())
46+
47+
args = parser.parse_args()
48+
try:
49+
args.exec(args)
50+
except AttributeError:
51+
logger.info('Please, choose a command or chain of subcommands. Use -h or --help for more details')
52+
53+
54+
if __name__ == '__main__':
55+
app = Application()
56+
app.run()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from export.issues.attachments import mock_dir
2+
3+
4+
class AttachmentsMockCommandHandler:
5+
6+
def __call__(self, *args, **kwargs):
7+
if len(args) < 1:
8+
raise ValueError('Expecting at least 1 argument, got 0')
9+
10+
cmdline_args = args[0]
11+
if 'path' not in cmdline_args:
12+
raise ValueError('Parameter "path" is required!')
13+
14+
# take only the arguments known to the mock_dir function
15+
known_arguments = {'project_key',
16+
'num_issues',
17+
'num_attachments_per_issue',
18+
'fileattachment_table_csv_export',
19+
'dates_hours_diff'}
20+
21+
kwargs = {k: v for k, v in cmdline_args.__dict__.items() if k in known_arguments}
22+
mock_dir(cmdline_args.path, **kwargs)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from command.export.issues.attachments.AttachmentsMockCommandHandler import AttachmentsMockCommandHandler

export/issues/attachments.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import os
2+
from datetime import datetime, timezone, tzinfo, timedelta
3+
from random import randint
4+
5+
current_year = datetime.now().year
6+
7+
8+
def get_random_date_underscored(date_timezone: tzinfo = None):
9+
return datetime(year=randint(2005, current_year),
10+
month=randint(1, 12),
11+
day=randint(1, 28),
12+
tzinfo=date_timezone)\
13+
.strftime('%Y_%m_%d')
14+
15+
16+
def get_random_date_iso(date_timezone=None):
17+
18+
return datetime(year=randint(2005, current_year),
19+
month=randint(1, 12),
20+
day=randint(1, 28),
21+
tzinfo=date_timezone)\
22+
.isoformat()
23+
24+
25+
def mkdir_if_not_exists(path):
26+
try:
27+
# create the directory if it doesn't already exist
28+
os.mkdir(path)
29+
except FileExistsError:
30+
# ignore the error, this means the path exists
31+
pass
32+
33+
34+
def mock_dir(path, project_key='ABC', num_issues=10, num_attachments_per_issue=2,
35+
fileattachment_table_csv_export_file_name='fileattachment.csv', dates_hours_diff=0):
36+
37+
if not project_key:
38+
raise ValueError('project_key cannot be empty')
39+
40+
if 1 > num_issues > 10000:
41+
raise ValueError('num_issues cannot be less than 1 or more than 10 000')
42+
43+
if 1 > num_attachments_per_issue > 100:
44+
raise ValueError('num_attachments_per_issue cannot be less than 1 or more than 100')
45+
46+
if not fileattachment_table_csv_export_file_name:
47+
raise ValueError('fileattachment_table_csv_export cannot be empty')
48+
49+
mkdir_if_not_exists(path)
50+
51+
dates_timezone = timezone(timedelta(hours=dates_hours_diff))
52+
with open(f'{path}/{fileattachment_table_csv_export_file_name}', 'w') as f:
53+
# write the header for the csv
54+
f.write('id,issueid,mimetype,filename,created,filesize,author,zip,thumbnailable\n')
55+
56+
issue_id = 100
57+
attachment_id = 1
58+
for issue_key in range(1, num_issues + 1):
59+
issue_path = f'{path}/{project_key}-{issue_key}'
60+
mkdir_if_not_exists(issue_path)
61+
62+
for _ in range(num_attachments_per_issue):
63+
with open(f'{issue_path}/{attachment_id}', 'w') as _:
64+
pass
65+
66+
mime_type = 'image/png'
67+
file_name = f'Screenshot_{get_random_date_underscored(dates_timezone)}.png'
68+
created = get_random_date_iso(dates_timezone)
69+
file_size = randint(1000, 10000)
70+
f.write(f'{attachment_id},{issue_id},{mime_type},{file_name},{created},{file_size},unknown,,1\n')
71+
attachment_id += 1
72+
73+
issue_id += 1

logger.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import logging
2+
import sys
3+
4+
LOGGER_NAME = 'jira-bot'
5+
6+
_logger = logging.getLogger(LOGGER_NAME)
7+
_logger.setLevel(logging.INFO)
8+
9+
# create logger formatter
10+
_formatter = logging.Formatter("%(asctime)s:%(levelname)s:%(message)s")
11+
12+
# log to stdout
13+
_stdout_handler = logging.StreamHandler(sys.stdout)
14+
_stdout_handler.setFormatter(_formatter)
15+
_logger.addHandler(_stdout_handler)
16+
17+
# log the same to a file
18+
_file_handler = logging.FileHandler('log.txt')
19+
_file_handler.setFormatter(_formatter)
20+
_logger.addHandler(_file_handler)
21+
22+
23+
def info(message, *args, **kwargs):
24+
_logger.info(message, *args, **kwargs)

0 commit comments

Comments
 (0)