Jaypore CI

> Jaypore CI: Minimal, Offline, Local CI system.
Log | Files | Refs | README

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:
A.gitignore | 1+
A.jaypore_ci/cicd.py | 13+++++++++++++
A.jaypore_ci/pre-push.githook | 32++++++++++++++++++++++++++++++++
A.pylintrc | 518+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MDockerfile | 16++++++++++++++++
MREADME.md | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Dcicd/in_parallel.py | 12------------
Dcicd/in_sequence.py | 14--------------
Dcicd/integration_test.py | 37-------------------------------------
Dcicd/on_file_changes.py | 21---------------------
Dcicd/with_db.py | 32--------------------------------
Dcompose/reference.yml | 9---------
Mjaypore_ci/__init__.py | 2+-
Rcicd/__init__.py -> jaypore_ci/__main__.py | 0
Ajaypore_ci/docker.py | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajaypore_ci/gitea.py | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajaypore_ci/interfaces.py | 40++++++++++++++++++++++++++++++++++++++++
Mjaypore_ci/jci.py | 470++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Ajaypore_ci/logging.py | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Djaypore_ci/primitives.py | 11-----------
Apoetry.lock | 684+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpyproject.toml | 10++++++++++
Asetup.sh | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/test_jaypore_ci.py | 2+-
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"