Compare commits

...
This repository has been archived on 2024-02-23. You can view files and clone it, but cannot push or open issues or pull requests.

11 Commits

24 changed files with 1813 additions and 289 deletions

6
.gitignore vendored
View File

@ -1,3 +1,7 @@
__pycache__/ /.coverage
/.mypy_cache/
/memberplumbing.egg-info/
/venv/ /venv/
__pycache__/
/config.yaml /config.yaml

View File

@ -33,3 +33,15 @@ Retrieves events from the HID Evo Solo door controllers, and pushes them to a SQ
## Systemd ## Systemd
There are systemd units in the [`systemd`](./systemd/) folder, which can be used to run the various scripts regularly. There are systemd units in the [`systemd`](./systemd/) folder, which can be used to run the various scripts regularly.
## SSL Certificates
The HID Evo Solo door controllers we use have a self-signed certificate, which is included in this repo as [`hidglobal.com.pem`](./hidglobal.com.pem).
If you need to use a different certificate, you can either download it with your browser or the following command (replacing `SERVER` by the address of the door):
```sh
openssl s_client -connect SERVER:443 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > example.pem
```
Then set `doorControllerCA_BUNDLE` in `config.yaml` to the path to the created pem file. If your doors have different certificates, (which ours annoyingly don't), you will need to concatenate the certificates together into a single file.

View File

@ -1,5 +0,0 @@
#!/usr/bin/env python3
from memberPlumbing import doorUpdater
doorUpdater.main()

View File

@ -1,5 +0,0 @@
#!/usr/bin/env python3
from memberPlumbing import hidEvents
hidEvents.main()

View File

@ -1,5 +0,0 @@
#!/usr/bin/env python3
from memberPlumbing import sqlExport
sqlExport.main()

View File

@ -1,5 +0,0 @@
#!/usr/bin/env python3
from memberPlumbing import ucsAccounts
ucsAccounts.main()

View File

@ -6,6 +6,8 @@ doorControllers:
Wood Shop Rear: {ip: 172.18.51.15, access: Wood Shop} Wood Shop Rear: {ip: 172.18.51.15, access: Wood Shop}
Storage Closet: {ip: 172.18.51.16, access: Storage Closet} Storage Closet: {ip: 172.18.51.16, access: Storage Closet}
doorControllerCA_BUNDLE: "hidglobal.com.pem"
# {member type: door schedule} # {member type: door schedule}
memberLevels: memberLevels:
CMS Staff: 7x24 CMS Staff: 7x24
@ -34,6 +36,12 @@ DOOR_PASSWORD: ""
MEMBERSHIPWORKS_USERNAME: "" MEMBERSHIPWORKS_USERNAME: ""
MEMBERSHIPWORKS_PASSWORD: "" MEMBERSHIPWORKS_PASSWORD: ""
# arguments for https://udm-rest-client.readthedocs.io/en/latest/udm_rest_client.html#udm_rest_client.udm.UDM
UCS:
url: ""
username: ""
password: ""
MEMBERSHIPWORKS_DB: MEMBERSHIPWORKS_DB:
database: "" database: ""
user: "" user: ""

24
hidglobal.com.pem Normal file
View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID9TCCAt2gAwIBAgIJAPBotjnyfGu6MA0GCSqGSIb3DQEBCwUAMIGQMQswCQYD
VQQGEwJVUzERMA8GA1UECAwIQ29sb3JhZG8xFDASBgNVBAcMC1dlc3RtaW5zdGVy
MQwwCgYDVQQKDANISUQxDDAKBgNVBAsMA05BUzEWMBQGA1UEAwwNaGlkZ2xvYmFs
LmNvbTEkMCIGCSqGSIb3DQEJARYVc3VwcG9ydEBoaWRnbG9iYWwuY29tMB4XDTE5
MDcyMjEwMTQxMFoXDTI5MDcxOTEwMTQxMFowgZAxCzAJBgNVBAYTAlVTMREwDwYD
VQQIDAhDb2xvcmFkbzEUMBIGA1UEBwwLV2VzdG1pbnN0ZXIxDDAKBgNVBAoMA0hJ
RDEMMAoGA1UECwwDTkFTMRYwFAYDVQQDDA1oaWRnbG9iYWwuY29tMSQwIgYJKoZI
hvcNAQkBFhVzdXBwb3J0QGhpZGdsb2JhbC5jb20wggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDSibuXB9Tn0EdwL2jDig26s/b1D9SX5B4xnZM+xZ4/mE6U
Meg5xbiTSMiWqtoSMVxG1WJDxogJWxCgZis2qk3AG89PBarg17pBmxPLYyCLricx
alyNvTJBxYgA/zKagPof6h6UqKOkhsW9qvulEmPe+TKk47pmlZXe+v+1A6PQDY5B
Y3MtqE23cnZ5nBTVanFAc1vbokMXUCCtRvE1Y/KhvuaJr2VjOSJ/KV3vcdTSCLGc
W9/n/Fv8udvI/eIkoPNpCUwngm8j3Aa7qN/OSg3SvVvBcl/Ykc08STSyZPMJGBaR
EuUcAraEBZbUDOCinDS488jKVHAXhrnvzzi7RMlhAgMBAAGjUDBOMB0GA1UdDgQW
BBSvOzxCjQi86ZUsuW0o4aa7GNAeSTAfBgNVHSMEGDAWgBSvOzxCjQi86ZUsuW0o
4aa7GNAeSTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA/8FCv6x8v
PHm2Ya/hbZ/S3amdl7/E1illeApNRZodTGCn/rlVSGanCfWYzEY2naHiDC2ImhZq
NkHK9uvUtxaVQFq5VN6WQMo351J78LLfcoqpKOLGX3b9byFvrw7WporZx3C7yL1U
LS3oxI/pgavxy1KbOIw/yl+QgV50vlfvQ7sKZ1E5YOrgWLP5nJ9OeEKRdsASJyZS
Jjl0k/eGaZreSvAZPmx4kaePfbi7DNDA+mNhSFygwt6AakjjVoF2xUZ1F+qwBtER
GPxdZWldywUYsdBRG1PPvBsMo9ME46HpPdXRIjMge8P01fsaMr/6H86ojWg9uJmH
cCKtiouo08hL
-----END CERTIFICATE-----

View File

@ -22,6 +22,7 @@ class Config:
self.DOOR_PASSWORD, self.DOOR_PASSWORD,
name=doorName, name=doorName,
access=doorData["access"], access=doorData["access"],
cert=self.doorControllerCA_BUNDLE
) )
for doorName, doorData in self.doorControllers.items() for doorName, doorData in self.doorControllers.items()
} }

View File

@ -178,48 +178,14 @@ class DoorMember(Member):
"custom2": self.membershipWorksID, "custom2": self.membershipWorksID,
} }
def make_schedules(self, schedulesMap):
roles = [
E.Role(
{
"roleID": self.cardholderID,
"scheduleID": schedulesMap[schedule],
"resourceID": "0",
}
)
for schedule in self.schedules
]
return E.RoleSet(
{"action": "UD", "roleSetID": self.cardholderID}, E.Roles(*roles)
)
def make_credentials(self, newCredentials, cardFormats):
out = [
E.Credential(
{
"formatName": str(credential.code[0]),
"cardNumber": str(credential.code[1]),
"formatID": cardFormats[str(credential.code[0])],
"isCard": "true",
"cardholderID": self.cardholderID,
}
)
for credential in newCredentials
]
return E.Credentials({"action": "AD"}, *out)
def update_door(door, members): def update_door(door, members):
cardFormats = door.get_cardFormats()
cardholders = { cardholders = {
member.membershipWorksID: member member.membershipWorksID: member
for member in [ for member in [
DoorMember.from_cardholder(ch, door) for ch in door.get_cardholders() DoorMember.from_cardholder(ch, door) for ch in door.get_cardholders()
] ]
} }
schedulesMap = door.get_scheduleMap()
allCredentials = set( allCredentials = set(
Credential(hex=c.attrib["rawCardNumber"]) for c in door.get_credentials() Credential(hex=c.attrib["rawCardNumber"]) for c in door.get_credentials()
) )
@ -231,9 +197,7 @@ def update_door(door, members):
if member.membershipWorksID not in cardholders: if member.membershipWorksID not in cardholders:
print("- Adding Member {member.forename} {member.surname}:") print("- Adding Member {member.forename} {member.surname}:")
print(f" - {member.attribs()}") print(f" - {member.attribs()}")
resp = door.doXMLRequest( resp = door.add_cardholder(member.attribs)
ROOT(E.Cardholders({"action": "AD"}, E.Cardholder(member.attribs())))
)
member.cardholderID = resp.find("{*}Cardholders/{*}Cardholder").attrib[ member.cardholderID = resp.find("{*}Cardholders/{*}Cardholder").attrib[
"cardholderID" "cardholderID"
] ]
@ -252,14 +216,7 @@ def update_door(door, members):
print(f"- Updating profile for {member.forename} {member.surname}") print(f"- Updating profile for {member.forename} {member.surname}")
print(f" - Old: {ch.attribs()}") print(f" - Old: {ch.attribs()}")
print(f" - New: {member.attribs()}") print(f" - New: {member.attribs()}")
door.doXMLRequest( door.update_cardholder(member.cardholderID, member.attribs)
ROOT(
E.Cardholders(
{"action": "UD", "cardholderID": member.cardholderID},
E.CardHolder(member.attribs()),
)
)
)
if member.credentials != ch.credentials: if member.credentials != ch.credentials:
print(f"- Updating card for {member.forename} {member.surname}") print(f"- Updating card for {member.forename} {member.surname}")
@ -277,18 +234,7 @@ def update_door(door, members):
# cards removed, and won't be reassigned to someone else # cards removed, and won't be reassigned to someone else
for card in (oldCards - newCards) - allNewCards: for card in (oldCards - newCards) - allNewCards:
door.doXMLRequest( door.assign_credential(card, None)
ROOT(
E.Credentials(
{
"action": "UD",
"rawCardNumber": card.hex,
"isCard": "true",
},
E.Credential({"cardholderID": ""}),
)
)
)
if newCards - oldCards: # cards added if newCards - oldCards: # cards added
for card in newCards & allNewCards: # new card exists in another member for card in newCards & allNewCards: # new card exists in another member
@ -304,28 +250,11 @@ def update_door(door, members):
# card existed in door, and needs to be reassigned # card existed in door, and needs to be reassigned
for card in newCards & allCredentials: for card in newCards & allCredentials:
door.doXMLRequest( door.assign_credential(card, member.cardholderID)
ROOT(
E.Credentials(
{
"action": "UD",
"rawCardNumber": card.hex,
"isCard": "true",
},
E.Credential({"cardholderID": member.cardholderID}),
)
)
)
# cards that never existed, and need to be created # cards that never existed, and need to be created
if newCards - allCredentials: if newCards - allCredentials:
door.doXMLRequest( door.add_credentials(newCards - allCredentials, member.cardholderID)
ROOT(
member.make_credentials(
newCards - allCredentials, cardFormats
)
)
)
if member.schedules != ch.schedules: if member.schedules != ch.schedules:
print( print(
@ -333,7 +262,7 @@ def update_door(door, members):
+ f" {member.forename} {member.surname}:" + f" {member.forename} {member.surname}:"
+ f" {ch.schedules} -> {member.schedules}" + f" {ch.schedules} -> {member.schedules}"
) )
door.doXMLRequest(ROOT(member.make_schedules(schedulesMap))) door.set_cardholder_schedules(member.cardholderID, member.schedules)
# TODO: delete cardholders that are no longer members? # TODO: delete cardholders that are no longer members?

