hirundo 0.1.8__py3-none-any.whl → 0.1.16__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.
@@ -0,0 +1,42 @@
1
+ import typing
2
+ from pathlib import Path
3
+
4
+ from pydantic import BaseModel
5
+ from typing_extensions import TypeAliasType
6
+
7
+ from hirundo._dataframe import has_pandas, has_polars
8
+
9
+ DataFrameType = TypeAliasType("DataFrameType", None)
10
+
11
+ if has_pandas:
12
+ from hirundo._dataframe import pd
13
+
14
+ DataFrameType = TypeAliasType("DataFrameType", typing.Union[pd.DataFrame, None])
15
+ if has_polars:
16
+ from hirundo._dataframe import pl
17
+
18
+ DataFrameType = TypeAliasType("DataFrameType", typing.Union[pl.DataFrame, None])
19
+
20
+
21
+ T = typing.TypeVar("T")
22
+
23
+
24
+ class DatasetOptimizationResults(BaseModel, typing.Generic[T]):
25
+ model_config = {"arbitrary_types_allowed": True}
26
+
27
+ cached_zip_path: Path
28
+ """
29
+ The path to the cached zip file of the results
30
+ """
31
+ suspects: T
32
+ """
33
+ A polars/pandas DataFrame containing the results of the optimization run
34
+ """
35
+ object_suspects: typing.Optional[T]
36
+ """
37
+ A polars/pandas DataFrame containing the object-level results of the optimization run
38
+ """
39
+ warnings_and_errors: T
40
+ """
41
+ A polars/pandas DataFrame containing the warnings and errors of the optimization run
42
+ """
hirundo/git.py CHANGED
@@ -1,14 +1,15 @@
1
+ import datetime
1
2
  import re
2
3
  import typing
3
- from typing import Annotated
4
4
 
5
5
  import pydantic
6
6
  import requests
7
7
  from pydantic import BaseModel, field_validator
8
8
  from pydantic_core import Url
9
9
 
10
+ from hirundo._constraints import RepoUrl
10
11
  from hirundo._env import API_HOST
11
- from hirundo._headers import get_auth_headers, json_headers
12
+ from hirundo._headers import get_headers
12
13
  from hirundo._http import raise_for_status_with_reason
13
14
  from hirundo._timeouts import MODIFY_TIMEOUT, READ_TIMEOUT
14
15
  from hirundo.logger import get_logger
@@ -16,7 +17,7 @@ from hirundo.logger import get_logger
16
17
  logger = get_logger(__name__)
17
18
 
18
19
 
19
- class GitPlainAuthBase(BaseModel):
20
+ class GitPlainAuth(BaseModel):
20
21
  username: str
21
22
  """
22
23
  The username for the Git repository
@@ -27,7 +28,7 @@ class GitPlainAuthBase(BaseModel):
27
28
  """
28
29
 
29
30
 
30
- class GitSSHAuthBase(BaseModel):
31
+ class GitSSHAuth(BaseModel):
31
32
  ssh_key: str
32
33
  """
33
34
  The SSH key for the Git repository
@@ -48,10 +49,10 @@ class GitRepo(BaseModel):
48
49
  """
49
50
  A name to identify the Git repository in the Hirundo system.
50
51
  """
51
- repository_url: Annotated[str, Url]
52
+ repository_url: typing.Union[str, RepoUrl]
52
53
  """
53
54
  The URL of the Git repository, it should start with `ssh://` or `https://` or be in the form `user@host:path`.
54
- If it is in the form `user@host:path`, it will be rewritten to `ssh://user@host:path`.
55
+ If it is in the form `user@host:path`, it will be rewritten to `ssh://user@host/path`.
55
56
  """
56
57
  organization_id: typing.Optional[int] = None
57
58
  """
@@ -59,14 +60,14 @@ class GitRepo(BaseModel):
59
60
  If not provided, it will be assigned to your default organization.
60
61
  """
61
62
 
62
- plain_auth: typing.Optional[GitPlainAuthBase] = pydantic.Field(
63
+ plain_auth: typing.Optional[GitPlainAuth] = pydantic.Field(
63
64
  default=None, examples=[None, {"username": "ben", "password": "password"}]
64
65
  )
65
66
  """
66
67
  The plain authentication details for the Git repository.
67
68
  Use this if using a special user with a username and password for authentication.
