Repository: elastic/apm-agent-python

Issue:
FastApi / Starlette setup without client argument not working
**Describe the bug**: 

The `ElasticAPM` middleware for Starlette appears to be missing a default value for the `client` parameter, resulting in a `TypeError` when using the middleware without explicitly providing a `client`. 

**To Reproduce**

1. Add the `ElasticAPM` middleware without explicitly providing a `client`.
2. Observe the `TypeError` mentioned above.

**Environment (please complete the following information)**
- OS: Linux
- Python version: 3.11
- Framework and version [e.g. Django 2.1]: fastapi 0.104.1
- APM Server version: ---
- Agent version: 6.20.0


**Additional context**

Add any other context about the problem here.

### Error:

The `ElasticAPM` middleware should allow for the `client` parameter to be optional, as indicated in the documentation, and should default to `None` if not provided.
From docs: https://www.elastic.co/guide/en/apm/agent/python/current/starlette-support.html

 ```
from starlette.applications import Starlette
from elasticapm.contrib.starlette import ElasticAPM

app = Starlette()
app.add_middleware(ElasticAPM)
```

Result:
 ```
.venv/lib/python3.11/site-packages/fastapi/applications.py", line 1015, in build_middleware_stack
    app = cls(app=app, **options)
          ^^^^^^^^^^^^^^^^^^^^^^^
TypeError: ElasticAPM.__init__() missing 1 required positional argument: 'client'
```

in the doc string shows that client its optional in type, but doesnt have None as default value, so passing:
 ```
app.add_middleware(ElasticAPM, client=None)
```

it seems to work.

### Proposed Solution
Change the `ElasticAPM` constructor signature to include `Optional[Client] = None` for the `client` parameter, making it optional with a default value of `None` in https://github.com/elastic/apm-agent-python/blob/4f5661277becc1034ee588bae4b018a4b22cc02b/elasticapm/contrib/starlette/__init__.py#L108C41-L108C41.

```python
def __init__(self, app: ASGIApp, client: Optional[Client] = None, **kwargs) -> None:
    """
    Args:
        app (ASGIApp): Starlette app
        client (Optional[Client]): ElasticAPM Client
    """
    ```





Discussion:
Good find! @gabriel-f-santos, we would love to see a PR if you're willing. You've done all the legwork, so you deserve the credit! But if you don't have time, I can open one.



> Good find! @gabriel-f-santos, we would love to see a PR if you're willing. You've done all the legwork, so you deserve the credit! But if you don't have time, I can open one.

Thanks for the quick response! I appreciate the offer, and I'm more than happy to open a pull request to address this issue. I'll work on the changes and submit the PR as soon as possible.

Resolving patch:
diff --git a/elasticapm/contrib/starlette/__init__.py b/elasticapm/contrib/starlette/__init__.py
--- a/elasticapm/contrib/starlette/__init__.py
+++ b/elasticapm/contrib/starlette/__init__.py
@@ -105,7 +105,7 @@ class ElasticAPM:
     >>> elasticapm.capture_message('hello, world!')
     """
 
-    def __init__(self, app: ASGIApp, client: Optional[Client], **kwargs) -> None:
+    def __init__(self, app: ASGIApp, client: Optional[Client] = None, **kwargs) -> None:
         """
 
         Args:


Test patch:
diff --git a/tests/contrib/asyncio/starlette_tests.py b/tests/contrib/asyncio/starlette_tests.py
--- a/tests/contrib/asyncio/starlette_tests.py
+++ b/tests/contrib/asyncio/starlette_tests.py
@@ -534,3 +534,11 @@ def test_transaction_active_in_base_exception_handler(app, elasticapm_client):
         assert exc.transaction_id
 
     assert len(elasticapm_client.events[constants.TRANSACTION]) == 1
+
+
+def test_middleware_without_client_arg():
+    with mock.patch.dict("os.environ", {"ELASTIC_APM_SERVICE_NAME": "foo"}):
+        app = Starlette()
+        elasticapm = ElasticAPM(app)
+
+    assert elasticapm.client.config.service_name == "foo"