View File

@ -1,11 +1,14 @@
import bitstring import bitstring
from typing import Tuple, Optional
# Reference for H10301 card format: # Reference for H10301 card format:
# https://www.hidglobal.com/sites/default/files/hid-understanding_card_data_formats-wp-en.pdf # https://www.hidglobal.com/sites/default/files/hid-understanding_card_data_formats-wp-en.pdf
class Credential: class Credential:
def __init__(self, code=None, hex=None): def __init__(
self, code: Optional[Tuple[int, int]] = None, hex: Optional[str] = None
) -> None:
if code is None and hex is None: if code is None and hex is None:
raise TypeError("Must set either code or hex for a Credential") raise TypeError("Must set either code or hex for a Credential")
elif code is not None and hex is not None: elif code is not None and hex is not None:
@ -21,21 +24,24 @@ class Credential:
elif hex is not None: elif hex is not None:
self.bits = bitstring.Bits(hex=hex) self.bits = bitstring.Bits(hex=hex)
def __repr__(self): def __repr__(self) -> str:
return f"Credential({self.code})" return f"Credential({self.code})"
def __eq__(self, other): def __eq__(self, other: object) -> bool:
if isinstance(other, Credential):
return self.bits == other.bits return self.bits == other.bits
else:
return False
def __hash__(self): def __hash__(self) -> int:
return self.bits.int return self.bits.int
@property @property
def code(self): def code(self) -> Tuple[int, int]:
facility = self.bits[7:15].uint facility = self.bits[7:15].uint
code = self.bits[15:31].uint code = self.bits[15:31].uint
return (facility, code) return (facility, code)
@property @property
def hex(self): def hex(self) -> str:
return self.bits.hex.upper() return self.bits.hex.upper()

View File

@ -1,11 +1,25 @@
import csv import csv
from datetime import datetime from datetime import datetime
from io import StringIO from io import StringIO
from typing import (
IO,
Callable,
Iterable,
List,
Literal,
Mapping,
Optional,
Tuple,
Union,
)
import requests import requests
import urllib3
from lxml import etree from lxml import etree
from lxml.builder import ElementMaker from lxml.builder import ElementMaker
from requests import Session
from requests.adapters import HTTPAdapter
from .Credential import Credential
E_plain = ElementMaker(nsmap={"hid": "http://www.hidglobal.com/VertX"}) E_plain = ElementMaker(nsmap={"hid": "http://www.hidglobal.com/VertX"})
E = ElementMaker( E = ElementMaker(
@ -22,33 +36,65 @@ fieldnames = "CardNumber,CardFormat,PinRequired,PinCode,ExtendedAccess,ExpiryDat
"," ","
) )
# TODO: where should this live?
# it's fine, ssl certs are for losers anyway class HostNameIgnoringAdapter(HTTPAdapter):
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def init_poolmanager(self, *args, **kwargs) -> None:
super().init_poolmanager(*args, **kwargs, assert_hostname=False)
class RemoteError(Exception): class RemoteError(Exception):
def __init__(self, r): def __init__(self, r: requests.Response) -> None:
super().__init__(f"Door Updating Error: {r.status_code} {r.reason}\n{r.text}") super().__init__(f"Door Updating Error: {r.status_code} {r.reason}\n{r.text}")
class DoorController: class DoorController:
def __init__(self, ip, username, password, name="", access=""): def __init__(
self,
ip: str,
username: str,
password: str,
name: str = "",
access: str = "",
cert: Optional[str] = None,
) -> None:
self.ip = ip self.ip = ip
self.username = username self.username = username
self.password = password self.password = password
self.name = name self.name = name
self.access = access self.access = access
self.session = Session()
if cert is not None:
self.session.mount("https://", HostNameIgnoringAdapter())
self.session.verify = cert
def doImport(self, params=None, files=None): self._cardFormats: Optional[Mapping[str, str]] = None
self._scheduleMap: Optional[Mapping[str, str]] = None
# lazy evaluated, hopefully won't change for the lifetime of this object
@property
def cardFormats(self) -> Mapping[str, str]:
if not self._cardFormats:
self._cardFormats = self.get_cardFormats()
return self._cardFormats
@property
def scheduleMap(self) -> Mapping[str, str]:
if not self._scheduleMap:
self._scheduleMap = self.get_scheduleMap()
return self._scheduleMap
def doImport(
self,
params: Optional[Mapping[str, str]] = None,
files: Optional[Mapping[str, Tuple[str, Union[IO[str], str], str]]] = None,
) -> None:
"""Send a request to the door control import script""" """Send a request to the door control import script"""
r = requests.post( r = self.session.post(
"https://" + self.ip + "/cgi-bin/import.cgi", "https://" + self.ip + "/cgi-bin/import.cgi",
params=params, params=params,
files=files, files=files,
auth=requests.auth.HTTPDigestAuth(self.username, self.password), auth=requests.auth.HTTPDigestAuth(self.username, self.password),
timeout=60, timeout=60,
verify=False,
) # ignore insecure SSL ) # ignore insecure SSL
xml = etree.XML(r.content) xml = etree.XML(r.content)
if ( if (
@ -57,7 +103,7 @@ class DoorController:
): ):
raise RemoteError(r) raise RemoteError(r)
def doCSVImport(self, csv): def doCSVImport(self, csv: Union[IO[str], str]) -> None:
"""Do the CSV import procedure on a door control""" """Do the CSV import procedure on a door control"""
self.doImport({"task": "importInit"}) self.doImport({"task": "importInit"})
self.doImport( self.doImport(
@ -66,22 +112,29 @@ class DoorController:
) )
self.doImport({"task": "importDone"}) self.doImport({"task": "importDone"})
def doXMLRequest(self, xml, prefix=b'<?xml version="1.0" encoding="UTF-8"?>'): def doXMLRequest(
self,
xml: Union[etree.Element, bytes],
prefix: bytes = b'<?xml version="1.0" encoding="UTF-8"?>',
) -> etree.XML:
if not isinstance(xml, bytes): if not isinstance(xml, bytes):
xml = etree.tostring(xml) xml = etree.tostring(xml)
r = requests.get( r = self.session.get(
"https://" + self.ip + "/cgi-bin/vertx_xml.cgi", "https://" + self.ip + "/cgi-bin/vertx_xml.cgi",
params={"XML": prefix + xml}, params={"XML": prefix + xml},
auth=requests.auth.HTTPDigestAuth(self.username, self.password), auth=requests.auth.HTTPDigestAuth(self.username, self.password),
verify=False,
) )
resp_xml = etree.XML(r.content)
if r.status_code != 200:
raise RemoteError(r)
# probably meed to be more sane about this # probably meed to be more sane about this
if r.status_code != 200 or len(resp_xml.findall("{*}Error")) > 0: resp_xml = etree.XML(r.content)
if len(resp_xml.findall("{*}Error")) > 0:
raise RemoteError(r) raise RemoteError(r)
return resp_xml return resp_xml
def get_scheduleMap(self): def get_scheduleMap(self) -> Mapping[str, str]:
schedules = self.doXMLRequest( schedules = self.doXMLRequest(
ROOT(E.Schedules({"action": "LR", "recordOffset": "0", "recordCount": "8"})) ROOT(E.Schedules({"action": "LR", "recordOffset": "0", "recordCount": "8"}))
) )
@ -89,7 +142,7 @@ class DoorController:
fmt.attrib["scheduleName"]: fmt.attrib["scheduleID"] for fmt in schedules[0] fmt.attrib["scheduleName"]: fmt.attrib["scheduleID"] for fmt in schedules[0]
} }
def get_schedules(self): def get_schedules(self) -> etree.Element:
# TODO: might be able to do in one request # TODO: might be able to do in one request
schedules = self.doXMLRequest(ROOT(E.Schedules({"action": "LR"}))) schedules = self.doXMLRequest(ROOT(E.Schedules({"action": "LR"})))
etree.dump(schedules) etree.dump(schedules)
@ -106,7 +159,7 @@ class DoorController:
) )
return ROOT(E_corp.Schedules({"action": "AD"}, *[s[0] for s in data])) return ROOT(E_corp.Schedules({"action": "AD"}, *[s[0] for s in data]))
def set_schedules(self, schedules): def set_schedules(self, schedules: etree.Element) -> None:
# clear all people # clear all people
outString = StringIO() outString = StringIO()
writer = csv.DictWriter(outString, fieldnames) writer = csv.DictWriter(outString, fieldnames)
@ -131,7 +184,27 @@ class DoorController:
# load new schedules # load new schedules
self.doXMLRequest(schedules) self.doXMLRequest(schedules)
def get_cardFormats(self): def set_cardholder_schedules(
self, cardholderID: str, schedules: Iterable[str]
) -> etree.XML:
roles = [
E.Role(
{
"roleID": cardholderID,
"scheduleID": self.scheduleMap[schedule],
"resourceID": "0",
}
)
for schedule in schedules
]
roleSet = E.RoleSet(
{"action": "UD", "roleSetID": cardholderID}, E.Roles(*roles)
)
return self.doXMLRequest(ROOT(roleSet))
def get_cardFormats(self) -> Mapping[str, str]:
cardFormats = self.doXMLRequest( cardFormats = self.doXMLRequest(
ROOT(E.CardFormats({"action": "LR", "responseFormat": "expanded"})) ROOT(E.CardFormats({"action": "LR", "responseFormat": "expanded"}))
) )
@ -141,7 +214,9 @@ class DoorController:
for fmt in cardFormats[0].findall("{*}CardFormat[{*}FixedField]") for fmt in cardFormats[0].findall("{*}CardFormat[{*}FixedField]")
} }
def set_cardFormat(self, formatName, templateID, facilityCode): def set_cardFormat(
self, formatName: str, templateID: int, facilityCode: int
) -> etree.XML:
# TODO: add ability to delete formats # TODO: add ability to delete formats
# delete example: <hid:CardFormats action="DD" formatID="7-1-244"/> # delete example: <hid:CardFormats action="DD" formatID="7-1-244"/>
@ -156,8 +231,14 @@ class DoorController:
) )
return self.doXMLRequest(el) return self.doXMLRequest(el)
def get_records(self, req, count, params={}, stopFunction=None): def get_records(
result = [] self,
req: ElementMaker,
count: int,
params: Mapping[str, str] = {},
stopFunction: Optional[Callable[[List[etree.Element]], bool]] = None,
) -> List[etree.Element]:
result: List[etree.Element] = []
recordCount = 0 recordCount = 0
moreRecords = True moreRecords = True
@ -189,17 +270,76 @@ class DoorController:
return result return result
def get_cardholders(self): def get_cardholders(self) -> List[etree.Element]:
return self.get_records(E.Cardholders, 1000, {"responseFormat": "expanded"}) return self.get_records(E.Cardholders, 1000, {"responseFormat": "expanded"})
def get_credentials(self): def add_cardholder(self, attribs: Mapping[str, str]) -> etree.XML:
return self.doXMLRequest(
ROOT(E.Cardholders({"action": "AD"}, E.Cardholder(attribs)))
)
def update_cardholder(
self, cardholderID: str, attribs: Mapping[str, str]
) -> etree.XML:
return self.doXMLRequest(
ROOT(
E.Cardholders(
{"action": "UD", "cardholderID": cardholderID},
E.CardHolder(attribs),
)
)
)
def get_credentials(self) -> List[etree.Element]:
return self.get_records(E.Credentials, 1000) return self.get_records(E.Credentials, 1000)
def get_events(self, threshold): def add_credentials(
def event_newer_than_threshold(event): self, credentials: Iterable[Credential], cardholderID: Optional[str] = None
) -> etree.XML:
"""Create new Credentials. If a cardholderID is provided, assign the
new credentials to that cardholder"""
creds = [
E.Credential(
{
"formatName": str(credential.code[0]),
"cardNumber": str(credential.code[1]),
"formatID": self.cardFormats[str(credential.code[0])],
"isCard": "true",
**({"cardholderID": cardholderID} if cardholderID else {}),
}
)
for credential in credentials
]
return self.doXMLRequest(ROOT(E.Credentials({"action": "AD"}, *creds)))
def assign_credential(
self, credential: Credential, cardholderID: Optional[str] = None
) -> etree.XML:
# empty string removes assignment
if cardholderID is None:
cardholderID = ""
return self.doXMLRequest(
ROOT(
E.Credentials(
{
"action": "UD",
"rawCardNumber": credential.hex,
"isCard": "true",
},
E.Credential({"cardholderID": cardholderID}),
)
)
)
def get_events(self, threshold: datetime) -> List[etree.Element]:
def event_newer_than_threshold(event: etree.Element) -> bool:
return datetime.fromisoformat(event.attrib["timestamp"]) > threshold return datetime.fromisoformat(event.attrib["timestamp"]) > threshold
def last_event_newer_than_threshold(events): def last_event_newer_than_threshold(
events: List[etree.Element],
) -> etree.Element:
return (not events) or event_newer_than_threshold(events[-1]) return (not events) or event_newer_than_threshold(events[-1])
return [ return [
@ -210,13 +350,13 @@ class DoorController:
if event_newer_than_threshold(event) if event_newer_than_threshold(event)
] ]
def get_lock(self): def get_lock(self) -> Union[Literal["locked"], Literal["unlocked"]]:
el = ROOT(E.Doors({"action": "LR", "responseFormat": "status"})) el = ROOT(E.Doors({"action": "LR", "responseFormat": "status"}))
xml = self.doXMLRequest(el) xml = self.doXMLRequest(el)
relayState = xml.find("./{*}Doors/{*}Door").attrib["relayState"] relayState = xml.find("./{*}Doors/{*}Door").attrib["relayState"]
return "unlocked" if relayState == "set" else "locked" return "unlocked" if relayState == "set" else "locked"
def set_lock(self, lock=True): def set_lock(self, lock: bool = True) -> etree.XML:
el = ROOT( el = ROOT(
E.Doors({"action": "CM", "command": "lockDoor" if lock else "unlockDoor"}) E.Doors({"action": "CM", "command": "lockDoor" if lock else "unlockDoor"})
) )

