# Python Microsoft Fabric - Picking up results when a notebook finishes

Run a notebook and read what it wrote, or have Fabric call your service when it is done.

A notebook - a page of Spark code that runs in Fabric - often ends by writing its results to a table, and a system outside Fabric needs those rows. On this page, a service reads the rows from that table.

The service learns that the notebook is done in one of two ways. Either Zato starts the notebook and checks its status until it completes, or a Fabric schedule runs the notebook and Fabric calls Zato when it is finished. Both ways read the same table.

> **What you need from your Fabric admin**
>
> The workspace ID and the lakehouse ID, from the address bar when the lakehouse is open, and the notebook's item ID, from the address bar when the notebook is open - the part after `/synapsenotebooks/`. For the second way, permission to add a Web activity to the pipeline that runs the notebook.

## The table the notebook writes {#the-table-the-notebook-writes}

Whichever way it is run, the notebook writes the table `reminder_candidates`:

Reading it is one query, and both ways end by invoking this service:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class ReadResults(Service):

    name = 'results.read'

    def handle(self):

        workspace_id = '11111111-2222-3333-4444-555555555555'
        lakehouse_id = '66666666-7777-8888-9999-000000000000'

        conn = self.microsoft.fabric['My Fabric']

        sql = 'select appointment_id, location, starts_at from reminder_candidates'
        rows = conn.query(workspace_id, lakehouse_id, sql)

        for row in rows:
            appointment_id = row['appointment_id']
            location = row['location']
            starts_at = row['starts_at']
            self.logger.info(f'Reminder for {appointment_id} at {location}, {starts_at}')

        self.response.payload = {'rows': len(rows)}
```

## Zato runs the notebook {#zato-runs-the-notebook}

The first service starts the notebook and keeps the job's ID in the cache. A second service, scheduled every two minutes, checks the job's status, and when the job has completed, invokes `results.read` and deletes the job's ID from the cache.

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class StartNotebook(Service):

    name = 'results.start-notebook'

    def handle(self):

        workspace_id = '11111111-2222-3333-4444-555555555555'
        notebook_id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'

        conn = self.microsoft.fabric['My Fabric']

        # Start the notebook - the call returns as soon as Fabric has accepted the job ..
        job_id = conn.run_job(workspace_id, notebook_id, 'RunNotebook')

        # .. and store the job's ID for the check service.
        self.cache.set('results.notebook_job', job_id)

        self.response.payload = {'job_id': job_id}
```

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

_running = 'NotStarted', 'InProgress'

class CheckNotebook(Service):

    name = 'results.check-notebook'

    def handle(self):

        job_id = self.cache.get('results.notebook_job')

        # No job was started.
        if not job_id:
            return

        workspace_id = '11111111-2222-3333-4444-555555555555'
        notebook_id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'

        conn = self.microsoft.fabric['My Fabric']

        job = conn.get_job(workspace_id, notebook_id, job_id)
        status = job['status']

        # Still running - the next check is in two minutes.
        if status in _running:
            return

        # The job has ended, so its ID is no longer needed.
        self.cache.delete('results.notebook_job')

        # The notebook completed and the table is ready ..
        if status == 'Completed':
            self.invoke('results.read')

        # .. or it failed or was cancelled.
        else:
            self.logger.warning(f'Notebook job {job_id} ended with {status}')
```

Schedule `results.start-notebook` once a day at the time the results are needed and `results.check-notebook` every two minutes, both under `Scheduler → Config` in the Dashboard. While the notebook runs, Fabric's monitoring hub shows the job going from In progress to Completed:

## Fabric calls Zato {#fabric-calls-zato}

When a scheduled Fabric pipeline already runs the notebook, Zato does not start it. Instead, the pipeline gets one more step after the notebook - a Web activity that posts to a REST channel in Zato, and the channel's service is `results.read`.

Under `Connections → REST → Channels`, create the channel with the URL path `/api/fabric/results-ready`, the service `results.read` and an API key. In enmasse YAML:

```yaml
security:
  - name: Fabric Pipeline Key
    type: apikey
    username: fabric-pipeline
    password: Zato_Enmasse_Env.FabricPipelineKey

channel_rest:
  - name: api.fabric.results-ready
    service: results.read
    url_path: /api/fabric/results-ready
    security: Fabric Pipeline Key
    data_format: json
```

In the pipeline, add a Web activity after the notebook activity, connected on success, with the channel's full URL, method POST, the header `X-API-Key` set to the key, and an empty JSON object as the body:

From then on, `results.read` runs each time the notebook completes.

## See also {#see-also}

- [Running a pipeline and refreshing a report](https://zato.io/docs/dev/examples/cloud/fabric/pipelines-and-reports.html) - Running a job and waiting for it inside one service
- [Letting Fabric pipelines read your local systems](https://zato.io/docs/dev/examples/cloud/fabric/local-systems.html) - The other thing a pipeline can do with a REST channel
- [Jobs](https://zato.io/docs/dev/examples/cloud/fabric/api/jobs.html) - run\_job, get\_job and cancel\_job in detail

## Learn more {#learn-more}

- [Development documentation](https://zato.io/docs/dev/) - Everything about writing services, in one place
- [Requests and responses](https://zato.io/docs/dev/request-response/) - What a service receives, what it returns and how to shape both
- [Integration examples](https://zato.io/docs/dev/examples/) - Ready-made code for the systems you are likely to connect to
- [IDE and debugging](https://zato.io/docs/dev/ide/) - Write services in the Dashboard or in your own editor
- [Data models](https://zato.io/docs/dev/model/) - Declare inputs and outputs and have them validated for you
- [In-depth API tutorial](https://zato.io/tutorials/main/01.html) - The full platform tutorial, from installation to production patterns
