This commit is contained in:
Reid 'arrdem' McKenzie 2021-05-31 12:28:46 -06:00
parent 4c5d2aaed2
commit 03b37675b5
32 changed files with 193 additions and 169 deletions

View file

@ -2,7 +2,7 @@
from os import path from os import path
from setuptools import setup, find_namespace_packages from setuptools import find_namespace_packages, setup
# Fetch the README contents # Fetch the README contents

View file

@ -3,7 +3,7 @@ Some shared scaffolding for building terminal "REPL" drivers.
""" """
import curses import curses
from curses.textpad import Textbox, rectangle from curses.textpad import rectangle, Textbox
def curse_repl(handle_buffer): def curse_repl(handle_buffer):

View file

@ -9,9 +9,9 @@ parsing, linting or other use.
import io import io
import re import re
from calf.token import CalfToken
from calf.io.reader import PeekPosReader
from calf.grammar import TOKENS from calf.grammar import TOKENS
from calf.io.reader import PeekPosReader
from calf.token import CalfToken
from calf.util import * from calf.util import *

View file

@ -5,8 +5,8 @@ The Calf parser.
from itertools import tee from itertools import tee
import logging import logging
from calf.lexer import CalfLexer, lex_buffer, lex_file
from calf.grammar import MATCHING, WHITESPACE_TYPES from calf.grammar import MATCHING, WHITESPACE_TYPES
from calf.lexer import CalfLexer, lex_buffer, lex_file
from calf.token import * from calf.token import *

View file

@ -4,4 +4,5 @@ Fixtures for testing Calf.
import pytest import pytest
parametrize = pytest.mark.parametrize parametrize = pytest.mark.parametrize

View file

@ -7,7 +7,6 @@ trip through the lexer.
import calf.lexer as cl import calf.lexer as cl
from conftest import parametrize from conftest import parametrize
import pytest import pytest

View file

@ -4,7 +4,6 @@ Tests of calf.parser
import calf.parser as cp import calf.parser as cp
from conftest import parametrize from conftest import parametrize
import pytest import pytest

View file

@ -1,9 +1,8 @@
""" """
""" """
from conftest import parametrize
from calf.reader import read_buffer from calf.reader import read_buffer
from conftest import parametrize
@parametrize( @parametrize(

View file

@ -58,13 +58,13 @@ from datalog.debris import Timing
from datalog.evaluator import select from datalog.evaluator import select
from datalog.reader import pr_str, read_command, read_dataset from datalog.reader import pr_str, read_command, read_dataset
from datalog.types import ( from datalog.types import (
CachedDataset, CachedDataset,
Constant, Constant,
Dataset, Dataset,
LVar, LVar,
PartlyIndexedDataset, PartlyIndexedDataset,
Rule, Rule,
TableIndexedDataset, TableIndexedDataset
) )
from prompt_toolkit import print_formatted_text, prompt, PromptSession from prompt_toolkit import print_formatted_text, prompt, PromptSession

View file

@ -7,14 +7,7 @@ from itertools import chain
from datalog.parser import parse from datalog.parser import parse
from datalog.reader import pr_str, read from datalog.reader import pr_str, read
from datalog.types import ( from datalog.types import CachedDataset, Constant, Dataset, LVar, Rule, TableIndexedDataset
CachedDataset,
Constant,
Dataset,
LVar,
Rule,
TableIndexedDataset,
)
def match(tuple, expr, bindings=None): def match(tuple, expr, bindings=None):

View file

@ -2,13 +2,13 @@
from datalog.easy import read, select from datalog.easy import read, select
from datalog.types import ( from datalog.types import (
CachedDataset, CachedDataset,
Constant, Constant,
Dataset, Dataset,
LVar, LVar,
PartlyIndexedDataset, PartlyIndexedDataset,
Rule, Rule,
TableIndexedDataset, TableIndexedDataset
) )
import pytest import pytest

View file

@ -2,9 +2,10 @@
Reader tests. Reader tests.
""" """
from datalog.reader import read
import pytest import pytest
from datalog.reader import read
EXS = [ EXS = [
"%foo\n", "%foo\n",

View file

@ -1,5 +1,6 @@
from setuptools import setup from setuptools import setup
setup( setup(
name="arrdem.flowmetal", name="arrdem.flowmetal",
# Package metadata # Package metadata

View file

@ -2,9 +2,8 @@
The Flowmetal server entry point. The Flowmetal server entry point.
""" """
from flowmetal import frontend, interpreter, scheduler, reaper
import click import click
from flowmetal import frontend, interpreter, reaper, scheduler
@click.group() @click.group()

View file

@ -2,8 +2,8 @@
Quick and shitty Gandi REST API driver Quick and shitty Gandi REST API driver
""" """
import json
from datetime import datetime from datetime import datetime
import json
import requests import requests

View file

@ -2,21 +2,21 @@
A quick and dirty public DNS script, super tightly coupled to my infrastructure. A quick and dirty public DNS script, super tightly coupled to my infrastructure.
""" """
import sys
import os
import argparse import argparse
import re import os
from pprint import pprint from pprint import pprint
import re
import sys
for e in sys.path: for e in sys.path:
print(e) print(e)
from gandi.client import GandiAPI from gandi.client import GandiAPI
import jinja2 import jinja2
import meraki
import pkg_resources import pkg_resources
import yaml import yaml
import meraki
RECORD_LINE_PATTERN = re.compile( RECORD_LINE_PATTERN = re.compile(

View file

@ -1,5 +1,6 @@
from setuptools import setup from setuptools import setup
setup( setup(
name="arrdem.ratchet", name="arrdem.ratchet",
# Package metadata # Package metadata

View file

@ -8,7 +8,7 @@ from abc import ABC, abstractmethod
class Message: class Message:
"""Messages can be sent. That's it. """Messages can be sent. That's it.
Messages have headers, which may Messages have headers, which may
Other things can filter the stream of inbound messages and do log processing, but that's the whole basis of the Other things can filter the stream of inbound messages and do log processing, but that's the whole basis of the
thing. thing.
@ -67,57 +67,51 @@ class Driver(ABC):
"""Shared interface for Ratchet backend drivers.""" """Shared interface for Ratchet backend drivers."""
@abstractmethod @abstractmethod
def __init__(message_ttl=60000, def __init__(message_ttl=60000, message_space="_", message_author=""):
message_space="_",
message_author=""):
"""Initialize the driver.""" """Initialize the driver."""
@abstractmethod @abstractmethod
def create_message(self, def create_message(
message: str, self, message: str, ttl: int = None, space: str = None, author: str = None
ttl: int = None, ) -> Message:
space: str = None,
author: str = None) -> Message:
"""Create a single message.""" """Create a single message."""
@abstractmethod @abstractmethod
def create_event(self, def create_event(
timeout: int, self, timeout: int, ttl: int = None, space: str = None, author: str = None
ttl: int = None, ):
space: str = None,
author: str = None):
"""Create a (pending) event.""" """Create a (pending) event."""
@abstractmethod @abstractmethod
def set_event(self, def set_event(
timeout: int, self, timeout: int, ttl: int = None, space: str = None, author: str = None
ttl: int = None, ):
space: str = None,
author: str = None):
"""Attempt to mark an event as set.""" """Attempt to mark an event as set."""
@abstractmethod @abstractmethod
def create_request(self, def create_request(
body: str, self,
timeout: int, body: str,
ttl: int = None, timeout: int,
space: str = None, ttl: int = None,
author: str = None): space: str = None,
author: str = None,
):
"""Create a (pending) request.""" """Create a (pending) request."""
@abstractmethod @abstractmethod
def deliver_request(self, def deliver_request(
request_id, self,
response: str, request_id,
ttl: int = None, response: str,
space: str = None, ttl: int = None,
author: str = None): space: str = None,
author: str = None,
):
"""Deliver a response to a (pending) request.""" """Deliver a response to a (pending) request."""
@abstractmethod @abstractmethod
def revoke_request(self, def revoke_request(
request_id, self, request_id, ttl: int = None, space: str = None, author: str = None
ttl: int = None, ):
space: str = None,
author: str = None):
"""Revoke a (pending) request.""" """Revoke a (pending) request."""

View file

@ -2,12 +2,12 @@
An implementation of the ratchet model against SQLite. An implementation of the ratchet model against SQLite.
""" """
import os
import sqlite3 as sql
from contextlib import closing from contextlib import closing
import os
import socket import socket
import sqlite3 as sql
from ratchet import Message, Event, Request from ratchet import Event, Message, Request
SCHEMA_SCRIPT = """ SCHEMA_SCRIPT = """

View file

@ -1,5 +1,6 @@
from setuptools import setup from setuptools import setup
setup( setup(
name="arrdem.yamlschema", name="arrdem.yamlschema",
# Package metadata # Package metadata

View file

@ -2,11 +2,11 @@
JSONSchema linting for YAML documents. JSONSchema linting for YAML documents.
""" """
import logging
import typing as t
from enum import Enum from enum import Enum
from io import StringIO from io import StringIO
import logging
import re import re
import typing as t
import yaml import yaml
from yaml.nodes import MappingNode, Node, ScalarNode, SequenceNode from yaml.nodes import MappingNode, Node, ScalarNode, SequenceNode
@ -58,9 +58,13 @@ class YamlLinter(object):
schema = self._schema schema = self._schema
for e in path: for e in path:
if not e: if not e:
raise ValueError(f"Unable to dereference {ref}; contains empty segment!") raise ValueError(
f"Unable to dereference {ref}; contains empty segment!"
)
if not (schema := schema.get(e)): if not (schema := schema.get(e)):
raise ValueError(f"Unable to dereference {ref}; references missing sub-document!") raise ValueError(
f"Unable to dereference {ref}; references missing sub-document!"
)
return schema return schema
@ -175,7 +179,10 @@ class YamlLinter(object):
else: else:
yield LintRecord( yield LintRecord(
LintLevel.MISSMATCH, node, schema, f"Expected an integer, got a {node.tag}" LintLevel.MISSMATCH,
node,
schema,
f"Expected an integer, got a {node.tag}",
) )
def lint_number(self, schema, node: Node) -> t.Iterable[LintRecord]: def lint_number(self, schema, node: Node) -> t.Iterable[LintRecord]:
@ -185,7 +192,10 @@ class YamlLinter(object):
else: else:
yield LintRecord( yield LintRecord(
LintLevel.MISSMATCH, node, schema, f"Expected an integer, got a {node.tag}" LintLevel.MISSMATCH,
node,
schema,
f"Expected an integer, got a {node.tag}",
) )
def _lint_num_range(self, schema, node: Node, value) -> t.Iterable[LintRecord]: def _lint_num_range(self, schema, node: Node, value) -> t.Iterable[LintRecord]:

View file

@ -2,9 +2,8 @@
Tests covering the YAML linter. Tests covering the YAML linter.
""" """
from yamlschema import lint_buffer
import pytest import pytest
from yamlschema import lint_buffer
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -100,20 +99,31 @@ def test_lint_document_fails(msg, schema, obj):
assert list(lint_buffer(schema, obj)), msg assert list(lint_buffer(schema, obj)), msg
@pytest.mark.parametrize("msg, schema, obj", [ @pytest.mark.parametrize(
("Basic usage of $ref", "msg, schema, obj",
{"$ref": "#/definitions/Foo", [
"definitions": { (
"Foo": {"type": "string"}, "Basic usage of $ref",
}}, {
"---\nfoo"), "$ref": "#/definitions/Foo",
("Use of nested references", "definitions": {
{"$ref": "#/definitions/Foos", "Foo": {"type": "string"},
"definitions": { },
"Foos": {"type": "array", "items": {"$ref": "#/definitions/Foo"}}, },
"Foo": {"type": "string"}, "---\nfoo",
}}, ),
"---\n- foo\n- bar\n- baz"), (
]) "Use of nested references",
{
"$ref": "#/definitions/Foos",
"definitions": {
"Foos": {"type": "array", "items": {"$ref": "#/definitions/Foo"}},
"Foo": {"type": "string"},
},
},
"---\n- foo\n- bar\n- baz",
),
],
)
def test_ref_references(msg, schema, obj): def test_ref_references(msg, schema, obj):
assert not list(lint_buffer(schema, obj)), msg assert not list(lint_buffer(schema, obj)), msg

View file

@ -70,6 +70,7 @@ py_pytest(
], ],
deps = [ deps = [
py_requirement("requests"), py_requirement("requests"),
py_requirement("requirements-parser"),
] ]
) )

View file

@ -9,6 +9,7 @@ import sys
from autoflake import main from autoflake import main
if __name__ == "__main__": if __name__ == "__main__":
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
sys.exit(main()) sys.exit(main())

View file

@ -1,10 +1,10 @@
"""A shim for executing pytest.""" """A shim for executing pytest."""
import os
import sys import sys
import pytest import pytest
if __name__ == "__main__": if __name__ == "__main__":
cmdline = ["--ignore=external"] + sys.argv[1:] cmdline = ["--ignore=external"] + sys.argv[1:]
sys.exit(pytest.main(cmdline)) sys.exit(pytest.main(cmdline))

View file

@ -9,6 +9,7 @@ import sys
from isort.main import main from isort.main import main
if __name__ == "__main__": if __name__ == "__main__":
sys.argv[0] = re.sub(r"(-script\.pyw?|\.exe)?$", "", sys.argv[0]) sys.argv[0] = re.sub(r"(-script\.pyw?|\.exe)?$", "", sys.argv[0])
sys.exit(main()) sys.exit(main())

View file

@ -7,6 +7,7 @@ import sys
from openapi_spec_validator.__main__ import main from openapi_spec_validator.__main__ import main
if __name__ == "__main__": if __name__ == "__main__":
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
sys.exit(main()) sys.exit(main())

View file

@ -64,6 +64,7 @@ redis==3.5.3
regex==2021.4.4 regex==2021.4.4
requests==2.25.1 requests==2.25.1
requests-toolbelt==0.9.1 requests-toolbelt==0.9.1
requirements-parser==0.2.0
rfc3986==1.5.0 rfc3986==1.5.0
SecretStorage==3.3.1 SecretStorage==3.3.1
six==1.15.0 six==1.15.0

View file

@ -12,6 +12,7 @@ import click
import jinja2 import jinja2
import yaml import yaml
FONTMATTER_PATTERN = re.compile( FONTMATTER_PATTERN = re.compile(
r"^(---\n\r?(?P<fontmatter>.*?)\n\r?---\n\r?)?(?P<content>.+)$", re.DOTALL r"^(---\n\r?(?P<fontmatter>.*?)\n\r?---\n\r?)?(?P<content>.+)$", re.DOTALL
) )

View file

@ -6,35 +6,51 @@ import re
import pytest import pytest
import requests import requests
import requirements
from requirements.requirement import Requirement
# Licenses approved as representing non-copyleft and not precluding commercial usage. # Licenses approved as representing non-copyleft and not precluding commercial usage.
# This is all easy, there's a good schema here. # This is all easy, there's a good schema here.
APPROVED_LICENSES = [ APPROVED_LICENSES = [
"License :: OSI Approved :: MIT License", MIT := "License :: OSI Approved :: MIT License",
"License :: OSI Approved :: Apache Software License", APACHE := "License :: OSI Approved :: Apache Software License",
"License :: OSI Approved :: BSD License", BSD := "License :: OSI Approved :: BSD License",
"License :: OSI Approved :: Mozilla Public License 1.0 (MPL)", MPL10 := "License :: OSI Approved :: Mozilla Public License 1.0 (MPL)",
"License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)", MPL11 := "License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)",
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", MPL20 := "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
"License :: OSI Approved :: Python Software Foundation License", PSFL := "License :: OSI Approved :: Python Software Foundation License",
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", LGPL := "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"License :: OSI Approved :: ISC License (ISCL)", ISCL := "License :: OSI Approved :: ISC License (ISCL)",
]
UNAPPROVED_LICENSES = [
GPL1 := "License :: OSI Approved :: GNU General Public License",
GPL2 := "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
GPL3 := "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
] ]
# This data is GARBO. # This data is GARBO.
LICENSES_BY_LOWERNAME = { LICENSES_BY_LOWERNAME = {
"apache 2.0": "License :: OSI Approved :: Apache Software License", "apache 2.0": APACHE,
"apache": "License :: OSI Approved :: Apache Software License", "apache": APACHE,
"bsd 3 clause": "License :: OSI Approved :: BSD License", "http://www.apache.org/licenses/license-2.0": APACHE,
"bsd 3-clause": "License :: OSI Approved :: BSD License",
"bsd": "License :: OSI Approved :: BSD License", "bsd 3": BSD,
"gplv3": "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "bsd": BSD,
"http://www.apache.org/licenses/license-2.0": "License :: OSI Approved :: Apache Software License",
"isc": "License :: OSI Approved :: ISC License (ISCL)", "gpl": GPL1,
"mit": "License :: OSI Approved :: MIT License", "gpl2": GPL2,
"mpl 2.0": "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "gpl3": GPL3,
"mpl": "License :: OSI Approved :: Mozilla Public License 1.0 (MPL)",
"psf": "License :: OSI Approved :: Python Software Foundation License", "isc": ISCL,
"mit": MIT,
"mpl": MPL10,
"mpl 2.0": MPL20,
"psf": PSFL,
} }
# Mash in some cases. # Mash in some cases.
@ -49,52 +65,38 @@ APPROVED_PACKAGES = [
"anosql", # BSD "anosql", # BSD
] ]
REQ_PATTERN = re.compile(
r"(?P<pkgname>[a-zA-Z0-9_-]+)(?P<features>\[.*?\])?==(?P<version>[^\s;#]+)|(.*?#egg=(?P<eggname>[a-zA-Z0-9_-]+))"
)
with open("tools/python/requirements.txt") as fd:
def parse_requirement(line): PACKAGES = list(requirements.parse(fd))
"""Given a requirement return the requirement name and version as a tuple.
Only the strict `==` version pinning subset is supported.
Features are supported.
"""
if m := re.match(REQ_PATTERN, line):
return (m.group("pkgname") or m.group("eggname")), m.group("version")
@pytest.mark.parametrize(
"line,t",
[
("foo==1.2.3", ("foo", "1.2.3")),
("foo[bar]==1.2.3", ("foo", "1.2.3")),
("foo[bar, baz, qux]==1.2.3", ("foo", "1.2.3")),
# Various stuff we should ignore
("# comment line", None),
(" # garbage whitespace", None),
(" \t", None),
],
)
def test_parse_requirement(line, t):
"""The irony of testing one"s tests is not lost."""
assert parse_requirement(line) == t
with open("tools/python/requirements.txt") as f:
PACKAGES = [parse_requirement(l) for l in f.readlines()]
def bash_license(ln): def bash_license(ln):
if ln: while True:
ln = re.sub("[(),]|( version)|( license)", "", ln.lower()) lnn = re.sub(r"[(),]|( version)|( license)|( ?v(?=\d))|([ -]clause)", "", ln.lower())
ln = LICENSES_BY_LOWERNAME.get(ln, ln) if ln != lnn:
ln = lnn
else:
break
ln = LICENSES_BY_LOWERNAME.get(ln, ln)
return ln return ln
def licenses(package, version): @pytest.mark.parametrize("a,b", [
("MIT", MIT),
("mit", MIT),
("BSD", BSD),
("BSD 3-clause", BSD),
("BSD 3 clause", BSD),
("GPL3", GPL3),
("GPL v3", GPL3),
("GPLv3", GPL3),
])
def test_bash_license(a, b):
assert bash_license(a) == b
def licenses(package: Requirement):
"""Get package metadata (the licenses list) from PyPi. """Get package metadata (the licenses list) from PyPi.
pip and other tools use the local package metadata to introspect licenses which requires that pip and other tools use the local package metadata to introspect licenses which requires that
@ -104,11 +106,16 @@ def licenses(package, version):
""" """
l = [] l = []
version = next((v for op, v in package.specs if op == "=="), None)
print(package.name, version)
# If we don't have a version (eg. forked git dep) assume we've got the same license constraints # If we don't have a version (eg. forked git dep) assume we've got the same license constraints
# as the latest upstream release. After all we can't re-license stuff. # as the latest upstream release. After all we can't re-license stuff.
if not version: if not version:
blob = requests.get(f"https://pypi.python.org/pypi/{package}/json").json() blob = requests.get(
f"https://pypi.org/pypi/{package.name}/json",
headers={"Accept": "application/json"}
).json()
if ln := bash_license(blob.get("license")): if ln := bash_license(blob.get("license")):
l.append(ln) l.append(ln)
else: else:
@ -120,7 +127,8 @@ def licenses(package, version):
# If we have a version, try to pull that release's metadata since it may have more/better. # If we have a version, try to pull that release's metadata since it may have more/better.
if version: if version:
blob = requests.get( blob = requests.get(
f"https://pypi.python.org/pypi/{package}/{version}/json" f"https://pypi.org/pypi/{package.name}/{version}/json",
headers={"Accept": "application/json"}
).json() ).json()
l = [ l = [
c c
@ -134,11 +142,11 @@ def licenses(package, version):
return l return l
@pytest.mark.parametrize("package,version", PACKAGES) @pytest.mark.parametrize("package", PACKAGES)
def test_approved_license(package, version): def test_approved_license(package):
"""Ensure that a given package is either allowed by name or uses an approved license.""" """Ensure that a given package is either allowed by name or uses an approved license."""
_licenses = licenses(package, version) _licenses = licenses(package)
assert package in APPROVED_PACKAGES or any( assert package.name in APPROVED_PACKAGES or any(
l in APPROVED_LICENSES for l in _licenses l in APPROVED_LICENSES for l in _licenses
), f"{package} was not approved and its license(s) were unknown {_licenses!r}" ), f"{package} was not approved and its license(s) were unknown {_licenses!r}"

View file

@ -7,5 +7,6 @@ Shim for executing isort.
from unify import main from unify import main
if __name__ == "__main__": if __name__ == "__main__":
exit(main()) exit(main())

View file

@ -9,6 +9,7 @@ import sys
from yamllint.cli import run from yamllint.cli import run
if __name__ == "__main__": if __name__ == "__main__":
sys.argv[0] = re.sub(r"(-script\.pyw?|\.exe)?$", "", sys.argv[0]) sys.argv[0] = re.sub(r"(-script\.pyw?|\.exe)?$", "", sys.argv[0])
sys.exit(run()) sys.exit(run())