View File

View File

@ -0,0 +1,13 @@
import pytest
from ..Credential import Credential
def test_code_to_hex() -> None:
cred = Credential(code=(123, 45678))
assert cred.hex == "02F764DD"
def test_hex_to_code() -> None:
cred = Credential(hex="02F764DD")
assert cred.code == (123, 45678)

View File

View File

@ -0,0 +1,688 @@
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"nam": {
"type": "string"
},
"adr": {
"type": "object",
"properties": {
"zip": {
"type": "string"
},
"cit": {
"type": "string"
},
"sta": {
"type": "string"
},
"con": {
"type": "string"
},
"ad1": {
"type": "string"
},
"loc": {
"type": "array",
"items": {
"type": "number"
}
}
},
"required": [
"ad1",
"cit",
"con",
"loc",
"sta",
"zip"
]
},
"org": {
"type": "integer"
},
"cur": {
"type": "string"
},
"ctc": {
"type": "string"
},
"atg": {
"type": "string"
},
"uid": {
"type": "string"
},
"typ": {
"type": "string"
},
"eml": {
"type": "string"
},
"phn": {
"type": "string"
},
"end": {
"type": "integer"
},
"tpl": {
"type": "object",
"properties": {
"anm": {
"type": "array",
"items": {
"type": "object",
"properties": {
"lbl": {
"type": "string"
},
"box": {
"type": "array",
"items": {
"type": "object",
"properties": {
"dat": {
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "object",
"properties": {
"_id": {
"type": "string"
},
"typ": {
"type": "integer"
},
"plh": {
"type": "string"
},
"req": {
"type": "integer"
},
"rxp": {
"type": "string"
},
"err": {
"type": "string"
},
"lbl": {
"type": "string"
},
"dtl": {
"type": "string"
}
},
"required": [
"typ"
]
}
}
]
},
"ttl": {
"type": "string"
},
"dtl": {
"type": "string"
}
},
"required": [
"dat"
]
}
},
"dir": {
"type": "integer"
}
},
"required": [
"box",
"lbl"
]
}
},
"acc": {
"type": "array",
"items": {
"type": "object",
"properties": {
"lbl": {
"type": "string"
},
"box": {
"type": "array",
"items": {
"type": "object",
"properties": {
"dat": {
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "object",
"properties": {
"_id": {
"type": "string"
},
"typ": {
"type": "integer"
},
"plh": {
"type": "string"
},
"req": {
"type": "integer"
},
"rxp": {
"type": "string"
},
"err": {
"type": "string"
},
"lbl": {
"type": "string"
}
},
"required": [
"_id",
"lbl",
"typ"
]
}
}
]
},
"ttl": {
"type": "string"
}
},
"required": [
"dat"
]
}
}
},
"required": [
"box",
"lbl"
]
}
},
"adm": {
"type": "array",
"items": {
"type": "object",
"properties": {
"lbl": {
"type": "string"
},
"box": {
"type": "array",
"items": {
"type": "object",
"properties": {
"ttl": {
"type": "string"
},
"dat": {
"anyOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "object",
"properties": {
"_id": {
"type": "string"
},
"typ": {
"type": "integer"
},
"plh": {
"type": "string"
},
"req": {
"type": "integer"
},
"rxp": {
"type": "string"
},
"err": {
"type": "string"
},
"lbl": {
"type": "string"
},
"def": {
"type": "string"
}
},
"required": [
"_id",
"typ"
]
}
}
]
},
"dtl": {
"type": "string"
}
},
"required": [
"dat"
]
}
}
},
"required": [
"box",
"lbl"
]
}
},
"dir": {
"type": "array",
"items": {
"type": "object",
"properties": {
"lbl": {
"type": "string"
},
"box": {
"type": "array",
"items": {
"type": "object",
"properties": {
"htm": {
"type": "string"
},
"dat": {
"type": "string"
}
}
}
}
},
"required": [
"box",
"lbl"
]
}
},
"crd": {
"type": "object",
"properties": {
"typ": {
"type": "integer"
},
"dtl": {
"type": "string"
}
},
"required": [
"dtl",
"typ"
]
},
"ylp": {
"type": "array",
"items": {
"type": "object",
"properties": {
"dat": {
"type": "string"
},
"ttl": {
"type": "string"
}
},
"required": [
"dat"
]
}
},
"evt": {
"type": "array",
"items": {
"type": "object",
"properties": {
"_id": {
"type": "string"
},
"typ": {
"type": "integer"
},
"req": {
"type": "integer"
},
"lbl": {
"type": "string"
},
"rxp": {
"type": "string"
},
"err": {
"type": "string"
}
},
"required": [
"_id",
"lbl",
"req",
"typ"
]
}
}
},
"required": [
"acc",
"adm",
"anm",
"crd",
"dir",
"evt",
"ylp"
]
},
"evg": {
"type": "array",
"items": {
"type": "object",
"properties": {
"ttl": {
"type": "string"
},
"cal": {
"type": "integer"
},
"opt": {
"type": "integer"
}
},
"required": [
"cal",
"opt",
"ttl"
]
}
},
"dcc": {
"type": "array",
"items": {
"type": "object",
"properties": {
"lbl": {
"type": "string"
},
"amt": {
"type": "integer"
},
"typ": {
"type": "integer"
},
"_id": {
"type": "string"
},
"cnt": {
"type": "integer"
},
"cap": {
"type": "integer"
}
},
"required": [
"_id",
"amt",
"lbl",
"typ"
]
}
},
"dek": {
"type": "array",
"items": {
"type": "object",
"properties": {
"did": {
"type": "string"
},
"lbl": {
"type": "string"
},
"dir": {
"type": "integer"
},
"aon": {
"type": "integer"
},
"typ": {
"type": "integer"
},
"dek": {
"type": "integer"
},
"pub": {
"type": "integer"
},
"dtl": {
"type": "string"
},
"mux": {
"type": "integer"
},
"itv": {
"type": "string"
},
"lvl": {
"type": "array",
"items": {
"type": "string"
}
},
"_id": {
"type": "string"
},
"ctc": {
"type": "array",
"items": {
"type": "string"
}
},
"tnm": {
"type": "string"
},
"dnm": {
"type": "string"
},
"cur": {
"type": "string"
},
"_rt": {
"type": "integer"
},
"bil": {
"type": "array",
"items": {
"type": "object",
"properties": {
"bid": {
"type": "string"
},
"ttl": {
"type": "string"
},
"way": {
"type": "integer"
},
"amt": {
"type": "number"
},
"itv": {
"type": "string"
},
"mux": {
"type": "integer"
},
"pub": {
"type": "integer"
},
"dcc": {
"type": "object"
},
"dso": {
"type": "string"
}
},
"required": [
"amt",
"bid",
"itv",
"mux",
"ttl",
"way"
]
}
},
"cat": {
"type": "integer"
},
"put": {
"type": "array",
"items": {
"type": "string"
}
},
"upg": {
"type": "array",
"items": {
"type": "string"
}
},
"nyn": {
"type": "integer"
},
"prp": {
"type": "integer"
},
"psm": {
"type": "integer"
},
"exp": {
"type": "string"
},
"enr": {
"type": "array",
"items": {
"type": "object",
"properties": {
"day": {
"type": "integer"
},
"ttl": {
"type": "string"
},
"dtl": {
"type": "string"
}
},
"required": [
"day",
"dtl",
"ttl"
]
}
},
"wen": {
"type": "string"
},
"pur": {
"type": "array",
"items": {
"type": "string"
}
},
"puk": {
"type": "array",
"items": {
"type": "string"
}
},
"puu": {
"type": "array",
"items": {
"type": "string"
}
},
"pus": {
"type": "array",
"items": {
"type": "string"
}
},
"pup": {
"type": "array",
"items": {
"type": "string"
}
},
"puq": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"_id",
"dek",
"did",
"dir",
"lbl",
"pub",
"typ"
]
}
},
"SF": {
"type": "string"
},
"_fd": {
"type": "integer"
},
"_ts": {
"type": "integer"
},
"_re": {
"type": "integer"
}
},
"required": [
"SF",
"_fd",
"_ts",
"adr",
"atg",
"cur",
"dcc",
"dek",
"eml",
"end",
"evg",
"nam",
"org",
"tpl",
"typ",
"uid"
]
}

