liminal-orm 4.4.9__tar.gz → 4.5.1__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.
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/PKG-INFO +2 -1
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/cli/utils.py +1 -5
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/connection/benchling_connection.py +8 -2
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/connection/benchling_service.py +136 -23
- liminal_orm-4.5.1/liminal/entity_schemas/api_v3.py +160 -0
- liminal_orm-4.5.1/liminal/entity_schemas/entity_schema_models_v3.py +86 -0
- liminal_orm-4.5.1/liminal/enums/benchling_api_entity_type.py +18 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/pyproject.toml +2 -1
- liminal_orm-4.4.9/liminal/.gitignore +0 -1
- liminal_orm-4.4.9/liminal/.lock +0 -0
- liminal_orm-4.4.9/liminal/CACHEDIR.TAG +0 -1
- liminal_orm-4.4.9/liminal/pyvenv.cfg +0 -5
- liminal_orm-4.4.9/liminal/share/jupyter/kernels/python3/kernel.json +0 -14
- liminal_orm-4.4.9/liminal/share/jupyter/kernels/python3/logo-32x32.png +0 -0
- liminal_orm-4.4.9/liminal/share/jupyter/kernels/python3/logo-64x64.png +0 -0
- liminal_orm-4.4.9/liminal/share/jupyter/kernels/python3/logo-svg.svg +0 -265
- liminal_orm-4.4.9/liminal/share/man/man1/ipython.1 +0 -60
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/.gitignore +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/LICENSE.md +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/README.md +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/__init__.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/base/base_dropdown.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/base/base_operation.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/base/base_validation_filters.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/base/compare_operation.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/base/name_template_parts.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/base/properties/base_field_properties.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/base/properties/base_name_template.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/base/properties/base_schema_properties.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/base/str_enum.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/cli/cli.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/cli/controller.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/cli/live_test_dropdown_migration.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/cli/live_test_entity_schema_migration.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/connection/__init__.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/dropdowns/api.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/dropdowns/compare.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/dropdowns/generate_files.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/dropdowns/operations.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/dropdowns/utils.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/entity_schemas/api.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/entity_schemas/compare.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/entity_schemas/entity_schema_models.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/entity_schemas/generate_files.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/entity_schemas/operations.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/entity_schemas/tag_schema_models.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/entity_schemas/utils.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/enums/__init__.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/enums/benchling_api_field_type.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/enums/benchling_entity_type.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/enums/benchling_field_type.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/enums/benchling_folder_item_type.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/enums/benchling_naming_strategy.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/enums/benchling_sequence_type.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/enums/name_template_part_type.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/enums/sequence_constraint.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/external/__init__.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/mappers.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/migrate/components.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/migrate/revision.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/migrate/revisions_timeline.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/base.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/base_model.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/base_results_model.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/base_tables/registry_entity.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/base_tables/schema.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/base_tables/user.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/column.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/mixins.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/name_template.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/name_template_parts.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/relationship.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/results_schema_properties.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/orm/schema_properties.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/py.typed +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/results_schemas/generate_files.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/results_schemas/models/results_schema_model.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/results_schemas/utils.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/tests/__init__.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/tests/conftest.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/tests/from benchling_sdk.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/tests/test_dropdown_compare.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/tests/test_entity_schema_compare.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/unit_dictionary/utils.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/utils.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/validation/__init__.py +0 -0
- {liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/validation/validation_severity.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: liminal-orm
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.5.1
|
|
4
4
|
Summary: An ORM and toolkit that builds on top of Benchling's platform to keep your schemas and downstream code dependencies in sync.
|
|
5
5
|
Project-URL: Homepage, https://github.com/dynotx/liminal-orm
|
|
6
6
|
Project-URL: Repository, https://github.com/dynotx/liminal-orm
|
|
@@ -15,6 +15,7 @@ Requires-Dist: click<8.2.0,>=8.0.0
|
|
|
15
15
|
Requires-Dist: lxml<6,>=5.3.0
|
|
16
16
|
Requires-Dist: numpy<2,>=1.23.5
|
|
17
17
|
Requires-Dist: pandas<3,>=1.5.3
|
|
18
|
+
Requires-Dist: playwright>=1.58.0
|
|
18
19
|
Requires-Dist: psycopg2-binary<3,>=2.9.10
|
|
19
20
|
Requires-Dist: pydantic<=2.13,>=2
|
|
20
21
|
Requires-Dist: requests<3,>=2.32.3
|
|
@@ -12,7 +12,7 @@ def _check_liminal_directory_initialized(liminal_dir_path: Path) -> None:
|
|
|
12
12
|
"""Raises an exception if the liminal directory does not exist at the given path."""
|
|
13
13
|
if not liminal_dir_path.exists() or not liminal_dir_path.is_dir():
|
|
14
14
|
raise Exception(
|
|
15
|
-
"
|
|
15
|
+
"/liminal directory not found at current working directory where `liminal` command was run. Run `liminal init` or ensure that your current working directory is where the /liminal environment is located."
|
|
16
16
|
)
|
|
17
17
|
else:
|
|
18
18
|
if not (liminal_dir_path / "env.py").exists():
|
|
@@ -67,10 +67,6 @@ def _read_local_env_file(
|
|
|
67
67
|
raise Exception(
|
|
68
68
|
"api_client_id and api_client_secret must be provided in BenchlingConnection in liminal/env.py. This is necessary for the migration service."
|
|
69
69
|
)
|
|
70
|
-
if not bc.internal_api_admin_email or not bc.internal_api_admin_password:
|
|
71
|
-
raise Exception(
|
|
72
|
-
"internal_api_admin_email and internal_api_admin_password must be provided in BenchlingConnection in liminal/env.py. This is necessary for the migration service."
|
|
73
|
-
)
|
|
74
70
|
return bc
|
|
75
71
|
raise Exception(
|
|
76
72
|
f"BenchlingConnection with tenant name or alias {benchling_tenant} not found in liminal/env.py. Please update the env.py file with a correctly defined BenchlingConnection."
|
|
@@ -37,9 +37,14 @@ class BenchlingConnection(BaseModel):
|
|
|
37
37
|
warehouse_connection_string: str | None = None
|
|
38
38
|
The connection string for the warehouse.
|
|
39
39
|
internal_api_admin_email: str | None = None
|
|
40
|
-
The email of the internal API admin.
|
|
40
|
+
The email of the internal API admin. If SSO is not enabled or optional on your Benchling tenant, this email is used to log in to Benchling, and give Liminal the authenticated internal API session cookie.
|
|
41
41
|
internal_api_admin_password: str | None = None
|
|
42
|
-
The password of the internal API admin.
|
|
42
|
+
The password of the internal API admin. If SSO is not enabled or optional on your Benchling tenant, this password is used to log in to Benchling, and give Liminal the authenticated internal API session cookie.
|
|
43
|
+
playwright_data_dir: str | None = "~/.liminal/chrome_data/"
|
|
44
|
+
The directory to store the playwright browser user data. If SSO is enabled and required on your Benchling tenant,
|
|
45
|
+
Liminal uses playwright so the user can log into Benchling in order to give Liminal the authenticated internal API session cookie.
|
|
46
|
+
This directory is used to store playwright's persistent context, allowing the user to set up a persistent chrome profile.
|
|
47
|
+
Set this to None in order to disable playwright's persistent context which enables automatic login.
|
|
43
48
|
fieldsets: bool = False
|
|
44
49
|
Whether your Benchling tenant has access to fieldsets.
|
|
45
50
|
config_flags: TenantConfigFlags = TenantConfigFlags()
|
|
@@ -54,6 +59,7 @@ class BenchlingConnection(BaseModel):
|
|
|
54
59
|
warehouse_connection_string: str | None = None
|
|
55
60
|
internal_api_admin_email: str | None = None
|
|
56
61
|
internal_api_admin_password: str | None = None
|
|
62
|
+
playwright_data_dir: str | None = "~/.liminal/playwright_chrome_data/"
|
|
57
63
|
fieldsets: bool = False
|
|
58
64
|
config_flags: TenantConfigFlags = TenantConfigFlags()
|
|
59
65
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import logging
|
|
3
|
+
import os
|
|
2
4
|
from typing import Any
|
|
5
|
+
from urllib.parse import urlparse
|
|
3
6
|
|
|
7
|
+
from playwright.async_api import async_playwright, Request
|
|
4
8
|
import requests
|
|
5
9
|
from benchling_sdk.auth.client_credentials_oauth2 import ClientCredentialsOAuth2
|
|
6
10
|
from benchling_sdk.benchling import Benchling
|
|
@@ -32,6 +36,10 @@ REMOTE_LIMINAL_SCHEMA_NAME = "liminal_remote"
|
|
|
32
36
|
REMOTE_REVISION_ID_FIELD_WH_NAME = "revision_id"
|
|
33
37
|
|
|
34
38
|
|
|
39
|
+
class SSODisabledError(ValueError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
35
43
|
class BenchlingService(Benchling):
|
|
36
44
|
"""
|
|
37
45
|
Class that creates a connection object that can be used to connect to Benchling's API, database, or internal API.
|
|
@@ -88,30 +96,28 @@ class BenchlingService(Benchling):
|
|
|
88
96
|
)
|
|
89
97
|
self.use_internal_api = use_internal_api
|
|
90
98
|
if use_internal_api:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
and connection.internal_api_admin_password
|
|
94
|
-
):
|
|
95
|
-
csrf_token, session = self.autogenerate_auth(
|
|
99
|
+
try:
|
|
100
|
+
authenticated_session, csrf_token = self.autogenerate_auth(
|
|
96
101
|
connection.tenant_name,
|
|
97
102
|
connection.internal_api_admin_email,
|
|
98
103
|
connection.internal_api_admin_password,
|
|
104
|
+
connection.playwright_data_dir,
|
|
99
105
|
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
self.custom_post_headers = {
|
|
104
|
-
"X-Csrftoken": csrf_token,
|
|
105
|
-
"Referer": f"https://{connection.tenant_name}.benchling.com/",
|
|
106
|
-
"Content-Type": "application/json",
|
|
107
|
-
}
|
|
108
|
-
LOGGER.info(
|
|
109
|
-
f"Tenant {connection.tenant_name}: Connected to Benchling internal API."
|
|
110
|
-
)
|
|
111
|
-
else:
|
|
112
|
-
raise ValueError(
|
|
113
|
-
"use_internal_api is True but internal_api_admin_email and internal_api_admin_password not provided in BenchlingConnection."
|
|
106
|
+
except SSODisabledError as e:
|
|
107
|
+
raise SSODisabledError(
|
|
108
|
+
f"{e} Please provide `internal_api_admin_email` and `internal_api_admin_password` in your BenchlingConnection."
|
|
114
109
|
)
|
|
110
|
+
self.custom_post_cookies = {
|
|
111
|
+
"session": authenticated_session,
|
|
112
|
+
}
|
|
113
|
+
self.custom_post_headers = {
|
|
114
|
+
"Referer": f"https://{connection.tenant_name}.benchling.com/",
|
|
115
|
+
"Content-Type": "application/json",
|
|
116
|
+
"x-csrftoken": csrf_token,
|
|
117
|
+
}
|
|
118
|
+
LOGGER.info(
|
|
119
|
+
f"Tenant {connection.tenant_name}: Connected to Benchling internal API."
|
|
120
|
+
)
|
|
115
121
|
|
|
116
122
|
@property
|
|
117
123
|
def session(self) -> Session:
|
|
@@ -249,6 +255,108 @@ class BenchlingService(Benchling):
|
|
|
249
255
|
f"Error finding field on {REMOTE_LIMINAL_SCHEMA_NAME} schema with warehouse_name {REMOTE_REVISION_ID_FIELD_WH_NAME}. Check schema fields to ensure this field exists and is defined according to documentation."
|
|
250
256
|
)
|
|
251
257
|
|
|
258
|
+
@classmethod
|
|
259
|
+
def autogenerate_auth(
|
|
260
|
+
cls,
|
|
261
|
+
benchling_tenant: str,
|
|
262
|
+
email: str | None = None,
|
|
263
|
+
password: str | None = None,
|
|
264
|
+
playwright_data_dir: str | None = None,
|
|
265
|
+
) -> tuple[str, str]:
|
|
266
|
+
"""Logs in to Benchling using the admin email and password or playwright and returns the session cookie and CSRF token.
|
|
267
|
+
If email and password are not passed in or if SSO is set to required on the Benchling tenant, playwright is used to log in.
|
|
268
|
+
Otherwise, the admin email and password are used to log in."""
|
|
269
|
+
with requests.Session() as session:
|
|
270
|
+
if email and password:
|
|
271
|
+
signin_page = session.get(
|
|
272
|
+
f"https://{benchling_tenant}.benchling.com/signin",
|
|
273
|
+
allow_redirects=False,
|
|
274
|
+
)
|
|
275
|
+
if signin_page.status_code == 200:
|
|
276
|
+
return cls.get_authenticated_session_benchling_admin_login(
|
|
277
|
+
benchling_tenant, email, password
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
else:
|
|
281
|
+
signin_page = session.get(
|
|
282
|
+
f"https://{benchling_tenant}.benchling.com/ext/saml/signin:begin",
|
|
283
|
+
allow_redirects=False,
|
|
284
|
+
)
|
|
285
|
+
if signin_page.status_code == 403 or signin_page.status_code == 400:
|
|
286
|
+
raise SSODisabledError(
|
|
287
|
+
f"admin_email and admin_password not provided when sso is turned off for Benchling tenant {benchling_tenant}."
|
|
288
|
+
)
|
|
289
|
+
if signin_page.status_code == 302:
|
|
290
|
+
try:
|
|
291
|
+
return asyncio.get_event_loop().run_until_complete(
|
|
292
|
+
cls.get_authenticated_session_sso_login_playwright(
|
|
293
|
+
benchling_tenant, playwright_data_dir
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
except RuntimeError as e:
|
|
297
|
+
raise RuntimeError(
|
|
298
|
+
f"{e}. If you are running this in a Jupyter notebook, use `nest_asyncio.apply()` to allow the async playwright login to run."
|
|
299
|
+
)
|
|
300
|
+
else:
|
|
301
|
+
raise ValueError(
|
|
302
|
+
f"Unexpected response: Status code {signin_page.status_code}: {signin_page.text}"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
@classmethod
|
|
306
|
+
async def get_authenticated_session_sso_login_playwright(
|
|
307
|
+
cls, benchling_tenant: str, playwright_data_dir: str | None = None
|
|
308
|
+
) -> tuple[str, str]:
|
|
309
|
+
"""Logs in to Benchling using playwright and returns the session cookie and CSRF token.
|
|
310
|
+
This can be used when SSO is enabled and required on the Benchling tenant."""
|
|
311
|
+
LOGGER.info(f"Log into your {benchling_tenant} Benchling tenant...")
|
|
312
|
+
async with async_playwright() as playwright:
|
|
313
|
+
if playwright_data_dir:
|
|
314
|
+
context = await playwright.chromium.launch_persistent_context(
|
|
315
|
+
channel="chrome",
|
|
316
|
+
headless=False,
|
|
317
|
+
user_data_dir=os.path.expanduser(playwright_data_dir),
|
|
318
|
+
)
|
|
319
|
+
else:
|
|
320
|
+
browser = await playwright.chromium.launch(
|
|
321
|
+
channel="chrome", headless=False
|
|
322
|
+
)
|
|
323
|
+
context = await browser.new_context()
|
|
324
|
+
page = await context.new_page()
|
|
325
|
+
|
|
326
|
+
headers = {}
|
|
327
|
+
|
|
328
|
+
async def async_get_request_headers(request: Request) -> None:
|
|
329
|
+
if urlparse(request.url).netloc == f"{benchling_tenant}.benchling.com":
|
|
330
|
+
headers.update(await request.all_headers())
|
|
331
|
+
|
|
332
|
+
page.on("request", async_get_request_headers)
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
await page.goto(f"https://{benchling_tenant}.benchling.com")
|
|
336
|
+
except Exception:
|
|
337
|
+
raise ValueError(
|
|
338
|
+
f"Error navigating to https://{benchling_tenant}.benchling.com"
|
|
339
|
+
)
|
|
340
|
+
try:
|
|
341
|
+
await page.wait_for_url(
|
|
342
|
+
f"**/{benchling_tenant}.benchling.com/**", timeout=600_000
|
|
343
|
+
)
|
|
344
|
+
except Exception:
|
|
345
|
+
raise TimeoutError(
|
|
346
|
+
f"Log in cancelled or timed out (2 min timeout). Did not detect SSO log in for https://{benchling_tenant}.benchling.com."
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
cookies = await context.cookies()
|
|
350
|
+
session_cookie = next(
|
|
351
|
+
(c["value"] for c in cookies if c["name"] == "session"), None
|
|
352
|
+
)
|
|
353
|
+
if not session_cookie:
|
|
354
|
+
raise ValueError("No session cookie found.")
|
|
355
|
+
csrf_token = headers.get("x-csrftoken", None)
|
|
356
|
+
if not csrf_token:
|
|
357
|
+
raise ValueError("No CSRF token found.")
|
|
358
|
+
return session_cookie, csrf_token
|
|
359
|
+
|
|
252
360
|
@classmethod
|
|
253
361
|
@retry(
|
|
254
362
|
stop=stop_after_attempt(3),
|
|
@@ -256,9 +364,11 @@ class BenchlingService(Benchling):
|
|
|
256
364
|
wait=wait_exponential(multiplier=1, min=1, max=8),
|
|
257
365
|
reraise=True,
|
|
258
366
|
)
|
|
259
|
-
def
|
|
367
|
+
def get_authenticated_session_benchling_admin_login(
|
|
260
368
|
cls, benchling_tenant: str, email: str, password: str
|
|
261
369
|
) -> tuple[str, str]:
|
|
370
|
+
"""Logs in to Benchling using the admin email and password and returns the session cookie and CSRF token.
|
|
371
|
+
This can be used when SSO is disabled or optional on the Benchling tenant."""
|
|
262
372
|
with requests.Session() as session:
|
|
263
373
|
homepage = session.get(f"https://{benchling_tenant}.benchling.com/signin")
|
|
264
374
|
soup = BeautifulSoup(homepage.content, features="lxml")
|
|
@@ -286,6 +396,9 @@ class BenchlingService(Benchling):
|
|
|
286
396
|
raise ValueError(
|
|
287
397
|
f"Failed to sign in to Benchling: {signin_response.text}"
|
|
288
398
|
)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
399
|
+
return (
|
|
400
|
+
signin_response.headers["Set-Cookie"]
|
|
401
|
+
.split("; Secure")[0]
|
|
402
|
+
.removeprefix("session="),
|
|
403
|
+
csrf_token,
|
|
404
|
+
)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from liminal.connection import BenchlingService
|
|
7
|
+
from liminal.enums.benchling_api_entity_type import BenchlingAPIEntityType
|
|
8
|
+
from liminal.utils import await_queued_response
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_entity_schema(
|
|
12
|
+
benchling_service: BenchlingService,
|
|
13
|
+
api_entity_type: BenchlingAPIEntityType,
|
|
14
|
+
payload: dict[str, Any],
|
|
15
|
+
) -> dict[str, Any]:
|
|
16
|
+
"""
|
|
17
|
+
Create a new entity schema.
|
|
18
|
+
"""
|
|
19
|
+
response = benchling_service.api.post_response(
|
|
20
|
+
url=f"/api/v3-alpha/benchling/{api_entity_type.value}", body=payload
|
|
21
|
+
)
|
|
22
|
+
if not (200 <= response.status_code < 300):
|
|
23
|
+
raise Exception("Failed to create entity schema:", response.content)
|
|
24
|
+
return json.loads(response.content)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_entity_schema(
|
|
28
|
+
benchling_service: BenchlingService,
|
|
29
|
+
api_entity_type: BenchlingAPIEntityType,
|
|
30
|
+
entity_schema_id: str,
|
|
31
|
+
) -> dict[str, Any]:
|
|
32
|
+
"""
|
|
33
|
+
Get an entity schema.
|
|
34
|
+
"""
|
|
35
|
+
response = benchling_service.api.get_response(
|
|
36
|
+
url=f"/api/v3-alpha/benchling/{api_entity_type.value}/{entity_schema_id}"
|
|
37
|
+
)
|
|
38
|
+
if not (200 <= response.status_code < 300):
|
|
39
|
+
raise Exception("Failed to get entity schema:", response.content)
|
|
40
|
+
return json.loads(response.content)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def list_entity_schemas(
|
|
44
|
+
benchling_service: BenchlingService,
|
|
45
|
+
) -> dict[str, Any]:
|
|
46
|
+
"""
|
|
47
|
+
List all entity schemas.
|
|
48
|
+
"""
|
|
49
|
+
response = benchling_service.api.get_response(
|
|
50
|
+
url="/api/v3-alpha/benchling/entity-schema/items"
|
|
51
|
+
)
|
|
52
|
+
if not (200 <= response.status_code < 300):
|
|
53
|
+
raise Exception("Failed to get entity schema:", response.content)
|
|
54
|
+
return json.loads(response.content)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_entity_schema_field_definitions(
|
|
58
|
+
benchling_service: BenchlingService,
|
|
59
|
+
api_entity_type: BenchlingAPIEntityType,
|
|
60
|
+
entity_schema_id: str,
|
|
61
|
+
) -> dict[str, Any]:
|
|
62
|
+
"""
|
|
63
|
+
Get the field definitions for an entity schema.
|
|
64
|
+
"""
|
|
65
|
+
response = benchling_service.api.get_response(
|
|
66
|
+
url=f"/api/v3-alpha/benchling/{api_entity_type.value}/{entity_schema_id}/field-definitions/items"
|
|
67
|
+
)
|
|
68
|
+
if not (200 <= response.status_code < 300):
|
|
69
|
+
raise Exception("Failed to get entity schema:", response.content)
|
|
70
|
+
return json.loads(response.content)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_entity_schema_fieldsets(
|
|
74
|
+
benchling_service: BenchlingService,
|
|
75
|
+
api_entity_type: BenchlingAPIEntityType,
|
|
76
|
+
entity_schema_id: str,
|
|
77
|
+
) -> dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
Get the fieldsets for an entity schema.
|
|
80
|
+
"""
|
|
81
|
+
response = benchling_service.api.get_response(
|
|
82
|
+
url=f"/api/v3-alpha/benchling/{api_entity_type.value}/{entity_schema_id}/fieldsets/items"
|
|
83
|
+
)
|
|
84
|
+
if not (200 <= response.status_code < 300):
|
|
85
|
+
raise Exception("Failed to get entity schema:", response.content)
|
|
86
|
+
return json.loads(response.content)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# def archive_tag_schemas(
|
|
90
|
+
# benchling_service: BenchlingService, entity_schema_ids: list[str]
|
|
91
|
+
# ) -> dict[str, Any]:
|
|
92
|
+
# """
|
|
93
|
+
# Archive a list of entity schema ids.
|
|
94
|
+
# """
|
|
95
|
+
# with requests.Session() as session:
|
|
96
|
+
# response = session.post(
|
|
97
|
+
# f"https://{benchling_service.benchling_tenant}.benchling.com/1/api/tag-schemas:bulk-archive",
|
|
98
|
+
# data=json.dumps({"ids": entity_schema_ids, "purpose": "Made in error"}),
|
|
99
|
+
# headers=benchling_service.custom_post_headers,
|
|
100
|
+
# cookies=benchling_service.custom_post_cookies,
|
|
101
|
+
# )
|
|
102
|
+
# if not response.ok:
|
|
103
|
+
# raise Exception("Failed to archive tag schemas:", response.content)
|
|
104
|
+
# return response.json()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# def unarchive_tag_schemas(
|
|
108
|
+
# benchling_service: BenchlingService, entity_schema_ids: list[str]
|
|
109
|
+
# ) -> dict[str, Any]:
|
|
110
|
+
# """
|
|
111
|
+
# Unarchive a list of entity schema ids.
|
|
112
|
+
# """
|
|
113
|
+
# with requests.Session() as session:
|
|
114
|
+
# response = session.post(
|
|
115
|
+
# f"https://{benchling_service.benchling_tenant}.benchling.com/1/api/tag-schemas:bulk-unarchive",
|
|
116
|
+
# data=json.dumps({"ids": entity_schema_ids}),
|
|
117
|
+
# headers=benchling_service.custom_post_headers,
|
|
118
|
+
# cookies=benchling_service.custom_post_cookies,
|
|
119
|
+
# )
|
|
120
|
+
# if not response.ok:
|
|
121
|
+
# raise Exception("Failed to unarchive tag schemas:", response.content)
|
|
122
|
+
# return response.json()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# def update_tag_schema(
|
|
126
|
+
# benchling_service: BenchlingService, entity_schema_id: str, payload: dict[str, Any]
|
|
127
|
+
# ) -> dict[str, Any]:
|
|
128
|
+
# """
|
|
129
|
+
# Update the tag schema with a new field.
|
|
130
|
+
# """
|
|
131
|
+
# with requests.Session() as session:
|
|
132
|
+
# queued_response = session.post(
|
|
133
|
+
# f"https://{benchling_service.benchling_tenant}.benchling.com/1/api/tag-schemas/{entity_schema_id}/actions/update",
|
|
134
|
+
# data=json.dumps(payload),
|
|
135
|
+
# headers=benchling_service.custom_post_headers,
|
|
136
|
+
# cookies=benchling_service.custom_post_cookies,
|
|
137
|
+
# )
|
|
138
|
+
# if not queued_response.ok:
|
|
139
|
+
# raise Exception("Failed to update tag schema:", queued_response.content)
|
|
140
|
+
# return await_queued_response(
|
|
141
|
+
# queued_response.json()["status_url"], benchling_service
|
|
142
|
+
# )
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# def set_tag_schema_name_template(
|
|
146
|
+
# benchling_service: BenchlingService, entity_schema_id: str, payload: dict[str, Any]
|
|
147
|
+
# ) -> dict[str, Any]:
|
|
148
|
+
# """
|
|
149
|
+
# Update the tag schema name template. Must be in a separate endpoint compared to update_tag_schema.
|
|
150
|
+
# """
|
|
151
|
+
# with requests.Session() as session:
|
|
152
|
+
# response = session.post(
|
|
153
|
+
# f"https://{benchling_service.benchling_tenant}.benchling.com/1/api/tag-schemas/{entity_schema_id}/actions/set-name-template",
|
|
154
|
+
# data=json.dumps(payload),
|
|
155
|
+
# headers=benchling_service.custom_post_headers,
|
|
156
|
+
# cookies=benchling_service.custom_post_cookies,
|
|
157
|
+
# )
|
|
158
|
+
# if not response.ok:
|
|
159
|
+
# raise Exception("Failed to set tag schema name template:", response.content)
|
|
160
|
+
# return response.json()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
from liminal.connection import BenchlingService
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EntitySchemaOrganizationRefV2(BaseModel):
|
|
10
|
+
id: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EntitySchemaConstraintV2(BaseModel):
|
|
14
|
+
fieldDefinitionNames: list[str] = Field(default_factory=list)
|
|
15
|
+
hasUniqueResidues: bool | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FieldDefinitionLinkRefV2(BaseModel):
|
|
19
|
+
id: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EntitySchemaFieldDefinitionV2(BaseModel):
|
|
23
|
+
id: str
|
|
24
|
+
name: str
|
|
25
|
+
archived: bool
|
|
26
|
+
systemName: str
|
|
27
|
+
isMulti: bool | None = None
|
|
28
|
+
isRequired: bool
|
|
29
|
+
derivationType: str
|
|
30
|
+
isParent: bool | None = None
|
|
31
|
+
archiveReason: str | None = None
|
|
32
|
+
description: str | None = None
|
|
33
|
+
linkDefinition: FieldDefinitionLinkRefV2 | None = None
|
|
34
|
+
__typename: str | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class EntitySchemaModelV2(BaseModel):
|
|
38
|
+
"""Pydantic model for Benchling entity schema API payloads (e.g. RnaSequenceSchema)."""
|
|
39
|
+
|
|
40
|
+
model_config = ConfigDict(extra="ignore")
|
|
41
|
+
|
|
42
|
+
id: str
|
|
43
|
+
fieldDefinitions: str
|
|
44
|
+
fields: list[EntitySchemaFieldDefinitionV2] = Field(default_factory=list)
|
|
45
|
+
systemName: str
|
|
46
|
+
itemIdPrefix: str
|
|
47
|
+
containableType: str
|
|
48
|
+
fieldsets: str
|
|
49
|
+
systemCategories: list[Any] = Field(default_factory=list)
|
|
50
|
+
organization: EntitySchemaOrganizationRefV2
|
|
51
|
+
modifiedAt: str
|
|
52
|
+
includeRegistryIdInChips: bool | None = None
|
|
53
|
+
useRegistryIdForItemDisplayLabel: bool | None = None
|
|
54
|
+
availableRegistrationNamingOptions: list[str] = Field(default_factory=list)
|
|
55
|
+
archived: bool | None = None
|
|
56
|
+
name: str
|
|
57
|
+
description: str | None = None
|
|
58
|
+
showResidues: bool | None = None
|
|
59
|
+
createdAt: str
|
|
60
|
+
constraint: EntitySchemaConstraintV2 | None = None
|
|
61
|
+
archiveReason: str | None = None
|
|
62
|
+
__typename: str | None = None
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def get_one(
|
|
66
|
+
cls,
|
|
67
|
+
benchling_service: BenchlingService,
|
|
68
|
+
wh_schema_name: str,
|
|
69
|
+
schemas_data: list[dict[str, Any]] | None = None,
|
|
70
|
+
) -> EntitySchemaModelV2:
|
|
71
|
+
if schemas_data is None:
|
|
72
|
+
schemas_data = cls.get_all_json(benchling_service)
|
|
73
|
+
schema = next(
|
|
74
|
+
(
|
|
75
|
+
schema
|
|
76
|
+
for schema in schemas_data
|
|
77
|
+
if schema["sqlIdentifier"] == wh_schema_name
|
|
78
|
+
and schema["registryId"] == benchling_service.registry_id
|
|
79
|
+
),
|
|
80
|
+
None,
|
|
81
|
+
)
|
|
82
|
+
if schema is None:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"Schema {wh_schema_name} not found in Benchling {benchling_service.benchling_tenant}."
|
|
85
|
+
)
|
|
86
|
+
return cls.model_validate(schema)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from liminal.base.str_enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BenchlingAPIEntityType(StrEnum):
|
|
5
|
+
"""This enum represents the different types a Benchling field can have on an entity schema.
|
|
6
|
+
These are ONLY USED FOR THE INTERNAL BENCHLING API."""
|
|
7
|
+
|
|
8
|
+
CUSTOM_ENTITY_SCHEMA = "custom-entity-schema"
|
|
9
|
+
AA_SEQUENCE_SCHEMA = "aa-sequence-schema"
|
|
10
|
+
DNA_OLIGO_SCHEMA = "dna-oligo-schema"
|
|
11
|
+
DNA_SEQUENCE_SCHEMA = "dna-sequence-schema"
|
|
12
|
+
OLIGO_CONJUGATE_SCHEMA = "oligo-conjugate-schema"
|
|
13
|
+
OLIGO_DUPLEX_SCHEMA = "oligo-duplex-schema"
|
|
14
|
+
RNA_OLIGO_SCHEMA = "rna-oligo-schema"
|
|
15
|
+
RNA_SEQUENCE_SCHEMA = "rna-sequence-schema"
|
|
16
|
+
MIXTURE_SCHEMA = "mixture-schema"
|
|
17
|
+
MOLECULE_SCHEMA = "molecule-schema"
|
|
18
|
+
EQUIPMENT_SCHEMA = "equipment-schema"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "liminal-orm"
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.5.1"
|
|
4
4
|
description = "An ORM and toolkit that builds on top of Benchling's platform to keep your schemas and downstream code dependencies in sync."
|
|
5
5
|
authors = [{ name = "DynoTx Open Source", email = "opensource@dynotx.com" }]
|
|
6
6
|
requires-python = ">=3.9,<4"
|
|
@@ -20,6 +20,7 @@ dependencies = [
|
|
|
20
20
|
"psycopg2-binary>=2.9.10,<3",
|
|
21
21
|
"tornado==6.5.0",
|
|
22
22
|
"click>=8.0.0,<8.2.0",
|
|
23
|
+
"playwright>=1.58.0",
|
|
23
24
|
]
|
|
24
25
|
|
|
25
26
|
[virtualenvs]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*
|
liminal_orm-4.4.9/liminal/.lock
DELETED
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Signature: 8a477f597d28d172789f06886806bc55
|
|
Binary file
|
|
Binary file
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
-
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
-
|
|
4
|
-
<svg
|
|
5
|
-
version="1.0"
|
|
6
|
-
id="svg2"
|
|
7
|
-
sodipodi:version="0.32"
|
|
8
|
-
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
|
9
|
-
sodipodi:docname="python-logo-only.svg"
|
|
10
|
-
width="83.371017pt"
|
|
11
|
-
height="101.00108pt"
|
|
12
|
-
inkscape:export-filename="python-logo-only.png"
|
|
13
|
-
inkscape:export-xdpi="232.44"
|
|
14
|
-
inkscape:export-ydpi="232.44"
|
|
15
|
-
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
16
|
-
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
17
|
-
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
18
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
19
|
-
xmlns:svg="http://www.w3.org/2000/svg"
|
|
20
|
-
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
21
|
-
xmlns:cc="http://creativecommons.org/ns#"
|
|
22
|
-
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
23
|
-
<metadata
|
|
24
|
-
id="metadata371">
|
|
25
|
-
<rdf:RDF>
|
|
26
|
-
<cc:Work
|
|
27
|
-
rdf:about="">
|
|
28
|
-
<dc:format>image/svg+xml</dc:format>
|
|
29
|
-
<dc:type
|
|
30
|
-
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
31
|
-
</cc:Work>
|
|
32
|
-
</rdf:RDF>
|
|
33
|
-
</metadata>
|
|
34
|
-
<sodipodi:namedview
|
|
35
|
-
inkscape:window-height="2080"
|
|
36
|
-
inkscape:window-width="1976"
|
|
37
|
-
inkscape:pageshadow="2"
|
|
38
|
-
inkscape:pageopacity="0.0"
|
|
39
|
-
guidetolerance="10.0"
|
|
40
|
-
gridtolerance="10.0"
|
|
41
|
-
objecttolerance="10.0"
|
|
42
|
-
borderopacity="1.0"
|
|
43
|
-
bordercolor="#666666"
|
|
44
|
-
pagecolor="#ffffff"
|
|
45
|
-
id="base"
|
|
46
|
-
inkscape:zoom="2.1461642"
|
|
47
|
-
inkscape:cx="91.558698"
|
|
48
|
-
inkscape:cy="47.9926"
|
|
49
|
-
inkscape:window-x="1092"
|
|
50
|
-
inkscape:window-y="72"
|
|
51
|
-
inkscape:current-layer="svg2"
|
|
52
|
-
width="210mm"
|
|
53
|
-
height="40mm"
|
|
54
|
-
units="mm"
|
|
55
|
-
inkscape:showpageshadow="2"
|
|
56
|
-
inkscape:pagecheckerboard="0"
|
|
57
|
-
inkscape:deskcolor="#d1d1d1"
|
|
58
|
-
inkscape:document-units="pt"
|
|
59
|
-
showgrid="false"
|
|
60
|
-
inkscape:window-maximized="0" />
|
|
61
|
-
<defs
|
|
62
|
-
id="defs4">
|
|
63
|
-
<linearGradient
|
|
64
|
-
id="linearGradient2795">
|
|
65
|
-
<stop
|
|
66
|
-
style="stop-color:#b8b8b8;stop-opacity:0.49803922;"
|
|
67
|
-
offset="0"
|
|
68
|
-
id="stop2797" />
|
|
69
|
-
<stop
|
|
70
|
-
style="stop-color:#7f7f7f;stop-opacity:0;"
|
|
71
|
-
offset="1"
|
|
72
|
-
id="stop2799" />
|
|
73
|
-
</linearGradient>
|
|
74
|
-
<linearGradient
|
|
75
|
-
id="linearGradient2787">
|
|
76
|
-
<stop
|
|
77
|
-
style="stop-color:#7f7f7f;stop-opacity:0.5;"
|
|
78
|
-
offset="0"
|
|
79
|
-
id="stop2789" />
|
|
80
|
-
<stop
|
|
81
|
-
style="stop-color:#7f7f7f;stop-opacity:0;"
|
|
82
|
-
offset="1"
|
|
83
|
-
id="stop2791" />
|
|
84
|
-
</linearGradient>
|
|
85
|
-
<linearGradient
|
|
86
|
-
id="linearGradient3676">
|
|
87
|
-
<stop
|
|
88
|
-
style="stop-color:#b2b2b2;stop-opacity:0.5;"
|
|
89
|
-
offset="0"
|
|
90
|
-
id="stop3678" />
|
|
91
|
-
<stop
|
|
92
|
-
style="stop-color:#b3b3b3;stop-opacity:0;"
|
|
93
|
-
offset="1"
|
|
94
|
-
id="stop3680" />
|
|
95
|
-
</linearGradient>
|
|
96
|
-
<linearGradient
|
|
97
|
-
id="linearGradient3236">
|
|
98
|
-
<stop
|
|
99
|
-
style="stop-color:#f4f4f4;stop-opacity:1"
|
|
100
|
-
offset="0"
|
|
101
|
-
id="stop3244" />
|
|
102
|
-
<stop
|
|
103
|
-
style="stop-color:white;stop-opacity:1"
|
|
104
|
-
offset="1"
|
|
105
|
-
id="stop3240" />
|
|
106
|
-
</linearGradient>
|
|
107
|
-
<linearGradient
|
|
108
|
-
id="linearGradient4671">
|
|
109
|
-
<stop
|
|
110
|
-
style="stop-color:#ffd43b;stop-opacity:1;"
|
|
111
|
-
offset="0"
|
|
112
|
-
id="stop4673" />
|
|
113
|
-
<stop
|
|
114
|
-
style="stop-color:#ffe873;stop-opacity:1"
|
|
115
|
-
offset="1"
|
|
116
|
-
id="stop4675" />
|
|
117
|
-
</linearGradient>
|
|
118
|
-
<linearGradient
|
|
119
|
-
id="linearGradient4689">
|
|
120
|
-
<stop
|
|
121
|
-
style="stop-color:#5a9fd4;stop-opacity:1;"
|
|
122
|
-
offset="0"
|
|
123
|
-
id="stop4691" />
|
|
124
|
-
<stop
|
|
125
|
-
style="stop-color:#306998;stop-opacity:1;"
|
|
126
|
-
offset="1"
|
|
127
|
-
id="stop4693" />
|
|
128
|
-
</linearGradient>
|
|
129
|
-
<linearGradient
|
|
130
|
-
x1="224.23996"
|
|
131
|
-
y1="144.75717"
|
|
132
|
-
x2="-65.308502"
|
|
133
|
-
y2="144.75717"
|
|
134
|
-
id="linearGradient2987"
|
|
135
|
-
xlink:href="#linearGradient4671"
|
|
136
|
-
gradientUnits="userSpaceOnUse"
|
|
137
|
-
gradientTransform="translate(100.2702,99.61116)" />
|
|
138
|
-
<linearGradient
|
|
139
|
-
x1="172.94208"
|
|
140
|
-
y1="77.475983"
|
|
141
|
-
x2="26.670298"
|
|
142
|
-
y2="76.313133"
|
|
143
|
-
id="linearGradient2990"
|
|
144
|
-
xlink:href="#linearGradient4689"
|
|
145
|
-
gradientUnits="userSpaceOnUse"
|
|
146
|
-
gradientTransform="translate(100.2702,99.61116)" />
|
|
147
|
-
<linearGradient
|
|
148
|
-
inkscape:collect="always"
|
|
149
|
-
xlink:href="#linearGradient4689"
|
|
150
|
-
id="linearGradient2587"
|
|
151
|
-
gradientUnits="userSpaceOnUse"
|
|
152
|
-
gradientTransform="translate(100.2702,99.61116)"
|
|
153
|
-
x1="172.94208"
|
|
154
|
-
y1="77.475983"
|
|
155
|
-
x2="26.670298"
|
|
156
|
-
y2="76.313133" />
|
|
157
|
-
<linearGradient
|
|
158
|
-
inkscape:collect="always"
|
|
159
|
-
xlink:href="#linearGradient4671"
|
|
160
|
-
id="linearGradient2589"
|
|
161
|
-
gradientUnits="userSpaceOnUse"
|
|
162
|
-
gradientTransform="translate(100.2702,99.61116)"
|
|
163
|
-
x1="224.23996"
|
|
164
|
-
y1="144.75717"
|
|
165
|
-
x2="-65.308502"
|
|
166
|
-
y2="144.75717" />
|
|
167
|
-
<linearGradient
|
|
168
|
-
inkscape:collect="always"
|
|
169
|
-
xlink:href="#linearGradient4689"
|
|
170
|
-
id="linearGradient2248"
|
|
171
|
-
gradientUnits="userSpaceOnUse"
|
|
172
|
-
gradientTransform="translate(100.2702,99.61116)"
|
|
173
|
-
x1="172.94208"
|
|
174
|
-
y1="77.475983"
|
|
175
|
-
x2="26.670298"
|
|
176
|
-
y2="76.313133" />
|
|
177
|
-
<linearGradient
|
|
178
|
-
inkscape:collect="always"
|
|
179
|
-
xlink:href="#linearGradient4671"
|
|
180
|
-
id="linearGradient2250"
|
|
181
|
-
gradientUnits="userSpaceOnUse"
|
|
182
|
-
gradientTransform="translate(100.2702,99.61116)"
|
|
183
|
-
x1="224.23996"
|
|
184
|
-
y1="144.75717"
|
|
185
|
-
x2="-65.308502"
|
|
186
|
-
y2="144.75717" />
|
|
187
|
-
<linearGradient
|
|
188
|
-
inkscape:collect="always"
|
|
189
|
-
xlink:href="#linearGradient4671"
|
|
190
|
-
id="linearGradient2255"
|
|
191
|
-
gradientUnits="userSpaceOnUse"
|
|
192
|
-
gradientTransform="matrix(0.562541,0,0,0.567972,-11.5974,-7.60954)"
|
|
193
|
-
x1="224.23996"
|
|
194
|
-
y1="144.75717"
|
|
195
|
-
x2="-65.308502"
|
|
196
|
-
y2="144.75717" />
|
|
197
|
-
<linearGradient
|
|
198
|
-
inkscape:collect="always"
|
|
199
|
-
xlink:href="#linearGradient4689"
|
|
200
|
-
id="linearGradient2258"
|
|
201
|
-
gradientUnits="userSpaceOnUse"
|
|
202
|
-
gradientTransform="matrix(0.562541,0,0,0.567972,-11.5974,-7.60954)"
|
|
203
|
-
x1="172.94208"
|
|
204
|
-
y1="76.176224"
|
|
205
|
-
x2="26.670298"
|
|
206
|
-
y2="76.313133" />
|
|
207
|
-
<radialGradient
|
|
208
|
-
inkscape:collect="always"
|
|
209
|
-
xlink:href="#linearGradient2795"
|
|
210
|
-
id="radialGradient2801"
|
|
211
|
-
cx="61.518883"
|
|
212
|
-
cy="132.28575"
|
|
213
|
-
fx="61.518883"
|
|
214
|
-
fy="132.28575"
|
|
215
|
-
r="29.036913"
|
|
216
|
-
gradientTransform="matrix(1,0,0,0.177966,0,108.7434)"
|
|
217
|
-
gradientUnits="userSpaceOnUse" />
|
|
218
|
-
<linearGradient
|
|
219
|
-
inkscape:collect="always"
|
|
220
|
-
xlink:href="#linearGradient4671"
|
|
221
|
-
id="linearGradient1475"
|
|
222
|
-
gradientUnits="userSpaceOnUse"
|
|
223
|
-
gradientTransform="matrix(0.562541,0,0,0.567972,-14.99112,-11.702371)"
|
|
224
|
-
x1="150.96111"
|
|
225
|
-
y1="192.35176"
|
|
226
|
-
x2="112.03144"
|
|
227
|
-
y2="137.27299" />
|
|
228
|
-
<linearGradient
|
|
229
|
-
inkscape:collect="always"
|
|
230
|
-
xlink:href="#linearGradient4689"
|
|
231
|
-
id="linearGradient1478"
|
|
232
|
-
gradientUnits="userSpaceOnUse"
|
|
233
|
-
gradientTransform="matrix(0.562541,0,0,0.567972,-14.99112,-11.702371)"
|
|
234
|
-
x1="26.648937"
|
|
235
|
-
y1="20.603781"
|
|
236
|
-
x2="135.66525"
|
|
237
|
-
y2="114.39767" />
|
|
238
|
-
<radialGradient
|
|
239
|
-
inkscape:collect="always"
|
|
240
|
-
xlink:href="#linearGradient2795"
|
|
241
|
-
id="radialGradient1480"
|
|
242
|
-
gradientUnits="userSpaceOnUse"
|
|
243
|
-
gradientTransform="matrix(1.7490565e-8,-0.23994696,1.054668,3.7915457e-7,-83.7008,142.46201)"
|
|
244
|
-
cx="61.518883"
|
|
245
|
-
cy="132.28575"
|
|
246
|
-
fx="61.518883"
|
|
247
|
-
fy="132.28575"
|
|
248
|
-
r="29.036913" />
|
|
249
|
-
</defs>
|
|
250
|
-
<path
|
|
251
|
-
style="fill:url(#linearGradient1478);fill-opacity:1"
|
|
252
|
-
d="M 54.918785,9.1927421e-4 C 50.335132,0.02221727 45.957846,0.41313697 42.106285,1.0946693 30.760069,3.0991731 28.700036,7.2947714 28.700035,15.032169 v 10.21875 h 26.8125 v 3.40625 h -26.8125 -10.0625 c -7.792459,0 -14.6157588,4.683717 -16.7499998,13.59375 -2.46181998,10.212966 -2.57101508,16.586023 0,27.25 1.9059283,7.937852 6.4575432,13.593748 14.2499998,13.59375 h 9.21875 v -12.25 c 0,-8.849902 7.657144,-16.656248 16.75,-16.65625 h 26.78125 c 7.454951,0 13.406253,-6.138164 13.40625,-13.625 v -25.53125 c 0,-7.2663386 -6.12998,-12.7247771 -13.40625,-13.9374997 C 64.281548,0.32794397 59.502438,-0.02037903 54.918785,9.1927421e-4 Z m -14.5,8.21875012579 c 2.769547,0 5.03125,2.2986456 5.03125,5.1249996 -2e-6,2.816336 -2.261703,5.09375 -5.03125,5.09375 -2.779476,-1e-6 -5.03125,-2.277415 -5.03125,-5.09375 -10e-7,-2.826353 2.251774,-5.1249996 5.03125,-5.1249996 z"
|
|
253
|
-
id="path1948" />
|
|
254
|
-
<path
|
|
255
|
-
style="fill:url(#linearGradient1475);fill-opacity:1"
|
|
256
|
-
d="m 85.637535,28.657169 v 11.90625 c 0,9.230755 -7.825895,16.999999 -16.75,17 h -26.78125 c -7.335833,0 -13.406249,6.278483 -13.40625,13.625 v 25.531247 c 0,7.266344 6.318588,11.540324 13.40625,13.625004 8.487331,2.49561 16.626237,2.94663 26.78125,0 6.750155,-1.95439 13.406253,-5.88761 13.40625,-13.625004 V 86.500919 h -26.78125 v -3.40625 h 26.78125 13.406254 c 7.792461,0 10.696251,-5.435408 13.406241,-13.59375 2.79933,-8.398886 2.68022,-16.475776 0,-27.25 -1.92578,-7.757441 -5.60387,-13.59375 -13.406241,-13.59375 z m -15.0625,64.65625 c 2.779478,3e-6 5.03125,2.277417 5.03125,5.093747 -2e-6,2.826354 -2.251775,5.125004 -5.03125,5.125004 -2.76955,0 -5.03125,-2.29865 -5.03125,-5.125004 2e-6,-2.81633 2.261697,-5.093747 5.03125,-5.093747 z"
|
|
257
|
-
id="path1950" />
|
|
258
|
-
<ellipse
|
|
259
|
-
style="opacity:0.44382;fill:url(#radialGradient1480);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:15.4174;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
260
|
-
id="path1894"
|
|
261
|
-
cx="55.816761"
|
|
262
|
-
cy="127.70079"
|
|
263
|
-
rx="35.930977"
|
|
264
|
-
ry="6.9673119" />
|
|
265
|
-
</svg>
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
.\" Hey, EMACS: -*- nroff -*-
|
|
2
|
-
.\" First parameter, NAME, should be all caps
|
|
3
|
-
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
|
|
4
|
-
.\" other parameters are allowed: see man(7), man(1)
|
|
5
|
-
.TH IPYTHON 1 "July 15, 2011"
|
|
6
|
-
.\" Please adjust this date whenever revising the manpage.
|
|
7
|
-
.\"
|
|
8
|
-
.\" Some roff macros, for reference:
|
|
9
|
-
.\" .nh disable hyphenation
|
|
10
|
-
.\" .hy enable hyphenation
|
|
11
|
-
.\" .ad l left justify
|
|
12
|
-
.\" .ad b justify to both left and right margins
|
|
13
|
-
.\" .nf disable filling
|
|
14
|
-
.\" .fi enable filling
|
|
15
|
-
.\" .br insert line break
|
|
16
|
-
.\" .sp <n> insert n+1 empty lines
|
|
17
|
-
.\" for manpage-specific macros, see man(7) and groff_man(7)
|
|
18
|
-
.\" .SH section heading
|
|
19
|
-
.\" .SS secondary section heading
|
|
20
|
-
.\"
|
|
21
|
-
.\"
|
|
22
|
-
.\" To preview this page as plain text: nroff -man ipython.1
|
|
23
|
-
.\"
|
|
24
|
-
.SH NAME
|
|
25
|
-
ipython \- Tools for Interactive Computing in Python.
|
|
26
|
-
.SH SYNOPSIS
|
|
27
|
-
.B ipython
|
|
28
|
-
.RI [ options ] " files" ...
|
|
29
|
-
|
|
30
|
-
.B ipython subcommand
|
|
31
|
-
.RI [ options ] ...
|
|
32
|
-
|
|
33
|
-
.SH DESCRIPTION
|
|
34
|
-
An interactive Python shell with automatic history (input and output), dynamic
|
|
35
|
-
object introspection, easier configuration, command completion, access to the
|
|
36
|
-
system shell, integration with numerical and scientific computing tools,
|
|
37
|
-
web notebook, Qt console, and more.
|
|
38
|
-
|
|
39
|
-
For more information on how to use IPython, see 'ipython \-\-help',
|
|
40
|
-
or 'ipython \-\-help\-all' for all available command\(hyline options.
|
|
41
|
-
|
|
42
|
-
.SH "ENVIRONMENT VARIABLES"
|
|
43
|
-
.sp
|
|
44
|
-
.PP
|
|
45
|
-
\fIIPYTHONDIR\fR
|
|
46
|
-
.RS 4
|
|
47
|
-
This is the location where IPython stores all its configuration files. The default
|
|
48
|
-
is $HOME/.ipython if IPYTHONDIR is not defined.
|
|
49
|
-
|
|
50
|
-
You can see the computed value of IPYTHONDIR with `ipython locate`.
|
|
51
|
-
|
|
52
|
-
.SH FILES
|
|
53
|
-
|
|
54
|
-
IPython uses various configuration files stored in profiles within IPYTHONDIR.
|
|
55
|
-
To generate the default configuration files and start configuring IPython,
|
|
56
|
-
do 'ipython profile create', and edit '*_config.py' files located in
|
|
57
|
-
IPYTHONDIR/profile_default.
|
|
58
|
-
|
|
59
|
-
.SH AUTHORS
|
|
60
|
-
IPython is written by the IPython Development Team <https://github.com/ipython/ipython>.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{liminal_orm-4.4.9 → liminal_orm-4.5.1}/liminal/results_schemas/models/results_schema_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|