@@ -83,7 +83,7 @@ To build the database tables run: | |||
## credits | |||
Content in the Experimental Publishing Compendium is © 2022–2023 [COPIM](https://copim.ac.uk) and licensed under a [Creative Commons Attribution 4.0 International License (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/). | |||
Content in the Experimental Publishing Compendium is © 2022–2025 [COPIM](https://copim.ac.uk) and licensed under a [Creative Commons Attribution 4.0 International License (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/). | |||
Book cover data is from the [Open Library Covers API](https://openlibrary.org/dev/docs/api/covers). | |||
@@ -1,8 +1,8 @@ | |||
version: "3.9" | |||
services: | |||
web: | |||
build: ./web | |||
container_name: python | |||
restart: unless-stopped | |||
expose: | |||
- 5000 | |||
volumes: |
@@ -1,8 +1,8 @@ | |||
version: "3.0" | |||
services: | |||
web: | |||
build: ./web | |||
container_name: python | |||
restart: unless-stopped | |||
ports: | |||
- "5000:5000" | |||
volumes: |
@@ -33,7 +33,7 @@ def get_books(): | |||
intro_text = markdown.markdown(intro_text) | |||
# DATABASE QUERY | |||
books_query = Resource.query.filter_by(type=resource_type) | |||
books_query = Resource.query.filter_by(type=resource_type).filter_by(published=True) | |||
# FILTERING | |||
for key in request.args.keys(): | |||
@@ -52,7 +52,7 @@ def get_books(): | |||
# FILTERS MENU | |||
# get values for filter menu dropdowns | |||
# practices | |||
practices_filter = Resource.query.filter_by(type='practice').with_entities(Resource.id, Resource.name).all() | |||
practices_filter = Resource.query.filter_by(type='practice').filter_by(published=True).with_entities(Resource.id, Resource.name).all() | |||
# year | |||
year_filter = get_filter_values('year', resource_type) | |||
# typology |
@@ -50,10 +50,24 @@ def test(): | |||
# route for about page | |||
@main.route('/about') | |||
def about(): | |||
# get last updated date | |||
last_updated = get_last_date() | |||
with open('content/about.md', 'r') as f: | |||
about_text = f.read() | |||
about_text = markdown.markdown(about_text) | |||
main_text = f.read() | |||
main_text = markdown.markdown(main_text) | |||
with open('content/colophon.md', 'r') as f: | |||
colophon_text = f.read() | |||
colophon_text = markdown.markdown(colophon_text) | |||
return render_template('about.html', about_text=about_text, colophon_text=colophon_text) | |||
sidebar_text = f.read() | |||
sidebar_text = markdown.markdown(sidebar_text) | |||
return render_template('about.html', main_text=main_text, sidebar_text=sidebar_text, last_updated=last_updated) | |||
# route for events page | |||
@main.route('/practice_seminars') | |||
@main.route('/expub_seminars') | |||
def events(): | |||
with open('content/seminar.md', 'r') as f: | |||
main_text = f.read() | |||
main_text = markdown.markdown(main_text) | |||
with open('content/seminar_details.md', 'r') as f: | |||
sidebar_text = f.read() | |||
sidebar_text = markdown.markdown(sidebar_text) | |||
return render_template('about.html', main_text=main_text, sidebar_text=sidebar_text) |
@@ -26,9 +26,11 @@ class Resource(db.Model): | |||
# all resource types | |||
id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy | |||
created = db.Column(db.DateTime, default=datetime.utcnow) | |||
published = db.Column(db.Boolean) | |||
type = db.Column(db.Text) | |||
name = db.Column(db.Text) | |||
description = db.Column(db.Text) | |||
videoUrl = db.Column(db.Text) | |||
# tools | |||
developer = db.Column(db.Text) | |||
developerUrl = db.Column(db.Text) |
@@ -35,7 +35,7 @@ def get_practices(): | |||
intro_text = markdown.markdown(intro_text) | |||
# DATABASE QUERY | |||
practices_query = Resource.query.filter_by(type=resource_type) | |||
practices_query = Resource.query.filter_by(type=resource_type).filter_by(published=True) | |||
# temporarily removing incomplete practices from main list | |||
practices_query = practices_query.filter( |
@@ -18,12 +18,12 @@ def get_relationships(primary_id): | |||
links = [] | |||
for relationship in primary_relationships: | |||
secondary_id = relationship.second_resource_id | |||
links.extend(Resource.query.filter_by(id=secondary_id).all()) | |||
links.extend(Resource.query.filter_by(id=secondary_id).filter_by(published=True).all()) | |||
secondary_relationships = Relationship.query.filter_by(second_resource_id=primary_id).all() | |||
if secondary_relationships: | |||
for relationship in secondary_relationships: | |||
primary_id = relationship.first_resource_id | |||
links.extend(Resource.query.filter_by(id=primary_id).all()) | |||
links.extend(Resource.query.filter_by(id=primary_id).filter_by(published=True).all()) | |||
return links | |||
else: | |||
secondary_relationships = Relationship.query.filter_by(second_resource_id=primary_id).all() | |||
@@ -31,7 +31,7 @@ def get_relationships(primary_id): | |||
links = [] | |||
for relationship in secondary_relationships: | |||
primary_id = relationship.first_resource_id | |||
links.extend(Resource.query.filter_by(id=primary_id).all()) | |||
links.extend(Resource.query.filter_by(id=primary_id).filter_by(published=True).all()) | |||
return links | |||
# function to append relationships to a resource object |
@@ -15,6 +15,7 @@ from .relationships import * | |||
from isbntools.app import * | |||
import requests | |||
import re | |||
import json | |||
from sqlalchemy.sql import func | |||
import markdown | |||
@@ -36,6 +37,11 @@ def get_full_resource(resource_id): | |||
book_data = get_book_data(resource.isbn) | |||
if book_data: | |||
resource.__dict__.update(book_data) | |||
# if there's a GitHub repository link, get last GitHub commit date | |||
if resource.repositoryUrl and "github" in resource.repositoryUrl: | |||
# get commit date | |||
date = get_commit_date(resource.repositoryUrl) | |||
resource.__dict__['commitDate'] = date | |||
return resource | |||
# function to get practice from Markdown file | |||
@@ -76,7 +82,7 @@ def extract_first_paragraph(markdown): | |||
# function to retrieve data about a curated list of resources | |||
def get_curated_resources(resource_ids): | |||
resources = Resource.query.filter(Resource.id.in_(resource_ids)).order_by(func.random()).all() | |||
resources = Resource.query.filter(Resource.id.in_(resource_ids)).filter_by(published=True).order_by(func.random()).all() | |||
# append relationships to each resource | |||
append_relationships_multiple(resources) | |||
return resources | |||
@@ -91,7 +97,7 @@ def delete_resource(resource_id): | |||
# function to get filters for a specific field | |||
def get_filter_values(field, type): | |||
# get field values for filter | |||
field_filter = Resource.query.filter_by(type=type).with_entities(getattr(Resource, field)) | |||
field_filter = Resource.query.filter_by(type=type).filter_by(published=True).with_entities(getattr(Resource, field)) | |||
# turn SQLAlchemy object into list | |||
field_filter = [i for i, in field_filter] | |||
# split each element on '/' (useful for scriptingLanguage only) | |||
@@ -125,4 +131,24 @@ def get_book_cover(book): | |||
else: | |||
book_cover = {'thumbnail': openl_url} | |||
book.update(book_cover) | |||
return book | |||
return book | |||
# function to retrieve last updated date from the database | |||
def get_last_date(): | |||
resource = Resource.query.order_by(Resource.created.desc()).filter_by(published=True).first() | |||
return resource.created.isoformat() | |||
# function to retrieve last commit date from a GitHub repository for a tool | |||
def get_commit_date(repositoryUrl): | |||
# change repository URL to API URL | |||
api_url = repositoryUrl.replace("https://github.com", "https://api.github.com/repos") | |||
# get default branch name | |||
r = requests.get(api_url) | |||
r = json.loads(r.content) | |||
branch = r['default_branch'] | |||
# get date of last commit on default branch | |||
api_url = api_url + "/branches/" + branch | |||
r = requests.get(api_url) | |||
r = json.loads(r.content) | |||
date = r['commit']['commit']['author']['date'][0:10] | |||
return date |
@@ -18,7 +18,7 @@ class UserSchema(ma.Schema): | |||
# schema for JSON transformation of Resource table via Marshmallow | |||
class ToolSchema(ma.Schema): | |||
class Meta: | |||
fields = ('id', 'name', 'description', 'developer', 'developerUrl', 'projectUrl', 'repositoryUrl', 'license', 'scriptingLanguage', 'expertiseToUse', 'expertiseToHost', 'dependencies', 'ingestFormats', 'outputFormats', 'status') | |||
fields = ('id', 'name', 'description', 'developer', 'developerUrl', 'projectUrl', 'repositoryUrl', 'license', 'scriptingLanguage', 'expertiseToUse', 'expertiseToHost', 'dependencies', 'ingestFormats', 'outputFormats', 'videoUrl', 'status') | |||
ordered = True | |||
class PracticeSchema(ma.Schema): | |||
@@ -28,7 +28,7 @@ class PracticeSchema(ma.Schema): | |||
class BookSchema(ma.Schema): | |||
class Meta: | |||
fields = ('id', 'name', 'description', 'author', 'year', 'bookUrl', 'isbn') | |||
fields = ('id', 'name', 'description', 'author', 'year', 'bookUrl', 'videoUrl', 'isbn') | |||
ordered = True | |||
# subschemas for nested fields |
@@ -10,7 +10,7 @@ | |||
"license": "ISC", | |||
"devDependencies": { | |||
"autoprefixer": "^10.4.2", | |||
"postcss": "^8.4.6", | |||
"postcss": "^8.4.31", | |||
"postcss-cli": "^9.1.0", | |||
"tailwindcss": "^3.0.18" | |||
} | |||
@@ -675,10 +675,16 @@ | |||
} | |||
}, | |||
"node_modules/nanoid": { | |||
"version": "3.3.4", | |||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", | |||
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", | |||
"version": "3.3.7", | |||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", | |||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", | |||
"dev": true, | |||
"funding": [ | |||
{ | |||
"type": "github", | |||
"url": "https://github.com/sponsors/ai" | |||
} | |||
], | |||
"bin": { | |||
"nanoid": "bin/nanoid.cjs" | |||
}, | |||
@@ -762,9 +768,9 @@ | |||
} | |||
}, | |||
"node_modules/postcss": { | |||
"version": "8.4.21", | |||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", | |||
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", | |||
"version": "8.4.31", | |||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", | |||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", | |||
"dev": true, | |||
"funding": [ | |||
{ | |||
@@ -774,10 +780,14 @@ | |||
{ | |||
"type": "tidelift", | |||
"url": "https://tidelift.com/funding/github/npm/postcss" | |||
}, | |||
{ | |||
"type": "github", | |||
"url": "https://github.com/sponsors/ai" | |||
} | |||
], | |||
"dependencies": { | |||
"nanoid": "^3.3.4", | |||
"nanoid": "^3.3.6", | |||
"picocolors": "^1.0.0", | |||
"source-map-js": "^1.0.2" | |||
}, |
@@ -8,7 +8,7 @@ | |||
}, | |||
"devDependencies": { | |||
"autoprefixer": "^10.4.2", | |||
"postcss": "^8.4.6", | |||
"postcss": "^8.4.31", | |||
"postcss-cli": "^9.1.0", | |||
"tailwindcss": "^3.0.18" | |||
}, |
@@ -191,7 +191,6 @@ a.link:hover { | |||
@apply opacity-100; | |||
} | |||
h2,h3 { | |||
hyphens: auto; | |||
} |
@@ -1172,6 +1172,10 @@ a.link:hover { | |||
opacity: 1; | |||
} | |||
h2 { | |||
font-size: 150%; | |||
margin-bottom: 1em; | |||
} | |||
h2,h3 { | |||
-webkit-hyphens: auto; | |||
@@ -1182,6 +1186,10 @@ h1, h2, h3 { | |||
font-family: 'ag-fett'; | |||
} | |||
#colophon h2:nth-of-type(n+2) { | |||
padding-top: 40px; | |||
} | |||
h4 { | |||
font-style: italic; | |||
font-weight: 700; | |||
@@ -1743,4 +1751,18 @@ select.form-select { | |||
color: #fff; | |||
background-color: #d9534f; | |||
border-color: #d43f3a; | |||
} | |||
#embedded-video { | |||
margin-top: 1rem; | |||
} | |||
div#embedded-video iframe { | |||
position: relative; | |||
top: 0; | |||
bottom: 0; | |||
left: 0; | |||
right: 0; | |||
height: 480px; | |||
width: 100%; | |||
} |
@@ -4,11 +4,16 @@ | |||
<div class="grid lg:grid-cols-[2fr,1fr] gap-4 container"> | |||
<div class="cell-margin text max-w-[65ch] lg:m-16 text-lg lg:text-xl"> | |||
{{ about_text|safe }} | |||
{{ main_text|safe }} | |||
</div> | |||
<div id="colophon" class="cell-margin text lg:my-16"> | |||
{{ colophon_text | safe }} | |||
{{ sidebar_text | safe }} | |||
{% if last_updated is defined %} | |||
<hr> | |||
Database last updated: {{ last_updated[0:10] }} | |||
{% endif %} | |||
</div> | |||
</div> | |||
@@ -332,6 +332,8 @@ | |||
<meta charset="utf-8"> | |||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||
<meta name="description" content="The Experimental Publishing Compendium is a guide for scholars, publishers, librarians, and artists who want to experiment with the form of scholarly books."> | |||
<meta name=”robots” content="index, follow"> | |||
<title>Experimental Publishing Compendium</title> | |||
<script src="{{ url_for('static',filename='js/alpine.min.js') }}" defer></script> |
@@ -128,6 +128,12 @@ | |||
</div> | |||
{% endif %} | |||
</div> | |||
{% if resource['videoUrl'] %} | |||
<div id="embedded-video" class="lg:col-span-12"> | |||
<h3>Accompanying video</h3> | |||
<iframe src="{{ resource['videoUrl']|safe }}" frameborder="0" webkitallowfullscreen="true" mozallowfullscreen="true" allowfullscreen></iframe> | |||
</div> | |||
{% endif %} | |||
</div> | |||
</div> | |||
</div> |
@@ -90,12 +90,21 @@ | |||
<div class=""> | |||
<h3>Platform status</h3> | |||
{{ resource['status'] }} | |||
{% if resource['commitDate'] %} | |||
: last <a href="{{ resource['repositoryUrl'] }}">GitHub commit</a> on {{ resource['commitDate']}} | |||
{% endif %} | |||
</div> | |||
{% endif %} | |||
{% elif resource.type == 'practice' %} | |||
{{ practice_markdown | safe }} | |||
{% endif %} | |||
</div> | |||
{% if resource['videoUrl'] %} | |||
<div id="embedded-video" class="lg:col-span-12"> | |||
<h3>Accompanying video</h3> | |||
<iframe src="{{ resource['videoUrl']|safe }}" frameborder="0" webkitallowfullscreen="true" mozallowfullscreen="true" allowfullscreen></iframe> | |||
</div> | |||
{% endif %} | |||
</div> | |||
</div> |
@@ -33,7 +33,7 @@ def get_tools(): | |||
intro_text = markdown.markdown(intro_text) | |||
# DATABASE QUERY | |||
tools_query = Resource.query.filter_by(type=resource_type) | |||
tools_query = Resource.query.filter_by(type=resource_type).filter_by(published=True) | |||
# FILTERING | |||
for key in request.args.keys(): | |||
@@ -55,7 +55,7 @@ def get_tools(): | |||
# FILTERS MENU | |||
# get values for filter menu dropdowns | |||
# practices | |||
practices_filter = Resource.query.filter_by(type='practice').with_entities(Resource.id, Resource.name).all() | |||
practices_filter = Resource.query.filter_by(type='practice').filter_by(published=True).with_entities(Resource.id, Resource.name).all() | |||
# license | |||
licenses_filter = get_filter_values('license', resource_type) | |||
# language |
@@ -6,10 +6,10 @@ Back-end coding by [Simon Bowie](https://simonxix.com), front-end coding by [Joe | |||
Special thanks to Gary Hall, Rebekka Kiesewetter, Marcell Mars, Toby Steiner, and Samuel Moore, and everyone who has provided feedback on our research or shared suggestions of examples to feature, including the participants of COPIM’s experimental publishing workshop, and Nicolás Arata, Dominique Babini, Maria Fernanda Pampin, Sebastian Nordhoff, Abel Packer, and Armanda Ramalho, and Agatha Morka. | |||
Our appreciation also goes out to the [Next Generation Library Publishing Project](https://educopia.org/next-generation-library-publishing/) for sharing an early catalogue-in-progress version of [SComCat](https://www.scomcat.net/) with us which formed one of the inspirations behind the Compendium. | |||
Our appreciation also goes out to the [Next Generation Library Publishing Project](https://educopia.org/next-generation-library-publishing/) for sharing an early catalogue-in-progress version of SComCat with us which formed one of the inspirations behind the Compendium. | |||
___ | |||
Copyright © 2023 [COPIM](https://copim.ac.uk/). | |||
Copyright © 2023–2025 [COPIM](https://copim.ac.uk/). | |||
Design © 2023 [Joel Galvez](https://joelgalvez.com/selected). | |||
@@ -1 +1,3 @@ | |||
The Experimental Publishing Compendium is a guide and reference for scholars, publishers, developers, librarians, and designers who want to challenge, push and redefine the shape, form and rationale of scholarly books. The compendium brings together tools, practices, and books to promote the publication of experimental scholarly works. [Read more](/about) | |||
The Open Book Futures Experimental Publishing Group and the Centre for Postdigital Cultures’ Post-Publishing research strand are presenting **a seminar series for for those interested in experimental scholarly book publishing** throughout 2024 and 2025. [Read more and register](/practice_seminars). |
@@ -0,0 +1,44 @@ | |||
## Experimental Book Publishing in Practice | |||
The Open Book Futures [Experimental Publishing Group](https://copim.pubpub.org/experimental-publishing-group) and the Centre for Postdigital Cultures’ [Post-Publishing](https://postpublishing.postdigitalcultures.org) research strand are pleased to present a seminar series for for those interested in experimental scholarly book publishing throughout 2024 and 2025. Each seminar will bring together authors and publishers with software and tool providers. | |||
Our next seminar will be Thursday 20th March 2025 at 17:00 CET / 16:00 GMT / 11:00 EST bringing together Stephany RunningHawk Johnson (Local Contexts) & Darcy Cullen (UBC Press) to discuss [Traditional Knowledge Labels](https://compendium.copim.ac.uk/tools/170), labels which allow Indigenous communities to express local and specific conditions for sharing and engaging in future research and relationships in ways that are consistent with already existing community rules, governance and protocols for using, sharing and circulating knowledge and data. | |||
Register via [Eventsforce](https://www.eventsforce.net/cugroup/frontEnd/reg/registerNew.csp?ef_sel_menu=9308&eventID=2039). | |||
Every seminar in this series will take place online using [kMeet](https://www.infomaniak.com/en/kmeet), the open source privacy-focused videoconferencing software from Infomaniak. kMeet is compatible with all modern browsers. | |||
![logos for Open Book Futures Experimental Publishing Group and the Centre for Postdigital Cultures](/static/images/seminar_logos.png) | |||
## Seminar recordings | |||
### Sesson 01: Manifold | |||
<br><div id="embedded-video"><iframe src="https://archive.org/embed/Experimental_Publishing_Seminar_01" frameborder="0" webkitallowfullscreen="true" mozallowfullscreen="true" allowfullscreen></iframe></div><br> | |||
View the [whole recording on the Internet Archive](https://archive.org/details/Experimental_Publishing_Seminar_01). | |||
### Sesson 02: Hypothes.is | |||
<br><div id="embedded-video"><iframe src="https://archive.org/embed/Experimental_Publishing_Seminar_02" frameborder="0" webkitallowfullscreen="true" mozallowfullscreen="true" allowfullscreen></iframe></div><br> | |||
View the [whole recording on the Internet Archive](https://archive.org/details/Experimental_Publishing_Seminar_02). | |||
<br> | |||
## Concept | |||
Last year saw the release of the [Experimental Publishing Compendium](https://compendium.copim.ac.uk/), a guide and reference for scholars, publishers, developers, librarians, and designers who want to challenge, push and redefine the shape, form and rationale of scholarly books. The Compendium brings together tools, practices, and books to promote the publication of experimental scholarly works. | |||
To complement the Compendium, this series of seminars will showcase a selection of open source tools, platforms, and software that support the publication of experimental, interactive, multimodal, and/or versioned books as well as experimental publishing practices from annotating and open reviewing to forking and rewriting. | |||
Organised by [Copim’s Experimental Publishing Group](https://copim.pubpub.org/experimental-publishing-group) in collaboration with the Centre for Postdigital Cultures’ [Post-Publishing](https://postpublishing.postdigitalcultures.org) research strand, these seminars respond to the previously stated need of both authors and publishers to learn more about experimental book publishing and the tools, practices, and workflows that support this (Adema and Stone, 2017; Adema et al. 2022). | |||
The aim of these seminars is to help authors and publishers get started with a tool or platform, which sometimes can be quite daunting or complicated without dedicated IT support. The seminars will feature short walkthroughs of platforms and tools, as well as talks by and Q&As with the people that have developed, designed or are currently maintaining them and will be complemented by presentations by authors or publishers that have used these tools or platforms to publish an experimental book, who will be sharing their experiences of doing so. | |||
Next to providing dedicated space for audience members to ask questions to the presenters during the seminars, the recordings of these events will be uploaded to the Experimental Publishing Compendium to complement the information on the discussed tools, practices, and books already included in the Compendium. | |||
The Open Book Futures project is funded by the [Research England Development (RED) Fund](https://re.ukri.org/funding/our-funds-overview/research-england-development-red-fund/) and [Arcadia](https://www.arcadiafund.org.uk/), a charitable fund of Lisbet Rausing and Peter Baldwin. | |||
![logos for Research England and Arcadia](/static/images/funder_logos.png) |
@@ -0,0 +1,31 @@ | |||
## Seminar 01: Manifold | |||
Date: Thursday 21st November 2024 | |||
Time: 17:00 CET / 16:00 GMT / 11:00 EST | |||
Location: Online using kMeet | |||
Speakers: Terence Smyre (Manifold), Matthew Gold (CUNY), & Whitney Trettien (University of Pennsylvania) | |||
## Seminar 02: Hypothes.is | |||
Date: Thursday 16th January 2025 | |||
Time: 17:00 CET / 16:00 GMT / 11:00 EST | |||
Location: Online using kMeet | |||
Speakers: Remi Kalir (Duke University), Sonja Visser (Hypothes.is), & Janneke Adema (Open Humanities Press) | |||
## Seminar 03: TK Labels | |||
Date: Thursday 20th March 2025 | |||
Time: 17:00 CET / 16:00 GMT / 11:00 EST | |||
Location: Online using kMeet | |||
Speakers: Stephany RunningHawk Johnson (Local Contexts) & Darcy Cullen (UBC Press) | |||
Register via [Eventsforce](https://www.eventsforce.net/cugroup/frontEnd/reg/registerNew.csp?ef_sel_menu=9308&eventID=2039). |
@@ -10,4 +10,5 @@ isbntools | |||
requests | |||
marshmallow | |||
flask-marshmallow | |||
marshmallow-sqlalchemy | |||
pandas |