68
69
  """
69
- ssh_auth: typing.Optional[GitSSHAuthBase] = pydantic.Field(
70
+ ssh_auth: typing.Optional[GitSSHAuth] = pydantic.Field(
70
71
  default=None,
71
72
  examples=[
72
73
  {
@@ -84,34 +85,46 @@ class GitRepo(BaseModel):
84
85
 
85
86
  @field_validator("repository_url", mode="before", check_fields=True)
86
87
  @classmethod
87
- def check_valid_repository_url(cls, repository_url: str):
88
- # Check if the URL already has a protocol
89
- if not re.match(r"^[a-z]+://", repository_url):
90
- # Check if the URL has the `@` and `:` pattern with a non-numeric section before the next slash
91
- match = re.match(r"([^@]+@[^:]+):([^0-9/][^/]*)/(.+)", repository_url)
92
- if match:
93
- user_host = match.group(1)
94
- path = match.group(2) + "/" + match.group(3)
95
- rewritten_url = f"ssh://{user_host}/{path}"
96
- logger.info("Modified Git repo to add SSH protocol", rewritten_url)
97
- return rewritten_url
98
- if not repository_url.startswith("ssh://") and not repository_url.startswith(
99
- "https://"
100
- ):
88
+ def check_valid_repository_url(cls, repository_url: typing.Union[str, RepoUrl]):
89
+ # Check if the URL has the `@` and `:` pattern with a non-numeric section before the next slash
90
+ match = re.match("([^@]+@[^:]+):([^0-9/][^/]*)/(.+)", str(repository_url))
91
+ if match:
92
+ user_host = match.group(1)
93
+ path = match.group(2) + "/" + match.group(3)
94
+ rewritten_url = Url(f"ssh://{user_host}/{path}")
95
+ # Check if the URL already has a protocol
96
+ url_scheme = rewritten_url.scheme
97
+ logger.info(
98
+ "Modified Git repo to replace %s@%s:%s/%s with %s",
99
+ url_scheme,
100
+ match.group(1),
101
+ match.group(2),
102
+ match.group(3),
103
+ rewritten_url,
104
+ )
105
+ return rewritten_url
106
+ if not str(repository_url).startswith("ssh://") and not str(
107
+ repository_url
108
+ ).startswith("https://"):
101
109
  raise ValueError("Repository URL must start with 'ssh://' or 'https://'")
110
+ if not isinstance(repository_url, Url):
111
+ repository_url = Url(repository_url)
102
112
  return repository_url
103
113
 
104
- def create(self):
114
+ def create(self, replace_if_exists: bool = False) -> int:
105
115
  """
106
116
  Create a Git repository in the Hirundo system.
117
+
118
+ Args:
119
+ replace_if_exists: If a Git repository with the same name already exists, replace it.
107
120
  """
108
121
  git_repo = requests.post(
109
122
  f"{API_HOST}/git-repo/",
110
- json=self.model_dump(),
111
- headers={
112
- **json_headers,
113
- **get_auth_headers(),
123
+ json={
124
+ **self.model_dump(mode="json"),
125
+ "replace_if_exists": replace_if_exists,
114
126
  },
127
+ headers=get_headers(),
115
128
  timeout=MODIFY_TIMEOUT,
116
129
  )
117
130
  raise_for_status_with_reason(git_repo)
@@ -120,19 +133,57 @@ class GitRepo(BaseModel):
120
133
  return git_repo_id
121
134
 
122
135
  @staticmethod
123
- def list():
136
+ def get_by_id(git_repo_id: int) -> "GitRepoOut":
137
+ """
138
+ Retrieves a `GitRepo` instance from the server by its ID
139
+
140
+ Args:
141
+ git_repo_id: The ID of the `GitRepo` to retrieve
142
+ """
143
+ git_repo = requests.get(
144
+ f"{API_HOST}/git-repo/{git_repo_id}",
145
+ headers=get_headers(),
146
+ timeout=READ_TIMEOUT,
147
+ )
148
+ raise_for_status_with_reason(git_repo)
149
+ return GitRepoOut(**git_repo.json())
150
+
151
+ @staticmethod
152
+ def get_by_name(
153
+ name: str,
154
+ ) -> "GitRepoOut":
155
+ """
156
+ Retrieves a `GitRepo` instance from the server by its name
157
+
158
+ Args:
159
+ name: The name of the `GitRepo` to retrieve
160
+ """
161
+ git_repo = requests.get(
162
+ f"{API_HOST}/git-repo/by-name/{name}",
163
+ headers=get_headers(),
164
+ timeout=READ_TIMEOUT,
165
+ )
166
+ raise_for_status_with_reason(git_repo)
167
+ return GitRepoOut(**git_repo.json())
168
+
169
+ @staticmethod
170
+ def list() -> list["GitRepoOut"]:
124
171
  """
125
172
  List all Git repositories in the Hirundo system.
126
173
  """
127
174
  git_repos = requests.get(
128
175
  f"{API_HOST}/git-repo/",
129
- headers={
130
- **get_auth_headers(),
131
- },
176
+ headers=get_headers(),
132
177
  timeout=READ_TIMEOUT,
133
178
  )
134
179
  raise_for_status_with_reason(git_repos)
135
- return git_repos.json()
180
+ git_repo_json = git_repos.json()
181
+ return [
182
+ GitRepoOut(
183
+ **git_repo,
184
+ )
185
+ for git_repo in git_repo_json
186
+ ]
136
187
 
137
188
  @staticmethod
138
189
  def delete_by_id(git_repo_id: int):
@@ -144,9 +195,7 @@ class GitRepo(BaseModel):
144
195
  """
145
196
  git_repo = requests.delete(
146
197
  f"{API_HOST}/git-repo/{git_repo_id}",
147
- headers={
148
- **get_auth_headers(),
149
- },
198
+ headers=get_headers(),
150
199
  timeout=MODIFY_TIMEOUT,
151
200
  )
152
201
  raise_for_status_with_reason(git_repo)
@@ -158,3 +207,12 @@ class GitRepo(BaseModel):
158
207
  if not self.id:
159
208
  raise ValueError("No GitRepo has been created")
160
209
  GitRepo.delete_by_id(self.id)
210
+
211
+
212
+ class GitRepoOut(BaseModel):
213
+ id: int
214
+ name: str
215
+ repository_url: RepoUrl
216
+
217
+ created_at: datetime.datetime
218
+ updated_at: datetime.datetime