process-bigraph-lang 0.1.0__py3-none-win_amd64.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.
@@ -0,0 +1,234 @@
1
+ """
2
+ `describe-model` for SBML: a model file's interface as data (see `docs/v2/design.md`, section 5.6).
3
+
4
+ `describe_sbml` reads an SBML file with the standard library's XML parser (no libsbml, no process code) and
5
+ returns a `ModelHeader`: the model's time unit, compartments, species (as concentrations unless the species has
6
+ only substance units) and parameters, each with its value and its units composed from the model's unit
7
+ definitions in a pint-readable spelling, plus the counts a solver cares about (reactions, events, rules) and the
8
+ SBML level, version and packages. `to_dict` is the JSON form (`pblang-model/0`), which the CLI renders as the
9
+ `.pblang` model header (`print_model_header`).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import tempfile
16
+ import xml.etree.ElementTree as ET
17
+ from dataclasses import dataclass, field
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ MODEL_HEADER_VERSION = "pblang-model/0"
22
+
23
+ _KIND_SYMBOLS = {
24
+ "litre": "L",
25
+ "liter": "L",
26
+ "mole": "mol",
27
+ "second": "s",
28
+ "gram": "g",
29
+ "kilogram": "kg",
30
+ "metre": "m",
31
+ "meter": "m",
32
+ "item": "count",
33
+ "dimensionless": "dimensionless",
34
+ "hertz": "Hz",
35
+ "kelvin": "K",
36
+ "joule": "J",
37
+ "newton": "N",
38
+ "pascal": "Pa",
39
+ "watt": "W",
40
+ "volt": "V",
41
+ "ampere": "A",
42
+ "coulomb": "C",
43
+ }
44
+ _SCALE_PREFIX = {3: "k", 2: "h", 1: "da", -1: "d", -2: "c", -3: "m", -6: "u", -9: "n", -12: "p", -15: "f"}
45
+ _TIME_MULTIPLES = {60.0: "min", 3600.0: "h", 86400.0: "day"}
46
+ _SBML_DEFAULTS = {"substance": "mol", "volume": "L", "time": "s", "area": "m**2", "length": "m"}
47
+
48
+
49
+ @dataclass
50
+ class Quantity:
51
+ value: float | None
52
+ units: str | None = None
53
+ constant: bool = True
54
+
55
+
56
+ @dataclass
57
+ class SpeciesDecl:
58
+ initial: float | None
59
+ units: str | None
60
+ kind: str # concentration | amount
61
+ compartment: str | None
62
+ constant: bool = False
63
+
64
+
65
+ @dataclass
66
+ class ModelHeader:
67
+ name: str
68
+ source: str
69
+ sbml_id: str | None = None
70
+ level: int | None = None
71
+ version: int | None = None
72
+ packages: list[str] = field(default_factory=list)
73
+ time_units: str | None = None
74
+ compartments: dict[str, Quantity] = field(default_factory=dict)
75
+ species: dict[str, SpeciesDecl] = field(default_factory=dict)
76
+ parameters: dict[str, Quantity] = field(default_factory=dict)
77
+ counts: dict[str, int] = field(default_factory=dict)
78
+ notes: list[str] = field(default_factory=list)
79
+
80
+ def to_dict(self) -> dict[str, Any]:
81
+ return {
82
+ "schema_version": MODEL_HEADER_VERSION,
83
+ "name": self.name,
84
+ "source": self.source,
85
+ "sbml": {"id": self.sbml_id, "level": self.level, "version": self.version, "packages": self.packages},
86
+ "time_units": self.time_units,
87
+ "compartments": {k: {"value": q.value, "units": q.units} for k, q in self.compartments.items()},
88
+ "species": {
89
+ k: {
90
+ "initial": s.initial,
91
+ "units": s.units,
92
+ "kind": s.kind,
93
+ "compartment": s.compartment,
94
+ "constant": s.constant,
95
+ }
96
+ for k, s in self.species.items()
97
+ },
98
+ "parameters": {
99
+ k: {"value": q.value, "units": q.units, "constant": q.constant} for k, q in self.parameters.items()
100
+ },
101
+ "counts": dict(self.counts),
102
+ "notes": list(self.notes),
103
+ }
104
+
105
+
106
+ def describe_sbml(path: str | Path) -> ModelHeader:
107
+ """The model header of an SBML file."""
108
+ path = Path(path)
109
+ root = ET.parse(path).getroot()
110
+ model = root.find("{*}model")
111
+ if model is None:
112
+ raise ValueError(f"{path}: no <model> element")
113
+ header = ModelHeader(name=_identifier(path.stem), source=path.name, sbml_id=model.get("id"))
114
+ header.level, header.version = _int(root.get("level")), _int(root.get("version"))
115
+ header.packages = sorted({_package(k) for k in root.attrib if k.endswith(":required")} - {""})
116
+ unit_defs = {d.get("id", ""): _unit_text(d) for d in model.findall("{*}listOfUnitDefinitions/{*}unitDefinition")}
117
+
118
+ def units(reference: str | None, default_key: str) -> str | None:
119
+ if reference:
120
+ return unit_defs.get(reference) or _KIND_SYMBOLS.get(reference, reference)
121
+ return unit_defs.get(model.get(f"{default_key}Units") or default_key) or _SBML_DEFAULTS.get(default_key)
122
+
123
+ header.time_units = units(None, "time")
124
+ for c in model.findall("{*}listOfCompartments/{*}compartment"):
125
+ dims = _int(c.get("spatialDimensions")) if c.get("spatialDimensions") else 3
126
+ default = {3: "volume", 2: "area", 1: "length"}.get(dims or 3, "volume")
127
+ header.compartments[c.get("id", "")] = Quantity(_float_or_none(c.get("size")), units(c.get("units"), default))
128
+ for sp in model.findall("{*}listOfSpecies/{*}species"):
129
+ name = sp.get("id", "")
130
+ compartment = sp.get("compartment")
131
+ substance = units(sp.get("substanceUnits"), "substance")
132
+ comp_units = (
133
+ header.compartments[compartment].units if compartment in header.compartments else units(None, "volume")
134
+ )
135
+ only_substance = sp.get("hasOnlySubstanceUnits") == "true"
136
+ if only_substance:
137
+ initial, kind, unit_text = _float_or_none(sp.get("initialAmount")), "amount", substance
138
+ elif sp.get("initialConcentration") is not None:
139
+ initial, kind, unit_text = (
140
+ _float_or_none(sp.get("initialConcentration")),
141
+ "concentration",
142
+ _ratio(substance, comp_units),
143
+ )
144
+ else:
145
+ amount = _float_or_none(sp.get("initialAmount"))
146
+ size = header.compartments[compartment].value if compartment in header.compartments else None
147
+ initial = amount / size if amount is not None and size else amount
148
+ kind, unit_text = "concentration", _ratio(substance, comp_units)
149
+ if amount is not None and size:
150
+ header.notes.append(
151
+ f"species {name}: initial amount {amount:g} converted to concentration by compartment size {size:g}"
152
+ )
153
+ header.species[name] = SpeciesDecl(initial, unit_text, kind, compartment, constant=sp.get("constant") == "true")
154
+ for pr in model.findall("{*}listOfParameters/{*}parameter"):
155
+ header.parameters[pr.get("id", "")] = Quantity(
156
+ _float_or_none(pr.get("value")),
157
+ units(pr.get("units"), "") if pr.get("units") else None,
158
+ pr.get("constant") != "false",
159
+ )
160
+ header.counts = {
161
+ "reactions": len(model.findall("{*}listOfReactions/{*}reaction")),
162
+ "events": len(model.findall("{*}listOfEvents/{*}event")),
163
+ "rules": len(model.findall("{*}listOfRules/*")),
164
+ "function_definitions": len(model.findall("{*}listOfFunctionDefinitions/{*}functionDefinition")),
165
+ }
166
+ return header
167
+
168
+
169
+ def print_model_header(header: ModelHeader) -> str:
170
+ """The `.pblang` model header: a generated declaration to `import`, not a composite (rendered by the CLI)."""
171
+ from process_bigraph_lang.v2.langium import run_cli
172
+
173
+ with tempfile.TemporaryDirectory() as tmp:
174
+ source = Path(tmp) / "model.json"
175
+ source.write_text(json.dumps(header.to_dict()), encoding="utf-8")
176
+ return run_cli("model-header", str(source)).stdout
177
+
178
+
179
+ # --- helpers -----------------------------------------------------------------------------------------------------
180
+
181
+
182
+ def _unit_text(definition: ET.Element) -> str:
183
+ parts = []
184
+ for unit in definition.findall("{*}listOfUnits/{*}unit"):
185
+ kind = unit.get("kind", "dimensionless")
186
+ symbol = _KIND_SYMBOLS.get(kind, kind)
187
+ scale = _int(unit.get("scale")) or 0
188
+ multiplier = _float_or_none(unit.get("multiplier")) or 1.0
189
+ exponent = _float_or_none(unit.get("exponent")) or 1.0
190
+ if symbol == "dimensionless" and multiplier == 1.0 and scale == 0:
191
+ continue
192
+ if symbol == "s" and scale == 0 and multiplier in _TIME_MULTIPLES:
193
+ text = _TIME_MULTIPLES[multiplier]
194
+ elif multiplier == 1.0 and scale in _SCALE_PREFIX and symbol != "dimensionless":
195
+ text = f"{_SCALE_PREFIX[scale]}{symbol}"
196
+ elif multiplier == 1.0 and scale == 0:
197
+ text = symbol
198
+ else:
199
+ text = f"({multiplier:g}e{scale} * {symbol})"
200
+ if exponent != 1.0:
201
+ text += f"**{exponent:g}"
202
+ parts.append(text)
203
+ return " * ".join(parts) if parts else "dimensionless"
204
+
205
+
206
+ def _ratio(numerator: str | None, denominator: str | None) -> str | None:
207
+ if not numerator:
208
+ return None
209
+ if not denominator or denominator == "dimensionless":
210
+ return numerator
211
+ return f"{numerator} / {denominator}" if " " not in denominator else f"{numerator} / ({denominator})"
212
+
213
+
214
+ def _package(attribute: str) -> str:
215
+ return attribute.split("}")[0].lstrip("{").rsplit("/", 2)[-2] if "}" in attribute else ""
216
+
217
+
218
+ def _identifier(text: str) -> str:
219
+ cleaned = "".join(c if c.isalnum() or c == "_" else "_" for c in text)
220
+ return cleaned if cleaned and not cleaned[0].isdigit() else f"m_{cleaned}"
221
+
222
+
223
+ def _int(text: str | None) -> int | None:
224
+ try:
225
+ return int(text) if text is not None else None
226
+ except ValueError:
227
+ return None
228
+
229
+
230
+ def _float_or_none(text: str | None) -> float | None:
231
+ try:
232
+ return float(text) if text is not None else None
233
+ except ValueError:
234
+ return None
@@ -0,0 +1,244 @@
1
+ """
2
+ Type expressions as data, for the normalizer and for `describe`: `parse_type` accepts both bigraph-schema
3
+ forms, the string grammar (`list[float]`, `a:float|b:integer`, `A~B`, `array[(3|4),float]`, `float{3.5}`) and
4
+ the dict form (`{"_type": "map", "_value": ...}`), and keeps the original (`source`) so that `to_bigraph` is
5
+ lossless. The DSL spelling of a type lives in TypeScript (`dsl/src/language/v2/types.ts`).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import re
12
+ from dataclasses import dataclass, field
13
+ from typing import Any, Final
14
+
15
+ from bigraph_schema.parse import parse_expression # type: ignore[import-untyped]
16
+
17
+
18
+ class _Missing:
19
+ """The absence of a default (distinct from `None`)."""
20
+
21
+ _instance: _Missing | None = None
22
+
23
+ def __new__(cls) -> _Missing:
24
+ if cls._instance is None:
25
+ cls._instance = super().__new__(cls)
26
+ return cls._instance
27
+
28
+ def __repr__(self) -> str:
29
+ return "MISSING"
30
+
31
+ def __bool__(self) -> bool:
32
+ return False
33
+
34
+
35
+ MISSING: Final = _Missing()
36
+
37
+
38
+ @dataclass
39
+ class Named:
40
+ """A (possibly parametric) named type: `float`, `list[float]`, `map[string,float]`, `array[(3|4),float]`."""
41
+
42
+ name: str
43
+ args: list[TypeExpr] = field(default_factory=list)
44
+ default: Any = MISSING
45
+ units: str | None = None
46
+ source: Any = None
47
+
48
+
49
+ @dataclass
50
+ class Struct:
51
+ """A dict schema: `a:float|b:integer` or `{"a": "float", "b": "integer"}`."""
52
+
53
+ fields: dict[str, TypeExpr]
54
+ source: Any = None
55
+
56
+
57
+ @dataclass
58
+ class UnionType:
59
+ """A union: `A~B`."""
60
+
61
+ options: list[TypeExpr]
62
+ source: Any = None
63
+
64
+
65
+ @dataclass
66
+ class Group:
67
+ """A shape group inside an array type: `(3|4)`."""
68
+
69
+ items: list[int | str]
70
+
71
+
72
+ @dataclass
73
+ class Raw:
74
+ """A dict-form type with attributes that have no dedicated representation; kept verbatim."""
75
+
76
+ attrs: dict[str, Any]
77
+ source: Any = None
78
+
79
+
80
+ TypeExpr = Named | Struct | UnionType | Group | Raw
81
+
82
+ _PARAM_KEYS: dict[str, tuple[str, ...]] = {
83
+ "list": ("_element",),
84
+ "set": ("_element",),
85
+ "tree": ("_leaf",),
86
+ "map": ("_key", "_value"),
87
+ "tuple": ("_values",),
88
+ "enum": ("_values",),
89
+ "union": ("_options",),
90
+ "maybe": ("_value",),
91
+ "overwrite": ("_value",),
92
+ "const": ("_value",),
93
+ "quote": ("_value",),
94
+ }
95
+ _ARRAY_KEYS = ("_shape", "_data", "_units")
96
+ _BIGRAPH_NAMES = {"int": "integer", "bool": "boolean"}
97
+ _TRAILING_DEFAULT = re.compile(r"^([^{}]*)\{(.*)\}$")
98
+
99
+
100
+ def parse_type(spec: Any) -> TypeExpr:
101
+ """Parse a bigraph-schema type (string or dict form) into a `TypeExpr` that remembers its source."""
102
+ if isinstance(spec, str):
103
+ return _from_string(spec)
104
+ if isinstance(spec, dict):
105
+ return _from_dict(spec)
106
+ raise TypeError(f"not a type expression: {spec!r}")
107
+
108
+
109
+ def _from_string(text: str) -> TypeExpr:
110
+ source = text
111
+ stripped = text.strip()
112
+ default: Any = MISSING
113
+ match = _TRAILING_DEFAULT.match(stripped)
114
+ if match:
115
+ stripped, default = match.group(1), _literal(match.group(2))
116
+ expr = _from_parsed(parse_expression(stripped))
117
+ if default is not MISSING and isinstance(expr, Named):
118
+ expr.default = default
119
+ if not isinstance(expr, Group):
120
+ expr.source = source
121
+ return expr
122
+
123
+
124
+ def _literal(text: str) -> Any:
125
+ try:
126
+ return json.loads(text)
127
+ except ValueError:
128
+ return text
129
+
130
+
131
+ def _from_parsed(parsed: Any) -> TypeExpr:
132
+ if isinstance(parsed, str):
133
+ return Named(parsed)
134
+ if isinstance(parsed, list):
135
+ name, params = parsed
136
+ return Named(str(name), [_from_parsed(p) for p in params])
137
+ if isinstance(parsed, tuple):
138
+ return Group([_dim(p) for p in parsed])
139
+ if isinstance(parsed, dict):
140
+ if "_union" in parsed:
141
+ return UnionType([_from_parsed(p) for p in parsed["_union"]])
142
+ return Struct({str(k): _from_parsed(v) for k, v in parsed.items()})
143
+ raise TypeError(f"unexpected parse result: {parsed!r}")
144
+
145
+
146
+ def _dim(value: Any) -> int | str:
147
+ if isinstance(value, (int, float)) and not isinstance(value, bool):
148
+ return int(value)
149
+ text = str(value)
150
+ return int(text) if text.lstrip("-").isdigit() else text
151
+
152
+
153
+ def _from_dict(spec: dict[str, Any]) -> TypeExpr:
154
+ if "_type" not in spec:
155
+ if any(str(k).startswith("_") for k in spec):
156
+ return Raw(dict(spec), source=spec)
157
+ return Struct({str(k): parse_type(v) for k, v in spec.items()}, source=spec)
158
+ name = spec["_type"]
159
+ if not isinstance(name, str):
160
+ return Raw(dict(spec), source=spec)
161
+ if any(c in name for c in "[{|~") and set(spec) <= {"_type", "_default"}:
162
+ inner = _from_string(name) # `{"_type": "list[string]", "_default": []}`
163
+ if isinstance(inner, Named) and "_default" in spec:
164
+ inner.default = spec["_default"]
165
+ if not isinstance(inner, Group):
166
+ inner.source = spec
167
+ return inner
168
+ keys = set(spec) - {"_type", "_default", "_units"}
169
+ args: list[TypeExpr] = []
170
+ units: str | None = spec.get("_units")
171
+ if name in _PARAM_KEYS and keys <= set(_PARAM_KEYS[name]):
172
+ for key in _PARAM_KEYS[name]:
173
+ if key not in spec:
174
+ continue
175
+ if key in ("_values", "_options"):
176
+ args.extend(Named(str(v)) if name == "enum" else parse_type(v) for v in spec[key])
177
+ else:
178
+ args.append(parse_type(spec[key]))
179
+ elif keys and keys <= set(_ARRAY_KEYS):
180
+ if "_shape" in spec:
181
+ args.append(Group([_dim(d) for d in spec["_shape"]]))
182
+ if "_data" in spec:
183
+ args.append(parse_type(spec["_data"]))
184
+ elif keys:
185
+ return Raw(dict(spec), source=spec)
186
+ return Named(name, args, default=spec.get("_default", MISSING), units=units, source=spec)
187
+
188
+
189
+ def to_bigraph(expr: TypeExpr) -> Any:
190
+ """Render a `TypeExpr` as a bigraph-schema type (the original form when it came from one)."""
191
+ if not isinstance(expr, Group) and expr.source is not None:
192
+ return expr.source
193
+ if isinstance(expr, Named):
194
+ return _named_to_bigraph(expr)
195
+ if isinstance(expr, Struct):
196
+ return {k: to_bigraph(v) for k, v in expr.fields.items()}
197
+ if isinstance(expr, UnionType):
198
+ rendered = [to_bigraph(o) for o in expr.options]
199
+ if all(isinstance(r, str) for r in rendered):
200
+ return "~".join(rendered)
201
+ return {"_type": "union", "_options": rendered}
202
+ if isinstance(expr, Group):
203
+ return "(" + "|".join(str(i) for i in expr.items) + ")"
204
+ return expr.attrs
205
+
206
+
207
+ def _named_to_bigraph(expr: Named) -> Any:
208
+ name = _BIGRAPH_NAMES.get(expr.name, expr.name)
209
+ shape = next((a for a in expr.args if isinstance(a, Group)), None)
210
+ others = [to_bigraph(a) for a in expr.args if not isinstance(a, Group)]
211
+ rendered: Any
212
+ if not expr.args:
213
+ rendered = name
214
+ elif shape is not None:
215
+ rendered = {"_type": name, "_shape": list(shape.items)}
216
+ if others:
217
+ rendered["_data"] = others[0]
218
+ elif all(isinstance(r, str) for r in others):
219
+ rendered = f"{name}[{','.join(others)}]"
220
+ elif name in ("list", "set"):
221
+ rendered = {"_type": name, "_element": others[0]}
222
+ elif name == "tree":
223
+ rendered = {"_type": name, "_leaf": others[0]}
224
+ elif name == "map":
225
+ rendered = {"_type": name, "_value": others[-1]}
226
+ if len(others) == 2:
227
+ rendered["_key"] = others[0]
228
+ elif name in ("tuple", "enum"):
229
+ rendered = {"_type": name, "_values": others}
230
+ elif name == "union":
231
+ rendered = {"_type": name, "_options": others}
232
+ else:
233
+ rendered = {"_type": name, "_value": others[0]}
234
+ if expr.units is not None:
235
+ rendered = (
236
+ {**rendered, "_units": expr.units}
237
+ if isinstance(rendered, dict)
238
+ else {"_type": rendered, "_units": expr.units}
239
+ )
240
+ if expr.default is not MISSING:
241
+ if isinstance(rendered, dict):
242
+ return {**rendered, "_default": expr.default}
243
+ return {"_type": rendered, "_default": expr.default}
244
+ return rendered
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.4
2
+ Name: process_bigraph_lang
3
+ Version: 0.1.0
4
+ Summary: A language server, CLI and Python package for a domain specific language that describes process bigraphs.
5
+ License-File: LICENSE
6
+ Author: Jim Schaff
7
+ Author-email: schaff@uchc.edu
8
+ Requires-Python: >=3.11,<4.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Programming Language :: Python :: 3.15
15
+ Requires-Dist: bigraph-schema (>=1.6.0,<2.0.0)
16
+ Requires-Dist: numpy (>=1.26)
17
+ Project-URL: Documentation, https://biosimulations.github.io/process-bigraph-lang/
18
+ Project-URL: Repository, https://github.com/biosimulations/process-bigraph-lang
19
+ Description-Content-Type: text/markdown
20
+
21
+ # process-bigraph-lang
22
+
23
+ [![Release](https://img.shields.io/github/v/release/biosimulations/process-bigraph-lang)](https://img.shields.io/github/v/release/biosimulations/process-bigraph-lang)
24
+ [![Build status](https://img.shields.io/github/actions/workflow/status/biosimulations/process-bigraph-lang/main.yml?branch=main)](https://github.com/biosimulations/process-bigraph-lang/actions/workflows/main.yml?query=branch%3Amain)
25
+ [![codecov](https://codecov.io/gh/biosimulations/process-bigraph-lang/branch/main/graph/badge.svg)](https://codecov.io/gh/biosimulations/process-bigraph-lang)
26
+ [![Commit activity](https://img.shields.io/github/commit-activity/m/biosimulations/process-bigraph-lang)](https://img.shields.io/github/commit-activity/m/biosimulations/process-bigraph-lang)
27
+ [![License](https://img.shields.io/github/license/biosimulations/process-bigraph-lang)](https://img.shields.io/github/license/biosimulations/process-bigraph-lang)
28
+
29
+ A language server, CLI and Python package for `.pblang`, a domain specific language that describes
30
+ [process bigraph](https://github.com/vivarium-collective/process-bigraph) composites: a tree-shaped, verifiable
31
+ projection of the composite document, with imported interface declarations, parameters, open sites, units and
32
+ model headers that the checker verifies before anything runs. See [the architecture](docs/architecture.md) and
33
+ [the design](docs/v2/design.md).
34
+
35
+ ```bash
36
+ pip install process_bigraph_lang
37
+ python -m process_bigraph_lang.v2 compile composite.pblang --interfaces headers.manifest.json -o composite.json
38
+ python -m process_bigraph_lang.v2 decompile composite.json
39
+ python -m process_bigraph_lang.v2 check composite.pblang --interfaces headers.manifest.json
40
+ python -m process_bigraph_lang.v2 describe my_package.processes --manifest headers.manifest.json --out headers/
41
+ ```
42
+
43
+ - **Github repository**: <https://github.com/biosimulations/process-bigraph-lang/>
44
+ - **Documentation** <https://biosimulations.github.io/process-bigraph-lang/>
45
+
46
+ ## Getting started with your project
47
+
48
+ First, create a repository on GitHub with the same name as this project, and then run the following commands:
49
+
50
+ ```bash
51
+ git init -b main
52
+ git add .
53
+ git commit -m "init commit"
54
+ git remote add origin git@github.com:biosimulations/process-bigraph-lang.git
55
+ git push -u origin main
56
+ ```
57
+
58
+ Finally, install the environment and the pre-commit hooks with
59
+
60
+ ```bash
61
+ make install
62
+ ```
63
+
64
+ You are now ready to start development on your project!
65
+ The CI/CD pipeline will be triggered when you open a pull request, merge to main, or when you create a new release.
66
+
67
+ To finalize the set-up for publishing to PyPI or Artifactory, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/publishing/#set-up-for-pypi).
68
+ For activating the automatic documentation with MkDocs, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/mkdocs/#enabling-the-documentation-on-github).
69
+ To enable the code coverage reports, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/codecov/).
70
+
71
+ ## Releasing a new version
72
+
73
+ - Create an API Token on [PyPI](https://pypi.org/).
74
+ - Add the API Token to your projects secrets with the name `PYPI_TOKEN` by visiting [this page](https://github.com/biosimulations/process-bigraph-lang/settings/secrets/actions/new).
75
+ - Create a [new release](https://github.com/biosimulations/process-bigraph-lang/releases/new) on Github.
76
+ - Create a new tag in the form `*.*.*`.
77
+ - For more details, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/cicd/#how-to-trigger-a-release).
78
+
79
+ ---
80
+
81
+ Repository initiated with [fpgmaas/cookiecutter-poetry](https://github.com/fpgmaas/cookiecutter-poetry).
82
+
@@ -0,0 +1,17 @@
1
+ process_bigraph_lang/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ process_bigraph_lang/bin/.gitignore,sha256=Mc0TXahfdSsEA2MHbJ2xOafJsQEV0Lh5ZnAmdeB8C2g,75
3
+ process_bigraph_lang/bin/cli-native.exe,sha256=EssQrGqEByA2sVeOsHo13LF1zdYeccoASv0eet6ttKQ,82848027
4
+ process_bigraph_lang/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ process_bigraph_lang/v2/__init__.py,sha256=XbRZCZq13KSoa5i2qCqUjJ1GhVQpEwFo1kYj7rin7qg,2053
6
+ process_bigraph_lang/v2/__main__.py,sha256=wvL0b5Lr7wvgFHQnIakOunwVBmIarr8fPpAzzZFWNbA,8773
7
+ process_bigraph_lang/v2/api.py,sha256=7dVRu2F6E10IMORQTr5YbeOOZA4d_LhBm9GUH2tQZFU,7481
8
+ process_bigraph_lang/v2/check.py,sha256=FnW39sxCYL1_c0M-9Edqvtx6v4s_Gpbf2HZDkZZFcDM,2835
9
+ process_bigraph_lang/v2/interfaces.py,sha256=AYox41unDoCBjV-8P6OQoWhVqzUAByzNDmQf1GfkAoQ,9277
10
+ process_bigraph_lang/v2/langium.py,sha256=KRT94Ptyy_yVLZ_rWgPW_dxy0ly9ni8HsO-dve4Wcq8,1597
11
+ process_bigraph_lang/v2/normalize.py,sha256=y5aNmM9Q4Z_qietsFJxuf0Uk_M8nzIRNTKezqyg2TWw,17080
12
+ process_bigraph_lang/v2/sbml.py,sha256=J1LFxPzOTVOOy0_6dpME6TJyuz24TDgT_fF16uAU_nk,9732
13
+ process_bigraph_lang/v2/types.py,sha256=mX0LPaBFbnrMHUofVIztTTDpgJQN2WR6mQ_o3DD9z4M,8306
14
+ process_bigraph_lang-0.1.0.dist-info/licenses/LICENSE,sha256=uuKYfnhjRd2IOKltacW_z7JSZLVs9Mqj50o9fVnRFyI,1089
15
+ process_bigraph_lang-0.1.0.dist-info/METADATA,sha256=lNb9wBSZemfTFhRyzPfFEyO17XASWjnWHDXUAbKlnHQ,4510
16
+ process_bigraph_lang-0.1.0.dist-info/WHEEL,sha256=hMN48yOIL1Tonvh8WM1EhFkq5MVedJfjGh-0PiW51jw,96
17
+ process_bigraph_lang-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.5.0
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_amd64
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025, Jim Schaff
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.