metafold 0.10.0__tar.gz → 0.12.dev0__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.
Files changed (39) hide show
  1. {metafold-0.10.0 → metafold-0.12.dev0}/PKG-INFO +21 -18
  2. {metafold-0.10.0 → metafold-0.12.dev0}/README.md +11 -1
  3. {metafold-0.10.0 → metafold-0.12.dev0}/metafold/__init__.py +4 -5
  4. {metafold-0.10.0 → metafold-0.12.dev0}/metafold/api.py +4 -4
  5. {metafold-0.10.0 → metafold-0.12.dev0}/metafold/assets.py +31 -15
  6. {metafold-0.10.0 → metafold-0.12.dev0}/metafold/auth.py +1 -2
  7. {metafold-0.10.0 → metafold-0.12.dev0}/metafold/client.py +7 -7
  8. {metafold-0.10.0 → metafold-0.12.dev0}/metafold/jobs.py +26 -27
  9. metafold-0.12.dev0/metafold/materials.py +368 -0
  10. {metafold-0.10.0 → metafold-0.12.dev0}/metafold/projects.py +40 -17
  11. metafold-0.12.dev0/metafold/simulation/__init__.py +29 -0
  12. metafold-0.12.dev0/metafold/simulation/compression_experiment.py +366 -0
  13. metafold-0.12.dev0/metafold/simulation/compression_simulation.py +2372 -0
  14. metafold-0.12.dev0/metafold/simulation/run_experiment.py +355 -0
  15. metafold-0.12.dev0/metafold/utils.py +44 -0
  16. {metafold-0.10.0 → metafold-0.12.dev0}/metafold/workflows.py +101 -22
  17. {metafold-0.10.0 → metafold-0.12.dev0}/metafold.egg-info/PKG-INFO +21 -18
  18. {metafold-0.10.0 → metafold-0.12.dev0}/metafold.egg-info/SOURCES.txt +8 -4
  19. metafold-0.12.dev0/metafold.egg-info/requires.txt +13 -0
  20. {metafold-0.10.0 → metafold-0.12.dev0}/pyproject.toml +48 -21
  21. metafold-0.12.dev0/tests/test_compession_experiment.py +274 -0
  22. metafold-0.12.dev0/tests/test_compression_simulation.py +920 -0
  23. metafold-0.12.dev0/tests/test_run_experiment.py +501 -0
  24. {metafold-0.10.0 → metafold-0.12.dev0}/tests/test_workflows.py +50 -0
  25. metafold-0.10.0/metafold/func.py +0 -1156
  26. metafold-0.10.0/metafold/func_types.py +0 -238
  27. metafold-0.10.0/metafold/nx.py +0 -49
  28. metafold-0.10.0/metafold/utils.py +0 -27
  29. metafold-0.10.0/metafold.egg-info/requires.txt +0 -21
  30. metafold-0.10.0/tests/test_func.py +0 -214
  31. {metafold-0.10.0 → metafold-0.12.dev0}/LICENSE +0 -0
  32. {metafold-0.10.0 → metafold-0.12.dev0}/metafold/exceptions.py +0 -0
  33. {metafold-0.10.0 → metafold-0.12.dev0}/metafold.egg-info/dependency_links.txt +0 -0
  34. {metafold-0.10.0 → metafold-0.12.dev0}/metafold.egg-info/top_level.txt +0 -0
  35. {metafold-0.10.0 → metafold-0.12.dev0}/setup.cfg +0 -0
  36. {metafold-0.10.0 → metafold-0.12.dev0}/tests/test_assets.py +0 -0
  37. {metafold-0.10.0 → metafold-0.12.dev0}/tests/test_jobs.py +0 -0
  38. {metafold-0.10.0 → metafold-0.12.dev0}/tests/test_projects.py +0 -0
  39. {metafold-0.10.0 → metafold-0.12.dev0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metafold
3
- Version: 0.10.0
3
+ Version: 0.12.dev0
4
4
  Summary: Metafold SDK for Python
5
5
  Author-email: Metafold 3D <info@metafold3d.com>
6
6
  License: Copyright 2024 Metafold 3D
@@ -21,13 +21,11 @@ Classifier: Intended Audience :: Developers
21
21
  Classifier: Intended Audience :: Science/Research
22
22
  Classifier: License :: OSI Approved :: MIT License
23
23
  Classifier: Operating System :: OS Independent
24
- Classifier: Programming Language :: Python :: 3.9
25
- Classifier: Programming Language :: Python :: 3.10
26
- Classifier: Programming Language :: Python :: 3.11
27
24
  Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
28
26
  Classifier: Topic :: Scientific/Engineering
29
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
- Requires-Python: >=3.9
28
+ Requires-Python: >=3.12
31
29
  Description-Content-Type: text/markdown
32
30
  License-File: LICENSE
33
31
  Requires-Dist: attrs~=23.2
@@ -35,18 +33,13 @@ Requires-Dist: auth0-python~=4.7
35
33
  Requires-Dist: numpy~=2.0
36
34
  Requires-Dist: requests~=2.31
37
35
  Requires-Dist: scipy~=1.11
38
- Provides-Extra: docs
39
- Requires-Dist: sphinx~=7.2; extra == "docs"
40
- Requires-Dist: sphinx_rtd_theme~=2.0; extra == "docs"
41
- Provides-Extra: lint
42
- Requires-Dist: mypy~=1.10; extra == "lint"
43
- Requires-Dist: types-requests~=2.31; extra == "lint"
44
- Provides-Extra: test
45
- Requires-Dist: pytest~=7.3; extra == "test"
46
- Requires-Dist: requests-toolbelt~=1.0; extra == "test"
47
- Provides-Extra: networkx
48
- Requires-Dist: networkx~=3.2; extra == "networkx"
49
- Requires-Dist: types-networkx~=3.2; extra == "networkx"
36
+ Provides-Extra: simulation
37
+ Requires-Dist: dotenv>=0.9.9; extra == "simulation"
38
+ Requires-Dist: pandas>=2.3.3; extra == "simulation"
39
+ Requires-Dist: plyfile>=1.1.3; extra == "simulation"
40
+ Requires-Dist: pyyaml>=6.0.3; extra == "simulation"
41
+ Requires-Dist: simulation-configurator==0.1.1; extra == "simulation"
42
+ Requires-Dist: tables>=3.11.1; extra == "simulation"
50
43
  Dynamic: license-file
51
44
 
52
45
  # Metafold SDK for Python
@@ -58,7 +51,7 @@ Dynamic: license-file
58
51
 
59
52
  ## Installation
60
53
 
61
- ```
54
+ ```console
62
55
  pip install metafold
63
56
  ```
64
57
 
@@ -83,3 +76,13 @@ Read the [documentation][] for more info or play around with one of the
83
76
  [examples](examples).
84
77
 
85
78
  [documentation]: https://Metafold3d.github.io/metafold-python/
79
+
80
+ ## Development
81
+
82
+ This project uses [uv](https://docs.astral.sh/uv/) to manage dependencies.
83
+
84
+ ### Run tests
85
+
86
+ ```console
87
+ uv run pytest ./tests
88
+ ```
@@ -7,7 +7,7 @@
7
7
 
8
8
  ## Installation
9
9
 
10
- ```
10
+ ```console
11
11
  pip install metafold
12
12
  ```
13
13
 
@@ -32,3 +32,13 @@ Read the [documentation][] for more info or play around with one of the
32
32
  [examples](examples).
33
33
 
34
34
  [documentation]: https://Metafold3d.github.io/metafold-python/
35
+
36
+ ## Development
37
+
38
+ This project uses [uv](https://docs.astral.sh/uv/) to manage dependencies.
39
+
40
+ ### Run tests
41
+
42
+ ```console
43
+ uv run pytest ./tests
44
+ ```
@@ -4,7 +4,6 @@ from metafold.assets import AssetsEndpoint
4
4
  from metafold.jobs import JobsEndpoint
5
5
  from metafold.workflows import WorkflowsEndpoint
6
6
  from metafold.auth import AuthProvider
7
- from typing import Optional
8
7
 
9
8
 
10
9
  class MetafoldClient(Client):
@@ -22,10 +21,10 @@ class MetafoldClient(Client):
22
21
 
23
22
  def __init__(
24
23
  self,
25
- access_token: Optional[str] = None,
26
- project_id: Optional[str] = None,
27
- client_id: Optional[str] = None,
28
- client_secret: Optional[str] = None,
24
+ access_token: str | None = None,
25
+ project_id: str | None = None,
26
+ client_id: str | None = None,
27
+ client_secret: str | None = None,
29
28
  auth_domain: str = "metafold3d.us.auth0.com",
30
29
  base_url: str = "https://api.metafold3d.com/",
31
30
  ) -> None:
@@ -1,9 +1,9 @@
1
1
  from datetime import datetime, timezone
2
2
  from functools import wraps
3
- from typing import Any, Callable, Optional, TypeVar, Union
3
+ from typing import Any, Callable, TypeVar
4
4
 
5
5
 
6
- def asdatetime(s: Union[str, datetime]) -> datetime:
6
+ def asdatetime(s: str | datetime) -> datetime:
7
7
  """Parse Metafold API datetime.
8
8
 
9
9
  Note datetime strings returned by the Metafold API are RFC 1123 formatted,
@@ -30,10 +30,10 @@ T = TypeVar("T")
30
30
  U = TypeVar("U")
31
31
 
32
32
 
33
- def optional(f: Callable[[T], U]) -> Callable[[Optional[T]], Optional[U]]:
33
+ def optional(f: Callable[[T], U]) -> Callable[[T | None], U | None]:
34
34
  """Decorator to generate converters that accept optional values."""
35
35
  @wraps(f)
36
- def decorator(v: Optional[T]) -> Optional[U]:
36
+ def decorator(v: T | None) -> U | None:
37
37
  if v is None:
38
38
  return None
39
39
  return f(v)
@@ -4,7 +4,7 @@ from metafold.api import asdatetime, asdict
4
4
  from metafold.client import Client
5
5
  from os import PathLike
6
6
  from requests import Response
7
- from typing import IO, Optional, Union
7
+ from typing import IO
8
8
  import requests
9
9
 
10
10
 
@@ -29,7 +29,7 @@ class Asset:
29
29
  created: datetime = field(converter=asdatetime)
30
30
  modified: datetime = field(converter=asdatetime)
31
31
  project_id: str
32
- job_id: Optional[str] = None
32
+ job_id: str | None = None
33
33
 
34
34
 
35
35
  class AssetsEndpoint:
@@ -40,9 +40,9 @@ class AssetsEndpoint:
40
40
 
41
41
  def list(
42
42
  self,
43
- sort: Optional[str] = None,
44
- q: Optional[str] = None,
45
- project_id: Optional[str] = None,
43
+ sort: str | None = None,
44
+ q: str | None = None,
45
+ project_id: str | None = None,
46
46
  ) -> list[Asset]:
47
47
  """List assets.
48
48
 
@@ -63,7 +63,7 @@ class AssetsEndpoint:
63
63
  r: Response = self._client.get(url, params=payload)
64
64
  return [Asset(**a) for a in r.json()]
65
65
 
66
- def get(self, asset_id: str, project_id: Optional[str] = None) -> Asset:
66
+ def get(self, asset_id: str, project_id: str | None = None) -> Asset:
67
67
  """Get an asset.
68
68
 
69
69
  Args:
@@ -78,28 +78,44 @@ class AssetsEndpoint:
78
78
  r: Response = self._client.get(url)
79
79
  return Asset(**r.json())
80
80
 
81
- def download_file(
82
- self, asset_id: str, path: Union[str, PathLike],
83
- project_id: Optional[str] = None,
81
+ def download(
82
+ self, asset_id: str, f: IO[bytes],
83
+ project_id: str | None = None,
84
84
  ):
85
85
  """Download an asset.
86
86
 
87
87
  Args:
88
88
  asset_id: ID of asset to download.
89
- path: Path to downloaded file.
89
+ f: File-like object open for writing in binary mode.
90
90
  project_id: Asset project ID.
91
91
  """
92
92
  project_id = self._client.project_id(project_id)
93
93
  url = f"/projects/{project_id}/assets/{asset_id}"
94
94
  r: Response = self._client.get(url, params={"download": "true"})
95
95
  r = requests.get(r.json()["link"], stream=True)
96
- with open(path, "wb") as f:
96
+ try:
97
97
  for chunk in r.iter_content(chunk_size=65536): # 64 KiB
98
98
  f.write(chunk)
99
+ finally:
100
+ f.close()
101
+
102
+ def download_file(
103
+ self, asset_id: str, path: str | PathLike,
104
+ project_id: str | None = None,
105
+ ):
106
+ """Download an asset.
107
+
108
+ Args:
109
+ asset_id: ID of asset to download.
110
+ path: Path to downloaded file.
111
+ project_id: Asset project ID.
112
+ """
113
+ with open(path, "wb") as f:
114
+ self.download(asset_id, f, project_id)
99
115
 
100
116
  def create(
101
- self, f: Union[str, bytes, PathLike, IO[bytes]],
102
- project_id: Optional[str] = None,
117
+ self, f: str | bytes | PathLike | IO[bytes],
118
+ project_id: str | None = None,
103
119
  ) -> Asset:
104
120
  """Upload an asset.
105
121
 
@@ -119,7 +135,7 @@ class AssetsEndpoint:
119
135
  fp.close()
120
136
  return Asset(**r.json())
121
137
 
122
- def delete(self, asset_id: str, project_id: Optional[str] = None) -> None:
138
+ def delete(self, asset_id: str, project_id: str | None = None) -> None:
123
139
  """Delete an asset.
124
140
 
125
141
  Args:
@@ -131,7 +147,7 @@ class AssetsEndpoint:
131
147
  self._client.delete(url)
132
148
 
133
149
 
134
- def _open_file(f: Union[str, bytes, PathLike, IO[bytes]]) -> IO[bytes]:
150
+ def _open_file(f: str | bytes | PathLike | IO[bytes]) -> IO[bytes]:
135
151
  if isinstance(f, (str, bytes, PathLike)):
136
152
  return open(f, "rb")
137
153
  return f
@@ -1,7 +1,6 @@
1
1
  from auth0.authentication import GetToken # type: ignore
2
2
  from collections import namedtuple
3
3
  from datetime import datetime, timedelta, timezone
4
- from typing import Optional
5
4
 
6
5
  Token = namedtuple("Token", ["access_token", "expires_at"])
7
6
 
@@ -23,7 +22,7 @@ class AuthProvider:
23
22
  self._client_id,
24
23
  client_secret=client_secret,
25
24
  )
26
- self._token: Optional[Token] = None
25
+ self._token: Token | None = None
27
26
 
28
27
  def get_token(self) -> str:
29
28
  now = datetime.now(timezone.utc)
@@ -1,7 +1,7 @@
1
1
  from metafold.auth import AuthProvider
2
2
  from metafold.exceptions import PollTimeout
3
3
  from requests import HTTPError, Response, Session
4
- from typing import Any, Callable, Optional, Union
4
+ from typing import Any, Callable
5
5
  from urllib.parse import urljoin
6
6
  import platform
7
7
  import time
@@ -13,9 +13,9 @@ class Client:
13
13
  def __init__(
14
14
  self,
15
15
  base_url: str,
16
- access_token: Optional[str] = None,
17
- project_id: Optional[str] = None,
18
- auth: Optional[AuthProvider] = None
16
+ access_token: str | None = None,
17
+ project_id: str | None = None,
18
+ auth: AuthProvider | None = None
19
19
  ) -> None:
20
20
  if bool(auth) == bool(access_token):
21
21
  raise ValueError(
@@ -34,7 +34,7 @@ class Client:
34
34
  "Authorization": f"Bearer {access_token}",
35
35
  })
36
36
 
37
- def project_id(self, id: Optional[str] = None) -> str:
37
+ def project_id(self, id: str | None = None) -> str:
38
38
  id = id or self._default_project
39
39
  if not id:
40
40
  raise ValueError(
@@ -79,8 +79,8 @@ class Client:
79
79
 
80
80
  def poll(
81
81
  self, url: str,
82
- timeout: Union[int, float] = 120,
83
- every: Union[int, float] = 1,
82
+ timeout: int | float = 120,
83
+ every: int | float = 1,
84
84
  ) -> Response:
85
85
  """Poll the given URL in regular intervals.
86
86
 
@@ -5,15 +5,14 @@ from metafold.assets import Asset
5
5
  from metafold.client import Client
6
6
  from metafold.exceptions import PollTimeout
7
7
  from requests import Response
8
- from typing import Any, Optional, TypedDict, Union
9
- from typing_extensions import TypeAlias
8
+ from typing import Any, TypeAlias, TypedDict
10
9
 
11
10
 
12
- def _assets(v: list[Union[dict[str, Any], Asset]]) -> list[Asset]:
11
+ def _assets(v: list[dict[str, Any] | Asset]) -> list[Asset]:
13
12
  return [a if isinstance(a, Asset) else Asset(**a) for a in v]
14
13
 
15
14
 
16
- AssetDict: TypeAlias = dict[str, Union[dict[str, Any], Asset]]
15
+ AssetDict: TypeAlias = dict[str, dict[str, Any] | Asset]
17
16
 
18
17
 
19
18
  def _assets_dict(v: AssetDict) -> dict[str, Asset]:
@@ -24,8 +23,8 @@ def _assets_dict(v: AssetDict) -> dict[str, Asset]:
24
23
 
25
24
 
26
25
  class IODict(TypedDict):
27
- params: Optional[dict[str, Any]]
28
- assets: Optional[dict[str, AssetDict]]
26
+ params: dict[str, Any] | None
27
+ assets: dict[str, AssetDict] | None
29
28
 
30
29
 
31
30
  @frozen(kw_only=True)
@@ -36,8 +35,8 @@ class IO:
36
35
  params: JSON-encoded parameter values.
37
36
  assets: Related assets.
38
37
  """
39
- params: Optional[dict[str, Any]] = None
40
- assets: Optional[dict[str, Asset]] = field(
38
+ params: dict[str, Any] | None = None
39
+ assets: dict[str, Asset] | None = field(
41
40
  converter=lambda v: optional(_assets_dict)(v), default=None)
42
41
 
43
42
  @staticmethod
@@ -69,22 +68,22 @@ class Job:
69
68
  meta: (Deprecated) Additional metadata generated by the job.
70
69
  """
71
70
  id: str
72
- name: Optional[str] = None
71
+ name: str | None = None
73
72
  type: str
74
73
  state: str
75
74
  created: datetime = field(converter=asdatetime)
76
- started: Optional[datetime] = field(
75
+ started: datetime | None = field(
77
76
  converter=lambda v: optional_datetime(v), default=None)
78
- finished: Optional[datetime] = field(
77
+ finished: datetime | None = field(
79
78
  converter=lambda v: optional_datetime(v), default=None)
80
- error: Optional[str] = None
79
+ error: str | None = None
81
80
  inputs: IO = field(converter=lambda v: v if isinstance(v, IO) else IO.from_dict(v))
82
81
  outputs: IO = field(converter=lambda v: v if isinstance(v, IO) else IO.from_dict(v))
83
82
  needs: list[str]
84
- project_id: Optional[str] = None
85
- workflow_id: Optional[str] = None
83
+ project_id: str | None = None
84
+ workflow_id: str | None = None
86
85
  # NOTE(ryan): Deprecated
87
- assets: Optional[list[Asset]] = field(
86
+ assets: list[Asset] | None = field(
88
87
  converter=lambda v: optional(_assets)(v), default=None)
89
88
  parameters: dict[str, Any]
90
89
  meta: dict[str, Any]
@@ -98,9 +97,9 @@ class JobsEndpoint:
98
97
 
99
98
  def list(
100
99
  self,
101
- sort: Optional[str] = None,
102
- q: Optional[str] = None,
103
- project_id: Optional[str] = None,
100
+ sort: str | None = None,
101
+ q: str | None = None,
102
+ project_id: str | None = None,
104
103
  ) -> list[Job]:
105
104
  """List jobs.
106
105
 
@@ -121,7 +120,7 @@ class JobsEndpoint:
121
120
  r: Response = self._client.get(url, params=payload)
122
121
  return [Job(**j) for j in r.json()]
123
122
 
124
- def get(self, job_id: str, project_id: Optional[str] = None) -> Job:
123
+ def get(self, job_id: str, project_id: str | None = None) -> Job:
125
124
  """Get a job.
126
125
 
127
126
  Args:
@@ -138,9 +137,9 @@ class JobsEndpoint:
138
137
 
139
138
  def run(
140
139
  self, type: str, params: dict[str, Any],
141
- name: Optional[str] = None,
142
- timeout: Union[int, float] = 120,
143
- project_id: Optional[str] = None,
140
+ name: str | None = None,
141
+ timeout: int | float = 120,
142
+ project_id: str | None = None,
144
143
  ) -> Job:
145
144
  """Dispatch a new job and wait for a result.
146
145
 
@@ -167,8 +166,8 @@ class JobsEndpoint:
167
166
 
168
167
  def run_status(
169
168
  self, type: str, params: dict[str, Any],
170
- name: Optional[str] = None,
171
- project_id: Optional[str] = None,
169
+ name: str | None = None,
170
+ project_id: str | None = None,
172
171
  ) -> str:
173
172
  """Dispatch a new job and return immediately without waiting for result.
174
173
 
@@ -193,8 +192,8 @@ class JobsEndpoint:
193
192
 
194
193
  def update(
195
194
  self, job_id: str,
196
- name: Optional[str] = None,
197
- project_id: Optional[str] = None,
195
+ name: str | None = None,
196
+ project_id: str | None = None,
198
197
  ) -> Job:
199
198
  """Update a job.
200
199
 
@@ -212,7 +211,7 @@ class JobsEndpoint:
212
211
  r: Response = self._client.patch(url, data=payload)
213
212
  return Job(**r.json())
214
213
 
215
- def delete(self, job_id: str, project_id: Optional[str] = None):
214
+ def delete(self, job_id: str, project_id: str | None = None):
216
215
  """Delete a job.
217
216
 
218
217
  Args: