Compare commits
13 Commits
master
...
defunct-py
Author | SHA1 | Date | |
---|---|---|---|
e801f15066 | |||
f6fed08e26 | |||
982c7ad388 | |||
66b0fb1b60 | |||
1379e5f8a3 | |||
59fdc5b355 | |||
47ca307f8e | |||
ba9763484d | |||
3dfaa58751 | |||
c761bbb85e | |||
435ef9d77e | |||
243b01e156 | |||
06dc7c9996 |
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
__pycache__
|
||||||
|
secrets.py
|
||||||
|
runs
|
||||||
|
.ipynb_*
|
||||||
|
*.csv
|
||||||
|
.direnv
|
69
README.md
69
README.md
@ -0,0 +1,69 @@
|
|||||||
|
Providers are modules that export:
|
||||||
|
CONFIG = {
|
||||||
|
"config key": "optional | required",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
update(config, state) -> list[items]
|
||||||
|
|
||||||
|
And optionally:
|
||||||
|
action_NAME(config, state, item) -> None
|
||||||
|
|
||||||
|
on_create(config, state, item) -> None
|
||||||
|
|
||||||
|
on_delete(config, state, item) -> None
|
||||||
|
|
||||||
|
Sources are folders in the data directory with:
|
||||||
|
config: user-modified configuration values, drawn from the provider
|
||||||
|
state: provider-modified data
|
||||||
|
*.item: feed items
|
||||||
|
|
||||||
|
config reserved keys:
|
||||||
|
"provider": the provider the source uses
|
||||||
|
"name": given to sources by default, individuates sources from same provider
|
||||||
|
|
||||||
|
GET /config
|
||||||
|
Edit subfeed configs
|
||||||
|
|
||||||
|
GET /config/[feedname]
|
||||||
|
Edit config values for feedname
|
||||||
|
|
||||||
|
GET /feed/[feedname]
|
||||||
|
Feed view for feedname
|
||||||
|
|
||||||
|
<!-- GET /items
|
||||||
|
Get item JSON
|
||||||
|
?include=[tags]
|
||||||
|
?exclude=[tags]
|
||||||
|
?limit=100 -->
|
||||||
|
|
||||||
|
CLI:
|
||||||
|
intake create [provider] [--config KEY VALUE] [-c KEY VALUE] ...
|
||||||
|
load the provider
|
||||||
|
verify that the provider's config is satisfied
|
||||||
|
create the source and set the config values
|
||||||
|
|
||||||
|
intake update [source] [--reset]
|
||||||
|
load the source's config and state
|
||||||
|
use config.provider to load the provider
|
||||||
|
verify the source config against the provider config
|
||||||
|
call provider.update(config, state) to get current items
|
||||||
|
merge new items into current items
|
||||||
|
if --reset, new items overwrite everything, including active
|
||||||
|
|
||||||
|
intake deactivate [source] [--tag TAG] [--title TITLE]
|
||||||
|
load each item in the source and deactivate it
|
||||||
|
|
||||||
|
intake add [--id ID] [--title TITLE] [--link LINK] [--time TIME] [--author AUTHOR] [--body BODY] [--tags TAGS] [--ttl TTL] [--ttd TTD] [--tts TTS]
|
||||||
|
create the item and add it to the default source
|
||||||
|
|
||||||
|
intake feed [--json]
|
||||||
|
Dump active item feed to stdout
|
||||||
|
|
||||||
|
intake action [source] [item] [action]
|
||||||
|
load config, state, item
|
||||||
|
verify item supports this action
|
||||||
|
load config.provider
|
||||||
|
verify config
|
||||||
|
verify provider supports this action
|
||||||
|
action_[action](config, state, item)
|
7
default.nix
Normal file
7
default.nix
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
(import (
|
||||||
|
fetchTarball {
|
||||||
|
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
|
||||||
|
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
|
||||||
|
) {
|
||||||
|
src = ./.;
|
||||||
|
}).defaultNix
|
43
flake.lock
generated
Normal file
43
flake.lock
generated
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1659877975,
|
||||||
|
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1660162369,
|
||||||
|
"narHash": "sha256-pZukMP4zCA1FaBg0xHxf7KdE/Nv/C5YbDID7h2L8O7A=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "3a11db5f408095b8f08b098ec2066947f4b72ce2",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
33
flake.nix
Normal file
33
flake.nix
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = {self, nixpkgs, flake-utils}:
|
||||||
|
let
|
||||||
|
makeFlakeOutputs = system:
|
||||||
|
let
|
||||||
|
pname = "intake";
|
||||||
|
pkgs = nixpkgs.legacyPackages."${system}";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShell = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
python3Packages.poetry
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultPackage = with pkgs.poetry2nix; mkPoetryApplication {
|
||||||
|
projectDir = builtins.path { path = ./server; name = pname; };
|
||||||
|
preferWheels = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultApp = flake-utils.lib.mkApp {
|
||||||
|
drv = self.defaultPackage."${system}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
with flake-utils.lib; eachSystem defaultSystems makeFlakeOutputs;
|
||||||
|
|
||||||
|
}
|
21
providers/echo.py
Normal file
21
providers/echo.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from intake import BaseSettings, Setting
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
message = Setting(default="Hello, world!")
|
||||||
|
|
||||||
|
|
||||||
|
def update(config, state):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def on_create(config, state, item):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def on_delete(config, state, item):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def action_tick(config, state, item):
|
||||||
|
pass
|
1
server/intake/__init__.py
Normal file
1
server/intake/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .provider import Setting, BaseSettings
|
193
server/intake/cli.py
Normal file
193
server/intake/cli.py
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import argparse
|
||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
from signal import signal, SIGPIPE, SIG_DFL
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from intake.provider import (
|
||||||
|
AttributeTypeError,
|
||||||
|
FunctionSignatureError,
|
||||||
|
load_provider,
|
||||||
|
RequiredAttributeMissingError,
|
||||||
|
verify_action,
|
||||||
|
verify_oncreate,
|
||||||
|
verify_ondelete,
|
||||||
|
verify_settings,
|
||||||
|
verify_update,
|
||||||
|
)
|
||||||
|
from intake.source import initialize_source
|
||||||
|
|
||||||
|
|
||||||
|
def command_create(args):
|
||||||
|
"""Create a source."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="intake create",
|
||||||
|
description=command_create.__doc__)
|
||||||
|
parser.add_argument("provider",
|
||||||
|
help="Provider to create the source from",
|
||||||
|
metavar="provider")
|
||||||
|
parser.add_argument("--setting", "-s",
|
||||||
|
help="Define a provider setting",
|
||||||
|
nargs=2,
|
||||||
|
action="append",
|
||||||
|
metavar="key value",
|
||||||
|
dest="settings",
|
||||||
|
default=[])
|
||||||
|
parser.add_argument("--data",
|
||||||
|
help="Data folder path",
|
||||||
|
metavar="path",
|
||||||
|
type=os.path.abspath)
|
||||||
|
parser.add_argument("--path",
|
||||||
|
nargs="+",
|
||||||
|
help="Additional paths to add to INTAKEPATH",
|
||||||
|
metavar="path",
|
||||||
|
type=os.path.abspath,
|
||||||
|
default=[])
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
ret = 0
|
||||||
|
|
||||||
|
settings = {key: value for key, value in args.settings}
|
||||||
|
initialize_source(args.data, args.path, **settings)
|
||||||
|
|
||||||
|
|
||||||
|
def command_test(args):
|
||||||
|
"""Check for errors or misconfigurations."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="intake test",
|
||||||
|
description=command_test.__doc__)
|
||||||
|
parser.add_argument("--provider",
|
||||||
|
nargs="+",
|
||||||
|
help="Providers to test.",
|
||||||
|
metavar="name",
|
||||||
|
dest="providers",
|
||||||
|
default=[])
|
||||||
|
parser.add_argument("--path",
|
||||||
|
nargs="+",
|
||||||
|
help="Additional paths to add to INTAKEPATH",
|
||||||
|
metavar="path",
|
||||||
|
type=os.path.abspath,
|
||||||
|
default=[])
|
||||||
|
args = parser.parse_args(args)
|
||||||
|
|
||||||
|
ret = 0
|
||||||
|
|
||||||
|
search_path = args.path
|
||||||
|
print("INTAKEPATH:")
|
||||||
|
for path in search_path:
|
||||||
|
print(f" {path}")
|
||||||
|
for provider_name in args.providers:
|
||||||
|
print(f"Checking provider {provider_name}")
|
||||||
|
provider = load_provider(search_path, provider_name)
|
||||||
|
if not provider:
|
||||||
|
print(" x Not found")
|
||||||
|
ret = 1
|
||||||
|
continue
|
||||||
|
# Settings class
|
||||||
|
try:
|
||||||
|
verify_settings(provider)
|
||||||
|
print(" o Settings")
|
||||||
|
except RequiredAttributeMissingError:
|
||||||
|
print(" x Missing Settings class")
|
||||||
|
ret = 1
|
||||||
|
except AttributeTypeError:
|
||||||
|
print(" x Settings class does not inherit from intake.BaseSettings")
|
||||||
|
ret = 1
|
||||||
|
# update function
|
||||||
|
try:
|
||||||
|
verify_update(provider)
|
||||||
|
print(" o update")
|
||||||
|
except RequiredAttributeMissingError:
|
||||||
|
print(" x Missing update(config, state)")
|
||||||
|
ret = 1
|
||||||
|
except AttributeTypeError:
|
||||||
|
print(" x update is not callable")
|
||||||
|
ret = 1
|
||||||
|
except FunctionSignatureError:
|
||||||
|
print(" x update does not have signature (config, state)")
|
||||||
|
ret = 1
|
||||||
|
# on-create hook
|
||||||
|
try:
|
||||||
|
if verify_oncreate(provider):
|
||||||
|
print(" o on_create")
|
||||||
|
except AttributeTypeError:
|
||||||
|
print(" x on_create is not callable")
|
||||||
|
ret = 1
|
||||||
|
except FunctionSignatureError:
|
||||||
|
print(" x on_create does not have signature (config, state, item)")
|
||||||
|
ret = 1
|
||||||
|
# on-delete hook
|
||||||
|
try:
|
||||||
|
if verify_ondelete(provider):
|
||||||
|
print(" o on_delete")
|
||||||
|
except AttributeTypeError:
|
||||||
|
print(" x on_delete is not callable")
|
||||||
|
ret = 1
|
||||||
|
except FunctionSignatureError:
|
||||||
|
print(" x on_delete does not have signature (config, state, item)")
|
||||||
|
ret = 1
|
||||||
|
# actions
|
||||||
|
actions = [name for name in vars(provider) if name.startswith("action_")]
|
||||||
|
for action_name in actions:
|
||||||
|
try:
|
||||||
|
if verify_action(provider, action_name):
|
||||||
|
print(f" o {action_name}")
|
||||||
|
except AttributeTypeError:
|
||||||
|
print(f" x {action_name} is not callable")
|
||||||
|
ret = 1
|
||||||
|
except FunctionSignatureError:
|
||||||
|
print(f" x {action_name} does not have signature (config, state, item)")
|
||||||
|
ret = 1
|
||||||
|
print("Done")
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def command_help(args):
|
||||||
|
"""Print this help message and exit."""
|
||||||
|
print_usage()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""CLI entry point"""
|
||||||
|
# Enable piping
|
||||||
|
signal(SIGPIPE, SIG_DFL)
|
||||||
|
|
||||||
|
# Get the available commands by reflection
|
||||||
|
cli = sys.modules[__name__]
|
||||||
|
commands = {
|
||||||
|
name[8:]: func
|
||||||
|
for name, func in vars(cli).items()
|
||||||
|
if name.startswith("command_")
|
||||||
|
}
|
||||||
|
descriptions = "\n".join([
|
||||||
|
f" {name} - {func.__doc__}"
|
||||||
|
for name, func in commands.items()])
|
||||||
|
|
||||||
|
# Set up the top-level parser
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=f"Available commands:\n{descriptions}\n",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
parser.add_argument("command",
|
||||||
|
nargs="?",
|
||||||
|
default="help",
|
||||||
|
help="The command to execute",
|
||||||
|
choices=commands,
|
||||||
|
metavar="command")
|
||||||
|
parser.add_argument("args",
|
||||||
|
nargs=argparse.REMAINDER,
|
||||||
|
help="Command arguments",
|
||||||
|
metavar="args")
|
||||||
|
|
||||||
|
# Pass the parser's help printer to command_help via global
|
||||||
|
global print_usage
|
||||||
|
print_usage = parser.print_help
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Execute command
|
||||||
|
if args.command:
|
||||||
|
sys.exit(commands[args.command](args.args))
|
||||||
|
else:
|
||||||
|
parser.print_usage()
|
||||||
|
sys.exit(0)
|
193
server/intake/provider.py
Normal file
193
server/intake/provider.py
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import importlib.util
|
||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
class SettingMissingError(Exception):
|
||||||
|
"""
|
||||||
|
No value was provided for a required setting.
|
||||||
|
"""
|
||||||
|
def __init__(self, missing_names):
|
||||||
|
super().__init__("Missing required settings: {}".format(", ".join(missing_names)))
|
||||||
|
self.missing_names = missing_names
|
||||||
|
|
||||||
|
|
||||||
|
class Setting:
|
||||||
|
"""
|
||||||
|
A setting value declared by a provider and defined by a source.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, required=False, default=None):
|
||||||
|
self.required = required
|
||||||
|
self.value = default
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSettings:
|
||||||
|
"""
|
||||||
|
Base class for provider settings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = Setting(required=True)
|
||||||
|
|
||||||
|
provider = Setting(required=True)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
setting_names = [name for name in dir(self) if not name.startswith("__")]
|
||||||
|
missing = []
|
||||||
|
for setting_name in setting_names:
|
||||||
|
setting: Setting = getattr(self, setting_name)
|
||||||
|
if setting_name in kwargs:
|
||||||
|
setting.value = kwargs[setting_name]
|
||||||
|
elif setting.required:
|
||||||
|
missing.append(setting_name)
|
||||||
|
if missing:
|
||||||
|
raise SettingMissingError(missing)
|
||||||
|
|
||||||
|
|
||||||
|
def issetting(obj):
|
||||||
|
return isinstance(obj, Setting)
|
||||||
|
|
||||||
|
|
||||||
|
class chdir:
|
||||||
|
"""
|
||||||
|
A context manager that changes the working directory inside the context.
|
||||||
|
"""
|
||||||
|
def __init__(self, path):
|
||||||
|
self.cwd = os.getcwd()
|
||||||
|
os.chdir(path)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
os.chdir(self.cwd)
|
||||||
|
|
||||||
|
|
||||||
|
class add_to_sys_path:
|
||||||
|
"""
|
||||||
|
A context manager that adds a path to sys.path, allowing imports from it.
|
||||||
|
"""
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
self.not_present = path not in sys.path
|
||||||
|
if self.not_present:
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
if self.not_present:
|
||||||
|
sys.path.remove(self.path)
|
||||||
|
|
||||||
|
|
||||||
|
def load_provider(search_paths, provider_name):
|
||||||
|
"""
|
||||||
|
Load a provider on the search path. If the provider cannot be found,
|
||||||
|
return None.
|
||||||
|
"""
|
||||||
|
for search_path in search_paths:
|
||||||
|
with chdir(search_path), add_to_sys_path(search_path):
|
||||||
|
# Check if the provider is on this path.
|
||||||
|
provider_filename = f"{provider_name}.py"
|
||||||
|
if not os.path.isfile(provider_filename):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Import the provider by file path.
|
||||||
|
spec = importlib.util.spec_from_file_location(provider_name, provider_filename)
|
||||||
|
provider_module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(provider_module)
|
||||||
|
provider = importlib.import_module(provider_name)
|
||||||
|
|
||||||
|
return provider
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class ProviderError(Exception):
|
||||||
|
"""A provider's attributes are incorrect."""
|
||||||
|
|
||||||
|
|
||||||
|
class RequiredAttributeMissingError(ProviderError):
|
||||||
|
"""A provider is missing a required attribute."""
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeTypeError(ProviderError):
|
||||||
|
"""A provider has an attribute with a wrong type."""
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionSignatureError(ProviderError):
|
||||||
|
"""A provider has a function attribute with the wrong signature."""
|
||||||
|
|
||||||
|
|
||||||
|
def verify_settings(provider):
|
||||||
|
"""
|
||||||
|
Verify that a provider's Settings is valid.
|
||||||
|
"""
|
||||||
|
if not hasattr(provider, "Settings"):
|
||||||
|
raise RequiredAttributeMissingError()
|
||||||
|
settings = getattr(provider, "Settings")
|
||||||
|
if not issubclass(settings, BaseSettings):
|
||||||
|
raise AttributeTypeError()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def verify_update(provider):
|
||||||
|
"""
|
||||||
|
Verify that a provider's update() is valid.
|
||||||
|
"""
|
||||||
|
if not hasattr(provider, "update"):
|
||||||
|
raise RequiredAttributeMissingError()
|
||||||
|
update = getattr(provider, "update")
|
||||||
|
if not callable(update):
|
||||||
|
raise AttributeTypeError()
|
||||||
|
update_sig = inspect.signature(update)
|
||||||
|
if list(update_sig.parameters) != ["config", "state"]:
|
||||||
|
raise FunctionSignatureError()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def verify_oncreate(provider):
|
||||||
|
"""
|
||||||
|
Verify that a provider's on_create() is valid.
|
||||||
|
"""
|
||||||
|
if not hasattr(provider, "on_create"):
|
||||||
|
return False
|
||||||
|
on_create = getattr(provider, "on_create")
|
||||||
|
if not callable(on_create):
|
||||||
|
raise AttributeTypeError()
|
||||||
|
create_sig = inspect.signature(on_create)
|
||||||
|
if list(create_sig.parameters) != ["config", "state", "item"]:
|
||||||
|
raise FunctionSignatureError()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def verify_ondelete(provider):
|
||||||
|
"""
|
||||||
|
Verify that a provider's on_delete() is valid.
|
||||||
|
"""
|
||||||
|
if not hasattr(provider, "on_delete"):
|
||||||
|
return False
|
||||||
|
on_delete = getattr(provider, "on_delete")
|
||||||
|
if not callable(on_delete):
|
||||||
|
raise AttributeTypeError()
|
||||||
|
delete_sig = inspect.signature(on_delete)
|
||||||
|
if list(delete_sig.parameters) != ["config", "state", "item"]:
|
||||||
|
raise FunctionSignatureError()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def verify_action(provider, name):
|
||||||
|
"""
|
||||||
|
Verify that a provider's action_*() is valid.
|
||||||
|
"""
|
||||||
|
action_name = name if name.startswith("action_") else f"action_{name}"
|
||||||
|
if not hasattr(provider, "action_name"):
|
||||||
|
return False
|
||||||
|
action = getattr(provider, action_name)
|
||||||
|
if not callable(action):
|
||||||
|
raise AttributeTypeError()
|
||||||
|
action_sig = inspect.signature(action)
|
||||||
|
if list(action_sig.parameters) != ["config", "state", "item"]:
|
||||||
|
raise FunctionSignatureError()
|
||||||
|
return True
|
50
server/intake/source.py
Normal file
50
server/intake/source.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from intake.provider import (
|
||||||
|
load_provider,
|
||||||
|
issetting,
|
||||||
|
SettingMissingError,
|
||||||
|
verify_settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_source(data_path, search_paths, **setting_kvs):
|
||||||
|
"""
|
||||||
|
Create a source's data folder and store its settings.
|
||||||
|
"""
|
||||||
|
# Verify the base required settings
|
||||||
|
if "name" not in setting_kvs:
|
||||||
|
raise KeyError("Missing name in settings")
|
||||||
|
if "provider" not in setting_kvs:
|
||||||
|
raise KeyError("Missing provider in settings")
|
||||||
|
# Check if the directory exists already
|
||||||
|
source_name = setting_kvs.get("name")
|
||||||
|
source_path = os.path.join(data_path, source_name)
|
||||||
|
if os.path.exists(source_path):
|
||||||
|
raise FileExistsError(source_path)
|
||||||
|
# Load the provider
|
||||||
|
provider_name = setting_kvs.get("provider")
|
||||||
|
provider = load_provider(search_paths, provider_name)
|
||||||
|
if not provider:
|
||||||
|
raise FileNotFoundError(provider_name)
|
||||||
|
# Verify that the provider has settings
|
||||||
|
verify_settings(provider)
|
||||||
|
# Check that all the required settings are populated
|
||||||
|
settings = inspect.getmembers(provider.Settings, issetting)
|
||||||
|
for name, setting in settings:
|
||||||
|
if setting.required and name not in setting_kvs:
|
||||||
|
raise SettingMissingError()
|
||||||
|
|
||||||
|
# Create the directory
|
||||||
|
os.mkdir(source_path)
|
||||||
|
# Create the settings file
|
||||||
|
setting_json = {}
|
||||||
|
for name, setting in settings:
|
||||||
|
setting_json[name] = setting_kvs.get(name, setting.default)
|
||||||
|
with open(os.path.join(source_path, ".settings"), 'w') as f:
|
||||||
|
json.dump(setting_json, f, indent=2)
|
||||||
|
# Create the state file
|
||||||
|
with open(os.path.join(source_path, ".state"), 'w') as f:
|
||||||
|
json.dump({}, f, indent=2)
|
257
server/poetry.lock
generated
Normal file
257
server/poetry.lock
generated
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2022.6.15"
|
||||||
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charset-normalizer"
|
||||||
|
version = "2.1.0"
|
||||||
|
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
unicode_backport = ["unicodedata2"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.1.3"
|
||||||
|
description = "Composable command line interface toolkit"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.5"
|
||||||
|
description = "Cross-platform colored terminal text."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "feedparser"
|
||||||
|
version = "6.0.10"
|
||||||
|
description = "Universal feed parser, handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
sgmllib3k = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flask"
|
||||||
|
version = "2.2.2"
|
||||||
|
description = "A simple framework for building complex web applications."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
click = ">=8.0"
|
||||||
|
itsdangerous = ">=2.0"
|
||||||
|
Jinja2 = ">=3.0"
|
||||||
|
Werkzeug = ">=2.2.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
async = ["asgiref (>=3.2)"]
|
||||||
|
dotenv = ["python-dotenv"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.3"
|
||||||
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itsdangerous"
|
||||||
|
version = "2.1.2"
|
||||||
|
description = "Safely pass data to untrusted environments and back."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jinja2"
|
||||||
|
version = "3.1.2"
|
||||||
|
description = "A very fast and expressive template engine."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
MarkupSafe = ">=2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
i18n = ["Babel (>=2.7)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markupsafe"
|
||||||
|
version = "2.1.1"
|
||||||
|
description = "Safely add untrusted strings to HTML/XML markup."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests"
|
||||||
|
version = "2.28.1"
|
||||||
|
description = "Python HTTP for Humans."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7, <4"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = ">=2017.4.17"
|
||||||
|
charset-normalizer = ">=2,<3"
|
||||||
|
idna = ">=2.5,<4"
|
||||||
|
urllib3 = ">=1.21.1,<1.27"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||||
|
use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sgmllib3k"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "Py3k port of sgmllib."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urllib3"
|
||||||
|
version = "1.26.11"
|
||||||
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
|
||||||
|
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||||
|
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "werkzeug"
|
||||||
|
version = "2.2.2"
|
||||||
|
description = "The comprehensive WSGI web application library."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
MarkupSafe = ">=2.1.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
watchdog = ["watchdog"]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "1.1"
|
||||||
|
python-versions = "^3.10"
|
||||||
|
content-hash = "d214f7eef1fcb2260a812ea5bbd90854fbcf180a682003b33576d37dcbb20eb3"
|
||||||
|
|
||||||
|
[metadata.files]
|
||||||
|
certifi = [
|
||||||
|
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
||||||
|
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
||||||
|
]
|
||||||
|
charset-normalizer = [
|
||||||
|
{file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
|
||||||
|
{file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"},
|
||||||
|
]
|
||||||
|
click = [
|
||||||
|
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||||
|
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||||
|
]
|
||||||
|
colorama = [
|
||||||
|
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||||
|
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||||
|
]
|
||||||
|
feedparser = [
|
||||||
|
{file = "feedparser-6.0.10-py3-none-any.whl", hash = "sha256:79c257d526d13b944e965f6095700587f27388e50ea16fd245babe4dfae7024f"},
|
||||||
|
{file = "feedparser-6.0.10.tar.gz", hash = "sha256:27da485f4637ce7163cdeab13a80312b93b7d0c1b775bef4a47629a3110bca51"},
|
||||||
|
]
|
||||||
|
flask = [
|
||||||
|
{file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"},
|
||||||
|
{file = "Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"},
|
||||||
|
]
|
||||||
|
idna = [
|
||||||
|
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||||
|
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
||||||
|
]
|
||||||
|
itsdangerous = [
|
||||||
|
{file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
|
||||||
|
{file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
|
||||||
|
]
|
||||||
|
jinja2 = [
|
||||||
|
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
||||||
|
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
||||||
|
]
|
||||||
|
markupsafe = [
|
||||||
|
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
|
||||||
|
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
|
||||||
|
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
|
||||||
|
]
|
||||||
|
requests = [
|
||||||
|
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
||||||
|
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
||||||
|
]
|
||||||
|
sgmllib3k = [
|
||||||
|
{file = "sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"},
|
||||||
|
]
|
||||||
|
urllib3 = [
|
||||||
|
{file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"},
|
||||||
|
{file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"},
|
||||||
|
]
|
||||||
|
werkzeug = [
|
||||||
|
{file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"},
|
||||||
|
{file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"},
|
||||||
|
]
|
20
server/pyproject.toml
Normal file
20
server/pyproject.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "intake"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["Jaculabilis <jaculabilis@git.alogoulogoi.com>"]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.10"
|
||||||
|
Flask = "^2.2.2"
|
||||||
|
requests = "^2.28.1"
|
||||||
|
feedparser = "^6.0.10"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
intake = "intake.cli:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
Loading…
Reference in New Issue
Block a user