obographs 0.0.2__py3-none-any.whl → 0.0.4__py3-none-any.whl

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.
obographs/__init__.py CHANGED
@@ -1,13 +1,72 @@
1
1
  """A python data model for OBO Graphs."""
2
2
 
3
- from .model import Graph, GraphDocument, Meta, Node, Property, Synonym, Xref, read
3
+ from .model import (
4
+ Definition,
5
+ DomainRangeAxiom,
6
+ Edge,
7
+ EquivalentNodeSet,
8
+ ExistentialRestrictionExpression,
9
+ Graph,
10
+ GraphDocument,
11
+ LogicalDefinition,
12
+ Meta,
13
+ Node,
14
+ NodeType,
15
+ Property,
16
+ PropertyChainAxiom,
17
+ PropertyType,
18
+ Synonym,
19
+ Xref,
20
+ read,
21
+ )
22
+ from .standardized import (
23
+ StandardizedBaseModel,
24
+ StandardizedDefinition,
25
+ StandardizedDomainRangeAxiom,
26
+ StandardizedEdge,
27
+ StandardizedEquivalentNodeSet,
28
+ StandardizedExistentialRestriction,
29
+ StandardizedGraph,
30
+ StandardizedGraphDocument,
31
+ StandardizedLogicalDefinition,
32
+ StandardizedMeta,
33
+ StandardizedNode,
34
+ StandardizedProperty,
35
+ StandardizedPropertyChainAxiom,
36
+ StandardizedSynonym,
37
+ StandardizedXref,
38
+ )
4
39
 
