petisco


Namepetisco JSON
Version 0.37.6 PyPI version JSON
download
home_pagehttps://github.com/alice-biometrics/petisco
SummaryPetisco is a framework for helping Python developers to build clean Applications
upload_time2021-01-07 12:43:09
maintainer
docs_urlNone
authorALiCE Biometrics
requires_python
licenseMIT
keywords ddd use case clean architecture rest applications
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # petisco :cookie:  [![version](https://img.shields.io/github/release/alice-biometrics/petisco/all.svg)](https://github.com/alice-biometrics/petisco/releases) [![ci](https://github.com/alice-biometrics/petisco/workflows/ci/badge.svg)](https://github.com/alice-biometrics/petisco/actions) [![pypi](https://img.shields.io/pypi/dm/petisco)](https://pypi.org/project/petisco/)

<img src="https://github.com/alice-biometrics/custom-emojis/blob/master/images/alice_header.png" width=auto>

Petisco is a framework for helping Python developers to build clean Applications in Python.

:warning: disclaimer: not stable yet


## Table of Contents
- [Installation :computer:](#installation-computer)
- [Getting Started :chart_with_upwards_trend:](#getting-started-chart_with_upwards_trend)
    * [Flask Application (by petisco :cookie:)](#flask-application-by-petisco-cookie)
    * [Configure your Application :rocket:](#configure-your-application-rocket)
    * [Logging](#logging)
    * [Handlers](#handlers)
      - [Controller Handler](#controller-handler)
    * [Model your Domain](#model-your-domain)
      - [Value Objects](#value-objects)
      - [Aggregate Root](#aggregate-root)
      - [Events](#events)
      - [Webhooks](#webhooks)
- [Testing :white_check_mark:](#testing-white_check_mark)
- [Extras](#extras)
- [Contact :mailbox_with_mail:](#contact-mailbox_with_mail)


## Installation :computer:

```console
pip install petisco
```

Installation with Extras 

```console
pip install petisco[flask]
pip install petisco[sqlalchemy]
pip install petisco[redis]
pip install petisco[rabbitmq]
pip install petisco[fixtures]
pip install petisco[flask,sqlalchemy,redis,rabbitmq,fixtures]
```

## Getting Started :chart_with_upwards_trend:	

### Flask Application (by Petisco :cookie:)

Check the following repo to learn how to use petisco with flask: [petisco-task-manager](https://github.com/alice-biometrics/petisco-task-manager)

### Configure your Application :rocket:

Configure your app using the `petisco.yml`

```yaml
app:
  name: taskmanager
  version:
    from_file: VERSION
tasks:
  recurring-task:
    run_in: 5 # seconds
    interval: 10 # seconds
    handler: taskmanager.tasks.recurring_task
  scheduled-task:
    run_in: 10 # seconds
    handler: taskmanager.tasks.scheduled_task
  instant-task:
    handler: taskmanager.tasks.instant_task
framework:
    selected_framework: flask
    config_file: swagger.yaml
    port: 8080
    port_env: PETISCO_PORT
logger:
    selected_logger: logging
    name: petisco
    format: "%(name)s - %(levelname)s - %(message)s"
    config: taskmanager.src.config.logging.logging_config
persistence:
  config: taskmanager.src.config.persistence.config_persistence
  models:
    task: taskmanager.src.modules.tasks.infrastructure.persistence.models.task_model.TaskModel
    event: taskmanager.src.modules.events.infrastructure.persistence.models.event_model.EventModel
providers:
   services_provider: taskmanager.src.config.services.services_provider
   repositories_provider: taskmanager.src.config.repositories.repositories_provider
events:
  publish_deploy_event: True
  publisher:
    provider: taskmanager.src.config.events.publisher_provider
  subscriber:
    provider: taskmanager.src.config.events.subscriber_provider
    subscribers:
      store-event:
        organization: acme
        service: taskmanager
        topic: taskmanager-events
        dead_letter: True
        handler: taskmanager.src.modules.events.application.store.event_store.event_store
```

For instance, if your app don't need cron dispatchers, events persistence and repositories, you can remove it from the `petisco.yml`:

```yaml
app:
  name: taskmanager-nopersistence
  version:
    from_file: VERSION
framework:
    selected_framework: flask
    config_file: swagger.yaml
    port: 8080
    port_env: PETISCO_PORT
logger:
    selected_logger: logging
    name: petisco
    format: "%(name)s - %(levelname)s - %(message)s"
    config: taskmanager.src.config.logging.logging_config
providers:
   services_provider: taskmanager.src.config.services.services_provider
```

### Logging

If you use a logging-based logger

```yaml
logger:
    selected_logger: logging # <---
    name: petisco
    format: "%(name)s - %(levelname)s - %(message)s"
    config: taskmanager.src.config.logging.logging_config
```

You can set logging level with the environment variable `PETISCO_LOGGING_LEVEL`.

Options:

```
PETISCO_LOGGING_LEVEL: DEBUG
PETISCO_LOGGING_LEVEL: INFO
PETISCO_LOGGING_LEVEL: WARNING
PETISCO_LOGGING_LEVEL: ERROR
PETISCO_LOGGING_LEVEL: CRITICAL
```

### Handlers

**petisco** implement a sort of decorator to handle common behaviour of application elements.

#### Controller Handler

Add it to your entry point controller and manage the behaviour:

```python
    from petisco import controller_handler
    from meiga import Success

    @controller_handler()
    def my_controller(headers=None):
        return Success("Hello Petisco")
```
*controller_handler parameters:*

    Parameters
    ----------
    app_name
        Application Name. If not specified it will get it from Petisco.get_app_version().
    app_version
        Application Version. If not specified it will get it from Petisco.get_app_version().
    logger
        A ILogger implementation. If not specified it will get it from Petisco.get_logger(). You can also use NotImplementedLogger
    token_manager
        TokenManager object. Here, you can define how to deal with JWT Tokens
    success_handler
        Handler to deal with Success Results
    error_handler
        Handler to deal with Failure Results
    headers_provider
        Injectable function to provide headers. By default is used headers_provider
    logging_types_blacklist
        Logging Blacklist. Object of defined Type will not be logged. By default ( [bytes] ) bytes object won't be logged.
    publisher
        A IEventPublisher implementation. If not specified it will get it from Petisco.get_event_publisher().
    send_request_responded_event
        Boolean to select if RequestResponded event is send. It will use provided publisher
    """

### Model your Domain


#### Value Objects

Extend `ValueObject` to model your Value Objects.

Find some examples in [petisco/domain/value_objects](petisco/domain/value_objects)

#### Aggregate Root

Extend `AggregateRoot` to model your Aggregate Roots

```python
from petisco import AggregateRoot, UserId, Name
from my_code import UserCreated

class User(AggregateRoot):

    def __init__(self, name: Name, user_id: UserId):
        self.name = name
        self.user_id = user_id
        super().__init__()

    @staticmethod
    def create(name: Name):
        user = User(name, UserId.generate())
        user.record(UserCreated(user.user_id, user.name))
        return user
```

Use semantic constructors and `record` domain `Event`s very easy.

```python 
user = User.create(Name("Petisco"))
events = user.pull_domain_events() # Events ready to be published
```

#### Events

Extend `Event` to model your domain events.

```python
from petisco import Event, UserId, Name

class UserCreated(Event):
    user_id: UserId
    name: Name

    def __init__(self, user_id: UserId, name: Name):
        self.user_id = user_id
        self.name = name
        super().__init__()
```

To prevent the propagation of Id parameters throughout your domain, you can compose your Event with a [`InfoId`](petisco/domain/aggregate_roots/info_id.py)

```python
user_created = UserCreated(user_id, name).add_info_id(info_id)
```

How can we publish and consume events?

* We publish events using an `EventBus`, and
* Consume events using an `EventConsumer`.

To learn more about this topic, and how to configure it, please take a look to [EventManagement](doc/events/EventManagement.md) documentation.

#### Webhooks

Create your webhooks easily with `Webhook` class. 

Check how to use it with a simple example:

1. Run a [service](examples/webhooks/subscribed_app.py) that will expose an entry point where webhooks will be attended:
    ```console
    export FLASK_APP=examples/webhooks/subscribed_app.py; python -m flask run
    ```
2. Execute a [Webhook](examples/webhooks/execute_webhook.py):
     ```console
    python examples/webhooks/execute_webhook.py
    ```


### Testing :white_check_mark:

###### Petisco Fixtures

Import useful petisco fixtures with :

```python
from petisco.fixtures import *
```

We can use [petisco_client](petisco/fixtures/client.py) to simulate our client in acceptance tests

```python
import pytest

@pytest.mark.acceptance
@pytest.mark.persistence_source("acme")
def test_should_return_200_when_call_healthcheck(
    petisco_client
):
    response = petisco_client.get("/petisco/environment")
    assert response.status_code == 200
```

Included in *petisco_client* we can find [petisco_sql_database](petisco/fixtures/persistence.py).
This fixture will create and connect a database and after the test this will be deleted.

Note that to use these fixtures you must indicate with a marker the persistence source. In the above example the 
persistence source is named `acme`.


#### Extras

###### RabbitMQ <img src="https://github.com/alice-biometrics/custom-emojis/blob/master/images/rabbitmq.png" width="16">

To test RabbitEventManager you need to run locally a RabbitMQ application, otherwise related test will be skipped.
Please, check the official doc here: https://www.rabbitmq.com/download.html

Run RabbitMQ with docker

```console
docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
```

Please, check examples in [examples/pubsub](examples/pubsub)

Run a Subscriber

```console
python examples/pubsub/sub.py
```

Run a Publisher:

```console
python examples/pubsub/pub.py
```

Run a Subscriber linked to a dead letter queues.

```console
python examples/pubsub/dl_sub.py
```

This can be used to requeue nack events.


##### Configurations

* `RABBITMQ_HEARTBEAT`: (default: 60 s)
* `RABBITMQ_USER`: (default: guest)
* `RABBITMQ_PASSWORD`: (default: guest)
* `RABBITMQ_HOST`: (default: localhost)
* `RABBITMQ_HOST`: (default: 5672)
* `RABBITMQ_CONNECTION_NUM_MAX_RETRIES`: (default: 15)
* `RABBITMQ_CONNECTION_WAIT_SECONDS_RETRY`: (default: 1)
* `RABBITMQ_MESSAGE_TTL`: (default 1000 ms) If a queue is already created it will generate a precodition failure.


## Development

### Using lume

```console
pip install lume
```

Then:

```console 
lume -install -all
```


## Contact :mailbox_with_mail:

support@alicebiometrics.com



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/alice-biometrics/petisco",
    "name": "petisco",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "DDD,Use Case,Clean Architecture,REST,Applications",
    "author": "ALiCE Biometrics",
    "author_email": "support@alicebiometrics.com",
    "download_url": "https://files.pythonhosted.org/packages/c1/1d/889bf125fffad3918a393d2ed8e4215216c2f45489914585a9160dce2a45/petisco-0.37.6.tar.gz",
    "platform": "",
    "description": "# petisco :cookie:  [![version](https://img.shields.io/github/release/alice-biometrics/petisco/all.svg)](https://github.com/alice-biometrics/petisco/releases) [![ci](https://github.com/alice-biometrics/petisco/workflows/ci/badge.svg)](https://github.com/alice-biometrics/petisco/actions) [![pypi](https://img.shields.io/pypi/dm/petisco)](https://pypi.org/project/petisco/)\n\n<img src=\"https://github.com/alice-biometrics/custom-emojis/blob/master/images/alice_header.png\" width=auto>\n\nPetisco is a framework for helping Python developers to build clean Applications in Python.\n\n:warning: disclaimer: not stable yet\n\n\n## Table of Contents\n- [Installation :computer:](#installation-computer)\n- [Getting Started :chart_with_upwards_trend:](#getting-started-chart_with_upwards_trend)\n    * [Flask Application (by petisco :cookie:)](#flask-application-by-petisco-cookie)\n    * [Configure your Application :rocket:](#configure-your-application-rocket)\n    * [Logging](#logging)\n    * [Handlers](#handlers)\n      - [Controller Handler](#controller-handler)\n    * [Model your Domain](#model-your-domain)\n      - [Value Objects](#value-objects)\n      - [Aggregate Root](#aggregate-root)\n      - [Events](#events)\n      - [Webhooks](#webhooks)\n- [Testing :white_check_mark:](#testing-white_check_mark)\n- [Extras](#extras)\n- [Contact :mailbox_with_mail:](#contact-mailbox_with_mail)\n\n\n## Installation :computer:\n\n```console\npip install petisco\n```\n\nInstallation with Extras \n\n```console\npip install petisco[flask]\npip install petisco[sqlalchemy]\npip install petisco[redis]\npip install petisco[rabbitmq]\npip install petisco[fixtures]\npip install petisco[flask,sqlalchemy,redis,rabbitmq,fixtures]\n```\n\n## Getting Started :chart_with_upwards_trend:\t\n\n### Flask Application (by Petisco :cookie:)\n\nCheck the following repo to learn how to use petisco with flask: [petisco-task-manager](https://github.com/alice-biometrics/petisco-task-manager)\n\n### Configure your Application :rocket:\n\nConfigure your app using the `petisco.yml`\n\n```yaml\napp:\n  name: taskmanager\n  version:\n    from_file: VERSION\ntasks:\n  recurring-task:\n    run_in: 5 # seconds\n    interval: 10 # seconds\n    handler: taskmanager.tasks.recurring_task\n  scheduled-task:\n    run_in: 10 # seconds\n    handler: taskmanager.tasks.scheduled_task\n  instant-task:\n    handler: taskmanager.tasks.instant_task\nframework:\n    selected_framework: flask\n    config_file: swagger.yaml\n    port: 8080\n    port_env: PETISCO_PORT\nlogger:\n    selected_logger: logging\n    name: petisco\n    format: \"%(name)s - %(levelname)s - %(message)s\"\n    config: taskmanager.src.config.logging.logging_config\npersistence:\n  config: taskmanager.src.config.persistence.config_persistence\n  models:\n    task: taskmanager.src.modules.tasks.infrastructure.persistence.models.task_model.TaskModel\n    event: taskmanager.src.modules.events.infrastructure.persistence.models.event_model.EventModel\nproviders:\n   services_provider: taskmanager.src.config.services.services_provider\n   repositories_provider: taskmanager.src.config.repositories.repositories_provider\nevents:\n  publish_deploy_event: True\n  publisher:\n    provider: taskmanager.src.config.events.publisher_provider\n  subscriber:\n    provider: taskmanager.src.config.events.subscriber_provider\n    subscribers:\n      store-event:\n        organization: acme\n        service: taskmanager\n        topic: taskmanager-events\n        dead_letter: True\n        handler: taskmanager.src.modules.events.application.store.event_store.event_store\n```\n\nFor instance, if your app don't need cron dispatchers, events persistence and repositories, you can remove it from the `petisco.yml`:\n\n```yaml\napp:\n  name: taskmanager-nopersistence\n  version:\n    from_file: VERSION\nframework:\n    selected_framework: flask\n    config_file: swagger.yaml\n    port: 8080\n    port_env: PETISCO_PORT\nlogger:\n    selected_logger: logging\n    name: petisco\n    format: \"%(name)s - %(levelname)s - %(message)s\"\n    config: taskmanager.src.config.logging.logging_config\nproviders:\n   services_provider: taskmanager.src.config.services.services_provider\n```\n\n### Logging\n\nIf you use a logging-based logger\n\n```yaml\nlogger:\n    selected_logger: logging # <---\n    name: petisco\n    format: \"%(name)s - %(levelname)s - %(message)s\"\n    config: taskmanager.src.config.logging.logging_config\n```\n\nYou can set logging level with the environment variable `PETISCO_LOGGING_LEVEL`.\n\nOptions:\n\n```\nPETISCO_LOGGING_LEVEL: DEBUG\nPETISCO_LOGGING_LEVEL: INFO\nPETISCO_LOGGING_LEVEL: WARNING\nPETISCO_LOGGING_LEVEL: ERROR\nPETISCO_LOGGING_LEVEL: CRITICAL\n```\n\n### Handlers\n\n**petisco** implement a sort of decorator to handle common behaviour of application elements.\n\n#### Controller Handler\n\nAdd it to your entry point controller and manage the behaviour:\n\n```python\n    from petisco import controller_handler\n    from meiga import Success\n\n    @controller_handler()\n    def my_controller(headers=None):\n        return Success(\"Hello Petisco\")\n```\n*controller_handler parameters:*\n\n    Parameters\n    ----------\n    app_name\n        Application Name. If not specified it will get it from Petisco.get_app_version().\n    app_version\n        Application Version. If not specified it will get it from Petisco.get_app_version().\n    logger\n        A ILogger implementation. If not specified it will get it from Petisco.get_logger(). You can also use NotImplementedLogger\n    token_manager\n        TokenManager object. Here, you can define how to deal with JWT Tokens\n    success_handler\n        Handler to deal with Success Results\n    error_handler\n        Handler to deal with Failure Results\n    headers_provider\n        Injectable function to provide headers. By default is used headers_provider\n    logging_types_blacklist\n        Logging Blacklist. Object of defined Type will not be logged. By default ( [bytes] ) bytes object won't be logged.\n    publisher\n        A IEventPublisher implementation. If not specified it will get it from Petisco.get_event_publisher().\n    send_request_responded_event\n        Boolean to select if RequestResponded event is send. It will use provided publisher\n    \"\"\"\n\n### Model your Domain\n\n\n#### Value Objects\n\nExtend `ValueObject` to model your Value Objects.\n\nFind some examples in [petisco/domain/value_objects](petisco/domain/value_objects)\n\n#### Aggregate Root\n\nExtend `AggregateRoot` to model your Aggregate Roots\n\n```python\nfrom petisco import AggregateRoot, UserId, Name\nfrom my_code import UserCreated\n\nclass User(AggregateRoot):\n\n    def __init__(self, name: Name, user_id: UserId):\n        self.name = name\n        self.user_id = user_id\n        super().__init__()\n\n    @staticmethod\n    def create(name: Name):\n        user = User(name, UserId.generate())\n        user.record(UserCreated(user.user_id, user.name))\n        return user\n```\n\nUse semantic constructors and `record` domain `Event`s very easy.\n\n```python \nuser = User.create(Name(\"Petisco\"))\nevents = user.pull_domain_events() # Events ready to be published\n```\n\n#### Events\n\nExtend `Event` to model your domain events.\n\n```python\nfrom petisco import Event, UserId, Name\n\nclass UserCreated(Event):\n    user_id: UserId\n    name: Name\n\n    def __init__(self, user_id: UserId, name: Name):\n        self.user_id = user_id\n        self.name = name\n        super().__init__()\n```\n\nTo prevent the propagation of Id parameters throughout your domain, you can compose your Event with a [`InfoId`](petisco/domain/aggregate_roots/info_id.py)\n\n```python\nuser_created = UserCreated(user_id, name).add_info_id(info_id)\n```\n\nHow can we publish and consume events?\n\n* We publish events using an `EventBus`, and\n* Consume events using an `EventConsumer`.\n\nTo learn more about this topic, and how to configure it, please take a look to [EventManagement](doc/events/EventManagement.md) documentation.\n\n#### Webhooks\n\nCreate your webhooks easily with `Webhook` class. \n\nCheck how to use it with a simple example:\n\n1. Run a [service](examples/webhooks/subscribed_app.py) that will expose an entry point where webhooks will be attended:\n    ```console\n    export FLASK_APP=examples/webhooks/subscribed_app.py; python -m flask run\n    ```\n2. Execute a [Webhook](examples/webhooks/execute_webhook.py):\n     ```console\n    python examples/webhooks/execute_webhook.py\n    ```\n\n\n### Testing :white_check_mark:\n\n###### Petisco Fixtures\n\nImport useful petisco fixtures with :\n\n```python\nfrom petisco.fixtures import *\n```\n\nWe can use [petisco_client](petisco/fixtures/client.py) to simulate our client in acceptance tests\n\n```python\nimport pytest\n\n@pytest.mark.acceptance\n@pytest.mark.persistence_source(\"acme\")\ndef test_should_return_200_when_call_healthcheck(\n    petisco_client\n):\n    response = petisco_client.get(\"/petisco/environment\")\n    assert response.status_code == 200\n```\n\nIncluded in *petisco_client* we can find [petisco_sql_database](petisco/fixtures/persistence.py).\nThis fixture will create and connect a database and after the test this will be deleted.\n\nNote that to use these fixtures you must indicate with a marker the persistence source. In the above example the \npersistence source is named `acme`.\n\n\n#### Extras\n\n###### RabbitMQ <img src=\"https://github.com/alice-biometrics/custom-emojis/blob/master/images/rabbitmq.png\" width=\"16\">\n\nTo test RabbitEventManager you need to run locally a RabbitMQ application, otherwise related test will be skipped.\nPlease, check the official doc here: https://www.rabbitmq.com/download.html\n\nRun RabbitMQ with docker\n\n```console\ndocker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management\n```\n\nPlease, check examples in [examples/pubsub](examples/pubsub)\n\nRun a Subscriber\n\n```console\npython examples/pubsub/sub.py\n```\n\nRun a Publisher:\n\n```console\npython examples/pubsub/pub.py\n```\n\nRun a Subscriber linked to a dead letter queues.\n\n```console\npython examples/pubsub/dl_sub.py\n```\n\nThis can be used to requeue nack events.\n\n\n##### Configurations\n\n* `RABBITMQ_HEARTBEAT`: (default: 60 s)\n* `RABBITMQ_USER`: (default: guest)\n* `RABBITMQ_PASSWORD`: (default: guest)\n* `RABBITMQ_HOST`: (default: localhost)\n* `RABBITMQ_HOST`: (default: 5672)\n* `RABBITMQ_CONNECTION_NUM_MAX_RETRIES`: (default: 15)\n* `RABBITMQ_CONNECTION_WAIT_SECONDS_RETRY`: (default: 1)\n* `RABBITMQ_MESSAGE_TTL`: (default 1000 ms) If a queue is already created it will generate a precodition failure.\n\n\n## Development\n\n### Using lume\n\n```console\npip install lume\n```\n\nThen:\n\n```console \nlume -install -all\n```\n\n\n## Contact :mailbox_with_mail:\n\nsupport@alicebiometrics.com\n\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Petisco is a framework for helping Python developers to build clean Applications",
    "version": "0.37.6",
    "split_keywords": [
        "ddd",
        "use case",
        "clean architecture",
        "rest",
        "applications"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "md5": "4d5b7c70632fab0b52226f1ae54607b7",
                "sha256": "c7f12b8096e181973c8e75a5c7de2973811136aca431dafdd99d33ec49ae6737"
            },
            "downloads": -1,
            "filename": "petisco-0.37.6-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "4d5b7c70632fab0b52226f1ae54607b7",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 244599,
            "upload_time": "2021-01-07T12:43:07",
            "upload_time_iso_8601": "2021-01-07T12:43:07.749219Z",
            "url": "https://files.pythonhosted.org/packages/b4/c0/6e1a7dbed8c8f21be9a76288ca6f95b81c449b2ac3f216eef41831f1dde9/petisco-0.37.6-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "md5": "b9e680b7dd87f3b5d2468f552b1d1dd2",
                "sha256": "655339fecff4c137e878383b1d66570fb6e3a3c8f49fd999973096e74e2749f3"
            },
            "downloads": -1,
            "filename": "petisco-0.37.6.tar.gz",
            "has_sig": false,
            "md5_digest": "b9e680b7dd87f3b5d2468f552b1d1dd2",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 131071,
            "upload_time": "2021-01-07T12:43:09",
            "upload_time_iso_8601": "2021-01-07T12:43:09.608741Z",
            "url": "https://files.pythonhosted.org/packages/c1/1d/889bf125fffad3918a393d2ed8e4215216c2f45489914585a9160dce2a45/petisco-0.37.6.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2021-01-07 12:43:09",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": null,
    "github_project": "alice-biometrics",
    "error": "Could not fetch GitHub repository",
    "lcname": "petisco"
}
        
Elapsed time: 0.24011s