View File

@ -0,0 +1,179 @@
import datetime
from unittest.mock import patch
import pytest
import responses
from ..MembershipWorks import BASE_URL, MembershipWorks, MembershipWorksRemoteError
@responses.activate
def test_login():
membershipworks = MembershipWorks()
responses.add(
responses.POST, BASE_URL + "usr", json={"SF": "test1", "org": 10000}, status=200
)
membershipworks.login("test1@example.com", "test2")
assert membershipworks.auth_token == "test1"
assert membershipworks.org_num == 10000
@responses.activate
def test_login_fail():
membershipworks = MembershipWorks()
responses.add(
responses.POST, BASE_URL + "usr", json={"error": "Login/password not accepted"},
)
with pytest.raises(MembershipWorksRemoteError) as e:
membershipworks.login("test1@example.com", "test2")
assert (
e.exception.args[0]
== "Error when attempting login: 200 OK\n"
+ '{"error":"Login/password not accepted"}'
)
@pytest.fixture
def membershipworks():
mw = MembershipWorks()
with responses.RequestsMock() as rsps:
rsps.add(
responses.POST,
BASE_URL + "usr",
json={
"SF": "test1",
"org": 10000,
"dek": [{"_id": "12345", "lbl": "test", "dek": 1}],
},
)
mw.login("test1@example.com", "test2")
return mw
@responses.activate
def test_get_member_ids(membershipworks: MembershipWorks):
responses.add(
responses.GET,
BASE_URL + "ylp",
json={
"typ": "a",
"usr": [{"uid": "asdf", "nam": "test", "rnk": 12.345}],
"_re": 631,
},
)
ids = membershipworks.get_member_ids(["test"])
assert ids == ["asdf"]
@responses.activate
@patch("memberPlumbing.MembershipWorks.MembershipWorks.get_member_ids")
def test_get_members(mockIDs, membershipworks: MembershipWorks):
mockIDs.return_value = ["asdf"]
responses.add(
responses.POST,
BASE_URL + "csv",
body=""""Account Name","First Name","Last Name","Account ID","Parent Account ID"
"bob whatever", "bob", "whatever", "aaaaaaaaaaaaaaaaaaaaaaaa", ""
"george whatever", "george", "whatever", "bbbbbbbbbbbbbbbbbbbbbbbb", ""
""",
)
members = membershipworks.get_members(["test"], "_id,nam")
assert members == [
{
"Account Name": "bob whatever",
"First Name": ' "bob"',
"Last Name": ' "whatever"',
"Account ID": ' "aaaaaaaaaaaaaaaaaaaaaaaa"',
"Parent Account ID": ' ""',
},
{
"Account Name": "george whatever",
"First Name": ' "george"',
"Last Name": ' "whatever"',
"Account ID": ' "bbbbbbbbbbbbbbbbbbbbbbbb"',
"Parent Account ID": ' ""',
},
]
@responses.activate
def test_get_transactions_csv(membershipworks: MembershipWorks):
responses.add(
responses.GET,
BASE_URL + "csv",
body=""""Date","Name","Contact Person","Full Address","Street","City","State/Province","Postal Code","Country","Phone","Email","Membership Sub-Total","Event Sub-Total","Donation Sub-Total","Cart Sub-Total","Other Sub-Total","Handling","Total Tax","Transaction Total","Transaction Fee","Total Payment Due","Transaction Type","For","Items","Payment ID","Account ID","Discount Code","Note"
"Jan 01, 2020","whatever","","PO Box 0, fakeplace NH, US","PO Box 0","fakeplace","NH","","US","000-000-0000","example@example.com",1,0,0,0,0,0,0,1,0.32,0,"Membership","","CMS Membership on hold - Membership on hold","ch_aaaaaaaaaaaaaaaaaaaaaaaa","aaaaaaaaaaaaaaaaaaaaaaaa","",""
""",
)
transactions = membershipworks.get_transactions(
datetime.datetime(2000, 1, 1), datetime.datetime(2021, 1, 1)
)
assert transactions == [
{
"Date": "Jan 01, 2020",
"Name": "whatever",
"Contact Person": "",
"Full Address": "PO Box 0, fakeplace NH, US",
"Street": "PO Box 0",
"City": "fakeplace",
"State/Province": "NH",
"Postal Code": "",
"Country": "US",
"Phone": "000-000-0000",
"Email": "example@example.com",
"Membership Sub-Total": "1",
"Event Sub-Total": "0",
"Donation Sub-Total": "0",
"Cart Sub-Total": "0",
"Other Sub-Total": "0",
"Handling": "0",
"Total Tax": "0",
"Transaction Total": "1",
"Transaction Fee": "0.32",
"Total Payment Due": "0",
"Transaction Type": "Membership",
"For": "",
"Items": "CMS Membership on hold - Membership on hold",
"Payment ID": "ch_aaaaaaaaaaaaaaaaaaaaaaaa",
"Account ID": "aaaaaaaaaaaaaaaaaaaaaaaa",
"Discount Code": "",
"Note": "",
}
]
@responses.activate
def test_get_transactions_json(membershipworks: MembershipWorks):
data_json = [
{
"cur": "usd",
"sid": "ch_aaaaaaaaaaaaaaaaaaaaaaaa",
"typ": 12,
"_dp": 1585100000,
"sum": 1,
"fee": 0.32,
"uid": "aaaaaaaaaaaaaaaaaaaaaaaa",
"nam": "whatever",
}
]
responses.add(responses.GET, BASE_URL + "csv", json=data_json)
transactions = membershipworks.get_transactions(
datetime.datetime(2000, 1, 1), datetime.datetime(2021, 1, 1), json=True
)
assert "&txl=" in responses.calls[0].request.url # json flag
assert transactions == data_json

View File

@ -0,0 +1,31 @@
import json
import os
import jsonschema
import requests
import pytest
from ..common import MEMBERSHIPWORKS_PASSWORD, MEMBERSHIPWORKS_USERNAME
from ..MembershipWorks import BASE_URL
schemas_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "json-schemas")
def test_login():
r = requests.post(
BASE_URL + "usr",
data={
"_st": "all",
"eml": MEMBERSHIPWORKS_USERNAME,
"org": "10000",
"pwd": MEMBERSHIPWORKS_PASSWORD,
},
)
with open(os.path.join(schemas_dir, "usr.schema")) as f:
schema = json.load(f)
usr = r.json()
jsonschema.validate(usr, schema)

View File

@ -1,74 +1,88 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import asyncio
import random import random
import re import re
import string import string
import subprocess
from udm_rest_client.udm import UDM
from udm_rest_client.exceptions import NoObject
from .config import Config from .config import Config
LDAP_BASE = "cn=users,dc=sawtooth,dc=claremontmakerspace,dc=org" USER_BASE = "cn=users,dc=sawtooth,dc=claremontmakerspace,dc=org"
GROUP_BASE = "cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org" GROUP_BASE = "cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org"
GROUPS_REGEX = "|".join(["Certified: .*", "Access .*\?", "CMS .*", "Volunteer: .*"]) GROUPS_REGEX = "|".join(["Certified: .*", "Access .*\?", "CMS .*", "Volunteer: .*"])
RAND_PW_LEN = 20 RAND_PW_LEN = 20
def makeGroups(members): # From an API error message:
# A group name must start and end with a letter, number or underscore.
# In between additionally spaces, dashes and dots are allowed.
def sanitize_group_name(name):
sanitized_body = re.sub(r"[^0-9A-Za-z_ -.]", ".", name)
sanitized_start_end = re.sub("^[^0-9A-Za-z_]|[^0-9A-Za-z_]$", "_", sanitized_body)
return "MW_" + sanitized_start_end
# From an API error message: "Username must only contain numbers, letters and dots!"
def sanitize_user_name(name):
return re.sub(r"[^0-9a-z.]", ".", name.lower())
async def make_groups(group_mod, members):
existing_group_names = [g.props.name async for g in group_mod.search()]
groups = [ groups = [
key.replace(":", ".").replace("?", "") sanitize_group_name(group_name)
for key in members[0].keys() for group_name in members[0].keys()
if re.match(GROUPS_REGEX, key) is not None if re.match(GROUPS_REGEX, group_name) is not None
] ]
for group in groups: for group_name in groups:
subprocess.call( if group_name not in existing_group_names:
[ group = await group_mod.new()
"udm", group.props.name = group_name
"groups/group", await group.save()
"create",
"--position",
GROUP_BASE,
"--set",
"name=" + group,
]
)
def makeSets(props): async def _main():
return sum([["--set", key + "=" + value] for key, value in props.items()], [])
def makeAppendGroups(member):
groups = [
key.replace(":", ".").replace("?", "")
for key, value in member.items()
if re.match(GROUPS_REGEX, key) is not None and value != ""
]
return sum(
[["--append", "groups=cn=" + group + "," + GROUP_BASE] for group in groups], []
)
def main():
config = Config() config = Config()
members = config.membershipworks.get_members( members = config.membershipworks.get_members(
["Members", "CMS Staff"], "lvl,phn,eml,lbl,nam,end,_id" ["Members", "CMS Staff"], "lvl,phn,eml,lbl,nam,end,_id"
) )
makeGroups(members)
# TODO: security!
async with UDM(**config.UCS) as udm:
user_mod = udm.get("users/user")
group_mod = udm.get("groups/group")
await make_groups(group_mod, members)
for member in members: for member in members:
randomPass = "".join( username = sanitize_user_name(member["Account Name"])
try: # try to get an existing user to update
user = await user_mod.get(f"uid={username},{USER_BASE}")
except NoObject: # create a new user
# TODO: search by employeeID and rename users when needed
user = await user_mod.new()
# set a random password and ensure it is changed at next login
user.props.password = "".join(
random.choice(string.ascii_letters + string.digits) random.choice(string.ascii_letters + string.digits)
for x in range(0, RAND_PW_LEN) for x in range(0, RAND_PW_LEN)
) )
username = member["Account Name"].lower().replace(" ", ".") user.props.pwdChangeNextLogin = True
props = { user.props.update(
{
"title": "", # Title "title": "", # Title
"firstname": member["First Name"], "firstname": member["First Name"],
"lastname": member["Last Name"], # (c) "lastname": member["Last Name"], # (c)
"username": username, # (cmr) "username": username, # (cmr)
"description": "", # Description "description": "", # Description
"password": randomPass, # (c) Password # "password": "", # (c) Password
# "mailPrimaryAddress": member["Email"], # Primary e-mail address # "mailPrimaryAddress": member["Email"], # Primary e-mail address
# "displayName": "", # Display name # "displayName": "", # Display name
# "birthday": "", # Birthdate # "birthday": "", # Birthdate
@ -78,29 +92,31 @@ def main():
"homedrive": "H:", # Windows home drive "homedrive": "H:", # Windows home drive
"sambahome": "\\\\ucs\\" + username, # Windows home path "sambahome": "\\\\ucs\\" + username, # Windows home path
"profilepath": "%LOGONSERVER%\\%USERNAME%\\windows-profiles\\default", # Windows profile directory "profilepath": "%LOGONSERVER%\\%USERNAME%\\windows-profiles\\default", # Windows profile directory
"disabled": "1" if member["Account on Hold"] != "" else "0", "disabled": member["Account on Hold"] != "",
# "userexpiry": member["Renewal Date"], # "userexpiry": member["Renewal Date"],
"pwdChangeNextLogin": "1", # User has to change password on next login # "pwdChangeNextLogin": "1", # User has to change password on next login
# "sambaLogonHours": "", # Permitted times for Windows logins # "sambaLogonHours": "", # Permitted times for Windows logins
"e-mail": member["Email"], # ([]) E-mail address "e-mail": [member["Email"]], # ([]) E-mail address
"phone": member["Phone"], # Telephone number "phone": [member["Phone"]], # Telephone number
# "PasswordRecoveryMobile": member["Phone"], # Mobile phone number # "PasswordRecoveryMobile": member["Phone"], # Mobile phone number
"PasswordRecoveryEmail": member["Email"], "PasswordRecoveryEmail": member["Email"],
} }
subprocess.call(
["udm", "users/user", "create", "--position", LDAP_BASE] + makeSets(props)
) )
# remove props we don't want to reset new_groups = [
props.pop("password") "cn=" + sanitize_group_name(group) + "," + GROUP_BASE
props.pop("pwdChangeNextLogin") for group, value in member.items()
if re.match(GROUPS_REGEX, group) is not None and value != ""
]
# groups not from this script
other_old_groups = [g for g in user.props.groups if g[3:].startswith("MW_")]
user.props.groups = other_old_groups + new_groups
subprocess.call( await user.save()
["udm", "users/user", "modify", "--dn", "uid=" + username + "," + LDAP_BASE]
+ makeSets(props)
+ makeAppendGroups(member) def main():
) asyncio.run(_main())
if __name__ == "__main__": if __name__ == "__main__":