5
40
  __all__ = [
41
+ "Definition",
42
+ "DomainRangeAxiom",
43
+ "Edge",
44
+ "EquivalentNodeSet",
45
+ "ExistentialRestrictionExpression",
6
46
  "Graph",
7
47
  "GraphDocument",
48
+ "LogicalDefinition",
8
49
  "Meta",
9
50
  "Node",
51
+ "NodeType",
10
52
  "Property",
53
+ "PropertyChainAxiom",
54
+ "PropertyType",
55
+ "StandardizedBaseModel",
56
+ "StandardizedDefinition",
57
+ "StandardizedDomainRangeAxiom",
58
+ "StandardizedEdge",
59
+ "StandardizedEquivalentNodeSet",
60
+ "StandardizedExistentialRestriction",
61
+ "StandardizedGraph",
62
+ "StandardizedGraphDocument",
63
+ "StandardizedLogicalDefinition",
64
+ "StandardizedMeta",
65
+ "StandardizedNode",
66
+ "StandardizedProperty",
67
+ "StandardizedPropertyChainAxiom",
68
+ "StandardizedSynonym",
69
+ "StandardizedXref",
11
70
  "Synonym",
12
71
  "Xref",
13
72
  "read",
obographs/model.py CHANGED
@@ -9,27 +9,35 @@
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
+ import gzip
12
13
  import json
13
14
  import logging
14
15
  from collections import defaultdict
15
16
  from pathlib import Path
16
- from typing import TYPE_CHECKING, Any, Literal, TypeAlias, overload
17
+ from typing import TYPE_CHECKING, Literal, TypeAlias, overload
17
18
 
19
+ import curies
20
+ from curies.vocabulary import SynonymScopeOIO
18
21
  from pydantic import BaseModel, Field
19
22
 
20
23
  if TYPE_CHECKING:
21
- import curies
22
-
23
24
  from .standardized import StandardizedGraph
24
25
 
25
26
  __all__ = [
26
27
  "Definition",
28
+ "DomainRangeAxiom",
27
29
  "Edge",
30
+ "EquivalentNodeSet",
31
+ "ExistentialRestrictionExpression",
28
32
  "Graph",
29
33
  "GraphDocument",
34
+ "LogicalDefinition",
30
35
  "Meta",
31
36
  "Node",
37
+ "NodeType",
32
38
  "Property",
39
+ "PropertyChainAxiom",
40
+ "PropertyType",
33
41
  "Synonym",
34
42
  "Xref",
35
43
  "read",
@@ -40,31 +48,24 @@ logger = logging.getLogger(__name__)
40
48
  OBO_URI_PREFIX = "http://purl.obolibrary.org/obo/"
41
49
  OBO_URI_PREFIX_LEN = len(OBO_URI_PREFIX)
42
50
 
43
- SynonymPredicate: TypeAlias = Literal[
44
- "hasExactSynonym",
45
- "hasBroadSynonym",
46
- "hasNarrowSynonym",
47
- "hasRelatedSynonym",
48
- ]
49
51
  NodeType: TypeAlias = Literal["CLASS", "PROPERTY", "INDIVIDUAL"]
50
52
 
51
- TimeoutHint = int | float | None
53
+ #: When node type is ``PROPERTY``, this is extra information
54
+ PropertyType: TypeAlias = Literal["ANNOTATION", "OBJECT", "DATA"]
52
55
 
53
- #: A mapping from OBO flat file format internal synonym types to OBO in OWL vocabulary
54
- #: identifiers. See https://owlcollab.github.io/oboformat/doc/GO.format.obo-1_4.html
55
- OBO_SYNONYM_TO_OIO: dict[str, SynonymPredicate] = {
56
- "EXACT": "hasExactSynonym",
57
- "BROAD": "hasBroadSynonym",
58
- "NARROW": "hasNarrowSynonym",
59
- "RELATED": "hasRelatedSynonym",
60
- }
56
+ TimeoutHint = int | float | None
61
57
 
62
58
 
63
59
  class Property(BaseModel):
64
60
  """Represent a property inside a metadata element."""
65
61
 
66
62
  pred: str
67
- val: str
63
+ val: str | None = Field(
64
+ None,
65
+ description="Stores the value of the property. This can be a string representing a "
66
+ "literal or IRI. This isn't supposed to be nullable, but it happens a lot - might be a "
67
+ "bug in OWLAPI or ROBOT",
68
+ )
68
69
  xrefs: list[str] | None = None
69
70
  meta: Meta | None = None
70
71
 
@@ -86,8 +87,8 @@ class Synonym(BaseModel):
86
87
  """Represents a synonym inside an object meta."""
87
88
 
88
89
  val: str | None = Field(default=None)
89
- pred: str = Field(default="hasExactSynonym")
90
- synonymType: str | None = Field(examples=["OMO:0003000"]) # noqa:N815
90
+ pred: SynonymScopeOIO = Field(default="hasExactSynonym")
91
+ synonymType: str | None = Field(None, examples=["OMO:0003000"]) # noqa:N815
91
92
  xrefs: list[str] = Field(
92
93
  default_factory=list,
93
94
  description="A list of CURIEs/IRIs for provenance for the synonym",
@@ -123,7 +124,52 @@ class Node(BaseModel):
123
124
  id: str = Field(..., description="The IRI for the node")
124
125
  lbl: str | None = Field(None, description="The name of the node")
125
126
  meta: Meta | None = None
126
- type: NodeType = Field(..., description="Type of node")
127
+ type: NodeType | None = Field(None, description="Type of node")
128
+ propertyType: PropertyType | None = Field( # noqa:N815
129
+ None, description="Type of property, if the node type is a property"
130
+ )
131
+
132
+
133
+ class DomainRangeAxiom(BaseModel):
134
+ """Represents a domain/range axiom."""
135
+
136
+ predicateId: str # noqa:N815
137
+ domainClassIds: list[str] | None = None # noqa:N815
138
+ rangeClassIds: list[str] | None = None # noqa:N815
139
+ allValuesFromEdges: list[Edge] | None = None # noqa:N815
140
+ meta: Meta | None = None
141
+
142
+
143
+ class PropertyChainAxiom(BaseModel):
144
+ """Represents a property chain axiom."""
145
+
146
+ predicateId: str # noqa:N815
147
+ chainPredicateIds: list[str] # noqa:N815
148
+ meta: Meta | None = None
149
+
150
+
151
+ class ExistentialRestrictionExpression(BaseModel):
152
+ """Represents an existential restriction."""
153
+
154
+ propertyId: str # noqa:N815
155
+ fillerId: str # noqa:N815
156
+
157
+
158
+ class LogicalDefinition(BaseModel):
159
+ """Represents a logical definition chain axiom."""
160
+
161
+ definedClassId: str # noqa:N815
162
+ genusIds: list[str] | None = None # noqa:N815
163
+ restrictions: list[ExistentialRestrictionExpression] | None = None
164
+ meta: Meta | None = None
165
+
166
+
167
+ class EquivalentNodeSet(BaseModel):
168
+ """Represents a set of equivalent nodes."""
169
+
170
+ representativeNodeId: str # noqa:N815
171
+ nodeIds: list[str] # noqa:N815
172
+ meta: Meta | None = None
127
173
 
128
174
 
129
175
  class Graph(BaseModel):
@@ -133,10 +179,10 @@ class Graph(BaseModel):
133
179
  meta: Meta | None = None
134
180
  nodes: list[Node] = Field(default_factory=list)
135
181
  edges: list[Edge] = Field(default_factory=list)
136
- equivalentNodesSets: list[Any] = Field(default_factory=list) # noqa:N815
137
- logicalDefinitionAxioms: list[Any] = Field(default_factory=list) # noqa:N815
138
- domainRangeAxioms: list[Any] = Field(default_factory=list) # noqa:N815
139
- propertyChainAxioms: list[Any] = Field(default_factory=list) # noqa:N815
182
+ equivalentNodesSets: list[EquivalentNodeSet] = Field(default_factory=list) # noqa:N815
183
+ logicalDefinitionAxioms: list[LogicalDefinition] = Field(default_factory=list) # noqa:N815
184
+ domainRangeAxioms: list[DomainRangeAxiom] = Field(default_factory=list) # noqa:N815
185
+ propertyChainAxioms: list[PropertyChainAxiom] = Field(default_factory=list) # noqa:N815
140
186
 
141
187
  def standardize(self, converter: curies.Converter) -> StandardizedGraph:
142
188
  """Standardize the graph."""
@@ -205,12 +251,14 @@ def read(
205
251
 
206
252
  elif isinstance(source, str | Path):
207
253
  path = Path(source).expanduser().resolve()
208
- if path.is_file():
209
- if path.suffix.endswith(".gz"):
210
- raise NotImplementedError
211
- else:
212
- with path.open() as file:
213
- graph_document = GraphDocument.model_validate(json.load(file))
254
+ if not path.is_file():
255
+ raise FileNotFoundError
256
+ if path.suffix.endswith(".gz"):
257
+ with gzip.open(path, mode="rt") as file:
258
+ graph_document = GraphDocument.model_validate(json.load(file))
259
+ else:
260
+ with path.open() as file:
261
+ graph_document = GraphDocument.model_validate(json.load(file))
214
262
  else:
215
263
  raise TypeError(f"Unhandled source: {source}")
216
264
 
obographs/standardized.py CHANGED
@@ -2,68 +2,172 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from curies import Converter, Reference
5
+ import logging
6
+ from abc import ABC, abstractmethod
7
+ from typing import Generic, TypeVar, cast
8
+
9
+ import curies.preprocessing
10
+ from curies import Converter, Reference, Triple, vocabulary
11
+ from curies.vocabulary import SynonymScopeOIO
6
12
  from pydantic import BaseModel, Field
7
13
  from typing_extensions import Self
8
14
 
9
- from obographs.model import Definition, Edge, Graph, Meta, Node, NodeType, Property, Synonym, Xref
15
+ from obographs.model import (
16
+ Definition,
17
+ DomainRangeAxiom,
18
+ Edge,
19
+ EquivalentNodeSet,
20
+ ExistentialRestrictionExpression,
21
+ Graph,
22
+ GraphDocument,
23
+ LogicalDefinition,
24
+ Meta,
25
+ Node,
26
+ NodeType,
27
+ Property,
28
+ PropertyChainAxiom,
29
+ PropertyType,
30
+ Synonym,
31
+ Xref,
32
+ )
10
33
 
11
34
  __all__ = [
35
+ "StandardizedBaseModel",
12
36
  "StandardizedDefinition",
37
+ "StandardizedDomainRangeAxiom",
13
38
  "StandardizedEdge",
39
+ "StandardizedEquivalentNodeSet",
40
+ "StandardizedExistentialRestriction",
14
41
  "StandardizedGraph",
42
+ "StandardizedGraphDocument",
43
+ "StandardizedLogicalDefinition",
15
44
  "StandardizedMeta",
16
45
  "StandardizedNode",
46
+ "StandardizedProperty",
47
+ "StandardizedPropertyChainAxiom",
48
+ "StandardizedSynonym",
17
49
  "StandardizedXref",
18
50
  ]
19
51
 
52
+ logger = logging.getLogger(__name__)
53
+
54
+
55
+ def _expand_list(references: list[Reference] | None, converter: Converter) -> list[str] | None:
56
+ if references is None or not references:
57
+ return None
58
+ return [converter.expand_reference(r, strict=True) for r in references]
59
+
60
+
61
+ X = TypeVar("X")
20
62
 
21
- class StandardizedProperty(BaseModel):
63
+
64
+ class StandardizedBaseModel(BaseModel, ABC, Generic[X]):
65
+ """A standardized property."""
66
+
67
+ @classmethod
68
+ @abstractmethod
69
+ def from_obograph_raw(
70
+ cls, obj: X, converter: Converter, *, strict: bool = False
71
+ ) -> Self | None:
72
+ """Instantiate by standardizing a raw OBO Graph object."""
73
+ raise NotImplementedError
74
+
75
+ @abstractmethod
76
+ def to_raw(self, converter: Converter) -> X:
77
+ """Create a raw object."""
78
+ raise NotImplementedError
79
+
80
+
81
+ class StandardizedProperty(StandardizedBaseModel[Property]):
22
82
  """A standardized property."""
23
83
 
24
84
  predicate: Reference
25
- value: Reference
85
+ value: Reference | str = Field(
86
+ ..., description="Parsed into a Reference if a CURIE or IRI, or a string if it's a literal"
87
+ )
26
88
  xrefs: list[Reference] | None = None
27
89
  meta: StandardizedMeta | None = None
28
90
 
29
91
  @classmethod
30
- def from_obograph_raw(cls, prop: Property, converter: Converter) -> Self:
92
+ def from_obograph_raw(
93
+ cls, prop: Property, converter: Converter, *, strict: bool = False
94
+ ) -> Self:
31
95
  """Instantiate by standardizing a raw OBO Graph object."""
96
+ if not prop.val or not prop.pred:
97
+ raise ValueError
98
+ value: Reference | str | None
99
+
100
+ if (
101
+ prop.val.startswith("http://")
102
+ or prop.val.startswith("https")
103
+ or converter.is_curie(prop.val)
104
+ or prop.val in BUILTINS
105
+ ):
106
+ value = _curie_or_uri_to_ref(prop.val, converter, strict=False) or prop.val
107
+ else:
108
+ value = prop.val
32
109
  return cls(
33
- predicate=_curie_or_uri_to_ref(prop.pred, converter),
34
- value=_curie_or_uri_to_ref(prop.val, converter),
110
+ predicate=_curie_or_uri_to_ref(prop.pred, converter, strict=strict),
111
+ value=value,
35
112
  )
36
113
 
114
+ def to_raw(self, converter: Converter) -> Property:
115
+ """Create a raw object."""
116
+ return Property(
117
+ pred=converter.expand_reference(self.predicate),
118
+ val=converter.expand_reference(self.value)
119
+ if isinstance(self.value, Reference)
120
+ else self.value,
121
+ xrefs=_expand_list(self.xrefs, converter),
122
+ meta=self.meta.to_raw(converter) if self.meta is not None else None,
123
+ )
37
124
 
38
- class StandardizedDefinition(BaseModel):
125
+
126
+ class StandardizedDefinition(StandardizedBaseModel[Definition]):
39
127
  """A standardized definition."""
40
128
 
41
129
  value: str | None = Field(default=None)
42
130
  xrefs: list[Reference] | None = Field(default=None)
43
131
 
44
132
  @classmethod
45
- def from_obograph_raw(cls, definition: Definition | None, converter: Converter) -> Self | None:
46
- """Instantiate by standardizing a raw OBO Graph object."""
133
+ def from_obograph_raw(
134
+ cls, definition: Definition | None, converter: Converter, *, strict: bool = False
135
+ ) -> Self | None:
136
+ """Parse a raw object."""
47
137
  if definition is None:
48
138
  return None
49
139
  return cls(
50
140
  value=definition.val,
51
- xrefs=_parse_list(definition.xrefs, converter),
141
+ xrefs=_parse_list(definition.xrefs, converter, strict=strict),
142
+ )
143
+
144
+ def to_raw(self, converter: Converter) -> Definition:
145
+ """Create a raw object."""
146
+ return Definition(
147
+ val=self.value,
148
+ xrefs=_expand_list(self.xrefs, converter),
52
149
  )
53
150
 
54
151
 
55
- class StandardizedXref(BaseModel):
152
+ class StandardizedXref(StandardizedBaseModel[Xref]):
56
153
  """A standardized database cross-reference."""
57
154
 
58
155
  reference: Reference
59
156
 
60
157
  @classmethod
61
- def from_obograph_raw(cls, xref: Xref, converter: Converter) -> Self:
158
+ def from_obograph_raw(cls, xref: Xref, converter: Converter, *, strict: bool = False) -> Self:
62
159
  """Instantiate by standardizing a raw OBO Graph object."""
63
- return cls(reference=_curie_or_uri_to_ref(xref.val, converter))
160
+ reference = _curie_or_uri_to_ref(xref.val, converter, strict=strict)
161
+ if reference is None:
162
+ raise ValueError(f"could not parse xref: {xref.val}")
163
+ return cls(reference=reference)
64
164
 
165
+ def to_raw(self, converter: Converter) -> Xref:
166
+ """Create a raw object."""
167
+ return Xref(val=self.reference.curie)
65
168
 
66
- class StandardizedSynonym(BaseModel):
169
+
170
+ class StandardizedSynonym(StandardizedBaseModel[Synonym]):
67
171
  """A standardized synonym."""
68
172
 
69
173
  text: str
@@ -72,17 +176,31 @@ class StandardizedSynonym(BaseModel):
72
176
  xrefs: list[Reference] | None = None
73
177
 
74
178
  @classmethod
75
- def from_obograph_raw(cls, synonym: Synonym, converter: Converter) -> Self:
179
+ def from_obograph_raw(
180
+ cls, synonym: Synonym, converter: Converter, *, strict: bool = False
181
+ ) -> Self:
76
182
  """Instantiate by standardizing a raw OBO Graph object."""
77
183
  return cls(
78
184
  text=synonym.val,
79
185
  predicate=Reference(prefix="oboInOwl", identifier=synonym.pred),
80
- type=synonym.synonymType and _curie_or_uri_to_ref(synonym.synonymType, converter),
81
- xrefs=_parse_list(synonym.xrefs, converter),
186
+ type=synonym.synonymType
187
+ and _curie_or_uri_to_ref(synonym.synonymType, converter, strict=strict),
188
+ xrefs=_parse_list(synonym.xrefs, converter, strict=strict),
189
+ )
190
+
191
+ def to_raw(self, converter: Converter) -> Synonym:
192
+ """Create a raw object."""
193
+ if self.predicate.prefix.lower() != "oboinowl":
194
+ raise ValueError
195
+ return Synonym(
196
+ val=self.text,
197
+ pred=cast(SynonymScopeOIO, self.predicate.identifier),
198
+ synonymType=converter.expand_reference(self.type) if self.type is not None else None,
199
+ xrefs=_expand_list(self.xrefs, converter) or [],
82
200
  )
83
201
 
84
202
 
85
- class StandardizedMeta(BaseModel):
203
+ class StandardizedMeta(StandardizedBaseModel[Meta]):
86
204
  """A standardized meta object."""
87
205
 
88
206
  definition: StandardizedDefinition | None
@@ -95,56 +213,131 @@ class StandardizedMeta(BaseModel):
95
213
  properties: list[StandardizedProperty] | None = None
96
214
 
97
215
  @classmethod
98
- def from_obograph_raw(cls, meta: Meta | None, converter: Converter) -> Self | None:
216
+ def from_obograph_raw( # noqa:C901
217
+ cls, meta: Meta | None, converter: Converter, flag: str = "", strict: bool = False
218
+ ) -> Self | None:
99
219
  """Instantiate by standardizing a raw OBO Graph object."""
100
220
  if meta is None:
101
221
  return None
222
+
223
+ xrefs = []
224
+ for raw_xref in meta.xrefs or []:
225
+ if raw_xref.val:
226
+ try:
227
+ st_xref = StandardizedXref.from_obograph_raw(raw_xref, converter, strict=strict)
228
+ except ValueError:
229
+ if strict:
230
+ raise
231
+ logger.debug("[%s] failed to standardize xref: %s", flag, raw_xref)
232
+ else:
233
+ xrefs.append(st_xref)
234
+
235
+ synonyms = []
236
+ for raw_synonym in meta.synonyms or []:
237
+ if raw_synonym.val:
238
+ try:
239
+ s = StandardizedSynonym.from_obograph_raw(raw_synonym, converter, strict=strict)
240
+ except ValueError:
241
+ if strict:
242
+ raise
243
+ logger.debug("[%s] failed to standardize synonym: %s", flag, raw_synonym)
244
+ else:
245
+ synonyms.append(s)
246
+
247
+ props = []
248
+ for raw_prop in meta.basicPropertyValues or []:
249
+ if raw_prop.val and raw_prop.pred:
250
+ try:
251
+ prop = StandardizedProperty.from_obograph_raw(
252
+ raw_prop, converter, strict=strict
253
+ )
254
+ except ValueError:
255
+ if strict:
256
+ raise
257
+ logger.debug("[%s] failed to standardize property: %s", flag, raw_prop)
258
+ else:
259
+ props.append(prop)
260
+
102
261
  return cls(
103
- definition=StandardizedDefinition.from_obograph_raw(meta.definition, converter),
104
- subsets=[_curie_or_uri_to_ref(subset, converter) for subset in meta.subsets]
105
- if meta.subsets
106
- else None,
107
- xrefs=[StandardizedXref.from_obograph_raw(xref, converter) for xref in meta.xrefs]
108
- if meta.xrefs
109
- else None,
110
- synonyms=[
111
- StandardizedSynonym.from_obograph_raw(synonym, converter)
112
- for synonym in meta.synonyms
262
+ definition=StandardizedDefinition.from_obograph_raw(
263
+ meta.definition, converter, strict=strict
264
+ ),
265
+ subsets=[
266
+ _curie_or_uri_to_ref(subset, converter, strict=strict) for subset in meta.subsets
113
267
  ]
114
- if meta.synonyms
268
+ if meta.subsets
115
269
  else None,
270
+ xrefs=xrefs or None,
271
+ synonyms=synonyms or None,
116
272
  comments=meta.comments,
117
273
  version=meta.version,
118
274
  deprecated=meta.deprecated,
119
- properties=[
120
- StandardizedProperty.from_obograph_raw(p, converter)
121
- for p in meta.basicPropertyValues
122
- ]
123
- if meta.basicPropertyValues
275
+ properties=props or None,
276
+ )
277
+
278
+ def to_raw(self, converter: Converter) -> Meta:
279
+ """Create a raw object."""
280
+ return Meta(
281
+ definition=self.definition.to_raw(converter)
282
+ if self.definition and self.definition.value
283
+ else None,
284
+ subsets=_expand_list(self.subsets, converter),
285
+ xrefs=[xref.to_raw(converter) for xref in self.xrefs] if self.xrefs else None,
286
+ synonyms=[s.to_raw(converter) for s in self.synonyms] if self.synonyms else None,
287
+ comments=self.comments,
288
+ version=self.version, # TODO might need some kind of expansion?
289
+ deprecated=self.deprecated,
290
+ basicPropertyValues=[p.to_raw(converter) for p in self.properties]
291
+ if self.properties
124
292
  else None,
125
293
  )
126
294
 
127
295
 
128
- class StandardizedNode(BaseModel):
296
+ class StandardizedNode(StandardizedBaseModel[Node]):
129
297
  """A standardized node."""
130
298
 
131
299
  reference: Reference
132
300
  label: str | None = Field(None)
133
301
  meta: StandardizedMeta | None = None
134
- type: NodeType = Field(..., description="Type of node")
302
+ type: NodeType | None = Field(None, description="Type of node")
303
+ property_type: PropertyType | None = Field(
304
+ None, description="Type of property, if the node type is a property"
305
+ )
135
306
 
136
307
  @classmethod
137
- def from_obograph_raw(cls, node: Node, converter: Converter) -> Self:
308
+ def from_obograph_raw(
309
+ cls, node: Node, converter: Converter, *, strict: bool = False
310
+ ) -> Self | None:
138
311
  """Instantiate by standardizing a raw OBO Graph object."""
312
+ reference = _curie_or_uri_to_ref(node.id, converter, strict=strict)
313
+ if reference is None:
314
+ if strict:
315
+ raise ValueError(f"failed to parse node's ID: {node.id}")
316
+ logger.warning("failed to parse node's ID %s", node.id)
317
+ return None
318
+
139
319
  return cls(
140
- reference=_curie_or_uri_to_ref(node.id, converter),
320
+ reference=reference,
141
321
  label=node.lbl,
142
- meta=StandardizedMeta.from_obograph_raw(node.meta, converter),
322
+ meta=StandardizedMeta.from_obograph_raw(
323
+ node.meta, converter, flag=reference.curie, strict=strict
324
+ ),
143
325
  type=node.type,
326
+ property_type=node.propertyType,
327
+ )
328
+
329
+ def to_raw(self, converter: Converter) -> Node:
330
+ """Create a raw object."""
331
+ return Node(
332
+ id=converter.expand_reference(self.reference),
333
+ lbl=self.label,
334
+ meta=self.meta.to_raw(converter) if self.meta is not None else None,
335
+ type=self.type,
336
+ propertyType=self.property_type,
144
337
  )
145
338
 
146
339
 
147
- class StandardizedEdge(BaseModel):
340
+ class StandardizedEdge(Triple, StandardizedBaseModel[Edge]):
148
341
  """A standardized edge."""
149
342
 
150
343
  subject: Reference
@@ -153,17 +346,205 @@ class StandardizedEdge(BaseModel):
153
346
  meta: StandardizedMeta | None = None
154
347
 
155
348
  @classmethod
156
- def from_obograph_raw(cls, node: Edge, converter: Converter) -> Self:
349
+ def from_obograph_raw(
350
+ cls, edge: Edge, converter: Converter, *, strict: bool = False
351
+ ) -> Self | None:
157
352
  """Instantiate by standardizing a raw OBO Graph object."""
353
+ subject = _curie_or_uri_to_ref(edge.sub, converter, strict=strict)
354
+ if not subject:
355
+ if strict:
356
+ raise ValueError
357
+ logger.warning("failed to parse edge's subject %s", edge.sub)
358
+ return None
359
+ predicate = _curie_or_uri_to_ref(edge.pred, converter, strict=strict)
360
+ if not predicate:
361
+ if strict:
362
+ raise ValueError
363
+ logger.warning("failed to parse edge's predicate %s", edge.pred)
364
+ return None
365
+ obj = _curie_or_uri_to_ref(edge.obj, converter, strict=strict)
366
+ if not obj:
367
+ if strict:
368
+ raise ValueError
369
+ logger.warning("failed to parse edge's object %s", edge.obj)
370
+ return None
158
371
  return cls(
159
- subject=_curie_or_uri_to_ref(node.sub, converter),
160
- predicate=_curie_or_uri_to_ref(node.pred, converter),
161
- object=_curie_or_uri_to_ref(node.obj, converter),
162
- meta=StandardizedMeta.from_obograph_raw(node.meta, converter),
372
+ subject=subject,
373
+ predicate=predicate,
374
+ object=obj,
375
+ meta=StandardizedMeta.from_obograph_raw(
376
+ edge.meta,
377
+ converter,
378
+ flag=f"{subject.curie} {predicate.curie} {obj.curie}",
379
+ strict=strict,
380
+ ),
381
+ )
382
+
383
+ def to_raw(self, converter: Converter) -> Edge:
384
+ """Create a raw object."""
385
+ if self.predicate in REVERSE_BUILTINS:
386
+ predicate = REVERSE_BUILTINS[self.predicate]
387
+ else:
388
+ predicate = converter.expand_reference(self.predicate, strict=True)
389
+
390
+ return Edge(
391
+ sub=converter.expand_reference(self.subject),
392
+ pred=predicate,
393
+ obj=converter.expand_reference(self.object),
394
+ meta=self.meta.to_raw(converter) if self.meta is not None else None,
163
395
  )
164
396
 
165
397
 
166
- class StandardizedGraph(BaseModel):
398
+ class StandardizedDomainRangeAxiom(StandardizedBaseModel[DomainRangeAxiom]):
399
+ """Represents a domain/range axiom."""
400
+
401
+ predicate: Reference
402
+ domains: list[Reference] = Field(default_factory=list)
403
+ ranges: list[Reference] = Field(default_factory=list)
404
+ all_values_from_edges: list[StandardizedEdge] = Field(default_factory=list)
405
+ meta: StandardizedMeta | None = None
406
+
407
+ @classmethod
408
+ def from_obograph_raw(
409
+ cls, obj: DomainRangeAxiom, converter: Converter, *, strict: bool = False
410
+ ) -> Self | None:
411
+ """Parse a raw object."""
412
+ return cls(
413
+ predicate=_curie_or_uri_to_ref(obj.predicateId, converter, strict=strict),
414
+ domains=_parse_list(obj.domainClassIds, converter, strict=strict) or [],
415
+ ranges=_parse_list(obj.rangeClassIds, converter, strict=strict) or [],
416
+ all_values_from_edges=[
417
+ StandardizedEdge.from_obograph_raw(edge, converter, strict=strict)
418
+ for edge in obj.allValuesFromEdges or []
419
+ ],
420
+ meta=StandardizedMeta.from_obograph_raw(obj.meta, converter, strict=strict),
421
+ )
422
+
423
+ def to_raw(self, converter: Converter) -> DomainRangeAxiom:
424
+ """Create a raw object."""
425
+ return DomainRangeAxiom(
426
+ predicateId=converter.expand_reference(self.predicate),
427
+ domainClassIds=_expand_list(self.domains, converter),
428
+ rangeClassIds=_expand_list(self.ranges, converter),
429
+ allValuesFromEdges=[edge.to_raw(converter) for edge in self.all_values_from_edges]
430
+ if self.all_values_from_edges
431
+ else None,
432
+ meta=self.meta.to_raw(converter) if self.meta is not None else None,
433
+ )
434
+
435
+
436
+ class StandardizedPropertyChainAxiom(StandardizedBaseModel[PropertyChainAxiom]):
437
+ """Represents a property chain axiom."""
438
+
439
+ predicate: Reference
440
+ chain: list[Reference] = Field(default_factory=list)
441
+ meta: StandardizedMeta | None = None
442
+
443
+ @classmethod
444
+ def from_obograph_raw(
445
+ cls, obj: PropertyChainAxiom, converter: Converter, *, strict: bool = False
446
+ ) -> Self | None:
447
+ """Parse a raw object."""
448
+ return cls(
449
+ predicate=_curie_or_uri_to_ref(obj.predicateId, converter, strict=strict),
450
+ chain=_parse_list(obj.chainPredicateIds, converter, strict=strict),
451
+ meta=StandardizedMeta.from_obograph_raw(obj.meta, converter, strict=strict),
452
+ )
453
+
454
+ def to_raw(self, converter: Converter) -> PropertyChainAxiom:
455
+ """Create a raw object."""
456
+ return PropertyChainAxiom(
457
+ predicateId=converter.expand_reference(self.predicate),
458
+ chainPredicateIds=_expand_list(self.chain, converter),
459
+ meta=self.meta.to_raw(converter) if self.meta is not None else None,
460
+ )
461
+
462
+
463
+ class StandardizedEquivalentNodeSet(StandardizedBaseModel[EquivalentNodeSet]):
464
+ """Represents an equivalence set."""
465
+
466
+ node: Reference
467
+ equivalents: list[Reference] = Field(default_factory=list)
468
+ meta: StandardizedMeta | None = None
469
+
470
+ @classmethod
471
+ def from_obograph_raw(
472
+ cls, obj: EquivalentNodeSet, converter: Converter, *, strict: bool = False
473
+ ) -> Self | None:
474
+ """Parse a raw object."""
475
+ return cls(
476
+ node=_curie_or_uri_to_ref(obj.representativeNodeId, converter, strict=strict),
477
+ equivalents=_parse_list(obj.nodeIds, converter, strict=strict),
478
+ meta=StandardizedMeta.from_obograph_raw(obj.meta, converter, strict=strict),
479
+ )
480
+
481
+ def to_raw(self, converter: Converter) -> EquivalentNodeSet:
482
+ """Create a raw object."""
483
+ return EquivalentNodeSet(
484
+ representativeNodeId=converter.expand_reference(self.node),
485
+ nodeIds=_expand_list(self.equivalents, converter),
486
+ meta=self.meta.to_raw(converter) if self.meta is not None else None,
487
+ )
488
+
489
+
490
+ class StandardizedExistentialRestriction(StandardizedBaseModel[ExistentialRestrictionExpression]):
491
+ """Represents an existential restriction expression."""
492
+
493
+ predicate: Reference
494
+ target: Reference
495
+
496
+ @classmethod
497
+ def from_obograph_raw(
498
+ cls, obj: ExistentialRestrictionExpression, converter: Converter, *, strict: bool = False
499
+ ) -> Self | None:
500
+ """Parse a raw object."""
501
+ return cls(
502
+ predicate=_curie_or_uri_to_ref(obj.propertyId, converter, strict=strict),
503
+ target=_curie_or_uri_to_ref(obj.fillerId, converter, strict=strict),
504
+ )
505
+
506
+ def to_raw(self, converter: Converter) -> ExistentialRestrictionExpression:
507
+ """Create a raw object."""
508
+ return ExistentialRestrictionExpression(
509
+ propertyId=converter.expand_reference(self.predicate),
510
+ fillerId=converter.expand_reference(self.target),
511
+ )
512
+
513
+
514
+ class StandardizedLogicalDefinition(StandardizedBaseModel[LogicalDefinition]):
515
+ """Represents a logical definition axiom."""
516
+
517
+ node: Reference
518
+ geni: list[Reference] = Field(default_factory=list)
519
+ restrictions: list[StandardizedExistentialRestriction] = Field(default_factory=list)
520
+ meta: StandardizedMeta | None = None
521
+
522
+ @classmethod
523
+ def from_obograph_raw(
524
+ cls, obj: LogicalDefinition, converter: Converter, *, strict: bool = False
525
+ ) -> Self | None:
526
+ """Parse a raw object."""
527
+ return cls(
528
+ node=_curie_or_uri_to_ref(obj.definedClassId, converter, strict=strict),
529
+ geni=_parse_list(obj.genusIds, converter, strict=strict),
530
+ restrictions=[
531
+ StandardizedExistentialRestriction.from_obograph_raw(r, converter, strict=strict)
532
+ for r in obj.restrictions or []
533
+ ],
534
+ meta=StandardizedMeta.from_obograph_raw(obj.meta, converter, strict=strict),
535
+ )
536
+
537
+ def to_raw(self, converter: Converter) -> LogicalDefinition:
538
+ """Create a raw object."""
539
+ return LogicalDefinition(
540
+ definedClassId=converter.expand_reference(self.node),
541
+ genusIds=_expand_list(self.geni, converter),
542
+ restrictions=[r.to_raw(converter) for r in self.restrictions],
543
+ meta=self.meta.to_raw(converter) if self.meta is not None else None,
544
+ )
545
+
546
+
547
+ class StandardizedGraph(StandardizedBaseModel[Graph]):
167
548
  """A standardized graph."""
168
549
 
169
550
  id: str | None = None
@@ -171,40 +552,148 @@ class StandardizedGraph(BaseModel):
171
552
  nodes: list[StandardizedNode] = Field(default_factory=list)
172
553
  edges: list[StandardizedEdge] = Field(default_factory=list)
173
554
 
174
- # TODO other bits
555
+ equivalent_node_sets: list[StandardizedEquivalentNodeSet] = Field(default_factory=list)
556
+ logical_definition_axioms: list[StandardizedLogicalDefinition] = Field(default_factory=list)
557
+ domain_range_axioms: list[StandardizedDomainRangeAxiom] = Field(default_factory=list)
558
+ property_chain_axioms: list[StandardizedPropertyChainAxiom] = Field(default_factory=list)
175
559
 
176
560
  @classmethod
177
- def from_obograph_raw(cls, graph: Graph, converter: Converter) -> Self:
561
+ def from_obograph_raw(cls, graph: Graph, converter: Converter, *, strict: bool = False) -> Self:
178
562
  """Instantiate by standardizing a raw OBO Graph object."""
179
563
  return cls(
180
564
  id=graph.id,
181
- meta=StandardizedMeta.from_obograph_raw(graph.meta, converter),
182
- nodes=[StandardizedNode.from_obograph_raw(node, converter) for node in graph.nodes],
183
- edges=[StandardizedEdge.from_obograph_raw(edge, converter) for edge in graph.edges],
565
+ meta=StandardizedMeta.from_obograph_raw(
566
+ graph.meta, converter, flag=graph.id or "", strict=strict
567
+ ),
568
+ nodes=[
569
+ s_node
570
+ for node in graph.nodes
571
+ if (s_node := StandardizedNode.from_obograph_raw(node, converter, strict=strict))
572
+ ],
573
+ edges=[
574
+ s_edge
575
+ for edge in graph.edges
576
+ if (s_edge := StandardizedEdge.from_obograph_raw(edge, converter, strict=strict))
577
+ ],
578
+ equivalent_node_sets=[
579
+ StandardizedEquivalentNodeSet.from_obograph_raw(e, converter, strict=strict)
580
+ for e in graph.equivalentNodesSets or []
581
+ ],
582
+ logical_definition_axioms=[
583
+ StandardizedLogicalDefinition.from_obograph_raw(e, converter, strict=strict)
584
+ for e in graph.logicalDefinitionAxioms or []
585
+ ],
586
+ property_chain_axioms=[
587
+ StandardizedPropertyChainAxiom.from_obograph_raw(e, converter, strict=strict)
588
+ for e in graph.propertyChainAxioms or []
589
+ ],
590
+ domain_range_axioms=[
591
+ StandardizedDomainRangeAxiom.from_obograph_raw(e, converter, strict=strict)
592
+ for e in graph.domainRangeAxioms or []
593
+ ],
594
+ )
595
+
596
+ def to_raw(self, converter: Converter) -> Graph:
597
+ """Create a raw object."""
598
+ return Graph(
599
+ id=self.id,
600
+ meta=self.meta.to_raw(converter) if self.meta is not None else None,
601
+ nodes=[node.to_raw(converter) for node in self.nodes],
602
+ edges=[edge.to_raw(converter) for edge in self.edges],
603
+ logicalDefinitionAxioms=[
604
+ axiom.to_raw(converter) for axiom in self.logical_definition_axioms
605
+ ],
606
+ propertyChainAxioms=[axiom.to_raw(converter) for axiom in self.property_chain_axioms],
607
+ domainRangeAxioms=[axiom.to_raw(converter) for axiom in self.domain_range_axioms],
608
+ equivalentNodesSets=[axiom.to_raw(converter) for axiom in self.equivalent_node_sets],
609
+ )
610
+
611
+ def _get_property(self, predicate: Reference) -> str | Reference | None:
612
+ if self.meta is None:
613
+ return None
614
+
615
+ for p in self.meta.properties or []:
616
+ if p.predicate == predicate:
617
+ return p.value
618
+
619
+ return None
620
+
621
+ @property
622
+ def name(self) -> str | None:
623
+ """Look up the name of the graph."""
624
+ r = self._get_property(Reference(prefix="dcterms", identifier="title"))
625
+ if isinstance(r, Reference):
626
+ raise TypeError
627
+ return r
628
+
629
+
630
+ class StandardizedGraphDocument(StandardizedBaseModel[GraphDocument]):
631
+ """A standardized graph document."""
632
+
633
+ graphs: list[StandardizedGraph]
634
+ meta: StandardizedMeta | None = None
635
+
636
+ @classmethod
637
+ def from_obograph_raw(
638
+ cls, graph_document: GraphDocument, converter: Converter, *, strict: bool = False
639
+ ) -> Self:
640
+ """Instantiate by standardizing a raw OBO Graph Document object."""
641
+ return cls(
642
+ graphs=[
643
+ StandardizedGraph.from_obograph_raw(graph, converter, strict=strict)
644
+ for graph in graph_document.graphs
645
+ ],
646
+ meta=StandardizedMeta.from_obograph_raw(graph_document.meta, converter, strict=strict),
647
+ )
648
+
649
+ def to_raw(self, converter: Converter) -> GraphDocument:
650
+ """Create a raw object."""
651
+ return GraphDocument(
652
+ graphs=[graph.to_raw(converter) for graph in self.graphs],
653
+ meta=self.meta.to_raw(converter) if self.meta is not None else None,
184
654
  )
185
655
 
186
656
 
187
- def _parse_list(ss: list[str] | None, converter: Converter) -> list[Reference] | None:
188
- if not ss:
657
+ def _parse_list(
658
+ curie_or_uris: list[str] | None, converter: Converter, *, strict: bool
659
+ ) -> list[Reference] | None:
660
+ if not curie_or_uris:
189
661
  return None
190
- return [_curie_or_uri_to_ref(x, converter) for x in ss]
662
+ return [
663
+ reference
664
+ for curie_or_uri in curie_or_uris
665
+ if (reference := _curie_or_uri_to_ref(curie_or_uri, converter, strict=strict))
666
+ ]
191
667
 
192
668
 
193
669
  #: defined in https://github.com/geneontology/obographs/blob/6676b10a5cce04707d75b9dd46fa08de70322b0b/obographs-owlapi/src/main/java/org/geneontology/obographs/owlapi/FromOwl.java#L36-L39
194
- BUILTINS = {
195
- "is_a": Reference(prefix="rdfs", identifier="subClassOf"),
196
- "subPropertyOf": Reference(prefix="rdfs", identifier="subPropertyOf"),
197
- "type": Reference(prefix="rdf", identifier="type"),
670
+ #: this list is complete.
671
+ BUILTINS: dict[str, Reference] = {
672
+ "is_a": vocabulary.is_a,
673
+ "subPropertyOf": vocabulary.subproperty_of,
674
+ "type": vocabulary.rdf_type,
198
675
  "inverseOf": Reference(prefix="owl", identifier="inverseOf"),
199
676
  }
200
677
 
678
+ """maybe add these later?
679
+ # predicates, see https://github.com/geneontology/obographs/blob/6676b10a5cce04707d75b9dd46fa08de70322b0b/obographs-core/src/test/java/org/geneontology/obographs/core/model/axiom/PropertyChainAxiomTest.java#L12-L14
680
+ # "part_of": vocabulary.part_of,
681
+ # "has_part": vocabulary.has_part,
682
+ # "overlaps": Reference(prefix="RO", identifier="0002131"),
683
+ """
201
684
 
202
- def _curie_or_uri_to_ref(s: str, converter: Converter) -> Reference:
685
+ REVERSE_BUILTINS: dict[Reference, str] = {v: k for k, v in BUILTINS.items()}
686
+
687
+
688
+ def _curie_or_uri_to_ref(s: str, converter: Converter, *, strict: bool) -> Reference | None:
203
689
  if s in BUILTINS:
204
690
  return BUILTINS[s]
205
- if converter.is_uri(s):
206
- p, o = converter.parse_uri(s)
207
- return Reference(prefix=p, identifier=o)
208
- elif converter.is_curie(s):
209
- pass
210
- raise ValueError(f"can't parse string: {s}")
691
+ try:
692
+ reference_tuple = converter.parse(s, strict=False)
693
+ except curies.preprocessing.BlocklistError:
694
+ return None
695
+ if reference_tuple is not None:
696
+ return reference_tuple.to_pydantic()
697
+ if strict:
698
+ raise ValueError(f"could not parse {s}")
699
+ return None
obographs/version.py CHANGED
@@ -12,7 +12,7 @@ __all__ = [
12
12
  "get_version",
13
13
  ]
14
14
 
15
- VERSION = "0.0.2"
15
+ VERSION = "0.0.4"
16
16
 
17
17
 
18
18
  def get_git_hash() -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: obographs
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: A python data model for OBO Graphs
5
5
  Keywords: snekpack,cookiecutter
6
6
  Author: Charles Tapley Hoyt
@@ -23,15 +23,8 @@ Classifier: Programming Language :: Python :: 3.13
23
23
  Classifier: Programming Language :: Python :: 3 :: Only
24
24
  Classifier: Typing :: Typed
25
25
  Requires-Dist: pydantic
26
- Requires-Dist: curies
26
+ Requires-Dist: curies>=0.10.19
27
27
  Requires-Dist: typing-extensions
28
- Requires-Dist: sphinx>=8 ; extra == 'docs'
29
- Requires-Dist: sphinx-rtd-theme>=3.0 ; extra == 'docs'
30
- Requires-Dist: sphinx-automodapi ; extra == 'docs'
31
- Requires-Dist: autodoc-pydantic ; extra == 'docs'
32
- Requires-Dist: requests ; extra == 'network'
33
- Requires-Dist: pytest ; extra == 'tests'
34
- Requires-Dist: coverage[toml] ; extra == 'tests'
35
28
  Maintainer: Charles Tapley Hoyt
36
29
  Maintainer-email: Charles Tapley Hoyt <cthoyt@gmail.com>
37
30
  Requires-Python: >=3.10
@@ -40,9 +33,6 @@ Project-URL: Documentation, https://obographs.readthedocs.io
40
33
  Project-URL: Funding, https://github.com/sponsors/cthoyt
41
34
  Project-URL: Homepage, https://github.com/cthoyt/obographs
42
35
  Project-URL: Repository, https://github.com/cthoyt/obographs.git
43
- Provides-Extra: docs
44
- Provides-Extra: network
45
- Provides-Extra: tests
46
36
  Description-Content-Type: text/markdown
47
37
 
48
38
  <!--
@@ -97,7 +87,7 @@ graph_raw = obographs.read(url)
97
87
  The OBO Graph JSON schema uses non-Pythonic names, and it's inherently not aware
98
88
  of semantics - it uses a combination of URIs and ad-hoc symbols as identifiers.
99
89
  `obographs` implements a standardization workflow that creates new data
100
- structures with parsed/normalized URIs and symbols that has Pythonic nams. Use
90
+ structures with parsed/normalized URIs and symbols that has Pythonic names. Use
101
91
  it like:
102
92
 
103
93
  ```python
@@ -129,18 +119,15 @@ $ python3 -m pip install obographs
129
119
  The most recent code and data can be installed directly from GitHub with uv:
130
120
 
131
121
  ```console
132
- $ uv --preview pip install git+https://github.com/cthoyt/obographs.git
122
+ $ uv pip install git+https://github.com/cthoyt/obographs.git
133
123
  ```
134
124
 
135
125
  or with pip:
136
126
 
137
127
  ```console
138
- $ UV_PREVIEW=1 python3 -m pip install git+https://github.com/cthoyt/obographs.git
128
+ $ python3 -m pip install git+https://github.com/cthoyt/obographs.git
139
129
  ```
140
130
 
141
- Note that this requires setting `UV_PREVIEW` mode enabled until the uv build
142
- backend becomes a stable feature.
143
-
144
131
  ## 👐 Contributing
145
132
 
146
133
  Contributions, whether filing an issue, making a pull request, or forking, are
@@ -203,18 +190,15 @@ To install in development mode, use the following:
203
190
  ```console
204
191
  $ git clone git+https://github.com/cthoyt/obographs.git
205
192
  $ cd obographs
206
- $ uv --preview pip install -e .
193
+ $ uv pip install -e .
207
194
  ```
208
195
 
209
196
  Alternatively, install using pip:
210
197
 
211
198
  ```console
212
- $ UV_PREVIEW=1 python3 -m pip install -e .
199
+ $ python3 -m pip install -e .
213
200
  ```
214
201
 
215
- Note that this requires setting `UV_PREVIEW` mode enabled until the uv build
216
- backend becomes a stable feature.
217
-
218
202
  ### Updating Package Boilerplate
219
203
 
220
204
  This project uses `cruft` to keep boilerplate (i.e., configuration, contribution
@@ -0,0 +1,10 @@
1
+ obographs/__init__.py,sha256=55f4bf18441bd7f6c41afa68d094532933b02b21dbdd20679cf8807c1791d494,1601
2
+ obographs/model.py,sha256=1f1bda132b83d8e08d7301eddb9be8d58b7e61c5b30e9baab9c24fd22348db53,8457
3
+ obographs/py.typed,sha256=01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b,1
4
+ obographs/standardized.py,sha256=69d26bbd35b49232b8d4bc2ae462f864f4fffcadf3acb7d541783fc8745dbd5e,25791
5
+ obographs/version.py,sha256=c50bb52fc81eb5b19dedfb8583a0c19b6127aff659301cb2d3d51f4d244fefc3,961
6
+ obographs-0.0.4.dist-info/licenses/LICENSE,sha256=4be0ec343e3bf11fd54321a6b576d5616ebb7d18898f741f63c517209e33bcb2,1076
7
+ obographs-0.0.4.dist-info/WHEEL,sha256=65f6765ba93534713730ff2172cd912ef280aa42867625a73f77c2fef0639dae,78
8
+ obographs-0.0.4.dist-info/entry_points.txt,sha256=9a9819cedd2186e28d5d42ddce5e3de1417b0db2b07392ff35f9adc7c86a8619,50
9
+ obographs-0.0.4.dist-info/METADATA,sha256=ac4daf3fa7f23379788e758debc91663ee15663ee1c9fabbf79bc69ca79c5e5b,12768
10
+ obographs-0.0.4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.5.31
2
+ Generator: uv 0.7.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,10 +0,0 @@
1
- obographs/version.py,sha256=04839f3ad0ad481ebb750f5228278ee8a55b26c17bd61c0a3f2ccc6da50204ae,961
2
- obographs/__init__.py,sha256=93807a9cc6f4002ac923a84a8c5e1a9b6301c5368cd61a957c9115adfaef1a56,254
3
- obographs/model.py,sha256=3387390f1a30fe47b22c7c346dfdf2b6a595ccbc7cd8d7d09debf4a13f8f6e6c,6846
4
- obographs/py.typed,sha256=01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b,1
5
- obographs/standardized.py,sha256=15a58dcb9af39a01565fdb096389caf2702f94052e933f9368d93caf620022f5,7099
6
- obographs-0.0.2.dist-info/licenses/LICENSE,sha256=4be0ec343e3bf11fd54321a6b576d5616ebb7d18898f741f63c517209e33bcb2,1076
7
- obographs-0.0.2.dist-info/WHEEL,sha256=e3765529bb0cc791d07188d72ec6a759d7625ff6d3a5e4b710d25409bae03770,79
8
- obographs-0.0.2.dist-info/entry_points.txt,sha256=9a9819cedd2186e28d5d42ddce5e3de1417b0db2b07392ff35f9adc7c86a8619,50
9
- obographs-0.0.2.dist-info/METADATA,sha256=3e577f9dcce8afd6b2b0694935c320b898e5253ba6f0dcf8520b4d1a996ef3a3,13429
10
- obographs-0.0.2.dist-info/RECORD,,