Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some more information to the docs #81

Merged
merged 5 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,46 @@
# Platformics
---
Platformics is a GraphQL API framework that relies on code generation to implement a full featured GraphQL API on top of a PostgreSQL database, with support for authorization policy enforcement and file persistence via S3.

Platformics is a GraphQL API framework that relies on code generation to implement a full featured GraphQL API on top of a PostgreSQL database, with support for authorization policy enforcement and file persistence via S3. It's built on top of the best available Python tools and frameworks!

The libraries and tools that make Platformics work:

![image](docs/images/platformics_libs.svg)

### Links to these tools/libraries
- [LinkML](https://linkml.io/) - Schema modeling language
- [FastAPI](https://fastapi.tiangolo.com/) - Async HTTP router
- [Strawberry](https://strawberry.rocks/) - GraphQL Framework
- [Pydantic](https://docs.pydantic.dev/latest/) - Data validation
- [Cerbos](https://www.cerbos.dev/) - Authorization
- [SQLAlchemy](https://www.sqlalchemy.org/) - Database Access / ORM
- [factory_boy](https://factoryboy.readthedocs.io/en/stable/) - Test fixtures
- [Alembic](https://alembic.sqlalchemy.org/en/latest/) - Database migrations

## Current Features
- [x] Express your schema in a straightforward YAML format
- [x] GraphQL Dataloader pattern (no n+1 queries!)
- [x] Authorization policy enforcement
- [x] Flexible Filtering
- [x] Data aggregation
- [x] Top-level pagination
- [x] Relationship traversal
- [x] DB migrations
- [x] Generated Test fixtures
- [x] pytest wiring
- [x] VSCode debugger integration
- [x] Authorized S3 file up/downloads
- [x] Add custom REST endpoints to generated API
- [x] Add custom GQL queries/mutations to generated API

## Roadmap
- [ ] Plugin hooks to add business logic to generated GQL resolvers
- [ ] Support arbitrary class inheritance hierarchies

## How to set up your own platformics API
1. Copy the test_app boilerplate code to your own repository.
2. Edit `schema/schema.yml` to reflect your application's data model.
3. Run `make build` and then `make init` to build and run your own GraphQL API service.
4. Browse to http://localhost:9009 to interact with your api!
4. Browse to http://localhost:9009/graphql to interact with your api!
5. Run `make token` to generate an authorization token that you can use to interact with the API. The `make` target copies the necessary headers to the system clipboard. Paste the token into the `headers` section at the bottom of the GraphQL explorer API

## Iterating on your schema
Expand All @@ -26,6 +60,10 @@ Platformics is a GraphQL API framework that relies on code generation to impleme
3. Click the "Run and Debug" icon in the icon bar on the right side of the VSCode window (or ctrl+shift+d). Then click the "start debugging" icon at the top of the run and debug panel (or press F5). This will launch a secondary instance of the API service that listens on port 9008.
4. Set all the breakpoints you want. Browse to the api at http://localhost:9008 to trigger them. Remember that the application restarts when files change, so you'll have to start and stop the debugger to pick up any changes you make!

## HOWTO
- [Extend the generated API](docs/HOWTO-extend-generated-api.md)
- [Customize Codegen templates](docs/HOWTO-customize-templates.md)

## Contributing
This project adheres to the Contributor Covenant code of conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to [email protected].

Expand Down
23 changes: 23 additions & 0 deletions docs/HOWTO-customize-templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# How To: Customize codegen templates
Platformics supports replacing one codegen template with another, either globally or for a specific type.


## Use a different template by default
1. Find the template that you want to replace in the [base platformics template directory](https://github.com/chanzuckerberg/platformics/tree/main/platformics/codegen/templates)
2. Create a directory that will contain your overriden templates, such as `template_overrides`
3. Copy that template to your overrides directory with the same path relative to `templates` in the platformics repo. For example, if you want to override `platformics/codegen/templates/database/models/class_name.py.j2`, copy it to `template_overrides/database/models/class_name.py.j2`
4. Modify the template as much as you want
5. When you run codegen, include the overrides folder as a parameter to the codegen tool. For example, update the `codegen` target in the `Makefile` for your project directory to look like:
```
$(docker_compose_run) $(APP_CONTAINER) platformics api generate --schemafile ./schema/schema.yaml --template-override-paths template_overrides --output-prefix .
```

## Use a different template for a specific class
1. Find the template that you want to replace in the [base platformics template directory](https://github.com/chanzuckerberg/platformics/tree/main/platformics/codegen/templates). Note that this **only works for files named `class_name.*`**!
2. Create a directory that will contain your overriden templates, such as `template_overrides`
3. Copy that template to your overrides directory with the same path relative to `templates` in the platformics repo, but the **filename needs to reflect the camel_case class name**. For example, if you want to override `platformics/codegen/templates/database/models/class_name.py.j2`, for a class called `MyData`, copy it to `template_overrides/database/models/my_data.py.j2`
4. Modify the template as much as you want
5. When you run codegen, include the overrides folder as a parameter to the codegen tool. For example, update the `codegen` target in the `Makefile` for your project directory to look like:
```
$(docker_compose_run) $(APP_CONTAINER) platformics api generate --schemafile ./schema/schema.yaml --template-override-paths template_overrides --output-prefix .
```
53 changes: 40 additions & 13 deletions docs/custom_code.md → docs/HOWTO-extend-generated-api.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,62 @@
# How To: Extending Platformics Generated Code
# How To: Extending Platformics Generated API's
Platformics will generate a GraphQL API that exposes CRUD operations for each entity type in your LinkML schema. It is often the case, however, that you will need to expose more functionality in your API than just CRUD. There are a few options for extending generated code to add custom business logic to your application.

## Adding root-level queries and mutations
Platformics will generate basic read and aggregate queries in `api/queries.py`. These are inherited by a query class in `custom_queries.py`, which will gain access to all the codegenned queries as well as allow you to implement your own queries. To add a new root level field (query), add the field to the query class in `custom_queries.py`:

## Adding custom REST endpoints
Platformics primarily generates a GraphQL API, but there are certain functionalities (such as file downloads) that aren't practical to expose via GraphQL. Fortunately, Platformics is built on top of [FastAPI](https://fastapi.tiangolo.com/), a popular REST API framework. Adding custom endpoints is fairly straightforward:

```python
@strawberry.type
class Query(BaseQuery):
foo: str = my_custom_field
# your_app/main.py

# This code is already in main.py (app is a standard FastAPI application)
app = get_app(settings, schema, models)

# This adds an extra REST endpoint at GET http://localhost:9009/example
@app.get("/example")
def read_root():
return {"Hello": "World"}

```
The resolver for the custom field should be decorated with `@strawberry.field`:

## Adding root-level GraphQL queries and mutations
Platformics will generate basic read and aggregate queries in `api/queries.py`. These are inherited by a query class in `custom_queries.py`, which will gain access to all the codegenned queries as well as allow you to implement your own queries. To add a new root level field (query), add the field to the query class in `custom_queries.py`:

```python
# your_app/custom_queries.py

import strawberry
from api.queries import Query as BaseQuery

# The resolver for the custom field should be decorated with `@strawberry.field`:
@strawberry.field
def my_custom_field(self) -> str:
return "foo"

@strawberry.type
class Query(BaseQuery):
foo: str = my_custom_field
```

Similarly, for custom mutations, root level mutations can be added to the `Mutation` class in `custom_mutations.py`, which inherits from the codegenned `Mutation` class in `api/mutations.py`

```python
@strawberry.type
class Mutation(BaseMutation):
bar: str = my_custom_mutation
```
The resolver for the custom mutation should be decorated with `@strawberry.mutation`:
```python
# your_app/custom_mutations.py
import strawberry
from api.mutations import Mutation as BaseMutation

# The resolver for the custom mutation should be decorated with `@strawberry.mutation`:
@strawberry.mutation
def my_custom_mutation(self) -> str:
return "bar"

@strawberry.type
class Mutation(BaseMutation):
bar: str = my_custom_mutation
```

Both the custom query and mutation classes are imported into `main.py` and used by Strawberry to generate an API at runtime:
```python
# your_app/main.py
from custom_mutations import Mutation
from custom_queries import Query

Expand All @@ -40,6 +66,7 @@ schema = strawberry.Schema(query=Query, mutation=Mutation, config=get_strawberry
### Overriding queries and mutations
By creating a field with the same name as an existing codegenned query or mutation, you can override the codegenned resolver.
```python
# your_app/custom_mutations.py
@strawberry.mutation
def my_override_mutation(self) -> str:
return "Sorry, I overrode the mutation"
Expand Down
1 change: 1 addition & 0 deletions docs/images/platformics_libs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading