commit db61ffc43b7a3199411d78a36b9d41b6249dfd52
parent ad10b66caf52b45c7d8f37f6267bc517ea75b91d
Author: arjoonn <arjoonn@noreply.localhost>
Date: Fri, 2 Dec 2022 20:03:07 +0000
Try to show commit status in this PR (!1)
- [x] Complex graph display
- [x] Update status on the fly
- [x] Auto trigger on push
- [x] Configure for new repo easily
<details>
<summary>JayporeCi: 🟢 8f3a13cc57</summary>
```mermaid
graph TB
N1(Black):::passed --> N0(+):::passed
N2(Pwd):::passed --> N0(+):::passed
N3(PyLint):::passed --> N0(+):::passed
N4(PyTest):::passed --> N0(+):::passed
N5(Tree):::passed --> N0(+):::passed
classDef pending fill:#aaa, color:black, stroke:black,stroke-width:2px,stroke-dasharray: 5 5;
classDef skipped fill:#aaa, color:black, stroke:black,stroke-width:2px;
classDef assigned fill:#ddd, color:black, stroke:black,stroke-width:2px;
classDef running fill:#bae1ff,color:black,stroke:black,stroke-width:2px,stroke-dasharray: 5 5;
classDef passed fill:#88d8b0, color:black, stroke:black;
classDef failed fill:#ff6f69, color:black, stroke:black;
classDef timeout fill:#ffda9e, color:black, stroke:black;
```
- <details>
<summary>Logs: JayporeCi</summary>
```
============== stdout =============
2022-12-02T20:02:33.000064Z [info ] Create network network_name=jaypore_140230145354512 pipe_id=140230145354512 subprocess=8c00dae2e121417bb385b422bb9e939bcef7fd1b6712fd9bef1e807333afea0a
2022-12-02T20:02:33.016968Z [info ] Found network network_name=jaypore_140230145354512 pipe_id=140230145354512 subprocess=CompletedProcess(args='docker network ls | grep jaypore_140230145354512', returncode=0, stdout=b'8c00dae2e121 jaypore_140230145354512 bridge local\n')
2022-12-02T20:02:33.017101Z [info ] Ok called branch=ci_status_api job_id=140230124080528 job_name=+ network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=None
2022-12-02T20:02:33.017174Z [info ] Trigger called branch=ci_status_api job_id=140230124080528 job_name=+ network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=None
2022-12-02T20:02:33.017228Z [info ] Trigger called branch=ci_status_api job_id=140230138106768 job_name=Pwd network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=None
2022-12-02T20:02:33.455364Z [info ] Trigger done branch=ci_status_api job_id=140230138106768 job_name=Pwd network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=ca9c319ac998edfde4e934abe7ab8858ea2d413f1e85f72ae8f5e26f112d6bd2
2022-12-02T20:02:33.455433Z [debug ] Checking job run branch=ci_status_api job_id=140230138106768 job_name=Pwd network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=ca9c319ac998edfde4e934abe7ab8858ea2d413f1e85f72ae8f5e26f112d6bd2
2022-12-02T20:02:33.736118Z [debug ] Check status exit_code=0 is_running=True network_name=jaypore_140230145354512 pipe_id=140230145354512 run_id=ca9c319ac998edfde4e934abe7ab8858ea2d413f1e85f72ae8f5e26f112d6bd2
2022-12-02T20:02:33.736214Z [debug ] Job run status found branch=ci_status_api exit_code=0 is_running=True job_id=140230138106768 job_name=Pwd network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=ca9c319ac998edfde4e934abe7ab8858ea2d413f1e85f72ae8f5e26f112d6bd2
2022-12-02T20:02:33.736268Z [debug ] >>> /jaypore_ci/run branch=ci_status_api job_id=140230138106768 job_name=Pwd network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=ca9c319ac998edfde4e934abe7ab8858ea2d413f1e85f72ae8f5e26f112d6bd2
2022-12-02T20:02:33.736314Z [debug ] Update report branch=ci_status_api job_id=140230138106768 job_name=Pwd network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=ca9c319ac998edfde4e934abe7ab8858ea2d413f1e85f72ae8f5e26f112d6bd2
2022-12-02T20:02:33.898647Z [debug ] Get PR Id branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=409
2022-12-02T20:02:34.093726Z [debug ] Get existing body branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=200
2022-12-02T20:02:34.230874Z [debug ] Published new report branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=201
2022-12-02T20:02:34.361869Z [debug ] Published new status branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status=pending status_code=201
2022-12-02T20:02:34.361967Z [info ] Trigger called branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=None
2022-12-02T20:02:34.837465Z [info ] Trigger done branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:34.837629Z [debug ] Checking job run branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.074784Z [debug ] Check status exit_code=0 is_running=True network_name=jaypore_140230145354512 pipe_id=140230145354512 run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.074907Z [debug ] Job run status found branch=ci_status_api exit_code=0 is_running=True job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075004Z [debug ] >>> . branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075062Z [debug ] >>> ├── Dockerfile branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075120Z [debug ] >>> ├── README.md branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075174Z [debug ] >>> ├── jaypore_ci branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075226Z [debug ] >>> │ ├── __init__.py branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075283Z [debug ] >>> │ ├── __main__.py branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075336Z [debug ] >>> │ ├── docker.py branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075388Z [debug ] >>> │ ├── gitea.py branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075439Z [debug ] >>> │ ├── interfaces.py branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075548Z [debug ] >>> │ ├── jci.py branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075604Z [debug ] >>> │ └── logging.py branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075656Z [debug ] >>> ├── poetry.lock branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075707Z [debug ] >>> ├── pyproject.toml branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075760Z [debug ] >>> ├── setup.sh branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075821Z [debug ] >>> └── tests branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075873Z [debug ] >>> ├── __init__.py branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075926Z [debug ] >>> └── test_jaypore_ci.py branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.075980Z [debug ] >>> branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.076031Z [debug ] >>> 2 directories, 14 files branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.076083Z [debug ] Update report branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:35.240586Z [debug ] Get PR Id branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=409
2022-12-02T20:02:35.382601Z [debug ] Get existing body branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=200
2022-12-02T20:02:35.571578Z [debug ] Published new report branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=201
2022-12-02T20:02:35.694271Z [debug ] Published new status branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status=pending status_code=201
2022-12-02T20:02:35.694597Z [info ] Trigger called branch=ci_status_api job_id=140230124080272 job_name=Black network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=None
2022-12-02T20:02:36.177554Z [info ] Trigger done branch=ci_status_api job_id=140230124080272 job_name=Black network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=e84571e8d81992386a1b0dfa4df2981fd5e97685ddb75939150d8fd6fa77bddd
2022-12-02T20:02:36.177615Z [debug ] Checking job run branch=ci_status_api job_id=140230124080272 job_name=Black network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=e84571e8d81992386a1b0dfa4df2981fd5e97685ddb75939150d8fd6fa77bddd
2022-12-02T20:02:36.239460Z [debug ] Check status exit_code=0 is_running=True network_name=jaypore_140230145354512 pipe_id=140230145354512 run_id=e84571e8d81992386a1b0dfa4df2981fd5e97685ddb75939150d8fd6fa77bddd
2022-12-02T20:02:36.239540Z [debug ] Job run status found branch=ci_status_api exit_code=0 is_running=True job_id=140230124080272 job_name=Black network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=e84571e8d81992386a1b0dfa4df2981fd5e97685ddb75939150d8fd6fa77bddd
2022-12-02T20:02:36.239589Z [debug ] >>> branch=ci_status_api job_id=140230124080272 job_name=Black network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=e84571e8d81992386a1b0dfa4df2981fd5e97685ddb75939150d8fd6fa77bddd
2022-12-02T20:02:36.239628Z [debug ] Update report branch=ci_status_api job_id=140230124080272 job_name=Black network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=e84571e8d81992386a1b0dfa4df2981fd5e97685ddb75939150d8fd6fa77bddd
2022-12-02T20:02:36.384859Z [debug ] Get PR Id branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=409
2022-12-02T20:02:36.530378Z [debug ] Get existing body branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=200
2022-12-02T20:02:36.731001Z [debug ] Published new report branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=201
2022-12-02T20:02:36.880937Z [debug ] Published new status branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status=pending status_code=201
2022-12-02T20:02:36.881319Z [info ] Trigger called branch=ci_status_api job_id=140230124079888 job_name=PyLint network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=None
2022-12-02T20:02:37.366336Z [info ] Trigger done branch=ci_status_api job_id=140230124079888 job_name=PyLint network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=43ba09f031ceb889a91439f74722efc276d6d259688225151cdda2ae0cc40e21
2022-12-02T20:02:37.366407Z [debug ] Checking job run branch=ci_status_api job_id=140230124079888 job_name=PyLint network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=43ba09f031ceb889a91439f74722efc276d6d259688225151cdda2ae0cc40e21
2022-12-02T20:02:37.460542Z [debug ] Check status exit_code=0 is_running=True network_name=jaypore_140230145354512 pipe_id=140230145354512 run_id=43ba09f031ceb889a91439f74722efc276d6d259688225151cdda2ae0cc40e21
2022-12-02T20:02:37.460678Z [debug ] Job run status found branch=ci_status_api exit_code=0 is_running=True job_id=140230124079888 job_name=PyLint network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=43ba09f031ceb889a91439f74722efc276d6d259688225151cdda2ae0cc40e21
2022-12-02T20:02:37.460760Z [debug ] >>> branch=ci_status_api job_id=140230124079888 job_name=PyLint network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=43ba09f031ceb889a91439f74722efc276d6d259688225151cdda2ae0cc40e21
2022-12-02T20:02:37.460828Z [debug ] Update report branch=ci_status_api job_id=140230124079888 job_name=PyLint network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=43ba09f031ceb889a91439f74722efc276d6d259688225151cdda2ae0cc40e21
2022-12-02T20:02:37.622004Z [debug ] Get PR Id branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=409
2022-12-02T20:02:37.796584Z [debug ] Get existing body branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=200
2022-12-02T20:02:38.008058Z [debug ] Published new report branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=201
2022-12-02T20:02:38.125218Z [debug ] Published new status branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status=pending status_code=201
2022-12-02T20:02:38.125381Z [info ] Trigger called branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=None
2022-12-02T20:02:38.664526Z [info ] Trigger done branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:38.664607Z [debug ] Checking job run branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:38.733008Z [debug ] Check status exit_code=0 is_running=True network_name=jaypore_140230145354512 pipe_id=140230145354512 run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:38.733098Z [debug ] Job run status found branch=ci_status_api exit_code=0 is_running=True job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:38.733153Z [debug ] >>> branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:38.733197Z [debug ] Update report branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:38.908417Z [debug ] Get PR Id branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=409
2022-12-02T20:02:39.075638Z [debug ] Get existing body branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=200
2022-12-02T20:02:39.271532Z [debug ] Published new report branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=201
2022-12-02T20:02:39.413168Z [debug ] Published new status branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status=pending status_code=201
2022-12-02T20:02:40.413485Z [debug ] Checking job run branch=ci_status_api job_id=140230138106768 job_name=Pwd network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=ca9c319ac998edfde4e934abe7ab8858ea2d413f1e85f72ae8f5e26f112d6bd2
2022-12-02T20:02:40.466563Z [debug ] Check status exit_code=0 is_running=False network_name=jaypore_140230145354512 pipe_id=140230145354512 run_id=ca9c319ac998edfde4e934abe7ab8858ea2d413f1e85f72ae8f5e26f112d6bd2
2022-12-02T20:02:40.466639Z [debug ] Job run status found branch=ci_status_api exit_code=0 is_running=False job_id=140230138106768 job_name=Pwd network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=ca9c319ac998edfde4e934abe7ab8858ea2d413f1e85f72ae8f5e26f112d6bd2
2022-12-02T20:02:40.466724Z [debug ] Update report branch=ci_status_api job_id=140230138106768 job_name=Pwd network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=ca9c319ac998edfde4e934abe7ab8858ea2d413f1e85f72ae8f5e26f112d6bd2
2022-12-02T20:02:40.638874Z [debug ] Get PR Id branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=409
2022-12-02T20:02:40.819390Z [debug ] Get existing body branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=200
2022-12-02T20:02:41.074171Z [debug ] Published new report branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=201
2022-12-02T20:02:41.191376Z [debug ] Published new status branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status=pending status_code=201
2022-12-02T20:02:41.191528Z [debug ] Checking job run branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:41.288607Z [debug ] Check status exit_code=0 is_running=False network_name=jaypore_140230145354512 pipe_id=140230145354512 run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:41.288708Z [debug ] Job run status found branch=ci_status_api exit_code=0 is_running=False job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:41.288775Z [debug ] Update report branch=ci_status_api job_id=140230124080144 job_name=Tree network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=b4135352935bc9b878d4d004a72239e17b542c213274f1e0d09cc13c9b29e847
2022-12-02T20:02:41.432452Z [debug ] Get PR Id branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=409
2022-12-02T20:02:41.601291Z [debug ] Get existing body branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=200
2022-12-02T20:02:41.835518Z [debug ] Published new report branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=201
2022-12-02T20:02:41.968670Z [debug ] Published new status branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status=pending status_code=201
2022-12-02T20:02:41.968758Z [debug ] Checking job run branch=ci_status_api job_id=140230124080272 job_name=Black network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=e84571e8d81992386a1b0dfa4df2981fd5e97685ddb75939150d8fd6fa77bddd
2022-12-02T20:02:42.015969Z [debug ] Check status exit_code=0 is_running=False network_name=jaypore_140230145354512 pipe_id=140230145354512 run_id=e84571e8d81992386a1b0dfa4df2981fd5e97685ddb75939150d8fd6fa77bddd
2022-12-02T20:02:42.016081Z [debug ] Job run status found branch=ci_status_api exit_code=0 is_running=False job_id=140230124080272 job_name=Black network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=e84571e8d81992386a1b0dfa4df2981fd5e97685ddb75939150d8fd6fa77bddd
2022-12-02T20:02:42.016148Z [debug ] >>> 10 files would be left unchanged. branch=ci_status_api job_id=140230124080272 job_name=Black network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=e84571e8d81992386a1b0dfa4df2981fd5e97685ddb75939150d8fd6fa77bddd
2022-12-02T20:02:42.016203Z [debug ] Update report branch=ci_status_api job_id=140230124080272 job_name=Black network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=e84571e8d81992386a1b0dfa4df2981fd5e97685ddb75939150d8fd6fa77bddd
2022-12-02T20:02:42.205671Z [debug ] Get PR Id branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=409
2022-12-02T20:02:42.374790Z [debug ] Get existing body branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=200
2022-12-02T20:02:42.592741Z [debug ] Published new report branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=201
2022-12-02T20:02:42.737065Z [debug ] Published new status branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status=pending status_code=201
2022-12-02T20:02:42.737276Z [debug ] Checking job run branch=ci_status_api job_id=140230124079888 job_name=PyLint network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=43ba09f031ceb889a91439f74722efc276d6d259688225151cdda2ae0cc40e21
2022-12-02T20:02:42.825941Z [debug ] Check status exit_code=0 is_running=False network_name=jaypore_140230145354512 pipe_id=140230145354512 run_id=43ba09f031ceb889a91439f74722efc276d6d259688225151cdda2ae0cc40e21
2022-12-02T20:02:42.826065Z [debug ] Job run status found branch=ci_status_api exit_code=0 is_running=False job_id=140230124079888 job_name=PyLint network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=43ba09f031ceb889a91439f74722efc276d6d259688225151cdda2ae0cc40e21
2022-12-02T20:02:42.826147Z [debug ] >>> Your code has been rated at 10.00/10 branch=ci_status_api job_id=140230124079888 job_name=PyLint network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=43ba09f031ceb889a91439f74722efc276d6d259688225151cdda2ae0cc40e21
2022-12-02T20:02:42.826215Z [debug ] Update report branch=ci_status_api job_id=140230124079888 job_name=PyLint network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=43ba09f031ceb889a91439f74722efc276d6d259688225151cdda2ae0cc40e21
2022-12-02T20:02:43.004050Z [debug ] Get PR Id branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=409
2022-12-02T20:02:43.236577Z [debug ] Get existing body branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=200
2022-12-02T20:02:43.569343Z [debug ] Published new report branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=201
2022-12-02T20:02:43.715164Z [debug ] Published new status branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status=pending status_code=201
2022-12-02T20:02:43.715327Z [debug ] Checking job run branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:43.795149Z [debug ] Check status exit_code=0 is_running=False network_name=jaypore_140230145354512 pipe_id=140230145354512 run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:43.795260Z [debug ] Job run status found branch=ci_status_api exit_code=0 is_running=False job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:43.795317Z [debug ] >>> platform linux -- Python 3.11.0, pytest-7.2.0, pluggy-1.0.0 branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:43.795362Z [debug ] >>> rootdir: /jaypore_ci/run branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:43.795405Z [debug ] >>> collected 1 item branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:43.795445Z [debug ] >>> branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:43.795485Z [debug ] >>> tests/test_jaypore_ci.py . [100%] branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:43.795525Z [debug ] >>> branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:43.795565Z [debug ] >>> ============================== 1 passed in 0.01s =============================== branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:43.795605Z [debug ] Update report branch=ci_status_api job_id=140230124080208 job_name=PyTest network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=6b4dd39b2e774e4dbad719ed8e08ace13a2e7f9050480e79a474ad1d5ddbfa2a
2022-12-02T20:02:43.987487Z [debug ] Get PR Id branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=409
2022-12-02T20:02:44.211293Z [debug ] Get existing body branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=200
2022-12-02T20:02:44.584917Z [debug ] Published new report branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status_code=201
2022-12-02T20:02:44.713888Z [debug ] Published new status branch=ci_status_api owner=midpath repo=jaypore_ci root=https://gitea.midpathsoftware.com status=pending status_code=201
2022-12-02T20:02:44.714010Z [info ] Trigger done branch=ci_status_api job_id=140230124080528 job_name=+ network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=pyrun_140230124080528
2022-12-02T20:02:44.714052Z [debug ] Checking job run branch=ci_status_api job_id=140230124080528 job_name=+ network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=pyrun_140230124080528
2022-12-02T20:02:44.714115Z [debug ] Checking job run branch=ci_status_api job_id=140230124080528 job_name=+ network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=pyrun_140230124080528
2022-12-02T20:02:44.714153Z [info ] Ok finished branch=ci_status_api job_id=140230124080528 job_name=+ network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=pyrun_140230124080528 status=<Status.PASSED: 50>
2022-12-02T20:02:44.714201Z [debug ] Update report branch=ci_status_api job_id=140230124080528 job_name=+ network_name=jaypore_140230145354512 owner=midpath pipe_id=140230145354512 repo=jaypore_ci root=https://gitea.midpathsoftware.com run_id=pyrun_140230124080528
```
</details>
- <details>
<summary>Logs: Pwd</summary>
```
============== stdout =============
/jaypore_ci/run
```
</details>
- <details>
<summary>Logs: Tree</summary>
```
============== stdout =============
.
├── Dockerfile
├── README.md
├── jaypore_ci
│ ├── __init__.py
│ ├── __main__.py
│ ├── docker.py
│ ├── gitea.py
│ ├── interfaces.py
│ ├── jci.py
│ └── logging.py
├── poetry.lock
├── pyproject.toml
├── setup.sh
└── tests
├── __init__.py
└── test_jaypore_ci.py
2 directories, 14 files
```
</details>
- <details>
<summary>Logs: Black</summary>
```
============== stdout =============
All done! ✨ 🍰 ✨
10 files would be left unchanged.
```
</details>
- <details>
<summary>Logs: PyLint</summary>
```
============== stdout =============
------------------------------------
Your code has been rated at 10.00/10
```
</details>
- <details>
<summary>Logs: PyTest</summary>
```
============== stdout =============
============================= test session starts ==============================
platform linux -- Python 3.11.0, pytest-7.2.0, pluggy-1.0.0
rootdir: /jaypore_ci/run
collected 1 item
tests/test_jaypore_ci.py . [100%]
============================== 1 passed in 0.01s ===============================
```
</details>
- <details>
<summary>Logs: +</summary>
```
============== stdout =============
Starting parallel run
Trigger job: 140230138106768 Pwd
Trigger job: 140230124080144 Tree
Trigger job: 140230124080272 Black
Trigger job: 140230124079888 PyLint
Trigger job: 140230124080208 PyTest
Checking: 140230138106768 Pwd is_complete: True
Checking: 140230124080144 Tree is_complete: True
Checking: 140230124080272 Black is_complete: True
Checking: 140230124079888 PyLint is_complete: True
Checking: 140230124080208 PyTest is_complete: True
Ok
```
</details>
</details>
Co-authored-by: arjoonn sharma <arjoonn@midpathsoftware.com>
Reviewed-on: https://gitea.midpathsoftware.com/midpath/jaypore_ci/pulls/1
Diffstat:
24 files changed, 2235 insertions(+), 148 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/.jaypore_ci/cicd.py b/.jaypore_ci/cicd.py
@@ -0,0 +1,13 @@
+from jaypore_ci import jci
+
+with jci.Pipeline(
+ image="arjoonn/jaypore_ci:latest", # NOTE: Change this to whatever you need
+ timeout=15 * 60,
+) as p:
+ p.in_parallel(
+ p.job("pwd", name="Pwd"),
+ p.job("tree", name="Tree"),
+ p.job("python3 -m black --check .", name="Black"),
+ p.job("python3 -m pylint jaypore_ci/ tests/", name="PyLint"),
+ p.job("python3 -m pytest tests/", name="PyTest"),
+ ).should_pass()
diff --git a/.jaypore_ci/pre-push.githook b/.jaypore_ci/pre-push.githook
@@ -0,0 +1,32 @@
+#! /bin/bash
+#
+set -o errexit
+set -o nounset
+set -o pipefail
+
+
+main() {
+ SHA=$(git rev-parse HEAD)
+ REPO_ROOT=$(git rev-parse --show-toplevel)
+ TOKEN=$(echo "url=$(git remote -v|grep push|awk '{print $2}')"|git credential fill|grep password|awk -F= '{print $2}')
+ # We will mount the current dir into /jaypore/repo
+ # Then we will copy things over to /jaypore/run
+ # Then we will run git clean to remove anything that is not in git
+ # Then we call the actual cicd code
+ #
+ # We also pass docker.sock to the run so that jaypore_ci can create docker containers
+ echo '----------------------------------------------'
+ echo "JayporeCi: "
+ JAYPORE_GITEA_TOKEN="${JAYPORE_GITEA_TOKEN:-$TOKEN}" docker run \
+ -d \
+ --name jaypore_ci_$SHA \
+ -e JAYPORE_GITEA_TOKEN \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v $REPO_ROOT:/jaypore/repo:ro \
+ -v /tmp/jaypore_$SHA:/jaypore/run \
+ --workdir /jaypore/run \
+ arjoonn/jaypore_ci:latest \
+ bash -c 'cp -r /jaypore/repo/. /jaypore/run && cd /jaypore/run/ && git clean -fdx && python .jaypore_ci/cicd.py'
+ echo '----------------------------------------------'
+}
+(main)
diff --git a/.pylintrc b/.pylintrc
@@ -0,0 +1,518 @@
+[MASTER]
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code.
+extension-pkg-whitelist=
+
+# Specify a score threshold to be exceeded before program exits with error.
+fail-under=10.0
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Add files or directories matching the regex patterns to the blacklist. The
+# regex matches against base names, not paths.
+ignore-patterns=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
+# number of processors available to use.
+jobs=0
+
+# Control the amount of potential inferred values when inferring a single
+# object. This can help the performance when dealing with large functions or
+# complex, nested conditions.
+limit-inference-results=100
+
+# List of plugins (as comma separated values of python module names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# When enabled, pylint would attempt to guess common misconfiguration and emit
+# user-friendly hints instead of false-positive error messages.
+suggestion-mode=yes
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
+confidence=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once). You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use "--disable=all --enable=classes
+# --disable=W".
+disable=missing-module-docstring,
+ missing-function-docstring,
+ missing-class-docstring,
+ too-few-public-methods,
+ too-many-ancestors,
+ invalid-name,
+ raising-bad-type,
+ cyclic-import,
+ R0801
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=c-extension-no-member
+
+
+[REPORTS]
+
+# Python expression which should return a score less than or equal to 10. You
+# have access to the variables 'error', 'warning', 'refactor', and 'convention'
+# which contain the number of messages in each category, as well as 'statement'
+# which is the total number of statements analyzed. This score is used by the
+# global evaluation report (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details.
+#msg-template=
+
+# Set the output format. Available formats are text, parseable, colorized, json
+# and msvs (visual studio). You can also give a reporter class, e.g.
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Tells whether to display a full report or only the messages.
+reports=no
+
+# Activate the evaluation score.
+score=yes
+
+
+[REFACTORING]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+# Complete name of functions that never returns. When checking for
+# inconsistent-return-statements if a never returning function is called then
+# it will be considered as an explicit return statement and no message will be
+# printed.
+never-returning-functions=sys.exit
+
+
+[BASIC]
+
+# Naming style matching correct argument names.
+argument-naming-style=snake_case
+
+# Regular expression matching correct argument names. Overrides argument-
+# naming-style.
+#argument-rgx=
+
+# Naming style matching correct attribute names.
+attr-naming-style=snake_case
+
+# Regular expression matching correct attribute names. Overrides attr-naming-
+# style.
+#attr-rgx=
+
+# Bad variable names which should always be refused, separated by a comma.
+bad-names=foo,
+ bar,
+ baz,
+ toto,
+ tutu,
+ tata
+
+# Bad variable names regexes, separated by a comma. If names match any regex,
+# they will always be refused
+bad-names-rgxs=
+
+# Naming style matching correct class attribute names.
+class-attribute-naming-style=any
+
+# Regular expression matching correct class attribute names. Overrides class-
+# attribute-naming-style.
+#class-attribute-rgx=
+
+# Naming style matching correct class names.
+class-naming-style=PascalCase
+
+# Regular expression matching correct class names. Overrides class-naming-
+# style.
+#class-rgx=
+
+# Naming style matching correct constant names.
+const-naming-style=UPPER_CASE
+
+# Regular expression matching correct constant names. Overrides const-naming-
+# style.
+#const-rgx=
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+# Naming style matching correct function names.
+function-naming-style=snake_case
+
+# Regular expression matching correct function names. Overrides function-
+# naming-style.
+#function-rgx=
+
+# Good variable names which should always be accepted, separated by a comma.
+good-names=i,
+ j,
+ k,
+ ex,
+ Run,
+ _
+
+# Good variable names regexes, separated by a comma. If names match any regex,
+# they will always be accepted
+good-names-rgxs=
+
+# Include a hint for the correct naming format with invalid-name.
+include-naming-hint=no
+
+# Naming style matching correct inline iteration names.
+inlinevar-naming-style=any
+
+# Regular expression matching correct inline iteration names. Overrides
+# inlinevar-naming-style.
+#inlinevar-rgx=
+
+# Naming style matching correct method names.
+method-naming-style=snake_case
+
+# Regular expression matching correct method names. Overrides method-naming-
+# style.
+#method-rgx=
+
+# Naming style matching correct module names.
+module-naming-style=snake_case
+
+# Regular expression matching correct module names. Overrides module-naming-
+# style.
+#module-rgx=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+# These decorators are taken in consideration only for invalid-name.
+property-classes=abc.abstractproperty
+
+# Naming style matching correct variable names.
+variable-naming-style=snake_case
+
+# Regular expression matching correct variable names. Overrides variable-
+# naming-style.
+#variable-rgx=
+
+
+[SIMILARITIES]
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+
+[TYPECHECK]
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# Tells whether to warn about missing members when the owner of the attribute
+# is inferred to be None.
+ignore-none=yes
+
+# This flag controls whether pylint should warn about no-member and similar
+# checks whenever an opaque object is returned when inferring. The inference
+# can return multiple potential results while evaluating a Python object, but
+# some branches might not be evaluated, which results in partial inference. In
+# that case, it might be useful to still emit no-member and other checks for
+# the rest of the inferred objects.
+ignore-on-opaque-inference=yes
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis). It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# Show a hint with possible names when a member name was not found. The aspect
+# of finding the hint is based on edit distance.
+missing-member-hint=yes
+
+# The minimum edit distance a name should have in order to be considered a
+# similar match for a missing member name.
+missing-member-hint-distance=1
+
+# The total number of similar names that should be taken in consideration when
+# showing a hint for a missing member.
+missing-member-max-choices=1
+
+# List of decorators that change the signature of a decorated function.
+signature-mutators=
+
+
+[VARIABLES]
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid defining new builtins when possible.
+additional-builtins=
+
+# Tells whether unused global variables should be treated as a violation.
+allow-global-unused-variables=yes
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,
+ _cb
+
+# A regular expression matching the name of dummy variables (i.e. expected to
+# not be used).
+dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore.
+ignored-argument-names=_.*|^ignored_|^unused_
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
+
+
+[STRING]
+
+# This flag controls whether inconsistent-quotes generates a warning when the
+# character used as a quote delimiter is used inconsistently within a module.
+check-quote-consistency=no
+
+# This flag controls whether the implicit-str-concat should generate a warning
+# on implicit string concatenation in sequences defined over several lines.
+check-str-concat-over-line-jumps=no
+
+
+[FORMAT]
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+# Maximum number of characters on a single line.
+max-line-length=100
+
+# Maximum number of lines in a module.
+max-module-lines=1000
+
+# Allow the body of a class to be on the same line as the declaration if body
+# contains single statement.
+single-line-class-stmt=no
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+
+[LOGGING]
+
+# The type of string formatting that logging methods do. `old` means using %
+# formatting, `new` is for `{}` formatting.
+logging-format-style=old
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format.
+logging-modules=logging
+
+
+[SPELLING]
+
+# Limits count of emitted suggestions for spelling mistakes.
+max-spelling-suggestions=4
+
+# Spelling dictionary name. Available dictionaries: none. To make it work,
+# install the python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains the private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to the private dictionary (see the
+# --spelling-private-dict-file option) instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,
+ XXX,
+ TODO
+
+# Regular expression of note tags to take in consideration.
+#notes-rgx=
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method.
+max-args=5
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Maximum number of boolean expressions in an if statement (see R0916).
+max-bool-expr=5
+
+# Maximum number of branch for function / method body.
+max-branches=12
+
+# Maximum number of locals for function / method body.
+max-locals=15
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of return / yield for function / method body.
+max-returns=6
+
+# Maximum number of statements in function / method body.
+max-statements=50
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+
+[IMPORTS]
+
+# List of modules that can be imported at any level, not just the top level
+# one.
+allow-any-import-level=
+
+# Allow wildcard imports from modules that define __all__.
+allow-wildcard-with-all=no
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+# Deprecated modules which should not be used, separated by a comma.
+deprecated-modules=optparse,tkinter.tix
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled).
+ext-import-graph=
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled).
+import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled).
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+# Couples of modules and preferred modules, separated by a comma.
+preferred-modules=
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,
+ __new__,
+ setUp,
+ __post_init__
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,
+ _fields,
+ _replace,
+ _source,
+ _make
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=cls
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "BaseException, Exception".
+overgeneral-exceptions=BaseException,
+ Exception
diff --git a/Dockerfile b/Dockerfile
@@ -6,3 +6,19 @@ add pyproject.toml .
add poetry.lock .
run poetry export --with dev > req.txt
run python3 -m pip install -r req.txt
+# Install docker
+run apt-get update
+run apt-get install ca-certificates curl gnupg lsb-release -y
+run mkdir -p /etc/apt/keyrings
+run curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
+run echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
+run apt-get update
+run apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin tree -y
+# Add jaypore_ci to this image
+add jaypore_ci/ /app/jaypore_ci
+run poetry build
+run ls -alR dist
+run python3 -m pip install dist/jaypore_ci-*.whl
+run rm -rf jaypore_ci dist
+run ls -alR .
+workdir /jaypore_ci/run/
diff --git a/README.md b/README.md
@@ -1,15 +1,74 @@
# Jaypore CI
-## Ideas
+ A CI system that sounds ancient and powerful. Like the city of Jaypore.
-1. Use developer laptop as a CI runner
- - Cloud runners should be added on demand / only if needed.
-2. Report logs / status in any pull request tracker as a comment.
- - Store files / etc in special git branches
+## Expected flow
-## Usage
+- `curl <link> | base` to install this in any repo.
+- Configure CI available at `.jaypore_ci/cicd.py`
+- Each git-push will trigger a CI job.
+
+## Use cases covered
-1. Add `jaypore_ci` as python dependency in your project.
-2. Define pipelines in Python however you want. See `cicd` folder in current project for examples.
-3. Add a docker-compose file to add a runner for the project.
+- Run offline / debug a job
+- Run on a cloud machine (more cores/ram /gpu / inside vpn)
+- Cache project dependencies in docker
+- Publish images / artifacts to docker / gitea
+## What I don't need
+
+- Spending money on CI for small/hobby/idea projects.
+- Lose my entire CI system if I move between gitlab / github / gitea / bitbucket.
+- Configure/worry about CI access for collaborators every time someone joins the project / leaves.
+- To be stuck without CI if I'm offline.
+- Trying to figure out how to get X/Y/Z done in the yaml/jsonnet config format for the CI of the day
+
+## Popular solutions that were considered
+
+System | Cause of rejection
+--------------------|-------------
+Github actions | non OSS, money, online only
+Gitlab CI | money, online only, heavy idle consumption
+Circle CI | money, online only
+Jenkins | heavy idle consumption, needs infra setup
+Travis CI | online only
+Agola ci | fragile, needs infra setup
+Drone CI | non OSS, needs infra setup
+Woodpecker CI | needs infra setup
+
+
+## What do I want?
+
+- Should work offline
+- One line install for any project. Something like `curl <link>|bash`
+- Zero infra other than docker.
+- CI configuration should be a proper programming language. I don't want to learn your custom flavour of yaml/jsonnet etc
+- Work with any remote like gitea / github / gitlab / bitbucket / email. Mainly gitea for now since that's what I use.
+- Has matrix jobs
+- Complex conditional jobs and dependencies
+- Needs to be able to run integration tests with services etc
+- Easy debugging. I don't want to debug someone else's system.
+
+## Installation
+
+1. Make sure you have `docker` installed on your machine.
+2. `cd ~/some/path/to/myrepo` You can be anywhere inside your project repo actually.
+3. `curl https://github.com/midpath/jaypore_ci | bash` to install `jaypore_ci` in your repository.
+4. `git add -Av && git commit -m 'added jaypore ci' && git push origin`.
+ - `Jaypore_ci` will run whenever you push to your remote.
+
+## Examples
+
+```python
+from jaypore_ci import jci
+
+with jci.Pipeline(
+ image="arjoonn/jaypore_ci:latest", # NOTE: Change this to whatever you need
+ timeout=15 * 60,
+) as p:
+ p.in_parallel(
+ p.job("python3 -m black --check .", name="Black"),
+ p.job("python3 -m pylint jaypore_ci/ tests/", name="PyLint"),
+ p.job("python3 -m pytest tests/", name="PyTest"),
+ ).should_pass()
+```
diff --git a/cicd/in_parallel.py b/cicd/in_parallel.py
@@ -1,12 +0,0 @@
-"""
-This pipeline runs jobs in parallel and waits for all of them to finish.
-"""
-from jaypore_ci import jci
-
-p = jci.Pipeline(image="python:3.11", timeout="15m")
-
-assert p.in_parallel(
- p.job("python3 -m black ."),
- p.job("python3 -m pylint src"),
- p.job("python3 -m pytest tests"),
-).ok()
diff --git a/cicd/in_sequence.py b/cicd/in_sequence.py
@@ -1,14 +0,0 @@
-"""
-This is one of the simplest pipelines we can have.
-
-It runs jobs one after the other.
-"""
-from jaypore_ci import jci
-
-p = jci.Pipeline(image="python:3.11", timeout="15m")
-
-assert p.in_sequence(
- p.job("python3 -m black ."),
- p.job("python3 -m pylint src"),
- p.job("python3 -m pytest tests"),
-).ok()
diff --git a/cicd/integration_test.py b/cicd/integration_test.py
@@ -1,37 +0,0 @@
-"""
-This example shows how integration testing can be done using:
- - Your own API
- - A static site built and served
- - A postgres database
- - A redis server
- - Your own integration tests
-"""
-from jaypore_ci import jci
-
-p = jci.Pipeline(
- image="python:3.11",
- timeout="15m",
- env={"DB_PWD": "simplepassword"},
-)
-
-with p.services(
- p.job(name="Redis cache", image="redis"),
- p.job(name="Database", image="postgres"),
- p.job(
- "yarn install",
- "yarn build",
- "python3 -m http.server",
- name="Static site",
- image="my/reactjs_env:v0.1",
- ),
- p.job(
- "python3 -m api",
- name="API service",
- image="my/django_env:v0.1",
- ),
-):
- assert p.in_sequence(
- p.job("python3 -m wait_for_services_to_be_up"),
- p.job("python3 -m myrepo.run_migrations"),
- p.job("python3 -m pytest tests -m integration_tests"),
- ).ok()
diff --git a/cicd/on_file_changes.py b/cicd/on_file_changes.py
@@ -1,21 +0,0 @@
-"""
-This example shows how you can run multiple jobs
-and make some of them only run when specific files have changed.
-"""
-from jaypore_ci import jci
-
-p = jci.Pipeline(image="python:3.11", timeout="15m")
-
-assert p.in_parallel(
- p.job("python3 -m black ."),
- (
- p.job("python3 -m pytest tests")
- if p.file_changes_in(*list(p.repo.path.glob("src/*")))
- else None
- ),
- (
- p.job("python3 -m cicd.check_changelog_format")
- if p.file_changes_in(p.repo.path / "CHANGELOG")
- else None
- ),
-).ok()
diff --git a/cicd/with_db.py b/cicd/with_db.py
@@ -1,32 +0,0 @@
-"""
-For example if you want to test your api server and need a database to run
-in order to do that.
-
-In this example we will run tests for a project that requires a redis server
-and a postgres server.
-"""
-from jaypore_ci import jci
-
-p = jci.Pipeline(
- image="python:3.11",
- timeout="15m",
- # These variables are available to the entire pipeline
- env={"POSTGRES_PASSWORD": "simplepassword"},
-)
-
-with p.services(
- p.job(image="redis"),
- p.job(
- image="postgres",
- # These variables can be used to configure the service
- env={"POSTGRES_INITDB_ARGS": "--data-checksums"},
- ),
-):
- assert p.in_sequence(
- p.job("python3 -m myrepo.run_migrations"),
- p.job(
- "python3 -m pytest tests",
- # These variables are merged with pipeline variables.
- env={"APP_REDIS_HOST": "redis", "APP_POSTGRES_HOST": "postgres"},
- ),
- ).ok()
diff --git a/compose/reference.yml b/compose/reference.yml
@@ -1,9 +0,0 @@
-version: '3.7'
-
-services:
- jaypore_ci_runner:
- image: jaypore_ci_runner:v0.1
- restart: always
- environment:
- GITEA_URL: "https://gitea.example.com"
- GITEA_TOKEN: "asdlfjwo223fkl"
diff --git a/jaypore_ci/__init__.py b/jaypore_ci/__init__.py
@@ -1 +1 @@
-__version__ = '0.1.0'
+__version__ = "0.1.0"
diff --git a/cicd/__init__.py b/jaypore_ci/__main__.py
diff --git a/jaypore_ci/docker.py b/jaypore_ci/docker.py
@@ -0,0 +1,114 @@
+import subprocess
+
+from rich import print as rprint
+
+from jaypore_ci.interfaces import Executor
+from jaypore_ci.logging import logger
+
+
+class Docker(Executor):
+ """
+ Run docker jobs
+ """
+
+ def check_output(self, cmd):
+ return (
+ subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
+ .decode()
+ .strip()
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.pipe_id = None
+
+ def logging(self):
+ return logger.bind(pipe_id=self.pipe_id, network_name=self.get_net())
+
+ def set_pipe_id(self, pipe_id):
+ if self.pipe_id is not None:
+ self.delete_network()
+ self.pipe_id = pipe_id
+ self.create_network()
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.delete_network()
+
+ def get_net(self):
+ return f"jaypore_{self.pipe_id}" if self.pipe_id is not None else None
+
+ def create_network(self):
+ assert self.pipe_id is not None, "Cannot create network if pipe is not set"
+ for _ in range(3):
+ net_ls = subprocess.run(
+ f"docker network ls | grep {self.get_net()}",
+ shell=True,
+ check=False,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
+ if net_ls.returncode == 0:
+ self.logging().info(
+ "Found network", network_name=self.get_net(), subprocess=net_ls
+ )
+ return net_ls
+ self.logging().info(
+ "Create network",
+ subprocess=self.check_output(
+ f"docker network create -d bridge {self.get_net()}"
+ ),
+ )
+ raise Exception("Cannot create network")
+
+ def delete_network(self):
+ assert self.pipe_id is not None, "Cannot delete network if pipe is not set"
+ self.logging().info(
+ "Delete network",
+ subprocess=self.check_output(
+ f"docker network rm {self.get_net()} || echo 'No such net'"
+ ),
+ )
+
+ def get_job_name(self, job):
+ name = "".join(
+ l
+ for l in job.name.lower().replace(" ", "_")
+ if l in "abcdefghijklmnopqrstuvwxyz_"
+ )
+ return f"{self.get_net()}_{name}"
+
+ def run(self, job: "Job") -> str:
+ assert self.pipe_id is not None, "Cannot run job if pipe id is not set"
+ self.pipe_id = id(job.pipeline) if self.pipe_id is None else self.pipe_id
+ env_vars = [f"--env {key}={val}" for key, val in job.get_env().items()]
+ trigger = [
+ "docker run -d",
+ "-v /var/run/docker.sock:/var/run/docker.sock",
+ f"-v /tmp/jaypore_{job.pipeline.remote.sha}:/jaypore_ci/run",
+ f"--name {self.get_job_name(job)}",
+ f"--network {self.get_net()}",
+ *env_vars,
+ job.image,
+ job.command,
+ ]
+ assert job.command
+ rprint(trigger)
+ return self.check_output(" ".join(trigger))
+
+ def get_status(self, run_id: str) -> (str, str):
+ ps_out = self.check_output(f"docker ps -f 'id={run_id}' --no-trunc")
+ is_running = run_id in ps_out
+ # --- exit code
+ exit_code = self.check_output(
+ f"docker inspect {run_id}" " --format='{{.State.ExitCode}}'"
+ )
+ exit_code = int(exit_code)
+ # --- logs
+ logs = self.check_output(f"docker logs {run_id}")
+ self.logging().debug(
+ "Check status",
+ run_id=run_id,
+ is_running=is_running,
+ exit_code=exit_code,
+ )
+ return is_running, exit_code, logs
diff --git a/jaypore_ci/gitea.py b/jaypore_ci/gitea.py
@@ -0,0 +1,130 @@
+import os
+import subprocess
+from pathlib import Path
+from urllib.parse import urlparse
+
+import requests
+from jaypore_ci.interfaces import Remote
+from jaypore_ci.logging import logger
+
+
+class Gitea(Remote): # pylint: disable=too-many-instance-attributes
+ @classmethod
+ def from_env(cls):
+ remote = (
+ subprocess.check_output(
+ "git remote -v | grep push | awk '{print $2}'", shell=True
+ )
+ .decode()
+ .strip()
+ )
+ assert "https://" in remote, "Only https remotes supported"
+ assert ".git" in remote
+ remote = urlparse(remote)
+ branch = (
+ subprocess.check_output(
+ r"git branch | grep \* | awk '{print $2}'", shell=True
+ )
+ .decode()
+ .strip()
+ )
+ sha = subprocess.check_output("git rev-parse HEAD", shell=True).decode().strip()
+ owner = Path(remote.path).parts[1]
+ repo = Path(remote.path).parts[2].replace(".git", "")
+ token = os.environ["JAYPORE_GITEA_TOKEN"]
+ return cls(
+ root=f"{remote.scheme}://{remote.netloc}",
+ owner=owner,
+ repo=repo,
+ branch=branch,
+ token=token,
+ sha=sha,
+ )
+
+ def __init__(
+ self, root, owner, repo, token, branch, sha
+ ): # pylint: disable=too-many-arguments
+
+ self.root = root
+ self.api = f"{root}/api/v1"
+ self.owner = owner
+ self.repo = repo
+ self.token = token
+ self.branch = branch
+ self.sha = sha
+ self.timeout = 10
+
+ def logging(self):
+ return logger.bind(
+ root=self.root, owner=self.owner, repo=self.repo, branch=self.branch
+ )
+
+ def get_pr_id(self):
+ r = requests.post(
+ f"{self.api}/repos/{self.owner}/{self.repo}/pulls",
+ params={"access_token": self.token},
+ timeout=self.timeout,
+ json={
+ "base": "main",
+ "body": "Branch auto created by JayporeCI",
+ "head": self.branch,
+ "title": self.branch,
+ },
+ )
+ self.logging().debug("Get PR Id", status_code=r.status_code)
+ if r.status_code == 409:
+ return r.text.split("issue_id:")[1].split(",")[0].strip()
+ if r.status_code == 201:
+ return self.get_pr_id()
+ raise Exception(r)
+
+ def publish(self, report: str, status: str):
+ """
+ report: Report to write to remote.
+ status: One of ["pending", "success", "error", "failure", "warning"]
+ """
+ assert status in ("pending", "success", "error", "failure", "warning")
+ issue_id = self.get_pr_id()
+ # Get existing PR body
+ r = requests.get(
+ f"{self.api}/repos/{self.owner}/{self.repo}/pulls/{issue_id}",
+ timeout=self.timeout,
+ params={"access_token": self.token},
+ )
+ self.logging().debug("Get existing body", status_code=r.status_code)
+ assert r.status_code == 200
+ body = r.json()["body"]
+ body = (line for line in body.split("\n"))
+ prefix = []
+ for line in body:
+ if "<summary>JayporeCi" in line:
+ prefix = prefix[:-1]
+ break
+ prefix.append(line)
+ while prefix and prefix[-1].strip() == "":
+ prefix = prefix[:-1]
+ prefix.append("")
+ # Post new body with report
+ report = "\n".join(prefix) + "\n" + report
+ r = requests.patch(
+ f"{self.api}/repos/{self.owner}/{self.repo}/pulls/{issue_id}",
+ data={"body": report},
+ timeout=self.timeout,
+ params={"access_token": self.token},
+ )
+ self.logging().debug("Published new report", status_code=r.status_code)
+ # Set commit status
+ r = requests.post(
+ f"{self.api}/repos/{self.owner}/{self.repo}/statuses/{self.sha}",
+ json={
+ "context": "JayporeCi",
+ "description": f"Pipeline status is: {status}",
+ "state": status,
+ "target_url": f"{self.root}/{self.owner}/{self.repo}/pulls/{issue_id}",
+ },
+ timeout=self.timeout,
+ params={"access_token": self.token},
+ )
+ self.logging().debug(
+ "Published new status", status=status, status_code=r.status_code
+ )
diff --git a/jaypore_ci/interfaces.py b/jaypore_ci/interfaces.py
@@ -0,0 +1,40 @@
+class Executor:
+ """
+ It can be docker / podman / shell etc.
+ Something that allows us to run a job.
+ """
+
+ def run(self, job: "Job") -> str:
+ "Run a job and return it's ID"
+ raise NotImplementedError()
+
+ def __init__(self):
+ self.pipe_id = None
+
+ def set_pipe_id(self, pipe_id):
+ self.pipe_id = pipe_id
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ pass
+
+
+class Remote:
+ """
+ It could be gitea / github / gitlab / email system.
+ Something that allows us to post the status of the CI.
+ """
+
+ def publish(self, report: str, status: str):
+ """
+ Publish this report somewhere.
+ """
+ raise NotImplementedError()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ pass
diff --git a/jaypore_ci/jci.py b/jaypore_ci/jci.py
@@ -1 +1,469 @@
-from jaypore_ci.primitives import Pipeline, Job
+import time
+from enum import Enum
+from itertools import product
+from collections import defaultdict, namedtuple
+from typing import List, Union, Callable
+
+import structlog
+import pendulum
+
+from jaypore_ci import gitea, docker
+from jaypore_ci.interfaces import Remote, Executor
+from jaypore_ci.logging import logger, jaypore_logs
+
+TZ = "UTC"
+
+
+class Status(Enum):
+ "Each pipeline can be in these statuses"
+ PENDING = 10
+ RUNNING = 30
+ FAILED = 40
+ PASSED = 50
+ TIMEOUT = 60
+ SKIPPED = 70
+
+
+# All of these statuses are considered "finished" statuses
+FIN_STATUSES = (Status.FAILED, Status.PASSED, Status.TIMEOUT, Status.SKIPPED)
+
+
+class Job: # pylint: disable=too-many-instance-attributes
+ """
+ This is the fundamental building block.
+ Each job goes through a lifecycle defined by `Status` class.
+ """
+
+ def __init__(
+ self,
+ name: str,
+ command: Union[str, Callable],
+ pipeline: "Pipeline",
+ *,
+ status: str = None,
+ image: str = None,
+ timeout: int = None,
+ env: dict = None,
+ children: List["Job"] = None,
+ parents: List["Job"] = None,
+ ):
+ self.name = name
+ self.command = command
+ self.image = image
+ self.status = status
+ self.timeout = timeout
+ self.pipeline = pipeline
+ self.env = env
+ self.children = children if children is not None else []
+ self.parents = parents if parents is not None else []
+ # --- run information
+ self.logs = defaultdict(list)
+ self.job_id = id(self)
+ self.run_id = None
+ self.run_start = None
+ self.last_check = None
+
+ def logging(self):
+ return self.pipeline.logging().bind(
+ job_id=self.job_id,
+ job_name=self.name,
+ run_id=self.run_id,
+ )
+
+ def update_report(self):
+ """
+ Update the report
+ Usually called when a job changes some of it's internal state like:
+ - logs
+ - status
+ - last_check
+ """
+ self.logging().debug("Update report")
+ status = {
+ Status.PENDING: "pending",
+ Status.RUNNING: "pending",
+ Status.FAILED: "failure",
+ Status.PASSED: "success",
+ Status.TIMEOUT: "warning",
+ Status.SKIPPED: "warning",
+ }[self.pipeline.get_status()]
+ self.pipeline.remote.publish(self.pipeline.render_report(), status)
+
+ def should_pass(self, *, is_internal_call=False):
+ """
+ This is the main thing. It allows you to run assertions on the job like:
+ assert job.should_pass()
+
+ This function will block until the status of the job is known.
+ It will also trigger and monitor all jobs required to obtain the status
+ for this job.
+ """
+ self.logging().info("Ok called")
+ if not is_internal_call:
+ self.pipeline.should_pass_called.add(self)
+ self.trigger()
+ self.monitor_until_completion()
+ self.logging().info("Ok finished", status=self.status)
+ self.update_report()
+ return self.status == Status.PASSED
+
+ def monitor_until_completion(self):
+ while not self.is_complete():
+ self.check_job()
+ time.sleep(1)
+ now = pendulum.now(TZ)
+ if (now - self.run_start).in_seconds() > self.timeout:
+ self.status = Status.TIMEOUT
+ self.logging().error(
+ "Timeout", seconds_elapsed=(now - self.run_start).in_seconds()
+ )
+ self.update_report()
+ break
+ self.check_job()
+
+ def get_graph(self):
+ """
+ Given the current job, builds a graph of all jobs that are it's
+ parents.
+
+ Returns a set of nodes & edges.
+ """
+ nodes = set([self])
+ edges = set()
+ for parent in self.parents:
+ edges.add((parent, self))
+ if parent not in nodes:
+ p_nodes, p_edges = parent.get_graph()
+ nodes |= set(p_nodes)
+ edges |= set(p_edges)
+ return list(sorted(nodes, key=lambda x: x.name)), list(
+ sorted(edges, key=lambda x: (x[0].name, x[1].name))
+ )
+
+ def trigger(self):
+ """
+ Trigger the job via the pipeline's executor.
+ This will immediately return and will not wait for the job to finish.
+ """
+ if self.status == Status.PENDING:
+ self.run_start = pendulum.now(TZ)
+ self.logging().info("Trigger called")
+ self.status = Status.RUNNING
+ if isinstance(self.command, str):
+ self.run_id = self.pipeline.executor.run(self)
+ else:
+ self.run_id = f"pyrun_{self.job_id}"
+ self.command(self)
+ self.logging().info("Trigger done")
+ else:
+ self.logging().info("Trigger called but job already running")
+ self.check_job()
+
+ def check_job(self):
+ self.logging().debug("Checking job run")
+ if isinstance(self.command, str):
+ is_running, exit_code, logs = self.pipeline.executor.get_status(self.run_id)
+ self.last_check = pendulum.now(TZ)
+ self.logging().debug(
+ "Job run status found", is_running=is_running, exit_code=exit_code
+ )
+ if is_running:
+ self.status = Status.RUNNING
+ else:
+ self.status = Status.PASSED if exit_code == 0 else Status.FAILED
+ log_lines = logs.split("\n")
+ for line in log_lines[len(self.logs["stdout"]) :]:
+ self.logging().debug(
+ f">>> {line.strip()}",
+ job_name=self.name,
+ run_id=self.run_id,
+ )
+ self.logs["stdout"] = log_lines
+ self.update_report()
+
+ def is_complete(self):
+ return self.status in FIN_STATUSES
+
+ def get_env(self):
+ return {**self.pipeline.env, **self.env}
+
+
+class Pipeline: # pylint: disable=too-many-instance-attributes
+ """
+ A pipeline acts as a controlling mechanism for multiple jobs.
+ We can use a pipeline to define:
+
+ - Running order of jobs. If they are to be run in sequence or in parallel.
+ - Common environment / timeout / configuration details.
+ - Where all to publish the CI report.
+ """
+
+ def __init__( # pylint: disable=too-many-arguments
+ self,
+ remote: Remote = None,
+ executor: Executor = None,
+ image: str = "python:3.11",
+ timeout: int = 15 * 60,
+ env: dict = None,
+ *,
+ graph_direction: str = "TB",
+ ) -> "Pipeline":
+ self.image = image
+ self.timeout = timeout
+ self.env = {} if env is None else env
+ self.jobs = []
+ self.should_pass_called = set()
+ self.remote = remote if remote is not None else gitea.Gitea.from_env()
+ self.executor = executor if executor is not None else docker.Docker()
+ self.graph_direction = graph_direction
+ self.executor.set_pipe_id(id(self))
+
+ def logging(self):
+ return logger.bind(
+ **{
+ **structlog.get_context(self.remote.logging()),
+ **structlog.get_context(self.executor.logging()),
+ "pipe_id": id(self),
+ }
+ )
+
+ def __enter__(self):
+ self.executor.__enter__()
+ self.remote.__enter__()
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.executor.__exit__(exc_type, exc_value, traceback)
+ self.remote.__exit__(exc_type, exc_value, traceback)
+ return False
+
+ def get_status(self):
+ pipe_status = Status.PENDING
+ for job in self.jobs:
+ if job.status == Status.RUNNING:
+ pipe_status = Status.RUNNING
+ break
+ for job in self.should_pass_called:
+ if job.is_complete():
+ pipe_status = job.status
+ break
+ return pipe_status
+
+ def get_status_dot(self):
+ if self.get_status() == Status.PASSED:
+ return "🟢"
+ if self.get_status() in (Status.FAILED, Status.TIMEOUT):
+ return "🔴"
+ if self.get_status() == Status.SKIPPED:
+ return "🔵"
+ return "🟡"
+
+ def render_report(self):
+ return f"""
+<details>
+ <summary>JayporeCi: {self.get_status_dot()} {self.remote.sha[:10]}</summary>
+
+{self.render_graph()}
+{self.render_logs()}
+
+</details>"""
+
+ def render_graph(self) -> str:
+ mermaid = ""
+ for job in self.should_pass_called:
+ nodes, edges = job.get_graph()
+ mermaid += f"""
+```mermaid
+graph {self.graph_direction}
+"""
+ ref = {n: f"N{i}" for i, n in enumerate(nodes)}
+ st_map = {
+ Status.PENDING: "pending",
+ Status.RUNNING: "running",
+ Status.FAILED: "failed",
+ Status.PASSED: "passed",
+ Status.TIMEOUT: "timeout",
+ Status.SKIPPED: "skipped",
+ }
+
+ for a, b in edges:
+ mermaid += f"""
+ {ref[a]}({a.name}):::{st_map[a.status]} --> {ref[b]}({b.name}):::{st_map[b.status]}"""
+ mermaid += """
+
+
+
+ classDef pending fill:#aaa, color:black, stroke:black,stroke-width:2px,stroke-dasharray: 5 5;
+ classDef skipped fill:#aaa, color:black, stroke:black,stroke-width:2px;
+ classDef assigned fill:#ddd, color:black, stroke:black,stroke-width:2px;
+ classDef running fill:#bae1ff,color:black,stroke:black,stroke-width:2px,stroke-dasharray: 5 5;
+ classDef passed fill:#88d8b0, color:black, stroke:black;
+ classDef failed fill:#ff6f69, color:black, stroke:black;
+ classDef timeout fill:#ffda9e, color:black, stroke:black;
+``` """
+ return mermaid
+
+ def render_logs(self):
+ all_logs = []
+ fake_job = namedtuple("fake_job", "name logs")(
+ "JayporeCi", {"stdout": jaypore_logs}
+ )
+ for job in [fake_job] + self.jobs:
+ job_log = []
+ for logname, stream in job.logs.items():
+ job_log += [f"============== {logname} ============="]
+ job_log += [line.strip() for line in stream]
+ if job_log:
+ all_logs += [
+ "- <details>",
+ f" <summary>Logs: {job.name}</summary>",
+ "",
+ " ```",
+ *[f" {line}" for line in job_log],
+ " ```",
+ "",
+ " </details>",
+ ]
+ return "\n".join(all_logs)
+
+ def job(
+ self,
+ *commands: List[str],
+ name: str,
+ image: str = None,
+ timeout: int = None,
+ env: dict = None,
+ ) -> Job:
+ job = Job(
+ name=name if name is not None else " ",
+ command="\n".join(commands),
+ status=Status.PENDING,
+ image=image if image is not None else self.image,
+ timeout=timeout if timeout is not None else self.timeout,
+ pipeline=self,
+ env=env if env is not None else {},
+ children=[],
+ )
+ self.jobs.append(job)
+ return job
+
+ def in_parallel(self, *jobs, image=None, timeout=None, env=None):
+ jobs = [job for job in jobs if job is not None]
+ timeout = (
+ max(
+ job.timeout if job.timeout is not None else self.timeout for job in jobs
+ )
+ if timeout is None
+ else timeout
+ )
+
+ def run_and_join(job_self):
+ job_self.logs["stdout"].append("Starting parallel run")
+ for job in jobs:
+ job_self.logs["stdout"].append(f"Trigger job: {job.job_id} {job.name}")
+ job.trigger()
+ something_is_running = True
+ while something_is_running:
+ time.sleep(1)
+ something_is_running = False
+ for job in jobs:
+ job.check_job()
+ job_self.logs["stdout"].append(
+ f"Checking: {job.job_id} {job.name} is_complete: {job.is_complete()}"
+ )
+ if not job.is_complete():
+ something_is_running = True
+ if (
+ job.is_complete()
+ and job.status != Status.PASSED
+ and job_self.status == Status.RUNNING
+ ):
+ job_self.status = Status.FAILED
+ msg = "Dependent job failed"
+ job_self.logging().error(msg, failed_job_id=job.job_id)
+ job_self.logs["stdout"].append(f"{msg}: {job.job_id}")
+ if job_self.status == Status.RUNNING:
+ job_self.status = Status.PASSED
+ job_self.logs["stdout"].append("Ok")
+
+ join = Job(
+ name="+",
+ command=run_and_join,
+ status=Status.PENDING,
+ image=self.image if image is None else image,
+ pipeline=self,
+ env={} if env is None else env,
+ children=[],
+ timeout=timeout,
+ parents=list(jobs),
+ )
+ self.jobs.append(join)
+ for job in jobs:
+ job.children.append(join)
+ return join
+
+ def in_sequence(self, *jobs, image=None, env=None, timeout=None):
+ jobs = [job for job in jobs if job is not None]
+
+ def run_seq(job_self):
+ job_self.logs["stdout"].append("Starting sequential run")
+ for job in jobs:
+ if job_self.status == Status.RUNNING:
+ job_self.logs["stdout"].append(
+ f"Running job: {job.job_id} {job.name}"
+ )
+ ok = job.should_pass(is_internal_call=True)
+ if not ok:
+ job_self.status = Status.FAILED
+ job_self.logs["stdout"].append(
+ f"Failed job: {job.job_id} {job.name}"
+ )
+ job_self.logging().error(
+ "Dependent job failed", failed_job_id=job.job_id
+ )
+ elif job_self.status == Status.FAILED:
+ job_self.logs["stdout"].append(
+ f"Skipping job: {job.job_id} {job.name}"
+ )
+ job.status = Status.SKIPPED
+ continue
+ if job_self.status == Status.RUNNING:
+ job_self.status = Status.PASSED
+ job_self.logs["stdout"].append("Ok")
+
+ last_job = None
+ for job in jobs:
+ if last_job is not None:
+ last_job.children.append(job)
+ job.parents.append(last_job)
+ last_job = job
+ # final chain job
+ timeout = (
+ sum(
+ job.timeout if job.timeout is not None else self.timeout for job in jobs
+ )
+ if timeout is None
+ else timeout
+ )
+ join = Job(
+ name="+",
+ command=run_seq,
+ status=Status.PENDING,
+ image=self.image if image is None else image,
+ pipeline=self,
+ env={} if env is None else env,
+ children=[],
+ timeout=timeout,
+ parents=[last_job],
+ )
+ self.jobs.append(join)
+ last_job.children.append(join)
+ return join
+
+ def env_matrix(self, **kwargs):
+ """
+ Return a cartesian product of all the provided kwargs
+ """
+ keys = list(sorted(kwargs.keys()))
+ for values in product(*[kwargs[key] for key in keys]):
+ yield dict(list(zip(keys, values)))
diff --git a/jaypore_ci/logging.py b/jaypore_ci/logging.py
@@ -0,0 +1,51 @@
+import logging
+from typing import Any
+
+import structlog
+
+# This is used to accumulate logs and is later sent over to the CI status as a
+# separate log list
+jaypore_logs = []
+
+
+class JayporeLogger:
+ def __getstate__(self) -> str:
+ return "stdout"
+
+ def __setstate__(self, state: Any) -> None:
+ pass
+
+ def __deepcopy__(self, memodict: dict[Any, Any] = None) -> "JayporeLogger":
+ return self.__class__()
+
+ def msg(self, message: str) -> None:
+ jaypore_logs.append(message)
+ print(message)
+
+ log = debug = info = warn = warning = msg
+ fatal = failure = err = error = critical = exception = msg
+
+
+class JayporeLoggerFactory:
+ def __init__(self):
+ pass
+
+ def __call__(self, *args) -> JayporeLogger:
+ return JayporeLogger()
+
+
+structlog.configure(
+ processors=[
+ structlog.contextvars.merge_contextvars,
+ structlog.processors.add_log_level,
+ # structlog.processors.StackInfoRenderer(),
+ # structlog.dev.set_exc_info,
+ structlog.processors.TimeStamper(fmt="iso"),
+ structlog.dev.ConsoleRenderer(colors=False),
+ ],
+ wrapper_class=structlog.make_filtering_bound_logger(logging.NOTSET),
+ context_class=dict,
+ logger_factory=JayporeLoggerFactory(),
+ cache_logger_on_first_use=False,
+)
+logger = structlog.get_logger()
diff --git a/jaypore_ci/primitives.py b/jaypore_ci/primitives.py
@@ -1,11 +0,0 @@
-class Pipeline:
- def __init__(self, image="python:3.11", timeout="15m"):
- self.image = image
- self.timeout = timeout
- self.__history__ = []
-
- def job(self, *commands, *, image=None, timeout=None):
- return self
-
- def ok(self):
- return self.last_command.exit_code == 0
diff --git a/poetry.lock b/poetry.lock
@@ -0,0 +1,684 @@
+[[package]]
+name = "astroid"
+version = "2.12.13"
+description = "An abstract syntax tree for Python with inference support."
+category = "dev"
+optional = false
+python-versions = ">=3.7.2"
+
+[package.dependencies]
+lazy-object-proxy = ">=1.4.0"
+typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""}
+wrapt = [
+ {version = ">=1.11,<2", markers = "python_version < \"3.11\""},
+ {version = ">=1.14,<2", markers = "python_version >= \"3.11\""},
+]
+
+[[package]]
+name = "attrs"
+version = "22.1.0"
+description = "Classes Without Boilerplate"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
+docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
+tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
+tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
+
+[[package]]
+name = "black"
+version = "22.10.0"
+description = "The uncompromising code formatter."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
+typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.7.4)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
+
+[[package]]
+name = "certifi"
+version = "2022.9.24"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "charset-normalizer"
+version = "2.1.1"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "main"
+optional = false
+python-versions = ">=3.6.0"
+
+[package.extras]
+unicode-backport = ["unicodedata2"]
+
+[[package]]
+name = "click"
+version = "8.1.3"
+description = "Composable command line interface toolkit"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+
+[[package]]
+name = "commonmark"
+version = "0.9.1"
+description = "Python parser for the CommonMark Markdown spec"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
+
+[[package]]
+name = "dill"
+version = "0.3.6"
+description = "serialize all of python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+graph = ["objgraph (>=1.7.2)"]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.0.4"
+description = "Backport of PEP 654 (exception groups)"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "idna"
+version = "3.4"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "iniconfig"
+version = "1.1.1"
+description = "iniconfig: brain-dead simple config-ini parsing"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "isort"
+version = "5.10.1"
+description = "A Python utility / library to sort Python imports."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1,<4.0"
+
+[package.extras]
+colors = ["colorama (>=0.4.3,<0.5.0)"]
+pipfile-deprecated-finder = ["pipreqs", "requirementslib"]
+plugins = ["setuptools"]
+requirements-deprecated-finder = ["pip-api", "pipreqs"]
+
+[[package]]
+name = "lazy-object-proxy"
+version = "1.8.0"
+description = "A fast and thorough lazy object proxy."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "mccabe"
+version = "0.7.0"
+description = "McCabe checker, plugin for flake8"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "mypy-extensions"
+version = "0.4.3"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "packaging"
+version = "21.3"
+description = "Core utilities for Python packages"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
+
+[[package]]
+name = "pathspec"
+version = "0.10.2"
+description = "Utility library for gitignore style pattern matching of file paths."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "pendulum"
+version = "2.1.2"
+description = "Python datetimes made easy"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+python-dateutil = ">=2.6,<3.0"
+pytzdata = ">=2020.1"
+
+[[package]]
+name = "platformdirs"
+version = "2.5.4"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"]
+test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
+
+[[package]]
+name = "pluggy"
+version = "1.0.0"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "pygments"
+version = "2.13.0"
+description = "Pygments is a syntax highlighting package written in Python."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+plugins = ["importlib-metadata"]
+
+[[package]]
+name = "pylint"
+version = "2.15.7"
+description = "python code static checker"
+category = "dev"
+optional = false
+python-versions = ">=3.7.2"
+
+[package.dependencies]
+astroid = ">=2.12.13,<=2.14.0-dev0"
+colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
+dill = ">=0.2"
+isort = ">=4.2.5,<6"
+mccabe = ">=0.6,<0.8"
+platformdirs = ">=2.2.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+tomlkit = ">=0.10.1"
+typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+spelling = ["pyenchant (>=3.2,<4.0)"]
+testutils = ["gitpython (>3)"]
+
+[[package]]
+name = "pyparsing"
+version = "3.0.9"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+category = "dev"
+optional = false
+python-versions = ">=3.6.8"
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
+name = "pytest"
+version = "7.2.0"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+attrs = ">=19.2.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
+
+[[package]]
+name = "python-dateutil"
+version = "2.8.2"
+description = "Extensions to the standard Python datetime module"
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "pytzdata"
+version = "2020.1"
+description = "The Olson timezone database for Python."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "requests"
+version = "2.28.1"
+description = "Python HTTP for Humans."
+category = "main"
+optional = false
+python-versions = ">=3.7, <4"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<3"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "rich"
+version = "12.6.0"
+description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
+category = "main"
+optional = false
+python-versions = ">=3.6.3,<4.0.0"
+
+[package.dependencies]
+commonmark = ">=0.9.0,<0.10.0"
+pygments = ">=2.6.0,<3.0.0"
+typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""}
+
+[package.extras]
+jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "structlog"
+version = "22.3.0"
+description = "Structured Logging for Python"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+dev = ["structlog[docs,tests,typing]"]
+docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "twisted"]
+tests = ["coverage[toml]", "freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"]
+typing = ["mypy", "rich", "twisted"]
+
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "tomlkit"
+version = "0.11.6"
+description = "Style preserving TOML library"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "typing-extensions"
+version = "4.4.0"
+description = "Backported and Experimental Type Hints for Python 3.7+"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "urllib3"
+version = "1.26.13"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+
+[[package]]
+name = "wrapt"
+version = "1.14.1"
+description = "Module for decorators, wrappers and monkey patching."
+category = "dev"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[metadata]
+lock-version = "1.1"
+python-versions = "^3.8"
+content-hash = "c2c3d36f8405c6ffef861c1112f8e0399f4f0f8629affd5e591d16daaa74511e"
+
+[metadata.files]
+astroid = [
+ {file = "astroid-2.12.13-py3-none-any.whl", hash = "sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907"},
+ {file = "astroid-2.12.13.tar.gz", hash = "sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7"},
+]
+attrs = [
+ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
+ {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
+]
+black = [
+ {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"},
+ {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"},
+ {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"},
+ {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"},
+ {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"},
+ {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"},
+ {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"},
+ {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"},
+ {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"},
+ {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"},
+ {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"},
+ {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"},
+ {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"},
+ {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"},
+ {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"},
+ {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"},
+ {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"},
+ {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"},
+ {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"},
+ {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"},
+ {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
+]
+certifi = [
+ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
+ {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
+]
+charset-normalizer = [
+ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
+ {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
+]
+click = [
+ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+ {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
+]
+colorama = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+commonmark = [
+ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
+ {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
+]
+dill = [
+ {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"},
+ {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"},
+]
+exceptiongroup = [
+ {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"},
+ {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"},
+]
+idna = [
+ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+]
+iniconfig = [
+ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
+ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
+]
+isort = [
+ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
+ {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
+]
+lazy-object-proxy = [
+ {file = "lazy-object-proxy-1.8.0.tar.gz", hash = "sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156"},
+ {file = "lazy_object_proxy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe"},
+ {file = "lazy_object_proxy-1.8.0-cp310-cp310-win32.whl", hash = "sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25"},
+ {file = "lazy_object_proxy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b"},
+ {file = "lazy_object_proxy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7"},
+ {file = "lazy_object_proxy-1.8.0-cp311-cp311-win32.whl", hash = "sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e"},
+ {file = "lazy_object_proxy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d"},
+ {file = "lazy_object_proxy-1.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c"},
+ {file = "lazy_object_proxy-1.8.0-cp37-cp37m-win32.whl", hash = "sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd"},
+ {file = "lazy_object_proxy-1.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858"},
+ {file = "lazy_object_proxy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada"},
+ {file = "lazy_object_proxy-1.8.0-cp38-cp38-win32.whl", hash = "sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f"},
+ {file = "lazy_object_proxy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c"},
+ {file = "lazy_object_proxy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288"},
+ {file = "lazy_object_proxy-1.8.0-cp39-cp39-win32.whl", hash = "sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f"},
+ {file = "lazy_object_proxy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0"},
+ {file = "lazy_object_proxy-1.8.0-pp37-pypy37_pp73-any.whl", hash = "sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891"},
+ {file = "lazy_object_proxy-1.8.0-pp38-pypy38_pp73-any.whl", hash = "sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec"},
+ {file = "lazy_object_proxy-1.8.0-pp39-pypy39_pp73-any.whl", hash = "sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8"},
+]
+mccabe = [
+ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
+ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
+]
+mypy-extensions = [
+ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
+packaging = [
+ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
+ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
+]
+pathspec = [
+ {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"},
+ {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"},
+]
+pendulum = [
+ {file = "pendulum-2.1.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe"},
+ {file = "pendulum-2.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739"},
+ {file = "pendulum-2.1.2-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:0731f0c661a3cb779d398803655494893c9f581f6488048b3fb629c2342b5394"},
+ {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3481fad1dc3f6f6738bd575a951d3c15d4b4ce7c82dce37cf8ac1483fde6e8b0"},
+ {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9702069c694306297ed362ce7e3c1ef8404ac8ede39f9b28b7c1a7ad8c3959e3"},
+ {file = "pendulum-2.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:fb53ffa0085002ddd43b6ca61a7b34f2d4d7c3ed66f931fe599e1a531b42af9b"},
+ {file = "pendulum-2.1.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:c501749fdd3d6f9e726086bf0cd4437281ed47e7bca132ddb522f86a1645d360"},
+ {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c807a578a532eeb226150d5006f156632df2cc8c5693d778324b43ff8c515dd0"},
+ {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2d1619a721df661e506eff8db8614016f0720ac171fe80dda1333ee44e684087"},
+ {file = "pendulum-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f888f2d2909a414680a29ae74d0592758f2b9fcdee3549887779cd4055e975db"},
+ {file = "pendulum-2.1.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e95d329384717c7bf627bf27e204bc3b15c8238fa8d9d9781d93712776c14002"},
+ {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4c9c689747f39d0d02a9f94fcee737b34a5773803a64a5fdb046ee9cac7442c5"},
+ {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1245cd0075a3c6d889f581f6325dd8404aca5884dea7223a5566c38aab94642b"},
+ {file = "pendulum-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:db0a40d8bcd27b4fb46676e8eb3c732c67a5a5e6bfab8927028224fbced0b40b"},
+ {file = "pendulum-2.1.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f5e236e7730cab1644e1b87aca3d2ff3e375a608542e90fe25685dae46310116"},
+ {file = "pendulum-2.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:de42ea3e2943171a9e95141f2eecf972480636e8e484ccffaf1e833929e9e052"},
+ {file = "pendulum-2.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7c5ec650cb4bec4c63a89a0242cc8c3cebcec92fcfe937c417ba18277d8560be"},
+ {file = "pendulum-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:33fb61601083f3eb1d15edeb45274f73c63b3c44a8524703dc143f4212bf3269"},
+ {file = "pendulum-2.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:29c40a6f2942376185728c9a0347d7c0f07905638c83007e1d262781f1e6953a"},
+ {file = "pendulum-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7"},
+ {file = "pendulum-2.1.2.tar.gz", hash = "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207"},
+]
+platformdirs = [
+ {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"},
+ {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"},
+]
+pluggy = [
+ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
+ {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
+]
+pygments = [
+ {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"},
+ {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"},
+]
+pylint = [
+ {file = "pylint-2.15.7-py3-none-any.whl", hash = "sha256:1d561d1d3e8be9dd880edc685162fbdaa0409c88b9b7400873c0cf345602e326"},
+ {file = "pylint-2.15.7.tar.gz", hash = "sha256:91e4776dbcb4b4d921a3e4b6fec669551107ba11f29d9199154a01622e460a57"},
+]
+pyparsing = [
+ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
+ {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+]
+pytest = [
+ {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"},
+ {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"},
+]
+python-dateutil = [
+ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
+ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+]
+pytzdata = [
+ {file = "pytzdata-2020.1-py2.py3-none-any.whl", hash = "sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f"},
+ {file = "pytzdata-2020.1.tar.gz", hash = "sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540"},
+]
+requests = [
+ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
+ {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
+]
+rich = [
+ {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"},
+ {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"},
+]
+six = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+structlog = [
+ {file = "structlog-22.3.0-py3-none-any.whl", hash = "sha256:b403f344f902b220648fa9f286a23c0cc5439a5844d271fec40562dbadbc70ad"},
+ {file = "structlog-22.3.0.tar.gz", hash = "sha256:e7509391f215e4afb88b1b80fa3ea074be57a5a17d794bd436a5c949da023333"},
+]
+tomli = [
+ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
+tomlkit = [
+ {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"},
+ {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"},
+]
+typing-extensions = [
+ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
+ {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
+]
+urllib3 = [
+ {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"},
+ {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"},
+]
+wrapt = [
+ {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
+ {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
+ {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
+ {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
+ {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
+ {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
+ {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
+ {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"},
+ {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
+ {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
+ {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
+ {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
+ {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
+ {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
+ {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
+ {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
+ {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
+ {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
+ {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
+ {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"},
+ {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
+ {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
+ {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
+ {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
+ {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
+ {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
+ {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
+ {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
+ {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"},
+ {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
+ {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
+ {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
+ {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
+ {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
+ {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
+ {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
+ {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
+ {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
+ {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"},
+ {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
+ {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
+ {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
+ {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
+ {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
+ {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
+ {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
+ {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
+ {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
+ {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"},
+ {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
+ {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
+ {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
+ {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
+ {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
+ {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
+]
diff --git a/pyproject.toml b/pyproject.toml
@@ -6,10 +6,20 @@ authors = ["arjoonn sharma <arjoonn.94@gmail.com>"]
[tool.poetry.dependencies]
python = "^3.8"
+requests = "^2.28.1"
+click = "^8.1.3"
+pendulum = "^2.1.2"
+rich = "^12.6.0"
+structlog = "^22.3.0"
[tool.poetry.dev-dependencies]
pytest = "^5.2"
+[tool.poetry.group.dev.dependencies]
+pylint = "^2.15.7"
+black = "^22.10.0"
+pytest = "^7.2.0"
+
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
diff --git a/setup.sh b/setup.sh
@@ -0,0 +1,87 @@
+set -o errexit
+set -o nounset
+set -o pipefail
+
+main (){
+ REPO_ROOT=$(git rev-parse --show-toplevel)
+ LOCAL_HOOK=$(echo $REPO_ROOT/.git/hooks/pre-push)
+ IMAGE='arjoonn/jaypore_ci:latest'
+ echo "Working in repo: $REPO_ROOT"
+ mkdir $REPO_ROOT/.jaypore_ci || echo 'Moving on..'
+ cat > $REPO_ROOT/.jaypore_ci/cicd.py << EOF
+from jaypore_ci import jci
+
+with jci.Pipeline(
+ image="$IMAGE", # NOTE: Change this to whatever you need
+ timeout=15 * 60
+) as p:
+ p.in_parallel(
+ p.job("pwd", name="Pwd"),
+ p.job("tree", name="Tree"),
+ p.job("python3 -m black --check .", name="Black"),
+ p.job("python3 -m pylint jaypore_ci/ tests/", name="PyLint"),
+ p.job("python3 -m pytest tests/", name="PyTest"),
+ ).should_pass()
+EOF
+
+ cat > $REPO_ROOT/.jaypore_ci/pre-push.githook << EOF
+#! /bin/bash
+#
+set -o errexit
+set -o nounset
+set -o pipefail
+
+
+main() {
+ SHA=\$(git rev-parse HEAD)
+ REPO_ROOT=\$(git rev-parse --show-toplevel)
+ TOKEN=\$(echo "url=\$(git remote -v|grep push|awk '{print \$2}')"|git credential fill|grep password|awk -F= '{print \$2}')
+ # We will mount the current dir into /jaypore/repo
+ # Then we will copy things over to /jaypore/run
+ # Then we will run git clean to remove anything that is not in git
+ # Then we call the actual cicd code
+ #
+ # We also pass docker.sock to the run so that jaypore_ci can create docker containers
+ echo '----------------------------------------------'
+ echo "JayporeCi: "
+ JAYPORE_GITEA_TOKEN="\${JAYPORE_GITEA_TOKEN:-\$TOKEN}" docker run \\
+ -d \\
+ --name jaypore_ci_\$SHA \\
+ -e JAYPORE_GITEA_TOKEN \\
+ -v /var/run/docker.sock:/var/run/docker.sock \\
+ -v \$REPO_ROOT:/jaypore/repo:ro \\
+ -v /tmp/jaypore_\$SHA:/jaypore/run \\
+ --workdir /jaypore/run \\
+ $IMAGE \\
+ bash -c 'cp -r /jaypore/repo/. /jaypore/run && cd /jaypore/run/ && git clean -fdx && python .jaypore_ci/cicd.py'
+ echo '----------------------------------------------'
+}
+(main)
+EOF
+ echo "Creating git hook for pre-commit"
+ chmod u+x $REPO_ROOT/.jaypore_ci/pre-push.githook
+
+ if test -f "$LOCAL_HOOK"; then
+ if test -f "$LOCAL_HOOK.local"; then
+ echo "$LOCAL_HOOK has already been moved once."
+ echo $LOCAL_HOOK
+ echo $LOCAL_HOOK.local
+ echo "Please link"
+ echo " Jaypore hook : $REPO_ROOT/.jaypore_ci/pre-push.githook"
+ echo "with"
+ echo " Existing hook: $LOCAL_HOOK"
+ echo "manually by editing the existing hook file"
+ echo "--------------------------------------"
+ echo "Stopping."
+ exit 1
+ else
+ echo "$LOCAL_HOOK exists. Moving to separate file"
+ mv $LOCAL_HOOK $REPO_ROOT/.git/hooks/pre-push.local
+ echo "$REPO_ROOT/.git/hooks/pre-push.local" >> $REPO_ROOT/.git/hooks/pre-push
+ fi
+ fi
+ echo "$REPO_ROOT/.jaypore_ci/pre-push.githook" >> $REPO_ROOT/.git/hooks/pre-push
+ chmod u+x $LOCAL_HOOK
+
+}
+(main)
diff --git a/tests/test_jaypore_ci.py b/tests/test_jaypore_ci.py
@@ -2,4 +2,4 @@ from jaypore_ci import __version__
def test_version():
- assert __version__ == '0.1.0'
+ assert __version__ == "0.1.0"