sapiopycommons 31.0.13.23.12__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.
- sapiopycommons-31.0.13.23.12/.gitignore +8 -0
- sapiopycommons-31.0.13.23.12/PKG-INFO +8 -0
- sapiopycommons-31.0.13.23.12/pyproject.toml +19 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/__init__.py +0 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/chem/IndigoMolecules.py +48 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/chem/Molecules.py +202 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/chem/__init__.py +0 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/datatype/__init__.py +0 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/datatype/attachment_util.py +65 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/eln/__init__.py +0 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/eln/experiment_handler.py +772 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/files/__init__.py +0 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/files/file_bridge.py +87 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/files/file_util.py +366 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/general/__init__.py +0 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/general/aliases.py +55 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/general/custom_report_util.py +64 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/general/exceptions.py +26 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/general/popup_util.py +447 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/general/time_util.py +128 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/recordmodel/__init__.py +0 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/recordmodel/record_handler.py +608 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/rules/__init__.py +0 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/rules/eln_rule_handler.py +95 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/rules/on_save_rule_handler.py +95 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/webhook/__init__.py +0 -0
- sapiopycommons-31.0.13.23.12/src/sapiopycommons/webhook/webhook_handlers.py +242 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: sapiopycommons
|
|
3
|
+
Version: 31.0.13.23.12
|
|
4
|
+
Summary: Sapio Commons for Python 3
|
|
5
|
+
Project-URL: Homepage, https://github.com/sapiosciences
|
|
6
|
+
Author-email: Yechen Qiao <yqiao@sapiosciences.com>
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: sapiopylib>=2023.12.13.174
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sapiopycommons"
|
|
7
|
+
version='31.0.13.23.12'
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Yechen Qiao", email="yqiao@sapiosciences.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "Sapio Commons for Python 3"
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = [
|
|
14
|
+
'sapiopylib>=2023.12.13.174'
|
|
15
|
+
]
|
|
16
|
+
homepage = "https://www.sapiosciences.com/"
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
"Homepage" = "https://github.com/sapiosciences"
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from indigo import Indigo, IndigoObject
|
|
2
|
+
from indigo.inchi import IndigoInchi
|
|
3
|
+
from indigo.renderer import IndigoRenderer
|
|
4
|
+
|
|
5
|
+
indigo = Indigo()
|
|
6
|
+
renderer = IndigoRenderer(indigo)
|
|
7
|
+
indigo.setOption("render-output-format", "svg")
|
|
8
|
+
indigo.setOption("ignore-stereochemistry-errors", True)
|
|
9
|
+
indigo.setOption("aromaticity-model", "generic")
|
|
10
|
+
indigo.setOption("render-coloring", True)
|
|
11
|
+
indigo_inchi = IndigoInchi(indigo);
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def highlight_mol_substructure(query: IndigoObject, sub_match: IndigoObject):
|
|
15
|
+
"""
|
|
16
|
+
Highlight the bonds and atoms for substructure search result
|
|
17
|
+
:param sub_match: The substructure search match obtained from indigo.substructureMatcher(mol).match(query)
|
|
18
|
+
:param query: The query we were running to match the original structure
|
|
19
|
+
"""
|
|
20
|
+
for qatom in query.iterateAtoms():
|
|
21
|
+
atom = sub_match.mapAtom(qatom)
|
|
22
|
+
if atom is None:
|
|
23
|
+
continue
|
|
24
|
+
atom.highlight()
|
|
25
|
+
|
|
26
|
+
for nei in atom.iterateNeighbors():
|
|
27
|
+
if not nei.isPseudoatom() and not nei.isRSite() and nei.atomicNumber() == 1:
|
|
28
|
+
nei.highlight()
|
|
29
|
+
nei.bond().highlight()
|
|
30
|
+
|
|
31
|
+
for bond in query.iterateBonds():
|
|
32
|
+
bond = sub_match.mapBond(bond)
|
|
33
|
+
if bond is None:
|
|
34
|
+
continue
|
|
35
|
+
bond.highlight()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def highlight_reactions(query_reaction_smarts: IndigoObject, reaction_match: IndigoObject):
|
|
39
|
+
"""
|
|
40
|
+
Highlight the bonds and atoms for substructure search result of reaction that's in the query and survived the mapping.
|
|
41
|
+
:param query_reaction_smarts: The query we ran substructure search on.
|
|
42
|
+
:param reaction_match: The substructure search match obtained from indigo.substructureMatcher(reaction).match(query)
|
|
43
|
+
:return:
|
|
44
|
+
"""
|
|
45
|
+
for q_mol in query_reaction_smarts.iterateMolecules():
|
|
46
|
+
matched_mol = reaction_match.mapMolecule(q_mol)
|
|
47
|
+
sub_match = indigo.substructureMatcher(matched_mol).match(q_mol)
|
|
48
|
+
highlight_mol_substructure(q_mol, sub_match)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Author Yechen Qiao
|
|
2
|
+
# Common Molecule Utilities for Molecule Transfers with Sapio
|
|
3
|
+
|
|
4
|
+
from rdkit import Chem
|
|
5
|
+
from rdkit.Chem import Crippen, MolToInchi
|
|
6
|
+
from rdkit.Chem import Descriptors
|
|
7
|
+
from rdkit.Chem import rdMolDescriptors
|
|
8
|
+
from rdkit.Chem.EnumerateStereoisomers import StereoEnumerationOptions, EnumerateStereoisomers
|
|
9
|
+
from rdkit.Chem.MolStandardize import rdMolStandardize
|
|
10
|
+
from rdkit.Chem.SaltRemover import SaltRemover
|
|
11
|
+
from rdkit.Chem.rdchem import Mol
|
|
12
|
+
|
|
13
|
+
from sapiopycommons.chem.IndigoMolecules import indigo, renderer, indigo_inchi
|
|
14
|
+
|
|
15
|
+
metal_disconnector = rdMolStandardize.MetalDisconnector()
|
|
16
|
+
tautomer_params = Chem.MolStandardize.rdMolStandardize.CleanupParameters()
|
|
17
|
+
tautomer_params.tautomerRemoveSp3Stereo = False
|
|
18
|
+
tautomer_params.tautomerRemoveBondStereo = False
|
|
19
|
+
tautomer_params.tautomerReassignStereo = False
|
|
20
|
+
tautomer_params.tautomerRemoveIsotopicHs = True
|
|
21
|
+
enumerator = rdMolStandardize.TautomerEnumerator(tautomer_params)
|
|
22
|
+
|
|
23
|
+
def neutralize_atoms(mol) -> Mol:
|
|
24
|
+
"""
|
|
25
|
+
Neutralize atoms per https://baoilleach.blogspot.com/2019/12/no-charge-simple-approach-to.html
|
|
26
|
+
"""
|
|
27
|
+
pattern = Chem.MolFromSmarts("[+1!h0!$([*]~[-1,-2,-3,-4]),-1!$([*]~[+1,+2,+3,+4])]")
|
|
28
|
+
at_matches = mol.GetSubstructMatches(pattern)
|
|
29
|
+
at_matches_list = [y[0] for y in at_matches]
|
|
30
|
+
if len(at_matches_list) > 0:
|
|
31
|
+
for at_idx in at_matches_list:
|
|
32
|
+
atom = mol.GetAtomWithIdx(at_idx)
|
|
33
|
+
chg = atom.GetFormalCharge()
|
|
34
|
+
hcount = atom.GetTotalNumHs()
|
|
35
|
+
atom.SetFormalCharge(0)
|
|
36
|
+
atom.SetNumExplicitHs(hcount - chg)
|
|
37
|
+
atom.UpdatePropertyCache()
|
|
38
|
+
return mol
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def find_all_possible_stereoisomers(m: Mol, only_unassigned=True, try_embedding=True, unique=True, max_isomers=200) \
|
|
42
|
+
-> list[Mol]:
|
|
43
|
+
"""
|
|
44
|
+
Find all possible candidates of stereoisomers given the current molecule
|
|
45
|
+
:param m: The molecule to search for
|
|
46
|
+
:param only_unassigned: Whether to only permute on unspecified stereocenter.
|
|
47
|
+
:param try_embedding: if set the process attempts to generate a standard RDKit distance geometry conformation for
|
|
48
|
+
the stereisomer.
|
|
49
|
+
If this fails, we assume that the stereoisomer is non-physical and don't return it
|
|
50
|
+
:param unique: whether to remove duplicates by isomer identity
|
|
51
|
+
:param max_isomers: Maximum number of search results to return.
|
|
52
|
+
"""
|
|
53
|
+
# noinspection PyBroadException
|
|
54
|
+
try:
|
|
55
|
+
opts = StereoEnumerationOptions(tryEmbedding=try_embedding, unique=unique, onlyUnassigned=only_unassigned,
|
|
56
|
+
maxIsomers=max_isomers)
|
|
57
|
+
return list(EnumerateStereoisomers(m, options=opts))
|
|
58
|
+
except:
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def has_chiral_centers(m: Mol):
|
|
63
|
+
"""
|
|
64
|
+
Returns true iff the molecule provided has at least 1 chiral centers (when stereochemistry is relevant)
|
|
65
|
+
:param m: The molecule to test.
|
|
66
|
+
"""
|
|
67
|
+
# noinspection PyBroadException
|
|
68
|
+
try:
|
|
69
|
+
chiral_centers: list = Chem.FindMolChiralCenters(m, force=True, includeUnassigned=True,
|
|
70
|
+
useLegacyImplementation=False)
|
|
71
|
+
return len(chiral_centers) > 0
|
|
72
|
+
except:
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def mol_to_img(mol_str: str) -> str:
|
|
77
|
+
"""
|
|
78
|
+
Convert molecule into image
|
|
79
|
+
:param mol_str: The molecule INCHI
|
|
80
|
+
:return: The SVG image text.
|
|
81
|
+
"""
|
|
82
|
+
mol = indigo.loadMolecule(mol_str)
|
|
83
|
+
return renderer.renderToString(mol)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def mol_to_sapio_partial_pojo(mol: Mol):
|
|
88
|
+
"""
|
|
89
|
+
Get the minimum information about molecule to Sapio, just its SMILES, V3000, and image data.
|
|
90
|
+
:param mol: The molecule to read the simplified data from
|
|
91
|
+
"""
|
|
92
|
+
Chem.SanitizeMol(mol)
|
|
93
|
+
mol.UpdatePropertyCache()
|
|
94
|
+
smiles = Chem.MolToSmiles(mol)
|
|
95
|
+
molBlock = Chem.MolToMolBlock(mol)
|
|
96
|
+
img = mol_to_img(mol)
|
|
97
|
+
molecule = dict()
|
|
98
|
+
molecule["smiles"] = smiles
|
|
99
|
+
molecule["molBlock"] = molBlock
|
|
100
|
+
molecule["image"] = img
|
|
101
|
+
return molecule
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def mol_to_sapio_substance(mol: Mol, include_stereoisomers: bool = False,
|
|
105
|
+
normalize: bool = False, remove_salt: bool = False, make_images: bool = False,
|
|
106
|
+
salt_def: str | None = None, canonical_tautomer: bool = True):
|
|
107
|
+
"""
|
|
108
|
+
Convert a molecule in RDKit to a molecule POJO in Sapio.
|
|
109
|
+
:param mol: The molecule in RDKit
|
|
110
|
+
:param include_stereoisomers: If true, will compute all stereoisomer permutations of this molecule.
|
|
111
|
+
:param normalize If true, will normalize the functional groups and return normalized result.
|
|
112
|
+
:param remove_salt If true, we will remove salts iteratively from the molecule before returning their data.
|
|
113
|
+
We will also populate desaltedList with molecules we deleted.
|
|
114
|
+
:param salt_def: if not none, specifies custom salt to be used during the desalt process.
|
|
115
|
+
:param canonical_tautomer: if True, we will attempt to compute canonical tautomer for the molecule. Slow!
|
|
116
|
+
This is needed for a registry. Note it stops after enumeration of 1000.
|
|
117
|
+
:return: The molecule POJO for Sapio.
|
|
118
|
+
"""
|
|
119
|
+
molecule = dict()
|
|
120
|
+
Chem.SanitizeMol(mol)
|
|
121
|
+
mol.UpdatePropertyCache()
|
|
122
|
+
Chem.GetSymmSSSR(mol)
|
|
123
|
+
if normalize:
|
|
124
|
+
try:
|
|
125
|
+
mol = Chem.RemoveHs(mol)
|
|
126
|
+
mol = metal_disconnector.Disconnect(mol)
|
|
127
|
+
mol = rdMolStandardize.Normalize(mol)
|
|
128
|
+
molecule["normError"] = ""
|
|
129
|
+
except Exception as e:
|
|
130
|
+
molecule["normError"] = str(e)
|
|
131
|
+
if remove_salt:
|
|
132
|
+
try:
|
|
133
|
+
remover = SaltRemover(defnData=salt_def)
|
|
134
|
+
mol, deleted = remover.StripMolWithDeleted(mol)
|
|
135
|
+
molecule["desaltedList"] = [Chem.MolToSmarts(x) for x in deleted]
|
|
136
|
+
molecule["desaltError"] = ""
|
|
137
|
+
except Exception as e:
|
|
138
|
+
molecule["desaltError"] = str(e)
|
|
139
|
+
molecule["desaltedList"] = []
|
|
140
|
+
if normalize or remove_salt:
|
|
141
|
+
mol = neutralize_atoms(mol)
|
|
142
|
+
#//CR-46021 Jarvis: no canonicalize tautomers.
|
|
143
|
+
if canonical_tautomer:
|
|
144
|
+
mol = enumerator.Canonicalize(mol)
|
|
145
|
+
Chem.SanitizeMol(mol)
|
|
146
|
+
mol.UpdatePropertyCache()
|
|
147
|
+
Chem.GetSymmSSSR(mol)
|
|
148
|
+
smiles = Chem.MolToSmiles(mol)
|
|
149
|
+
cLogP = Crippen.MolLogP(mol)
|
|
150
|
+
tpsa = Descriptors.TPSA(mol)
|
|
151
|
+
amw = Descriptors.MolWt(mol)
|
|
152
|
+
exactMass = Descriptors.ExactMolWt(mol)
|
|
153
|
+
molFormula = rdMolDescriptors.CalcMolFormula(mol)
|
|
154
|
+
charge = Chem.GetFormalCharge(mol)
|
|
155
|
+
molBlock = Chem.MolToMolBlock(mol)
|
|
156
|
+
|
|
157
|
+
molecule["cLogP"] = cLogP
|
|
158
|
+
molecule["tpsa"] = tpsa
|
|
159
|
+
molecule["amw"] = amw
|
|
160
|
+
molecule["exactMass"] = exactMass
|
|
161
|
+
molecule["molFormula"] = molFormula
|
|
162
|
+
molecule["charge"] = charge
|
|
163
|
+
molecule["numHBondAcceptors"] = rdMolDescriptors.CalcNumHBA(mol)
|
|
164
|
+
# This is number of H-Bond Donor
|
|
165
|
+
molecule["numHBonds"] = rdMolDescriptors.CalcNumHBD(mol)
|
|
166
|
+
molecule["molBlock"] = molBlock
|
|
167
|
+
rdkit_inchi = MolToInchi(mol)
|
|
168
|
+
# If INCHI is completely invalid, we fail this molecule.
|
|
169
|
+
if not rdkit_inchi:
|
|
170
|
+
MolToInchi(mol, treatWarningAsError=True)
|
|
171
|
+
if make_images:
|
|
172
|
+
img = mol_to_img(smiles)
|
|
173
|
+
molecule["image"] = img
|
|
174
|
+
else:
|
|
175
|
+
molecule["image"] = None
|
|
176
|
+
# We need to test the INCHI can be loaded back to indigo.
|
|
177
|
+
indigo_mol = indigo.loadMolecule(molBlock)
|
|
178
|
+
indigo_mol.aromatize()
|
|
179
|
+
indigo_inchi_str = indigo_inchi.getInchi(indigo_mol)
|
|
180
|
+
molecule["inchi"] = indigo_inchi_str
|
|
181
|
+
indigo_inchi_key_str = indigo_inchi.getInchiKey(indigo_inchi_str)
|
|
182
|
+
molecule["inchiKey"] = indigo_inchi_key_str
|
|
183
|
+
molecule["smiles"] = indigo_mol.smiles()
|
|
184
|
+
|
|
185
|
+
if include_stereoisomers and has_chiral_centers(mol):
|
|
186
|
+
stereoisomers = find_all_possible_stereoisomers(mol, only_unassigned=False, try_embedding=False, unique=True)
|
|
187
|
+
molecule["stereoisomers"] = [mol_to_sapio_partial_pojo(x) for x in stereoisomers]
|
|
188
|
+
return molecule
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def mol_to_sapio_compound(mol: Mol, include_stereoisomers: bool = False,
|
|
192
|
+
salt_def: str | None = None, resolve_canonical: bool = True,
|
|
193
|
+
make_images: bool = False, canonical_tautomer: bool = True):
|
|
194
|
+
ret = dict()
|
|
195
|
+
ret['originalMol'] = mol_to_sapio_substance(mol, include_stereoisomers,
|
|
196
|
+
normalize=False, remove_salt=False, make_images=make_images,
|
|
197
|
+
canonical_tautomer=canonical_tautomer)
|
|
198
|
+
if resolve_canonical:
|
|
199
|
+
ret['canonicalMol'] = mol_to_sapio_substance(mol, include_stereoisomers=False,
|
|
200
|
+
normalize=True, remove_salt=True, make_images=make_images,
|
|
201
|
+
salt_def=salt_def, canonical_tautomer=canonical_tautomer)
|
|
202
|
+
return ret
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import io
|
|
2
|
+
|
|
3
|
+
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
4
|
+
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
5
|
+
from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelManager
|
|
6
|
+
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
7
|
+
|
|
8
|
+
from sapiopycommons.general.aliases import AliasUtil, SapioRecord
|
|
9
|
+
from sapiopycommons.general.exceptions import SapioException
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
13
|
+
class AttachmentUtil:
|
|
14
|
+
@staticmethod
|
|
15
|
+
def get_attachment_bytes(context: SapioWebhookContext, attachment: SapioRecord) -> bytes:
|
|
16
|
+
"""
|
|
17
|
+
Get the data bytes for the given attachment record. Makes a webservice call to retrieve the data.
|
|
18
|
+
:param context: The current webhook context.
|
|
19
|
+
:param attachment: The attachment record.
|
|
20
|
+
:return: The bytes for the attachment's file data.
|
|
21
|
+
"""
|
|
22
|
+
attachment = AliasUtil.to_data_record(attachment)
|
|
23
|
+
with io.BytesIO() as data_sink:
|
|
24
|
+
def consume_data(chunk: bytes):
|
|
25
|
+
data_sink.write(chunk)
|
|
26
|
+
context.data_record_manager.get_attachment_data(attachment, consume_data)
|
|
27
|
+
data_sink.flush()
|
|
28
|
+
data_sink.seek(0)
|
|
29
|
+
file_bytes = data_sink.read()
|
|
30
|
+
return file_bytes
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def set_attachment_bytes(context: SapioWebhookContext, attachment: SapioRecord,
|
|
34
|
+
file_name: str, file_bytes: bytes) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Set the attachment data for a given attachment record. Makes a webservice call to set the data.
|
|
37
|
+
:param context: The current webhook context.
|
|
38
|
+
:param attachment: The attachment record. Must be an existing data record that is an attachment type.
|
|
39
|
+
:param file_name: The name of the attachment.
|
|
40
|
+
:param file_bytes: The bytes of the attachment data.
|
|
41
|
+
"""
|
|
42
|
+
if attachment.record_id < 0:
|
|
43
|
+
raise SapioException("Provided record cannot have its attachment data set, as it does not exist in the "
|
|
44
|
+
"system yet.")
|
|
45
|
+
attachment = AliasUtil.to_data_record(attachment)
|
|
46
|
+
with io.BytesIO(file_bytes) as stream:
|
|
47
|
+
context.data_record_manager.set_attachment_data(attachment, file_name, stream)
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def create_attachment(context: SapioWebhookContext, file_name: str, file_bytes: bytes,
|
|
51
|
+
wrapper_type: type[WrappedType]) -> WrappedType:
|
|
52
|
+
"""
|
|
53
|
+
Create an attachment data type and initialize its attachment bytes at the same time.
|
|
54
|
+
Makes a webservice call to create the attachment record and a second to set its bytes.
|
|
55
|
+
:param context: The current webhook context.
|
|
56
|
+
:param file_name: The name of the attachment.
|
|
57
|
+
:param file_bytes: THe bytes of the attachment data.
|
|
58
|
+
:param wrapper_type: The attachment type to create.
|
|
59
|
+
:return: A record model for the newly created attachment.
|
|
60
|
+
"""
|
|
61
|
+
inst_man = RecordModelManager(context.user).instance_manager
|
|
62
|
+
attachment: DataRecord = context.data_record_manager.add_data_record(wrapper_type.DATA_TYPE_NAME)
|
|
63
|
+
attachment: WrappedType = inst_man.add_existing_record_of_type(attachment, wrapper_type)
|
|
64
|
+
AttachmentUtil.set_attachment_bytes(context, attachment, file_name, file_bytes)
|
|
65
|
+
return attachment
|
|
File without changes
|