Skip to content

Commit 61373ca

Browse files
committed
minimum viable implementation
1 parent 0636a0c commit 61373ca

35 files changed

+2767
-9
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ __pycache__/
1717
# testing
1818
.hypothesis/
1919
.pytest_cache/
20-
.coverage
20+
.coverage

Pipfile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ logalpha = ">=2.0.2"
2323
cmdkit = ">=1.5.4"
2424
toml = ">=0.10.1"
2525
sqlalchemy = ">=1.3.19"
26-
flask = ">=1.1.2"
27-
gunicorn = ">=20.0.4"
2826
streamkit = {editable = true, path = "."}
2927

3028
[requires]

Pipfile.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

setup.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
__authors__,
2121
__contact__,
2222
__license__,
23+
__website__,
2324
__description__)
2425

2526

@@ -29,7 +30,7 @@
2930

3031
# core dependencies
3132
DEPENDENCIES = ['logalpha>=2.0.2', 'cmdkit>=1.5.4', 'toml>=0.10.1',
32-
'sqlalchemy>=1.3.19', 'flask>=1.1.2', 'gunicorn>=20.0.4']
33+
'sqlalchemy>=1.3.19', ]
3334

3435
# add dependencies for readthedocs.io
3536
if os.environ.get('READTHEDOCS') == 'True':
@@ -44,7 +45,7 @@
4445
description = __description__,
4546
license = __license__,
4647
keywords = 'pub-sub message broker',
47-
url = 'https://streamkit.readthedocs.io',
48+
url = __website__,
4849
packages = find_packages(),
4950
long_description = long_description,
5051
long_description_content_type = 'text/x-rst',
@@ -53,7 +54,7 @@
5354
'Programming Language :: Python :: 3.8',
5455
'Programming Language :: Python :: 3.9',
5556
'License :: OSI Approved :: Apache Software License', ],
56-
entry_points = {'console_scripts': []},
57+
entry_points = {'console_scripts': ['streamkit=streamkit.cli:main']},
5758
install_requires = DEPENDENCIES,
5859
extras_require = {
5960
'postgres': ['psycopg2>=2.8.5', ],

streamkit/__meta__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
__version__ = '0.0.1'
1616
__authors__ = 'Geoffrey Lentner'
1717
__contact__ = '<glentner@purdue.edu>'
18+
__website__ = 'https://github.com/glentner/streamkit'
1819
__license__ = 'Apache License'
1920
__copyright__ = 'Copyright (c) Geoffrey Lentner 2019. All Rights Reserved.'
2021
__description__ = 'A simple, cross-platform SDK for pub-sub messaging in Python.'

streamkit/assets/__init__.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# This program is free software: you can redistribute it and/or modify it under the
2+
# terms of the Apache License (v2.0) as published by the Apache Software Foundation.
3+
#
4+
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
5+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6+
# PARTICULAR PURPOSE. See the Apache License for more details.
7+
#
8+
# You should have received a copy of the Apache License along with this program.
9+
# If not, see <https://www.apache.org/licenses/LICENSE-2.0>.
10+
11+
"""Assets/templates required by StreamKit."""
12+
13+
# type annotations
14+
from typing import List, Dict, IO, Union, Generator
15+
16+
# standard libs
17+
import os
18+
import re
19+
import fnmatch
20+
import functools
21+
22+
# internal libs
23+
from ..core.logging import Logger
24+
25+
26+
# module level logger
27+
log = Logger(__name__)
28+
29+
30+
# either bytes or str depending on how the file was opened
31+
FileData = Union[str, bytes]
32+
33+
34+
# The absolute location of this directory.
35+
# The trailing path separator is necessary for reconstituting relative paths.
36+
DIRECTORY = os.path.dirname(__file__) + os.path.sep
37+
38+
39+
def abspath(relative_path: str) -> str:
40+
"""Construct the absolute path to the file within /assets."""
41+
path = relative_path.lstrip(os.path.sep)
42+
return os.path.normpath(os.path.join(DIRECTORY, path))
43+
44+
45+
# do not yield non-asset paths
46+
IGNORE_PATHS = r'.*\/(__init__.py$|__pycache__\/.*)'
47+
48+
49+
def _iter_paths() -> Generator[str, None, None]:
50+
"""Yield relative file paths below /assets"""
51+
ignore = re.compile(IGNORE_PATHS)
52+
for root, dirs, files in os.walk(DIRECTORY):
53+
yield from filter(lambda path: ignore.match(path) is None,
54+
map(functools.partial(os.path.join, root), files))
55+
56+
57+
def _match_glob(pattern: str, path: str) -> bool:
58+
"""True if `path` matches `pattern`."""
59+
return fnmatch.fnmatch(path, pattern)
60+
61+
62+
def _match_regex(pattern: str, path: str) -> bool:
63+
"""True if `path` matches `pattern`."""
64+
return re.match(pattern, path) is not None
65+
66+
67+
def find_files(pattern: str, regex: bool = False, relative: bool = True) -> List[str]:
68+
"""List the assets matching a glob/regex `pattern`."""
69+
pattern = pattern.lstrip(os.path.sep)
70+
paths = sorted(filter(functools.partial(_match_glob if not regex else _match_regex, pattern),
71+
map(lambda path: os.path.normpath(path).replace(DIRECTORY, ''), _iter_paths())))
72+
if relative:
73+
return paths
74+
else:
75+
return list(map(abspath, paths))
76+
77+
78+
def open_asset(relative_path: str, mode: str = 'r', **kwargs) -> IO:
79+
"""
80+
Open a file from the /assets subpackage.
81+
82+
Parameters:
83+
relative_path (str):
84+
The relative file path below /assets directory.
85+
mode (str):
86+
The mode to open the file with (default: 'r').
87+
**kwargs:
88+
Additional keyword arguments are passed to open.
89+
90+
Returns:
91+
file: IO
92+
The file descriptor for the open file asset.
93+
"""
94+
dirname = os.path.dirname(__file__)
95+
filepath = os.path.join(dirname, relative_path)
96+
try:
97+
return open(filepath, mode=mode, **kwargs)
98+
except FileNotFoundError:
99+
log.error(f'missing {relative_path}')
100+
raise
101+
102+
103+
@functools.lru_cache(maxsize=None)
104+
def load_asset(relative_path: str, mode: str = 'r', **kwargs) -> FileData:
105+
"""
106+
Load an asset from its `relative_path` below /assets.
107+
108+
Parameters:
109+
relative_path (str):
110+
The relative file path below /assets directory.
111+
mode (str):
112+
The mode to open the file with (default: 'r').
113+
**kwargs:
114+
Additional keyword arguments are passed to open.
115+
116+
Returns:
117+
content: Union[str, bytes]
118+
The content of the file (depends on the mode).
119+
"""
120+
with open_asset(relative_path, mode=mode, **kwargs) as source:
121+
content = source.read()
122+
log.debug(f'loaded /assets/{relative_path}')
123+
return content
124+
125+
126+
def load_assets(pattern: str, regex: bool = False, **kwargs) -> Dict[str, FileData]:
127+
"""
128+
Load all files matching `pattern`.
129+
130+
Parameters:
131+
pattern (str):
132+
Either a glob pattern or regular expression for the files to include.
133+
regex (bool):
134+
Whether to interpret the `pattern` as a regular expression (default: False).
135+
136+
Returns:
137+
file_data: Dict[str, Union[str, bytes]]
138+
A dictionary of the file data, indexed by the relative file path within
139+
the /assets directory. Use `mode='rb'` to return raw bytes data.
140+
"""
141+
return {path: load_asset(path, **kwargs) for path in find_files(pattern, regex=regex)}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[database]
2+
backend = "sqlite"
3+
database = ":memory:" # FIXME: this must be changed!
4+
5+
[logging]
6+
level = "warning"
7+
handler = "standard"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/* This program is free software: you can redistribute it and/or modify it under the
2+
* terms of the Apache License (v2.0) as published by the Apache Software Foundation.
3+
*
4+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
5+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6+
* PARTICULAR PURPOSE. See the Apache License for more details.
7+
*
8+
* You should have received a copy of the Apache License along with this program.
9+
* If not, see <https://www.apache.org/licenses/LICENSE-2.0>.
10+
*/
11+
12+
SELECT create_hypertable('{{ SCHEMA }}.message', 'time', chunk_time_interval => interval '1 day');

streamkit/cli/__init__.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# This program is free software: you can redistribute it and/or modify it under the
2+
# terms of the Apache License (v2.0) as published by the Apache Software Foundation.
3+
#
4+
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
5+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6+
# PARTICULAR PURPOSE. See the Apache License for more details.
7+
#
8+
# You should have received a copy of the Apache License along with this program.
9+
# If not, see <https://www.apache.org/licenses/LICENSE-2.0>.
10+
11+
"""Command-line interface for StreamKit."""
12+
13+
# standard libs
14+
import sys
15+
16+
# internal libs
17+
from ..core.logging import Logger
18+
from ..__meta__ import __version__, __website__
19+
20+
# external libs
21+
from cmdkit import logging as _cmdkit_logging
22+
from cmdkit.app import Application, ApplicationGroup
23+
from cmdkit.cli import Interface
24+
25+
# command groups
26+
from . import config, subscribe, publish, database
27+
28+
29+
COMMANDS = {
30+
'config': config.ConfigApp,
31+
'publish': publish.PublisherApp,
32+
'subscribe': subscribe.SubscriberApp,
33+
'database': database.DatabaseApp,
34+
}
35+
36+
USAGE = f"""\
37+
usage: streamkit [-h] [-v] <command> [<args>...]
38+
Command-line tools for Streamkit.\
39+
"""
40+
41+
EPILOG = f"""\
42+
Documentation and issue tracking at:
43+
{__website__}\
44+
"""
45+
46+
HELP = f"""\
47+
{USAGE}
48+
49+
commands:
50+
publish {publish.__doc__}
51+
subscribe {subscribe.__doc__}
52+
database {database.__doc__}
53+
config {config.__doc__}
54+
55+
options:
56+
-h, --help Show this message and exit.
57+
-v, --version Show the version and exit.
58+
59+
Use the -h/--help flag with the above commands to
60+
learn more about their usage.
61+
62+
{EPILOG}\
63+
"""
64+
65+
66+
# initialize module level logger
67+
log = Logger('streamkit')
68+
69+
70+
# inject logger back into cmdkit library
71+
_cmdkit_logging.log = log
72+
Application.log_error = log.critical
73+
74+
75+
class StreamKit(ApplicationGroup):
76+
"""Application class for streamkit entry-point."""
77+
78+
interface = Interface('streamkit', USAGE, HELP)
79+
interface.add_argument('-v', '--version', version=__version__, action='version')
80+
interface.add_argument('command')
81+
82+
command = None
83+
commands = COMMANDS
84+
85+
86+
def main() -> int:
87+
"""Entry-point for streamkit command-line interface."""
88+
return StreamKit.main(sys.argv[1:])

0 commit comments

Comments
 (0)