opencloning 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opencloning-0.1.0/LICENSE +7 -0
- opencloning-0.1.0/PKG-INFO +168 -0
- opencloning-0.1.0/README.md +138 -0
- opencloning-0.1.0/pyproject.toml +52 -0
- opencloning-0.1.0/src/opencloning/__init__.py +0 -0
- opencloning-0.1.0/src/opencloning/api_config_utils.py +97 -0
- opencloning-0.1.0/src/opencloning/app_settings.py +50 -0
- opencloning-0.1.0/src/opencloning/assembly2.py +1347 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/__init__.py +10 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/index.html +54 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/pombe/__init__.py +86 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/pombe/index.html +191 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/pombe/pombe_all.sh +9 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/pombe/pombe_clone.py +191 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/pombe/pombe_gather.py +66 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/pombe/pombe_get_primers.py +114 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/pombe/pombe_summary.py +104 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/ziqiang_et_al2024/__init__.py +179 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/ziqiang_et_al2024/index.html +132 -0
- opencloning-0.1.0/src/opencloning/batch_cloning/ziqiang_et_al2024/ziqiang_et_al2024.json +280 -0
- opencloning-0.1.0/src/opencloning/dna_functions.py +341 -0
- opencloning-0.1.0/src/opencloning/dna_utils.py +144 -0
- opencloning-0.1.0/src/opencloning/endpoints/annotation.py +64 -0
- opencloning-0.1.0/src/opencloning/endpoints/assembly.py +524 -0
- opencloning-0.1.0/src/opencloning/endpoints/external_import.py +371 -0
- opencloning-0.1.0/src/opencloning/endpoints/no_assembly.py +103 -0
- opencloning-0.1.0/src/opencloning/endpoints/no_input.py +108 -0
- opencloning-0.1.0/src/opencloning/endpoints/other.py +64 -0
- opencloning-0.1.0/src/opencloning/endpoints/primer_design.py +224 -0
- opencloning-0.1.0/src/opencloning/gateway.py +174 -0
- opencloning-0.1.0/src/opencloning/get_router.py +10 -0
- opencloning-0.1.0/src/opencloning/main.py +104 -0
- opencloning-0.1.0/src/opencloning/ncbi_requests.py +130 -0
- opencloning-0.1.0/src/opencloning/primer_design.py +214 -0
- opencloning-0.1.0/src/opencloning/pydantic_models.py +393 -0
- opencloning-0.1.0/src/opencloning/request_examples.py +102 -0
- opencloning-0.1.0/src/opencloning/utils.py +57 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2022 Manuel Lera Ramirez
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: opencloning
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Backend of OpenCloning, a web application to generate molecular cloning strategies in json format, and share them with others.
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Manuel Lera-Ramirez
|
|
7
|
+
Author-email: manulera14@gmail.com
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Dist: beautifulsoup4 (>=4.11.1,<5.0.0)
|
|
15
|
+
Requires-Dist: fastapi
|
|
16
|
+
Requires-Dist: httpx (>=0.25.0,<0.26.0)
|
|
17
|
+
Requires-Dist: opencloning-linkml (>=0.2a0,<0.3)
|
|
18
|
+
Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
|
|
19
|
+
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
|
20
|
+
Requires-Dist: pydantic (>=2.7.1,<3.0.0)
|
|
21
|
+
Requires-Dist: pydna (>=5.3,<6.0)
|
|
22
|
+
Requires-Dist: python-multipart
|
|
23
|
+
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
24
|
+
Requires-Dist: regex (>=2023.10.3,<2024.0.0)
|
|
25
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
26
|
+
Requires-Dist: uvicorn
|
|
27
|
+
Project-URL: Repository, https://github.com/manulera/OpenCloning_backend
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
[](https://github.com/manulera/OpenCloning_backend/actions/workflows/ci.yml)
|
|
31
|
+
[](https://codecov.io/gh/manulera/OpenCloning_backend)
|
|
32
|
+
|
|
33
|
+
# OpenCloning Backend API
|
|
34
|
+
|
|
35
|
+
This API is part of a bigger application, before going further, please go to the [main project readme](https://github.com/manulera/OpenCloning), where you can find an introduction.
|
|
36
|
+
|
|
37
|
+
This python API is built with [FastAPI](https://fastapi.tiangolo.com/) and is for *in silico* cloning.
|
|
38
|
+
|
|
39
|
+
## Summary
|
|
40
|
+
|
|
41
|
+
Read [main project readme](https://github.com/manulera/OpenCloning) first.
|
|
42
|
+
|
|
43
|
+
This API provides a series of entry points. The API documentation can be accessed [here](https://OpenCloning.api.genestorian.org/docs). You can use the documentation page to try some request directly on the browser. Otherwise, the API is open for you to make requests from a python script or command line at: [https://opencloning.api.genestorian.org/](https://opencloning.api.genestorian.org/).
|
|
44
|
+
|
|
45
|
+
## Getting started
|
|
46
|
+
|
|
47
|
+
If you want to quickly set up a local instance of the frontend and backend of the application, check [getting started in 5 minutes](https://github.com/manulera/OpenCloning#timer_clock-getting-started-in-5-minutes) in the main repository.
|
|
48
|
+
|
|
49
|
+
### Running locally
|
|
50
|
+
|
|
51
|
+
You can install this as a python package:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Create a virtual environment
|
|
55
|
+
python -m venv .venv
|
|
56
|
+
# Activate the virtual environment
|
|
57
|
+
source .venv/bin/activate
|
|
58
|
+
# Install the package from github (will be in pypi at some point)
|
|
59
|
+
pip install opencloning
|
|
60
|
+
# Run the API (uvicorn should be installed in the virtual environment)
|
|
61
|
+
uvicorn opencloning.main:app
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Running locally if you want to contribute
|
|
65
|
+
|
|
66
|
+
For the management of the dependencies `poetry` is used, if you don't have it, visit https://python-poetry.org/.
|
|
67
|
+
|
|
68
|
+
In the project directory:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# This should install the dependencies and create a virtual environment
|
|
72
|
+
poetry install
|
|
73
|
+
|
|
74
|
+
# Install the pre-commit hooks
|
|
75
|
+
pre-commit install
|
|
76
|
+
|
|
77
|
+
# Activate the virtual environment
|
|
78
|
+
poetry shell
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The virtual environment is installed in the project folder. This is convenient if you are using an IDE for development. For settings of vscode see the folder `.vscode`.
|
|
83
|
+
|
|
84
|
+
Now you should be able to run the api by running:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# The --reload argument will reload the API if you make changes to the code
|
|
88
|
+
uvicorn opencloning.main:app --reload --reload-exclude='.venv'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Then you should be able to open the API docs at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) to know that your API is working.
|
|
92
|
+
|
|
93
|
+
### Running locally with docker :whale:
|
|
94
|
+
|
|
95
|
+
If you want to serve the full site (backend and frontend) with docker, check [getting started in 5 minutes](https://github.com/manulera/OpenCloning#timer_clock-getting-started-in-5-minutes) in the main repository.
|
|
96
|
+
|
|
97
|
+
If you want to serve only the backend from a docker container, an image is available at [manulera/opencloningbackend](https://hub.docker.com/r/manulera/opencloningbackend). The image is built from the Dockerfile in the root of this repository and exposes the port 3000. To run it:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
docker build -t manulera/opencloningbackend .
|
|
101
|
+
docker run -d --name backendcontainer -p 8000:8000 manulera/opencloningbackend
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If you don't want to download the repository and build the image, you can fetch the latest image from dockerhub (same image that is used in [https://opencloning.api.genestorian.org/](https://opencloning.api.genestorian.org/))
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
docker pull manulera/opencloningbackend
|
|
109
|
+
docker run -d --name backendcontainer -p 8000:8000 manulera/opencloningbackend
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The api will be running at `http://localhost:8000`, so you should be able to access the docs at [http://localhost:8000/docs](http://localhost:8000/docs).
|
|
113
|
+
|
|
114
|
+
### Connecting to the frontend
|
|
115
|
+
|
|
116
|
+
If you want to receive requests from the [frontend](https://github.com/manulera/OpenCloning_frontend), or from another web application you may have to include the url of the frontend application in the CORS exceptions. By default, if you run the dev server with `uvicorn opencloning.main:app --reload --reload-exclude='.venv'`, the backend will accept requests coming from `http://localhost:3000`, which is the default address of the frontend dev server (ran with `yarn start`).
|
|
117
|
+
|
|
118
|
+
If you want to change the allowed origins, you can do so via env variables (comma-separated). e.g.:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001 uvicorn opencloning.main:app --reload --reload-exclude='.venv'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Similarly, the frontend should be configured to send requests to the backend address, [see here](https://github.com/manulera/OpenCloning_frontend#connecting-to-the-backend).
|
|
125
|
+
|
|
126
|
+
#### Serving the frontend from the backend
|
|
127
|
+
|
|
128
|
+
You may prefer to handle everything from a single server. You can do so by:
|
|
129
|
+
* Build the [frontend](https://github.com/manulera/OpenCloning_frontend) with `yarn build`.
|
|
130
|
+
* Copy the folder `build` from the frontend to the root directory of the backend, and rename it to `frontend`.
|
|
131
|
+
* Set the environment variable `SERVE_FRONTEND=1` when running the backend. By default this will remove all allowed origins, but you can still set them with `ALLOWED_ORIGINS`.
|
|
132
|
+
* Set the value of `backendUrl` in `frontend/config.js` to `/`.
|
|
133
|
+
* Now, when you go to the root of the backend (e.g. `http://localhost:8000`), you should receive the frontend instead of the greeting page of the API.
|
|
134
|
+
|
|
135
|
+
You can see how this is done in this [docker image](https://github.com/manulera/OpenCloning/blob/master/Dockerfile) and [docker-compose file](https://github.com/manulera/OpenCloning/blob/master/docker-compose.yml).
|
|
136
|
+
|
|
137
|
+
## Contributing :hammer_and_wrench:
|
|
138
|
+
|
|
139
|
+
Check [contribution guidelines in the main repository](https://github.com/manulera/OpenCloning/blob/master/CONTRIBUTING.md) for general guidelines.
|
|
140
|
+
|
|
141
|
+
For more specific tasks:
|
|
142
|
+
* Creating a new type of source: follow the [new source issue template](.github/ISSUE_TEMPLATE/new-source.md). You can create an issue like that [here](https://github.com/manulera/OpenCloning_backend/issues/new?assignees=&labels=new-source&projects=&template=new-source.md&title=New+source%3A+%3Cname-of-source%3E).
|
|
143
|
+
|
|
144
|
+
## Running the tests locally
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
pytest -v -ks
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Notes
|
|
151
|
+
|
|
152
|
+
### Ping a particular library version from github:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
poetry add git+https://github.com/BjornFJohansson/pydna#4fd760d075f77cceeb27969e017e04b42f6d0aa3
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Generating API stubs
|
|
159
|
+
|
|
160
|
+
For the frontend, it may be useful to produce stubs (I use them for writing the tests). See how this is implemented
|
|
161
|
+
by looking at the `RecordStubRoute` class in `api_config_utils.py`. To run the dev server and record stubs:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
RECORD_STUBS=1 uvicorn opencloning.main:app --reload --reload-exclude='.venv'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
This will record the stubs (requests and responses) in the `stubs` folder.
|
|
168
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
[](https://github.com/manulera/OpenCloning_backend/actions/workflows/ci.yml)
|
|
2
|
+
[](https://codecov.io/gh/manulera/OpenCloning_backend)
|
|
3
|
+
|
|
4
|
+
# OpenCloning Backend API
|
|
5
|
+
|
|
6
|
+
This API is part of a bigger application, before going further, please go to the [main project readme](https://github.com/manulera/OpenCloning), where you can find an introduction.
|
|
7
|
+
|
|
8
|
+
This python API is built with [FastAPI](https://fastapi.tiangolo.com/) and is for *in silico* cloning.
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Read [main project readme](https://github.com/manulera/OpenCloning) first.
|
|
13
|
+
|
|
14
|
+
This API provides a series of entry points. The API documentation can be accessed [here](https://OpenCloning.api.genestorian.org/docs). You can use the documentation page to try some request directly on the browser. Otherwise, the API is open for you to make requests from a python script or command line at: [https://opencloning.api.genestorian.org/](https://opencloning.api.genestorian.org/).
|
|
15
|
+
|
|
16
|
+
## Getting started
|
|
17
|
+
|
|
18
|
+
If you want to quickly set up a local instance of the frontend and backend of the application, check [getting started in 5 minutes](https://github.com/manulera/OpenCloning#timer_clock-getting-started-in-5-minutes) in the main repository.
|
|
19
|
+
|
|
20
|
+
### Running locally
|
|
21
|
+
|
|
22
|
+
You can install this as a python package:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Create a virtual environment
|
|
26
|
+
python -m venv .venv
|
|
27
|
+
# Activate the virtual environment
|
|
28
|
+
source .venv/bin/activate
|
|
29
|
+
# Install the package from github (will be in pypi at some point)
|
|
30
|
+
pip install opencloning
|
|
31
|
+
# Run the API (uvicorn should be installed in the virtual environment)
|
|
32
|
+
uvicorn opencloning.main:app
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Running locally if you want to contribute
|
|
36
|
+
|
|
37
|
+
For the management of the dependencies `poetry` is used, if you don't have it, visit https://python-poetry.org/.
|
|
38
|
+
|
|
39
|
+
In the project directory:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# This should install the dependencies and create a virtual environment
|
|
43
|
+
poetry install
|
|
44
|
+
|
|
45
|
+
# Install the pre-commit hooks
|
|
46
|
+
pre-commit install
|
|
47
|
+
|
|
48
|
+
# Activate the virtual environment
|
|
49
|
+
poetry shell
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The virtual environment is installed in the project folder. This is convenient if you are using an IDE for development. For settings of vscode see the folder `.vscode`.
|
|
54
|
+
|
|
55
|
+
Now you should be able to run the api by running:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# The --reload argument will reload the API if you make changes to the code
|
|
59
|
+
uvicorn opencloning.main:app --reload --reload-exclude='.venv'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then you should be able to open the API docs at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) to know that your API is working.
|
|
63
|
+
|
|
64
|
+
### Running locally with docker :whale:
|
|
65
|
+
|
|
66
|
+
If you want to serve the full site (backend and frontend) with docker, check [getting started in 5 minutes](https://github.com/manulera/OpenCloning#timer_clock-getting-started-in-5-minutes) in the main repository.
|
|
67
|
+
|
|
68
|
+
If you want to serve only the backend from a docker container, an image is available at [manulera/opencloningbackend](https://hub.docker.com/r/manulera/opencloningbackend). The image is built from the Dockerfile in the root of this repository and exposes the port 3000. To run it:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
docker build -t manulera/opencloningbackend .
|
|
72
|
+
docker run -d --name backendcontainer -p 8000:8000 manulera/opencloningbackend
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If you don't want to download the repository and build the image, you can fetch the latest image from dockerhub (same image that is used in [https://opencloning.api.genestorian.org/](https://opencloning.api.genestorian.org/))
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
docker pull manulera/opencloningbackend
|
|
80
|
+
docker run -d --name backendcontainer -p 8000:8000 manulera/opencloningbackend
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The api will be running at `http://localhost:8000`, so you should be able to access the docs at [http://localhost:8000/docs](http://localhost:8000/docs).
|
|
84
|
+
|
|
85
|
+
### Connecting to the frontend
|
|
86
|
+
|
|
87
|
+
If you want to receive requests from the [frontend](https://github.com/manulera/OpenCloning_frontend), or from another web application you may have to include the url of the frontend application in the CORS exceptions. By default, if you run the dev server with `uvicorn opencloning.main:app --reload --reload-exclude='.venv'`, the backend will accept requests coming from `http://localhost:3000`, which is the default address of the frontend dev server (ran with `yarn start`).
|
|
88
|
+
|
|
89
|
+
If you want to change the allowed origins, you can do so via env variables (comma-separated). e.g.:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001 uvicorn opencloning.main:app --reload --reload-exclude='.venv'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Similarly, the frontend should be configured to send requests to the backend address, [see here](https://github.com/manulera/OpenCloning_frontend#connecting-to-the-backend).
|
|
96
|
+
|
|
97
|
+
#### Serving the frontend from the backend
|
|
98
|
+
|
|
99
|
+
You may prefer to handle everything from a single server. You can do so by:
|
|
100
|
+
* Build the [frontend](https://github.com/manulera/OpenCloning_frontend) with `yarn build`.
|
|
101
|
+
* Copy the folder `build` from the frontend to the root directory of the backend, and rename it to `frontend`.
|
|
102
|
+
* Set the environment variable `SERVE_FRONTEND=1` when running the backend. By default this will remove all allowed origins, but you can still set them with `ALLOWED_ORIGINS`.
|
|
103
|
+
* Set the value of `backendUrl` in `frontend/config.js` to `/`.
|
|
104
|
+
* Now, when you go to the root of the backend (e.g. `http://localhost:8000`), you should receive the frontend instead of the greeting page of the API.
|
|
105
|
+
|
|
106
|
+
You can see how this is done in this [docker image](https://github.com/manulera/OpenCloning/blob/master/Dockerfile) and [docker-compose file](https://github.com/manulera/OpenCloning/blob/master/docker-compose.yml).
|
|
107
|
+
|
|
108
|
+
## Contributing :hammer_and_wrench:
|
|
109
|
+
|
|
110
|
+
Check [contribution guidelines in the main repository](https://github.com/manulera/OpenCloning/blob/master/CONTRIBUTING.md) for general guidelines.
|
|
111
|
+
|
|
112
|
+
For more specific tasks:
|
|
113
|
+
* Creating a new type of source: follow the [new source issue template](.github/ISSUE_TEMPLATE/new-source.md). You can create an issue like that [here](https://github.com/manulera/OpenCloning_backend/issues/new?assignees=&labels=new-source&projects=&template=new-source.md&title=New+source%3A+%3Cname-of-source%3E).
|
|
114
|
+
|
|
115
|
+
## Running the tests locally
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
pytest -v -ks
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Notes
|
|
122
|
+
|
|
123
|
+
### Ping a particular library version from github:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
poetry add git+https://github.com/BjornFJohansson/pydna#4fd760d075f77cceeb27969e017e04b42f6d0aa3
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Generating API stubs
|
|
130
|
+
|
|
131
|
+
For the frontend, it may be useful to produce stubs (I use them for writing the tests). See how this is implemented
|
|
132
|
+
by looking at the `RecordStubRoute` class in `api_config_utils.py`. To run the dev server and record stubs:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
RECORD_STUBS=1 uvicorn opencloning.main:app --reload --reload-exclude='.venv'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
This will record the stubs (requests and responses) in the `stubs` folder.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
authors = ["Manuel Lera-Ramirez <manulera14@gmail.com>"]
|
|
3
|
+
description = "Backend of OpenCloning, a web application to generate molecular cloning strategies in json format, and share them with others."
|
|
4
|
+
license = "MIT"
|
|
5
|
+
name = "opencloning"
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
package-mode = true
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
repository = "https://github.com/manulera/OpenCloning_backend"
|
|
10
|
+
|
|
11
|
+
[tool.poetry.dependencies]
|
|
12
|
+
beautifulsoup4 = "^4.11.1"
|
|
13
|
+
fastapi = "*"
|
|
14
|
+
httpx = "^0.25.0"
|
|
15
|
+
python = "^3.11"
|
|
16
|
+
python-multipart = "*"
|
|
17
|
+
uvicorn = "*"
|
|
18
|
+
pydna = "^5.3"
|
|
19
|
+
requests = "^2.31.0"
|
|
20
|
+
regex = "^2023.10.3"
|
|
21
|
+
pydantic = "^2.7.1"
|
|
22
|
+
pandas = "^2.2.3"
|
|
23
|
+
openpyxl = "^3.1.5"
|
|
24
|
+
pyyaml = "^6.0.2"
|
|
25
|
+
opencloning-linkml = "^0.2a0"
|
|
26
|
+
|
|
27
|
+
[tool.poetry.group.dev.dependencies]
|
|
28
|
+
autopep8 = "^2.0.4"
|
|
29
|
+
flake8-bugbear = "^24.2.6"
|
|
30
|
+
black = "^24.2.0"
|
|
31
|
+
pre-commit = "^3.6.2"
|
|
32
|
+
watchfiles = "^0.21.0"
|
|
33
|
+
|
|
34
|
+
[tool.poetry.group.test.dependencies]
|
|
35
|
+
pytest = "8.3.4"
|
|
36
|
+
pre-commit = "^3.6.2"
|
|
37
|
+
pytest-cov = "^4.1.0"
|
|
38
|
+
pytest-rerunfailures = "^14.0"
|
|
39
|
+
respx = "^0.21.1"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
[tool.poetry.group.ipython.dependencies]
|
|
43
|
+
ipython = "^8.20.0"
|
|
44
|
+
ipykernel = "^6.28.0"
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
build-backend = "poetry.core.masonry.api"
|
|
48
|
+
requires = ["poetry-core>=1.0.0"]
|
|
49
|
+
|
|
50
|
+
[tool.black]
|
|
51
|
+
skip-string-normalization = true
|
|
52
|
+
line-length = 119
|
|
File without changes
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import datetime
|
|
3
|
+
from fastapi.exceptions import RequestValidationError, HTTPException
|
|
4
|
+
from typing import Callable
|
|
5
|
+
from fastapi.routing import APIRoute
|
|
6
|
+
from fastapi import Request, Response
|
|
7
|
+
from fastapi.responses import JSONResponse
|
|
8
|
+
import os
|
|
9
|
+
import glob
|
|
10
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RecordStubRoute(APIRoute):
|
|
14
|
+
"""Subclass of APIRoute that stores the request and response of a route in the folder `stubs`"""
|
|
15
|
+
|
|
16
|
+
def get_route_handler(self) -> Callable:
|
|
17
|
+
original_route_handler = super().get_route_handler()
|
|
18
|
+
|
|
19
|
+
async def custom_route_handler(request: Request) -> Response:
|
|
20
|
+
if request.method != 'POST':
|
|
21
|
+
return await original_route_handler(request)
|
|
22
|
+
formatted_request = {
|
|
23
|
+
'path': request.url.path,
|
|
24
|
+
'method': request.method,
|
|
25
|
+
'body': await request.json(),
|
|
26
|
+
'headers': dict(request.headers),
|
|
27
|
+
}
|
|
28
|
+
try:
|
|
29
|
+
response: JSONResponse = await original_route_handler(request)
|
|
30
|
+
except RequestValidationError as exc:
|
|
31
|
+
errors = exc.errors()
|
|
32
|
+
for e in errors:
|
|
33
|
+
if 'ctx' in e:
|
|
34
|
+
# This is a ValueError Object that cannot be serialized
|
|
35
|
+
del e['ctx']
|
|
36
|
+
response = JSONResponse(content=errors, status_code=422)
|
|
37
|
+
except HTTPException as exc:
|
|
38
|
+
print('>>exception', exc)
|
|
39
|
+
detail = {'detail': exc.detail}
|
|
40
|
+
response = JSONResponse(content=detail, status_code=exc.status_code)
|
|
41
|
+
|
|
42
|
+
if type(response) is JSONResponse:
|
|
43
|
+
formatted_response = {
|
|
44
|
+
'statusCode': response.status_code,
|
|
45
|
+
'body': json.loads(response.body),
|
|
46
|
+
'headers': dict(response.headers),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
formatted_time = datetime.datetime.now().strftime('%Y_%m_%d-%H_%M_%S')
|
|
50
|
+
stub_folder = f'stubs{formatted_request["path"]}/{formatted_time}'
|
|
51
|
+
existing_folders = glob.glob(f'{stub_folder}_*')
|
|
52
|
+
stub_folder = f'{stub_folder}_{len(existing_folders)}'
|
|
53
|
+
if not os.path.exists(stub_folder):
|
|
54
|
+
os.makedirs(stub_folder)
|
|
55
|
+
with open(f'{stub_folder}/request.json', 'w') as f:
|
|
56
|
+
json.dump(formatted_request, f, indent=4)
|
|
57
|
+
with open(f'{stub_folder}/response.json', 'w') as f:
|
|
58
|
+
json.dump(formatted_response, f, indent=4)
|
|
59
|
+
with open(f'{stub_folder}/response_body.json', 'w') as f:
|
|
60
|
+
json.dump(formatted_response['body'], f, indent=4)
|
|
61
|
+
|
|
62
|
+
print(9 * ' ', '> stub written to', stub_folder)
|
|
63
|
+
return response
|
|
64
|
+
|
|
65
|
+
return custom_route_handler
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Workaround for internal server errors: https://github.com/tiangolo/fastapi/discussions/7847#discussioncomment-5144709
|
|
69
|
+
async def custom_http_exception_handler(request: Request, exc: Exception, app, allow_origins):
|
|
70
|
+
|
|
71
|
+
response = JSONResponse(content={'message': 'internal server error'}, status_code=500)
|
|
72
|
+
|
|
73
|
+
origin = request.headers.get('origin')
|
|
74
|
+
|
|
75
|
+
if origin:
|
|
76
|
+
# Have the middleware do the heavy lifting for us to parse
|
|
77
|
+
# all the config, then update our response headers
|
|
78
|
+
cors = CORSMiddleware(
|
|
79
|
+
app=app, allow_origins=allow_origins, allow_credentials=True, allow_methods=['*'], allow_headers=['*']
|
|
80
|
+
)
|
|
81
|
+
# Logic directly from Starlette's CORSMiddleware:
|
|
82
|
+
# https://github.com/encode/starlette/blob/master/starlette/middleware/cors.py#L152
|
|
83
|
+
|
|
84
|
+
response.headers.update(cors.simple_headers)
|
|
85
|
+
has_cookie = 'cookie' in request.headers
|
|
86
|
+
# If request includes any cookie headers, then we must respond
|
|
87
|
+
# with the specific origin instead of '*'.
|
|
88
|
+
if cors.allow_all_origins and has_cookie:
|
|
89
|
+
response.headers['Access-Control-Allow-Origin'] = origin
|
|
90
|
+
|
|
91
|
+
# If we only allow specific origins, then we have to mirror back
|
|
92
|
+
# the Origin header in the response.
|
|
93
|
+
elif not cors.allow_all_origins and cors.is_allowed_origin(origin=origin):
|
|
94
|
+
response.headers['Access-Control-Allow-Origin'] = origin
|
|
95
|
+
response.headers.add_vary_header('Origin')
|
|
96
|
+
|
|
97
|
+
return response
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the settings for the app that can be set via environment variables.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse_bool(value: str) -> bool:
|
|
10
|
+
return value in {'1', 'TRUE', 'true', 'True'}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# API settings ===============================================
|
|
14
|
+
SERVE_FRONTEND = parse_bool(os.environ['SERVE_FRONTEND']) if 'SERVE_FRONTEND' in os.environ else False
|
|
15
|
+
BATCH_CLONING = parse_bool(os.environ['BATCH_CLONING']) if 'BATCH_CLONING' in os.environ else True
|
|
16
|
+
RECORD_STUBS = parse_bool(os.environ['RECORD_STUBS']) if 'RECORD_STUBS' in os.environ else False
|
|
17
|
+
ALLOWED_ORIGINS = ['http://localhost:3000', 'http://localhost:5173']
|
|
18
|
+
if os.environ.get('ALLOWED_ORIGINS') is not None:
|
|
19
|
+
# Remove trailing slash from each origin if ends with one
|
|
20
|
+
ALLOWED_ORIGINS = [origin.rstrip('/') for origin in os.environ['ALLOWED_ORIGINS'].split(',')]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# External services settings =================================
|
|
24
|
+
NCBI_API_KEY = os.environ.get('NCBI_API_KEY')
|
|
25
|
+
PLANNOTATE_URL = os.environ['PLANNOTATE_URL'] if 'PLANNOTATE_URL' in os.environ else None
|
|
26
|
+
PLANNOTATE_TIMEOUT = int(os.environ['PLANNOTATE_TIMEOUT']) if 'PLANNOTATE_TIMEOUT' in os.environ else 20
|
|
27
|
+
# Handle trailing slash:
|
|
28
|
+
if PLANNOTATE_URL is not None and not PLANNOTATE_URL.endswith('/'):
|
|
29
|
+
PLANNOTATE_URL += '/'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Settings(BaseModel):
|
|
33
|
+
SERVE_FRONTEND: bool
|
|
34
|
+
BATCH_CLONING: bool
|
|
35
|
+
RECORD_STUBS: bool
|
|
36
|
+
NCBI_API_KEY: str | None
|
|
37
|
+
ALLOWED_ORIGINS: list[str]
|
|
38
|
+
PLANNOTATE_URL: str | None
|
|
39
|
+
PLANNOTATE_TIMEOUT: int
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
settings = Settings(
|
|
43
|
+
SERVE_FRONTEND=SERVE_FRONTEND,
|
|
44
|
+
BATCH_CLONING=BATCH_CLONING,
|
|
45
|
+
RECORD_STUBS=RECORD_STUBS,
|
|
46
|
+
NCBI_API_KEY=NCBI_API_KEY,
|
|
47
|
+
ALLOWED_ORIGINS=ALLOWED_ORIGINS,
|
|
48
|
+
PLANNOTATE_URL=PLANNOTATE_URL,
|
|
49
|
+
PLANNOTATE_TIMEOUT=PLANNOTATE_TIMEOUT,
|
|
50
|
+
)
|