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