619
poetry.lock generated
View File

@ -1,13 +1,56 @@
[[package]]
category = "main"
description = "Async http client/server framework (asyncio)"
name = "aiohttp"
optional = false
python-versions = ">=3.5.3"
version = "3.6.2"
[package.dependencies]
async-timeout = ">=3.0,<4.0"
attrs = ">=17.3.0"
chardet = ">=2.0,<4.0"
multidict = ">=4.5,<5.0"
yarl = ">=1.0,<2.0"
[package.extras]
speedups = ["aiodns", "brotlipy", "cchardet"]
[[package]] [[package]]
category = "dev" category = "dev"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
name = "appdirs" name = "appdirs"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.4.3" version = "1.4.4"
[[package]]
category = "main"
description = "Python decorator for async properties."
name = "async-property"
optional = false
python-versions = "*"
version = "0.2.1"
[[package]]
category = "main"
description = "Timeout context manager for asyncio programs"
name = "async-timeout"
optional = false
python-versions = ">=3.5.3"
version = "3.0.1"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Atomic file writes."
marker = "sys_platform == \"win32\""
name = "atomicwrites"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.4.0"
[[package]]
category = "main"
description = "Classes Without Boilerplate" description = "Classes Without Boilerplate"
name = "attrs" name = "attrs"
optional = false optional = false
@ -26,7 +69,7 @@ description = "Simple construction, analysis and modification of binary data."
name = "bitstring" name = "bitstring"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "3.1.6" version = "3.1.7"
[[package]] [[package]]
category = "dev" category = "dev"
@ -54,7 +97,7 @@ description = "Python package for providing Mozilla's CA Bundle."
name = "certifi" name = "certifi"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2020.4.5.1" version = "2020.6.20"
[[package]] [[package]]
category = "main" category = "main"
@ -65,12 +108,58 @@ python-versions = "*"
version = "3.0.4" version = "3.0.4"
[[package]] [[package]]
category = "dev" category = "main"
description = "Composable command line interface toolkit" description = "Composable command line interface toolkit"
name = "click" name = "click"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "7.1.1" version = "7.1.2"
[[package]]
category = "dev"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\""
name = "colorama"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.4.3"
[[package]]
category = "dev"
description = "Code coverage measurement for Python"
name = "coverage"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "5.2"
[package.dependencies]
[package.dependencies.toml]
optional = true
version = "*"
[package.extras]
toml = ["toml"]
[[package]]
category = "main"
description = "A Python library for the Docker Engine API."
name = "docker"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "4.2.2"
[package.dependencies]
requests = ">=2.14.2,<2.18.0 || >2.18.0"
six = ">=1.4.0"
websocket-client = ">=0.32.0"
[package.dependencies.pypiwin32]
python = ">=3.6"
version = "223"
[package.extras]
ssh = ["paramiko (>=2.4.2)"]
tls = ["pyOpenSSL (>=17.5.0)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"]
[[package]] [[package]]
category = "main" category = "main"
@ -78,7 +167,23 @@ description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna" name = "idna"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.9" version = "2.10"
[[package]]
category = "dev"
description = "Read metadata from Python packages"
marker = "python_version < \"3.8\""
name = "importlib-metadata"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "1.7.0"
[package.dependencies]
zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "rst.linker"]
testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
[[package]] [[package]]
category = "dev" category = "dev"
@ -100,7 +205,7 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li
name = "lxml" name = "lxml"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
version = "4.5.0" version = "4.5.2"
[package.extras] [package.extras]
cssselect = ["cssselect (>=0.7)"] cssselect = ["cssselect (>=0.7)"]
@ -108,6 +213,22 @@ html5 = ["html5lib"]
htmlsoup = ["beautifulsoup4"] htmlsoup = ["beautifulsoup4"]
source = ["Cython (>=0.29.7)"] source = ["Cython (>=0.29.7)"]
[[package]]
category = "dev"
description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
optional = false
python-versions = ">=3.5"
version = "8.4.0"
[[package]]
category = "main"
description = "multidict implementation"
name = "multidict"
optional = false
python-versions = ">=3.5"
version = "4.7.6"
[[package]] [[package]]
category = "main" category = "main"
description = "Python interface to MySQL" description = "Python interface to MySQL"
@ -116,6 +237,18 @@ optional = false
python-versions = "*" python-versions = "*"
version = "1.4.6" version = "1.4.6"
[[package]]
category = "dev"
description = "Core utilities for Python packages"
name = "packaging"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "20.4"
[package.dependencies]
pyparsing = ">=2.0.2"
six = "*"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Utility library for gitignore style pattern matching of file paths." description = "Utility library for gitignore style pattern matching of file paths."
@ -132,13 +265,92 @@ optional = false
python-versions = "*" python-versions = "*"
version = "3.13.3" version = "3.13.3"
[[package]]
category = "dev"
description = "plugin and hook calling mechanisms for python"
name = "pluggy"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.13.1"
[package.dependencies]
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.extras]
dev = ["pre-commit", "tox"]
[[package]]
category = "dev"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
name = "py"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.9.0"
[[package]]
category = "dev"
description = "Python parsing module"
name = "pyparsing"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.4.7"
[[package]]
category = "main"
description = ""
marker = "sys_platform == \"win32\" and python_version >= \"3.6\""
name = "pypiwin32"
optional = false
python-versions = "*"
version = "223"
[package.dependencies]
pywin32 = ">=223"
[[package]]
category = "dev"
description = "pytest: simple powerful testing with Python"
name = "pytest"
optional = false
python-versions = ">=3.5"
version = "5.4.3"
[package.dependencies]
atomicwrites = ">=1.0"
attrs = ">=17.4.0"
colorama = "*"
more-itertools = ">=4.0.0"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.5.0"
wcwidth = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.extras]
checkqa-mypy = ["mypy (v0.761)"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
category = "main"
description = "Python for Window Extensions"
marker = "sys_platform == \"win32\" and python_version >= \"3.6\""
name = "pywin32"
optional = false
python-versions = "*"
version = "228"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Alternative regular expression module, to replace re." description = "Alternative regular expression module, to replace re."
name = "regex" name = "regex"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2020.4.4" version = "2020.7.14"
[[package]] [[package]]
category = "main" category = "main"
@ -146,7 +358,7 @@ description = "Python HTTP for Humans."
name = "requests" name = "requests"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.23.0" version = "2.24.0"
[package.dependencies] [package.dependencies]
certifi = ">=2017.4.17" certifi = ">=2017.4.17"
@ -184,13 +396,32 @@ optional = false
python-versions = "*" python-versions = "*"
version = "0.2.0" version = "0.2.0"
[[package]]
category = "main"
description = "Python 2 and 3 compatibility utilities"
name = "six"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.15.0"
[[package]]
category = "dev"
description = "tasks runner for python projects"
name = "taskipy"
optional = false
python-versions = ">=3.6,<4.0"
version = "1.2.1"
[package.dependencies]
toml = ">=0.10.0,<0.11.0"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Python Library for Tom's Obvious, Minimal Language" description = "Python Library for Tom's Obvious, Minimal Language"
name = "toml" name = "toml"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.10.0" version = "0.10.1"
[[package]] [[package]]
category = "dev" category = "dev"
@ -200,6 +431,21 @@ optional = false
python-versions = "*" python-versions = "*"
version = "1.4.1" version = "1.4.1"
[[package]]
category = "main"
description = "Python library to interact with the Univention UDM REST API. Implements the simple Python UDM API."
name = "udm-rest-client"
optional = false
python-versions = ">=3.6"
version = "0.4.0"
[package.dependencies]
aiohttp = "*"
async-property = "*"
click = "*"
docker = "*"
requests = "*"
[[package]] [[package]]
category = "main" category = "main"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
@ -213,76 +459,219 @@ brotli = ["brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
[[package]]
category = "dev"
description = "Measures the displayed width of unicode strings in a terminal"
name = "wcwidth"
optional = false
python-versions = "*"
version = "0.2.5"
[[package]]
category = "main"
description = "WebSocket client for Python. hybi13 is supported."
name = "websocket-client"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.57.0"
[package.dependencies]
six = "*"
[[package]]
category = "main"
description = "Yet another URL library"
name = "yarl"
optional = false
python-versions = ">=3.5"
version = "1.4.2"
[package.dependencies]
idna = ">=2.0"
multidict = ">=4.0"
[[package]]
category = "dev"
description = "Backport of pathlib-compatible object wrapper for zip files"
marker = "python_version < \"3.8\""
name = "zipp"
optional = false
python-versions = ">=3.6"
version = "3.1.0"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"]
[metadata] [metadata]
content-hash = "5fe506efe8fd4c42bb9ef248033a7bd074d9f66e5b6a03727aa9cc554ae1d8fb" content-hash = "ccab831ceb4e390d9ffa4d7768046fa520130a2888438f575721116de4db5314"
python-versions = "^3.7" python-versions = "^3.7"
[metadata.files] [metadata.files]
aiohttp = [
{file = "aiohttp-3.6.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e"},
{file = "aiohttp-3.6.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec"},
{file = "aiohttp-3.6.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48"},
{file = "aiohttp-3.6.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59"},
{file = "aiohttp-3.6.2-cp36-cp36m-win32.whl", hash = "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a"},
{file = "aiohttp-3.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17"},
{file = "aiohttp-3.6.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a"},
{file = "aiohttp-3.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd"},
{file = "aiohttp-3.6.2-cp37-cp37m-win32.whl", hash = "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"},
{file = "aiohttp-3.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654"},
{file = "aiohttp-3.6.2-py3-none-any.whl", hash = "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4"},
{file = "aiohttp-3.6.2.tar.gz", hash = "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326"},
]
appdirs = [ appdirs = [
{file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
{file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
async-property = [
{file = "async_property-0.2.1-py2.py3-none-any.whl", hash = "sha256:f1f105009a6216ed9a13031aa13632754ed8a5c2e329fb8f9f2082d83529eacd"},
{file = "async_property-0.2.1.tar.gz", hash = "sha256:53826fd45a67d7d6cca3d7abbc0e8ba951f7c7618c830021fbd3675979b0b67d"},
]
async-timeout = [
{file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"},
{file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"},
]
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
] ]
attrs = [ attrs = [
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
] ]
bitstring = [ bitstring = [
{file = "bitstring-3.1.6-py2-none-any.whl", hash = "sha256:e392819965e7e0246e3cf6a51d5a54e731890ae03ebbfa3cd0e4f74909072096"}, {file = "bitstring-3.1.7.tar.gz", hash = "sha256:fdf3eb72b229d2864fb507f8f42b1b2c57af7ce5fec035972f9566de440a864a"},
{file = "bitstring-3.1.6-py3-none-any.whl", hash = "sha256:7b60b0c300d0d3d0a24ec84abfda4b0eaed3dc56dc90f6cbfe497166c9ad8443"},
{file = "bitstring-3.1.6.tar.gz", hash = "sha256:c97a8e2a136e99b523b27da420736ae5cb68f83519d633794a6a11192f69f8bf"},
] ]
black = [ black = [
{file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
{file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
] ]
certifi = [ certifi = [
{file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"},
{file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"}, {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"},
] ]
chardet = [ chardet = [
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
] ]
click = [ click = [
{file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"}, {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
{file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
]
colorama = [
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
]
coverage = [
{file = "coverage-5.2-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:d9ad0a988ae20face62520785ec3595a5e64f35a21762a57d115dae0b8fb894a"},
{file = "coverage-5.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:4bb385a747e6ae8a65290b3df60d6c8a692a5599dc66c9fa3520e667886f2e10"},
{file = "coverage-5.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9702e2cb1c6dec01fb8e1a64c015817c0800a6eca287552c47a5ee0ebddccf62"},
{file = "coverage-5.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:42fa45a29f1059eda4d3c7b509589cc0343cd6bbf083d6118216830cd1a51613"},
{file = "coverage-5.2-cp27-cp27m-win32.whl", hash = "sha256:41d88736c42f4a22c494c32cc48a05828236e37c991bd9760f8923415e3169e4"},
{file = "coverage-5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:bbb387811f7a18bdc61a2ea3d102be0c7e239b0db9c83be7bfa50f095db5b92a"},
{file = "coverage-5.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:3740b796015b889e46c260ff18b84683fa2e30f0f75a171fb10d2bf9fb91fc70"},
{file = "coverage-5.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ebf2431b2d457ae5217f3a1179533c456f3272ded16f8ed0b32961a6d90e38ee"},
{file = "coverage-5.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:d54d7ea74cc00482a2410d63bf10aa34ebe1c49ac50779652106c867f9986d6b"},
{file = "coverage-5.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:87bdc8135b8ee739840eee19b184804e5d57f518578ffc797f5afa2c3c297913"},
{file = "coverage-5.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ed9a21502e9223f563e071759f769c3d6a2e1ba5328c31e86830368e8d78bc9c"},
{file = "coverage-5.2-cp35-cp35m-win32.whl", hash = "sha256:509294f3e76d3f26b35083973fbc952e01e1727656d979b11182f273f08aa80b"},
{file = "coverage-5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:ca63dae130a2e788f2b249200f01d7fa240f24da0596501d387a50e57aa7075e"},
{file = "coverage-5.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:5c74c5b6045969b07c9fb36b665c9cac84d6c174a809fc1b21bdc06c7836d9a0"},
{file = "coverage-5.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c32aa13cc3fe86b0f744dfe35a7f879ee33ac0a560684fef0f3e1580352b818f"},
{file = "coverage-5.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1e58fca3d9ec1a423f1b7f2aa34af4f733cbfa9020c8fe39ca451b6071237405"},
{file = "coverage-5.2-cp36-cp36m-win32.whl", hash = "sha256:3b2c34690f613525672697910894b60d15800ac7e779fbd0fccf532486c1ba40"},
{file = "coverage-5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a4d511012beb967a39580ba7d2549edf1e6865a33e5fe51e4dce550522b3ac0e"},
{file = "coverage-5.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:32ecee61a43be509b91a526819717d5e5650e009a8d5eda8631a59c721d5f3b6"},
{file = "coverage-5.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6f91b4492c5cde83bfe462f5b2b997cdf96a138f7c58b1140f05de5751623cf1"},
{file = "coverage-5.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bfcc811883699ed49afc58b1ed9f80428a18eb9166422bce3c31a53dba00fd1d"},
{file = "coverage-5.2-cp37-cp37m-win32.whl", hash = "sha256:60a3d36297b65c7f78329b80120f72947140f45b5c7a017ea730f9112b40f2ec"},
{file = "coverage-5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:12eaccd86d9a373aea59869bc9cfa0ab6ba8b1477752110cb4c10d165474f703"},
{file = "coverage-5.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:d82db1b9a92cb5c67661ca6616bdca6ff931deceebb98eecbd328812dab52032"},
{file = "coverage-5.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:214eb2110217f2636a9329bc766507ab71a3a06a8ea30cdeebb47c24dce5972d"},
{file = "coverage-5.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8a3decd12e7934d0254939e2bf434bf04a5890c5bf91a982685021786a08087e"},
{file = "coverage-5.2-cp38-cp38-win32.whl", hash = "sha256:1dcebae667b73fd4aa69237e6afb39abc2f27520f2358590c1b13dd90e32abe7"},
{file = "coverage-5.2-cp38-cp38-win_amd64.whl", hash = "sha256:f50632ef2d749f541ca8e6c07c9928a37f87505ce3a9f20c8446ad310f1aa87b"},
{file = "coverage-5.2-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:7403675df5e27745571aba1c957c7da2dacb537c21e14007ec3a417bf31f7f3d"},
{file = "coverage-5.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0fc4e0d91350d6f43ef6a61f64a48e917637e1dcfcba4b4b7d543c628ef82c2d"},
{file = "coverage-5.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:25fe74b5b2f1b4abb11e103bb7984daca8f8292683957d0738cd692f6a7cc64c"},
{file = "coverage-5.2-cp39-cp39-win32.whl", hash = "sha256:d67599521dff98ec8c34cd9652cbcfe16ed076a2209625fca9dc7419b6370e5c"},
{file = "coverage-5.2-cp39-cp39-win_amd64.whl", hash = "sha256:10f2a618a6e75adf64329f828a6a5b40244c1c50f5ef4ce4109e904e69c71bd2"},
{file = "coverage-5.2.tar.gz", hash = "sha256:1874bdc943654ba46d28f179c1846f5710eda3aeb265ff029e0ac2b52daae404"},
]
docker = [
{file = "docker-4.2.2-py2.py3-none-any.whl", hash = "sha256:03a46400c4080cb6f7aa997f881ddd84fef855499ece219d75fbdb53289c17ab"},
{file = "docker-4.2.2.tar.gz", hash = "sha256:26eebadce7e298f55b76a88c4f8802476c5eaddbdbe38dbc6cce8781c47c9b54"},
] ]
idna = [ idna = [
{file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
{file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
]
importlib-metadata = [
{file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
{file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
] ]
isort = [ isort = [
{file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
{file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"},
] ]
lxml = [ lxml = [
{file = "lxml-4.5.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0701f7965903a1c3f6f09328c1278ac0eee8f56f244e66af79cb224b7ef3801c"}, {file = "lxml-4.5.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:74f48ec98430e06c1fa8949b49ebdd8d27ceb9df8d3d1c92e1fdc2773f003f20"},
{file = "lxml-4.5.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:06d4e0bbb1d62e38ae6118406d7cdb4693a3fa34ee3762238bcb96c9e36a93cd"}, {file = "lxml-4.5.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e70d4e467e243455492f5de463b72151cc400710ac03a0678206a5f27e79ddef"},
{file = "lxml-4.5.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5828c7f3e615f3975d48f40d4fe66e8a7b25f16b5e5705ffe1d22e43fb1f6261"}, {file = "lxml-4.5.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7ad7906e098ccd30d8f7068030a0b16668ab8aa5cda6fcd5146d8d20cbaa71b5"},
{file = "lxml-4.5.0-cp27-cp27m-win32.whl", hash = "sha256:afdb34b715daf814d1abea0317b6d672476b498472f1e5aacbadc34ebbc26e89"}, {file = "lxml-4.5.2-cp27-cp27m-win32.whl", hash = "sha256:92282c83547a9add85ad658143c76a64a8d339028926d7dc1998ca029c88ea6a"},
{file = "lxml-4.5.0-cp27-cp27m-win_amd64.whl", hash = "sha256:585c0869f75577ac7a8ff38d08f7aac9033da2c41c11352ebf86a04652758b7a"}, {file = "lxml-4.5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:05a444b207901a68a6526948c7cc8f9fe6d6f24c70781488e32fd74ff5996e3f"},
{file = "lxml-4.5.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:8a0ebda56ebca1a83eb2d1ac266649b80af8dd4b4a3502b2c1e09ac2f88fe128"}, {file = "lxml-4.5.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:94150231f1e90c9595ccc80d7d2006c61f90a5995db82bccbca7944fd457f0f6"},
{file = "lxml-4.5.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:fe976a0f1ef09b3638778024ab9fb8cde3118f203364212c198f71341c0715ca"}, {file = "lxml-4.5.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bea760a63ce9bba566c23f726d72b3c0250e2fa2569909e2d83cda1534c79443"},
{file = "lxml-4.5.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7bc1b221e7867f2e7ff1933165c0cec7153dce93d0cdba6554b42a8beb687bdb"}, {file = "lxml-4.5.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c3f511a3c58676147c277eff0224c061dd5a6a8e1373572ac817ac6324f1b1e0"},
{file = "lxml-4.5.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d068f55bda3c2c3fcaec24bd083d9e2eede32c583faf084d6e4b9daaea77dde8"}, {file = "lxml-4.5.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:59daa84aef650b11bccd18f99f64bfe44b9f14a08a28259959d33676554065a1"},
{file = "lxml-4.5.0-cp35-cp35m-win32.whl", hash = "sha256:e4aa948eb15018a657702fee0b9db47e908491c64d36b4a90f59a64741516e77"}, {file = "lxml-4.5.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:c9d317efde4bafbc1561509bfa8a23c5cab66c44d49ab5b63ff690f5159b2304"},
{file = "lxml-4.5.0-cp35-cp35m-win_amd64.whl", hash = "sha256:1f2c4ec372bf1c4a2c7e4bb20845e8bcf8050365189d86806bad1e3ae473d081"}, {file = "lxml-4.5.2-cp35-cp35m-win32.whl", hash = "sha256:9dc9006dcc47e00a8a6a029eb035c8f696ad38e40a27d073a003d7d1443f5d88"},
{file = "lxml-4.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:5d467ce9c5d35b3bcc7172c06320dddb275fea6ac2037f72f0a4d7472035cea9"}, {file = "lxml-4.5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:08fc93257dcfe9542c0a6883a25ba4971d78297f63d7a5a26ffa34861ca78730"},
{file = "lxml-4.5.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:95e67224815ef86924fbc2b71a9dbd1f7262384bca4bc4793645794ac4200717"}, {file = "lxml-4.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:121b665b04083a1e85ff1f5243d4a93aa1aaba281bc12ea334d5a187278ceaf1"},
{file = "lxml-4.5.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ebec08091a22c2be870890913bdadd86fcd8e9f0f22bcb398abd3af914690c15"}, {file = "lxml-4.5.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5591c4164755778e29e69b86e425880f852464a21c7bb53c7ea453bbe2633bbe"},
{file = "lxml-4.5.0-cp36-cp36m-win32.whl", hash = "sha256:deadf4df349d1dcd7b2853a2c8796593cc346600726eff680ed8ed11812382a7"}, {file = "lxml-4.5.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cc411ad324a4486b142c41d9b2b6a722c534096963688d879ea6fa8a35028258"},
{file = "lxml-4.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f2b74784ed7e0bc2d02bd53e48ad6ba523c9b36c194260b7a5045071abbb1012"}, {file = "lxml-4.5.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:1fa21263c3aba2b76fd7c45713d4428dbcc7644d73dcf0650e9d344e433741b3"},
{file = "lxml-4.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fa071559f14bd1e92077b1b5f6c22cf09756c6de7139370249eb372854ce51e6"}, {file = "lxml-4.5.2-cp36-cp36m-win32.whl", hash = "sha256:786aad2aa20de3dbff21aab86b2fb6a7be68064cbbc0219bde414d3a30aa47ae"},
{file = "lxml-4.5.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:edc15fcfd77395e24543be48871c251f38132bb834d9fdfdad756adb6ea37679"}, {file = "lxml-4.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:e1cacf4796b20865789083252186ce9dc6cc59eca0c2e79cca332bdff24ac481"},
{file = "lxml-4.5.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd52e796fee7171c4361d441796b64df1acfceb51f29e545e812f16d023c4bbc"}, {file = "lxml-4.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:80a38b188d20c0524fe8959c8ce770a8fdf0e617c6912d23fc97c68301bb9aba"},
{file = "lxml-4.5.0-cp37-cp37m-win32.whl", hash = "sha256:90ed0e36455a81b25b7034038e40880189169c308a3df360861ad74da7b68c1a"}, {file = "lxml-4.5.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ecc930ae559ea8a43377e8b60ca6f8d61ac532fc57efb915d899de4a67928efd"},
{file = "lxml-4.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:df533af6f88080419c5a604d0d63b2c33b1c0c4409aba7d0cb6de305147ea8c8"}, {file = "lxml-4.5.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a76979f728dd845655026ab991df25d26379a1a8fc1e9e68e25c7eda43004bed"},
{file = "lxml-4.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4b2c63cc7963aedd08a5f5a454c9f67251b1ac9e22fd9d72836206c42dc2a72"}, {file = "lxml-4.5.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cfd7c5dd3c35c19cec59c63df9571c67c6d6e5c92e0fe63517920e97f61106d1"},
{file = "lxml-4.5.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e5d842c73e4ef6ed8c1bd77806bf84a7cb535f9c0cf9b2c74d02ebda310070e1"}, {file = "lxml-4.5.2-cp37-cp37m-win32.whl", hash = "sha256:5a9c8d11aa2c8f8b6043d845927a51eb9102eb558e3f936df494e96393f5fd3e"},
{file = "lxml-4.5.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:63dbc21efd7e822c11d5ddbedbbb08cd11a41e0032e382a0fd59b0b08e405a3a"}, {file = "lxml-4.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4b4a111bcf4b9c948e020fd207f915c24a6de3f1adc7682a2d92660eb4e84f1a"},
{file = "lxml-4.5.0-cp38-cp38-win32.whl", hash = "sha256:4235bc124fdcf611d02047d7034164897ade13046bda967768836629bc62784f"}, {file = "lxml-4.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5dd20538a60c4cc9a077d3b715bb42307239fcd25ef1ca7286775f95e9e9a46d"},
{file = "lxml-4.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:d5b3c4b7edd2e770375a01139be11307f04341ec709cf724e0f26ebb1eef12c3"}, {file = "lxml-4.5.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2b30aa2bcff8e958cd85d907d5109820b01ac511eae5b460803430a7404e34d7"},
{file = "lxml-4.5.0.tar.gz", hash = "sha256:8620ce80f50d023d414183bf90cc2576c2837b88e00bea3f33ad2630133bbb60"}, {file = "lxml-4.5.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:aa8eba3db3d8761db161003e2d0586608092e217151d7458206e243be5a43843"},
{file = "lxml-4.5.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f0ec6b9b3832e0bd1d57af41f9238ea7709bbd7271f639024f2fc9d3bb01293"},
{file = "lxml-4.5.2-cp38-cp38-win32.whl", hash = "sha256:107781b213cf7201ec3806555657ccda67b1fccc4261fb889ef7fc56976db81f"},
{file = "lxml-4.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:f161af26f596131b63b236372e4ce40f3167c1b5b5d459b29d2514bd8c9dc9ee"},
{file = "lxml-4.5.2.tar.gz", hash = "sha256:cdc13a1682b2a6241080745b1953719e7fe0850b40a5c71ca574f090a1391df6"},
]
more-itertools = [
{file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"},
{file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"},
]
multidict = [
{file = "multidict-4.7.6-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000"},
{file = "multidict-4.7.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a"},
{file = "multidict-4.7.6-cp35-cp35m-win32.whl", hash = "sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5"},
{file = "multidict-4.7.6-cp35-cp35m-win_amd64.whl", hash = "sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3"},
{file = "multidict-4.7.6-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87"},
{file = "multidict-4.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2"},
{file = "multidict-4.7.6-cp36-cp36m-win32.whl", hash = "sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7"},
{file = "multidict-4.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463"},
{file = "multidict-4.7.6-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d"},
{file = "multidict-4.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255"},
{file = "multidict-4.7.6-cp37-cp37m-win32.whl", hash = "sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507"},
{file = "multidict-4.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c"},
{file = "multidict-4.7.6-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b"},
{file = "multidict-4.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7"},
{file = "multidict-4.7.6-cp38-cp38-win32.whl", hash = "sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d"},
{file = "multidict-4.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19"},
{file = "multidict-4.7.6.tar.gz", hash = "sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430"},
] ]
mysqlclient = [ mysqlclient = [
{file = "mysqlclient-1.4.6-cp36-cp36m-win_amd64.whl", hash = "sha256:4c82187dd6ab3607150fbb1fa5ef4643118f3da122b8ba31c3149ddd9cf0cb39"}, {file = "mysqlclient-1.4.6-cp36-cp36m-win_amd64.whl", hash = "sha256:4c82187dd6ab3607150fbb1fa5ef4643118f3da122b8ba31c3149ddd9cf0cb39"},
@ -290,6 +679,10 @@ mysqlclient = [
{file = "mysqlclient-1.4.6-cp38-cp38-win_amd64.whl", hash = "sha256:f646f8d17d02be0872291f258cce3813497bc7888cd4712a577fd1e719b2f213"}, {file = "mysqlclient-1.4.6-cp38-cp38-win_amd64.whl", hash = "sha256:f646f8d17d02be0872291f258cce3813497bc7888cd4712a577fd1e719b2f213"},
{file = "mysqlclient-1.4.6.tar.gz", hash = "sha256:f3fdaa9a38752a3b214a6fe79d7cae3653731a53e577821f9187e67cbecb2e16"}, {file = "mysqlclient-1.4.6.tar.gz", hash = "sha256:f3fdaa9a38752a3b214a6fe79d7cae3653731a53e577821f9187e67cbecb2e16"},
] ]
packaging = [
{file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
{file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
]
pathspec = [ pathspec = [
{file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
{file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"},
@ -297,32 +690,66 @@ pathspec = [
peewee = [ peewee = [
{file = "peewee-3.13.3.tar.gz", hash = "sha256:1269a9736865512bd4056298003aab190957afe07d2616cf22eaf56cb6398369"}, {file = "peewee-3.13.3.tar.gz", hash = "sha256:1269a9736865512bd4056298003aab190957afe07d2616cf22eaf56cb6398369"},
] ]
pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
]
py = [
{file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"},
{file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
]
pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
]
pypiwin32 = [
{file = "pypiwin32-223-py3-none-any.whl", hash = "sha256:67adf399debc1d5d14dffc1ab5acacb800da569754fafdc576b2a039485aa775"},
{file = "pypiwin32-223.tar.gz", hash = "sha256:71be40c1fbd28594214ecaecb58e7aa8b708eabfa0125c8a109ebd51edbd776a"},
]
pytest = [
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
]
pywin32 = [
{file = "pywin32-228-cp27-cp27m-win32.whl", hash = "sha256:37dc9935f6a383cc744315ae0c2882ba1768d9b06700a70f35dc1ce73cd4ba9c"},
{file = "pywin32-228-cp27-cp27m-win_amd64.whl", hash = "sha256:11cb6610efc2f078c9e6d8f5d0f957620c333f4b23466931a247fb945ed35e89"},
{file = "pywin32-228-cp35-cp35m-win32.whl", hash = "sha256:1f45db18af5d36195447b2cffacd182fe2d296849ba0aecdab24d3852fbf3f80"},
{file = "pywin32-228-cp35-cp35m-win_amd64.whl", hash = "sha256:6e38c44097a834a4707c1b63efa9c2435f5a42afabff634a17f563bc478dfcc8"},
{file = "pywin32-228-cp36-cp36m-win32.whl", hash = "sha256:ec16d44b49b5f34e99eb97cf270806fdc560dff6f84d281eb2fcb89a014a56a9"},
{file = "pywin32-228-cp36-cp36m-win_amd64.whl", hash = "sha256:a60d795c6590a5b6baeacd16c583d91cce8038f959bd80c53bd9a68f40130f2d"},
{file = "pywin32-228-cp37-cp37m-win32.whl", hash = "sha256:af40887b6fc200eafe4d7742c48417529a8702dcc1a60bf89eee152d1d11209f"},
{file = "pywin32-228-cp37-cp37m-win_amd64.whl", hash = "sha256:00eaf43dbd05ba6a9b0080c77e161e0b7a601f9a3f660727a952e40140537de7"},
{file = "pywin32-228-cp38-cp38-win32.whl", hash = "sha256:fa6ba028909cfc64ce9e24bcf22f588b14871980d9787f1e2002c99af8f1850c"},
{file = "pywin32-228-cp38-cp38-win_amd64.whl", hash = "sha256:9b3466083f8271e1a5eb0329f4e0d61925d46b40b195a33413e0905dccb285e8"},
{file = "pywin32-228-cp39-cp39-win32.whl", hash = "sha256:ed74b72d8059a6606f64842e7917aeee99159ebd6b8d6261c518d002837be298"},
{file = "pywin32-228-cp39-cp39-win_amd64.whl", hash = "sha256:8319bafdcd90b7202c50d6014efdfe4fde9311b3ff15fd6f893a45c0868de203"},
]
regex = [ regex = [
{file = "regex-2020.4.4-cp27-cp27m-win32.whl", hash = "sha256:90742c6ff121a9c5b261b9b215cb476eea97df98ea82037ec8ac95d1be7a034f"}, {file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"},
{file = "regex-2020.4.4-cp27-cp27m-win_amd64.whl", hash = "sha256:24f4f4062eb16c5bbfff6a22312e8eab92c2c99c51a02e39b4eae54ce8255cd1"}, {file = "regex-2020.7.14-cp27-cp27m-win_amd64.whl", hash = "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644"},
{file = "regex-2020.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:08119f707f0ebf2da60d2f24c2f39ca616277bb67ef6c92b72cbf90cbe3a556b"}, {file = "regex-2020.7.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc"},
{file = "regex-2020.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c9423a150d3a4fc0f3f2aae897a59919acd293f4cb397429b120a5fcd96ea3db"}, {file = "regex-2020.7.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067"},
{file = "regex-2020.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c087bff162158536387c53647411db09b6ee3f9603c334c90943e97b1052a156"}, {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd"},
{file = "regex-2020.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1cbe0fa0b7f673400eb29e9ef41d4f53638f65f9a2143854de6b1ce2899185c3"}, {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88"},
{file = "regex-2020.4.4-cp36-cp36m-win32.whl", hash = "sha256:0ce9537396d8f556bcfc317c65b6a0705320701e5ce511f05fc04421ba05b8a8"}, {file = "regex-2020.7.14-cp36-cp36m-win32.whl", hash = "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4"},
{file = "regex-2020.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:7e1037073b1b7053ee74c3c6c0ada80f3501ec29d5f46e42669378eae6d4405a"}, {file = "regex-2020.7.14-cp36-cp36m-win_amd64.whl", hash = "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f"},
{file = "regex-2020.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4385f12aa289d79419fede43f979e372f527892ac44a541b5446617e4406c468"}, {file = "regex-2020.7.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162"},
{file = "regex-2020.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a58dd45cb865be0ce1d5ecc4cfc85cd8c6867bea66733623e54bd95131f473b6"}, {file = "regex-2020.7.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf"},
{file = "regex-2020.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ccccdd84912875e34c5ad2d06e1989d890d43af6c2242c6fcfa51556997af6cd"}, {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7"},
{file = "regex-2020.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ea4adf02d23b437684cd388d557bf76e3afa72f7fed5bbc013482cc00c816948"}, {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89"},
{file = "regex-2020.4.4-cp37-cp37m-win32.whl", hash = "sha256:2294f8b70e058a2553cd009df003a20802ef75b3c629506be20687df0908177e"}, {file = "regex-2020.7.14-cp37-cp37m-win32.whl", hash = "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6"},
{file = "regex-2020.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:e91ba11da11cf770f389e47c3f5c30473e6d85e06d7fd9dcba0017d2867aab4a"}, {file = "regex-2020.7.14-cp37-cp37m-win_amd64.whl", hash = "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204"},
{file = "regex-2020.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5635cd1ed0a12b4c42cce18a8d2fb53ff13ff537f09de5fd791e97de27b6400e"}, {file = "regex-2020.7.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99"},
{file = "regex-2020.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:23069d9c07e115537f37270d1d5faea3e0bdded8279081c4d4d607a2ad393683"}, {file = "regex-2020.7.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e"},
{file = "regex-2020.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c162a21e0da33eb3d31a3ac17a51db5e634fc347f650d271f0305d96601dc15b"}, {file = "regex-2020.7.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e"},
{file = "regex-2020.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:fb95debbd1a824b2c4376932f2216cc186912e389bdb0e27147778cf6acb3f89"}, {file = "regex-2020.7.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a"},
{file = "regex-2020.4.4-cp38-cp38-win32.whl", hash = "sha256:2a3bf8b48f8e37c3a40bb3f854bf0121c194e69a650b209628d951190b862de3"}, {file = "regex-2020.7.14-cp38-cp38-win32.whl", hash = "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341"},
{file = "regex-2020.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bfed051dbff32fd8945eccca70f5e22b55e4148d2a8a45141a3b053d6455ae3"}, {file = "regex-2020.7.14-cp38-cp38-win_amd64.whl", hash = "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840"},
{file = "regex-2020.4.4.tar.gz", hash = "sha256:295badf61a51add2d428a46b8580309c520d8b26e769868b922750cf3ce67142"}, {file = "regex-2020.7.14.tar.gz", hash = "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb"},
] ]
requests = [ requests = [
{file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
{file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
] ]
"ruamel.yaml" = [ "ruamel.yaml" = [
{file = "ruamel.yaml-0.16.10-py2.py3-none-any.whl", hash = "sha256:0962fd7999e064c4865f96fb1e23079075f4a2a14849bcdc5cdba53a24f9759b"}, {file = "ruamel.yaml-0.16.10-py2.py3-none-any.whl", hash = "sha256:0962fd7999e064c4865f96fb1e23079075f4a2a14849bcdc5cdba53a24f9759b"},
@ -349,10 +776,17 @@ requests = [
{file = "ruamel.yaml.clib-0.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:be018933c2f4ee7de55e7bd7d0d801b3dfb09d21dad0cce8a97995fd3e44be30"}, {file = "ruamel.yaml.clib-0.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:be018933c2f4ee7de55e7bd7d0d801b3dfb09d21dad0cce8a97995fd3e44be30"},
{file = "ruamel.yaml.clib-0.2.0.tar.gz", hash = "sha256:b66832ea8077d9b3f6e311c4a53d06273db5dc2db6e8a908550f3c14d67e718c"}, {file = "ruamel.yaml.clib-0.2.0.tar.gz", hash = "sha256:b66832ea8077d9b3f6e311c4a53d06273db5dc2db6e8a908550f3c14d67e718c"},
] ]
six = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
taskipy = [
{file = "taskipy-1.2.1-py3-none-any.whl", hash = "sha256:99bdaf5b19791c2345806847147e0fc2d28e1ac9446058def5a8b6b3fc9f23e2"},
{file = "taskipy-1.2.1.tar.gz", hash = "sha256:5eb2c3b1606c896c7fa799848e71e8883b880759224958d07ba760e5db263175"},
]
toml = [ toml = [
{file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
{file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
{file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"},
] ]
typed-ast = [ typed-ast = [
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
@ -377,7 +811,42 @@ typed-ast = [
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
] ]
udm-rest-client = [
{file = "udm-rest-client-0.4.0.tar.gz", hash = "sha256:3402a2320b6f9da8a2e5c307f90f90e40e83df9942e39a0e642c1367bd014b3e"},
{file = "udm_rest_client-0.4.0-py2.py3-none-any.whl", hash = "sha256:048f15050189d96070965a527fd185a9b72230b3c5f714fb1127c95c614dd7bf"},
]
urllib3 = [ urllib3 = [
{file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
{file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"},
] ]
wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
]
websocket-client = [
{file = "websocket_client-0.57.0-py2.py3-none-any.whl", hash = "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549"},
{file = "websocket_client-0.57.0.tar.gz", hash = "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"},
]
yarl = [
{file = "yarl-1.4.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b"},
{file = "yarl-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1"},
{file = "yarl-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080"},
{file = "yarl-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a"},
{file = "yarl-1.4.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f"},
{file = "yarl-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea"},
{file = "yarl-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb"},
{file = "yarl-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70"},
{file = "yarl-1.4.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d"},
{file = "yarl-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce"},
{file = "yarl-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"},
{file = "yarl-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce"},
{file = "yarl-1.4.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b"},
{file = "yarl-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae"},
{file = "yarl-1.4.2-cp38-cp38-win32.whl", hash = "sha256:6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462"},
{file = "yarl-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6"},
{file = "yarl-1.4.2.tar.gz", hash = "sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b"},
]
zipp = [
{file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
{file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
]

View File

@ -22,10 +22,34 @@ bitstring = "^3.1.6"
lxml = "^4.5.0" lxml = "^4.5.0"
peewee = "^3.13.2" peewee = "^3.13.2"
mysqlclient = "^1.4.6" mysqlclient = "^1.4.6"
udm-rest-client = "^0.4.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
black = "^19.10b0" black = "^19.10b0"
isort = "^4.3.21" isort = "^4.3.21"
taskipy = "^1.2.1"
pytest = "^5.4.2"
coverage = {extras = ["toml"], version = "^5.1"}
[tool.taskipy.tasks]
test = "pytest"
mypy = "mypy --strict memberPlumbing/hid/ stubs"
cov = "coverage run && coverage html"
[tool.coverage.run]
branch = true
source = ["memberPlumbing"]
omit = ["*/tests/*"]
command_line = "-m pytest"
[tool.coverage.report]
skip_empty = true
[tool.poetry.scripts]
doorUpdater = 'memberPlumbing.doorUpdater:main'
hidEvents = 'memberPlumbing.hidEvents:main'
sqlExport = 'memberPlumbing.sqlExport:main'
ucsAccounts = 'memberPlumbing.ucsAccounts:main'
[build-system] [build-system]
requires = ["poetry>=0.12"] requires = ["poetry>=0.12"]

View File

@ -7,4 +7,4 @@ User=adam
Type=oneshot Type=oneshot
TimeoutStartSec=600 TimeoutStartSec=600
WorkingDirectory=/home/adam/memberPlumbing/ WorkingDirectory=/home/adam/memberPlumbing/
ExecStart=/usr/bin/python3 -u /home/adam/.poetry/bin/poetry run /home/adam/memberPlumbing/bin/doorUpdater ExecStart=/usr/bin/python3 -u /home/adam/.poetry/bin/poetry run doorUpdater

View File

@ -9,4 +9,4 @@ User=adam
Type=oneshot Type=oneshot
TimeoutStartSec=600 TimeoutStartSec=600
WorkingDirectory=/home/adam/memberPlumbing/ WorkingDirectory=/home/adam/memberPlumbing/
ExecStart=/usr/bin/python3 -u /home/adam/.poetry/bin/poetry run /home/adam/memberPlumbing/bin/hidEvents ExecStart=/usr/bin/python3 -u /home/adam/.poetry/bin/poetry run hidEvents

View File

@ -9,4 +9,4 @@ User=adam
Type=oneshot Type=oneshot
TimeoutStartSec=600 TimeoutStartSec=600
WorkingDirectory=/home/adam/memberPlumbing/ WorkingDirectory=/home/adam/memberPlumbing/
ExecStart=/usr/bin/python3 -u /home/adam/.poetry/bin/poetry run /home/adam/memberPlumbing/bin/sqlExport ExecStart=/usr/bin/python3 -u /home/adam/.poetry/bin/poetry run sqlExport