The idea is that this package provides everything you need to run a basic Python service.

The config (as configured using dashboard.op-dev.io) as available here:

```
from op_service.config import OP_DEV_CONFIG
```

You can declare JSON endpoints that can be called by op-dev clients using the following (they can be async of not):

```
@service.route('/my-api', { 'first_name': non_empty_str, 'last_name': non_empty_str, '[age]': int)
async def my_api(data):
  ...
```

If you want APIs with authentication, make sure you configure your JWT settings in your service config entry, then you
can use the following:

```
@service.jwt({ 'user_id': uuid_str })
@service.route('/my-api', { 'first_name': non_empty_str, 'last_name': non_empty_str, '[age]': int)
async def my_api(profile, data):
  ...
```

You can use `nullable` around the profile specification to make it optional:

```
@service.jwt(nullable({ 'user_id': uuid_str }))
...
```

If you choose to use JWT authentication, you need to implement a route that users can use to acquire a JWT (for
example, using an OAuth service like Google):

```
from google.oauth2 import id_token
from google.auth.transport import requests

@service.route('/get-jwt', { 'payload': non_empty_str })
def get_jwt(data):
  client_id = config['google_app_client_id'] + '.apps.googleusercontent.com'
    google_profile = id_token.verify_oauth2_token(data['payload'], requests.Request(), client_id)
    if google_profile['iss'] in [ 'accounts.google.com', 'https://accounts.google.com' ]:
      return service.jwt.encode({ 'email': google_profile['email'] })
    raise AuthenticationException()
```

That particular profile could then be used to call other APIs:

```
@service.jwt(nullable({ 'email': email_address }))
@service.route('/send-test-email', { 'msg': non_empty_str })
def send_test_email(profile, data):
  # TODO: send a test email to profile['email'] with the body from data['msg']
```

Or you could create a route that uses a simple API key for authentication (and still retrieves a JWT containing a
profile):

```
@service.route('/get-jwt', { 'api_key': non_empty_str })
def get_jwt(data):
  # TODO: check that this is a valid key and return the appropriate encoded profile if so
  return service.jwt.encode({ 'user_id': 'VALID_USER_ID' })
```


```
pip3 uninstall op-service -y
python3 setup.py clean --all && \
python3 setup.py build && \
python3 setup.py install --force && \
rm -rf build dist op_service.egg-info
```


To build the packages for distribution (or for testing in the context of other packages / services), first build it for
distribution in the 'dist' directory using this command (note that you'll want to make sure to increment the version
number so downstream dependencies pick up the changes), then make it available locally using a pypi package server
(TODO: make this hot-reloading by watching the package directory [except 'build' and 'dist'] for changes and run this
command automatically):

```
python3 setup.py clean --all && python3 setup.py build && python3 setup.py sdist && pypi-server run -p 8081 .
```

The from downstream packages / service, you can link to the package using `--extra-index-url` - you can do this inside
a `requirements.txt` file, but then you'd have to be careful to increment the version number every time you made a
change, and it would force the Docker layer to reinstall ALL your dependencies every time. Therefore, I like to simply
add this as a last line in the Dockerfile:

```
RUN pip install --no-deps --force-reinstall --index-url http://host.docker.internal:8081/simple/ --trusted-host host.docker.internal op-service
```

You also used the following to automatically reload when the docker image for the dependent package rebuilds (cache
busting) for rapid iteration:

```
ADD "https://www.random.org/cgi-bin/randbyte?nbytes=10&format=h" skipcache
RUN pip install --no-deps --force-reinstall --index-url http://host.docker.internal:8081/simple/ --trusted-host host.docker.internal op-service
```
