@atomic-ehr/codegen 0.0.4 → 0.0.5-canary.20251226085624.be72273

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,333 @@
1
+ using System.Net;
2
+ using System.Text;
3
+ using System.ComponentModel;
4
+ using System.Net.Http.Headers;
5
+
6
+ namespace CSharpSDK.Client;
7
+
8
+ public enum AuthMethods
9
+ {
10
+ [Description("Basic")]
11
+ BASIC,
12
+ }
13
+
14
+ public class AuthCredentials
15
+ {
16
+ public required string Username { get; set; }
17
+ public required string Password { get; set; }
18
+ }
19
+
20
+ public class Auth
21
+ {
22
+ public required AuthMethods Method { get; set; }
23
+ public required AuthCredentials Credentials { get; set; }
24
+ }
25
+
26
+ public class Client
27
+ {
28
+ private HttpClient HttpClient;
29
+ private string Url;
30
+
31
+ public Client(string url, Auth auth)
32
+ {
33
+ var httpClient = new HttpClient();
34
+
35
+ httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(GetMethodValue(auth.Method), EncodeCredentials(auth.Credentials));
36
+
37
+ this.HttpClient = httpClient;
38
+ this.Url = url;
39
+ }
40
+
41
+ public async Task<string> GetInfo()
42
+ {
43
+ UriBuilder resourcePath = new(this.Url) { Path = "$version" };
44
+
45
+ var httpClient = this.HttpClient;
46
+
47
+ var response = await httpClient.GetAsync(resourcePath.Uri);
48
+
49
+ if (!response.IsSuccessStatusCode)
50
+ {
51
+ throw new HttpRequestException($"Server returned error: {response.StatusCode}");
52
+ }
53
+
54
+ return await response.Content.ReadAsStringAsync() ?? throw new Exception("");
55
+ }
56
+
57
+ public async Task<(Bundle<T>? result, string? error)> Search<T>(string? queryString) where T : Resource
58
+ {
59
+ UriBuilder resourcePath = new(this.Url) { Path = Helper.ResourceMap[typeof(T)] };
60
+
61
+ if (queryString is not null)
62
+ {
63
+ resourcePath.Query = queryString;
64
+ }
65
+
66
+ try
67
+ {
68
+ var response = await this.HttpClient.GetAsync(resourcePath.Uri);
69
+
70
+ if (!response.IsSuccessStatusCode)
71
+ {
72
+ throw new HttpRequestException($"Server returned error: {response.StatusCode}");
73
+ }
74
+
75
+ var content = await response.Content.ReadAsStringAsync();
76
+
77
+ Bundle<T>? parsedContent = JsonSerializer.Deserialize<Bundle<T>>(content, Helper.JsonSerializerOptions);
78
+
79
+ return (parsedContent, default);
80
+ }
81
+ catch (HttpRequestException error)
82
+ {
83
+ return (default, error.Message);
84
+ }
85
+ }
86
+
87
+ public async Task<(T? result, string? error)> Read<T>(string id) where T : Resource
88
+ {
89
+ UriBuilder resourcePath = new(this.Url) { Path = Helper.ResourceMap[typeof(T)] };
90
+
91
+ var httpClient = this.HttpClient;
92
+
93
+ try
94
+ {
95
+ var response = await httpClient.GetAsync($"{resourcePath.Uri}/{id}");
96
+
97
+ if (!response.IsSuccessStatusCode)
98
+ {
99
+ throw new HttpRequestException($"Server returned error: {response.StatusCode}");
100
+ }
101
+
102
+ var content = await response.Content.ReadAsStringAsync();
103
+
104
+ T? parsedContent = JsonSerializer.Deserialize<T>(content, Helper.JsonSerializerOptions);
105
+
106
+ return (parsedContent, default);
107
+ }
108
+
109
+ catch (HttpRequestException error)
110
+ {
111
+ return (default, error.Message);
112
+ }
113
+ }
114
+
115
+ public async Task<(T? result, string? error)> Create<T>(T data) where T : Resource
116
+ {
117
+ UriBuilder resourcePath = new(this.Url) { Path = Helper.ResourceMap[typeof(T)] };
118
+
119
+ string jsonBody = JsonSerializer.Serialize<T>(data, Helper.JsonSerializerOptions);
120
+
121
+ HttpContent requestData = new StringContent(jsonBody, Encoding.UTF8, "application/json");
122
+
123
+ try
124
+ {
125
+ var response = await this.HttpClient.PostAsync(resourcePath.Uri, requestData);
126
+
127
+ if (!response.IsSuccessStatusCode)
128
+ {
129
+ throw new HttpRequestException($"Server returned error: {response.StatusCode}");
130
+ }
131
+
132
+ var content = await response.Content.ReadAsStringAsync();
133
+
134
+ T? parsedContent = JsonSerializer.Deserialize<T>(content, Helper.JsonSerializerOptions);
135
+
136
+ return (parsedContent, default);
137
+ }
138
+
139
+ catch (HttpRequestException error)
140
+ {
141
+ return (default, error.Message);
142
+ }
143
+ }
144
+
145
+ public async Task<(T? result, string? error)> Delete<T>(string id) where T : Resource
146
+ {
147
+ UriBuilder resourcePath = new(this.Url) { Path = Helper.ResourceMap[typeof(T)] };
148
+
149
+ var httpClient = this.HttpClient;
150
+
151
+ try
152
+ {
153
+ var response = await httpClient.DeleteAsync($"{resourcePath.Uri}/{id}");
154
+
155
+ if (!response.IsSuccessStatusCode)
156
+ {
157
+ throw new HttpRequestException($"Server returned error: {response.StatusCode}");
158
+ }
159
+
160
+ if (response.StatusCode == HttpStatusCode.NoContent)
161
+ {
162
+ throw new HttpRequestException($"The resource with id \"{id}\" does not exist");
163
+ }
164
+
165
+ var content = await response.Content.ReadAsStringAsync();
166
+
167
+ T? parsedContent = JsonSerializer.Deserialize<T>(content, Helper.JsonSerializerOptions);
168
+
169
+ return (parsedContent, default);
170
+ }
171
+
172
+ catch (HttpRequestException error)
173
+ {
174
+ return (default, error.Message);
175
+ }
176
+ }
177
+
178
+ public async Task<(T? result, string? error)> Update<T>(T resource) where T : Resource
179
+ {
180
+ UriBuilder resourcePath = new(this.Url) { Path = Helper.ResourceMap[typeof(T)] };
181
+
182
+ string jsonBody = JsonSerializer.Serialize<T>(resource, Helper.JsonSerializerOptions);
183
+
184
+ HttpContent requestData = new StringContent(jsonBody, Encoding.UTF8, "application/json");
185
+
186
+ var httpClient = this.HttpClient;
187
+
188
+ try
189
+ {
190
+ var response = await httpClient.PutAsync($"{resourcePath.Uri}/{resource.Id}", requestData);
191
+
192
+ if (!response.IsSuccessStatusCode)
193
+ {
194
+ throw new HttpRequestException($"Server returned error: {response.StatusCode}");
195
+ }
196
+
197
+ var content = await response.Content.ReadAsStringAsync();
198
+
199
+ T? parsedContent = JsonSerializer.Deserialize<T>(content, Helper.JsonSerializerOptions);
200
+
201
+ return (parsedContent, default);
202
+ }
203
+
204
+ catch (HttpRequestException error)
205
+ {
206
+ return (default, error.Message);
207
+ }
208
+ }
209
+
210
+ private string EncodeCredentials(AuthCredentials credentials)
211
+ {
212
+ byte[] credentialsBytes = System.Text.Encoding.UTF8.GetBytes($"{credentials.Username}:{credentials.Password}");
213
+
214
+ return Convert.ToBase64String(credentialsBytes);
215
+ }
216
+
217
+ private string GetMethodValue(AuthMethods method)
218
+ {
219
+ var fieldInfo = method.GetType().GetField(method.ToString());
220
+
221
+ if (fieldInfo == null)
222
+ {
223
+ return method.ToString();
224
+ }
225
+
226
+ var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
227
+
228
+ return attributes.Length > 0 ? attributes[0].Description : method.ToString();
229
+ }
230
+ }
231
+
232
+ public class MetaResponse
233
+ {
234
+ public string? LastUpdated { get; set; }
235
+ public string? CreatedAt { get; set; }
236
+ public required string VersionId { get; set; }
237
+ }
238
+
239
+ public class Link
240
+ {
241
+ public required string Relation { get; set; }
242
+ public required string Url { get; set; }
243
+ }
244
+
245
+ public class Search
246
+ {
247
+ public required string Mode { get; set; }
248
+ }
249
+
250
+ public class Entry<T>
251
+ {
252
+ public required T Resource { get; set; }
253
+ public required Search Search { get; set; }
254
+ public required string FullUrl { get; set; }
255
+ public required Link[] Link { get; set; }
256
+ }
257
+
258
+ public class ApiResourcesResponse<T>
259
+ {
260
+ [JsonPropertyName("query-time")]
261
+ public int? QueryTime { get; set; }
262
+
263
+ public MetaResponse? Meta { get; set; }
264
+ public string? Type { get; set; }
265
+ public string? ResourceType { get; set; }
266
+ public int? Total { get; set; }
267
+ public Link[]? Link { get; set; }
268
+
269
+ [JsonPropertyName("query-timeout")]
270
+ public int? QueryTimeout { get; set; }
271
+ public Entry<T>[]? Entry { get; set; }
272
+
273
+ [JsonPropertyName("query-sql")]
274
+ public object[]? QuerySql { get; set; }
275
+ }
276
+
277
+ public class Bundle<T> : Resource where T : Resource
278
+ {
279
+ public BundleLink[]? Link { get; set; }
280
+ public required string Type { get; set; }
281
+ public BundleEntry[]? Entry { get; set; }
282
+ public uint? Total { get; set; }
283
+ public Signature? Signature { get; set; }
284
+ public string? Timestamp { get; set; }
285
+ public Identifier? Identifier { get; set; }
286
+
287
+ public class BundleLink : BackboneElement
288
+ {
289
+ public required string Url { get; set; }
290
+ public required string Relation { get; set; }
291
+ }
292
+
293
+ public class BundleEntryLink : BackboneElement
294
+ {
295
+ public required string Url { get; set; }
296
+ public required string Relation { get; set; }
297
+ }
298
+
299
+ public class BundleEntrySearch : BackboneElement
300
+ {
301
+ public string? Mode { get; set; }
302
+ public string? Score { get; set; }
303
+ }
304
+
305
+ public class BundleEntryRequest : BackboneElement
306
+ {
307
+ public required string Url { get; set; }
308
+ public required string Method { get; set; }
309
+ public string? IfMatch { get; set; }
310
+ public string? IfNoneExist { get; set; }
311
+ public string? IfNoneMatch { get; set; }
312
+ public string? IfModifiedSince { get; set; }
313
+ }
314
+
315
+ public class BundleEntryResponse : BackboneElement
316
+ {
317
+ public string? Etag { get; set; }
318
+ public required string Status { get; set; }
319
+ public Resource? Outcome { get; set; }
320
+ public string? Location { get; set; }
321
+ public string? LastModified { get; set; }
322
+ }
323
+
324
+ public class BundleEntry : BackboneElement
325
+ {
326
+ public BundleEntryLink[]? Link { get; set; }
327
+ public BundleEntrySearch? Search { get; set; }
328
+ public string? FullUrl { get; set; }
329
+ public BundleEntryRequest? Request { get; set; }
330
+ public T? Resource { get; set; }
331
+ public BundleEntryResponse? Response { get; set; }
332
+ }
333
+ }
@@ -0,0 +1,19 @@
1
+ namespace CSharpSDK;
2
+
3
+ public class LowercaseNamingPolicy : JsonNamingPolicy
4
+ {
5
+ public override string ConvertName(string name) => name.ToLower();
6
+ }
7
+
8
+ public class Helper
9
+ {
10
+ public static readonly JsonSerializerOptions JsonSerializerOptions = new()
11
+ {
12
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
13
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
14
+ Converters = { new JsonStringEnumConverter(new LowercaseNamingPolicy()) },
15
+ WriteIndented = true
16
+ };
17
+
18
+ public static readonly Dictionary<Type, string> ResourceMap = ResourceDictionary.Map;
19
+ }
@@ -0,0 +1,5 @@
1
+ requests>=2.32.0,<3.0.0
2
+ pytest>=8.3.0,<9.0.0
3
+ pydantic>=2.11.0,<3.0.0
4
+ mypy>=1.9.0,<2.0.0
5
+ types-requests>=2.32.0,<3.0.0
@@ -0,0 +1,92 @@
1
+ import re
2
+ import importlib
3
+ import importlib.util
4
+ from typing import Any, Annotated, List
5
+
6
+ from pydantic import BeforeValidator, BaseModel, ValidationError
7
+ from pydantic_core import ValidationError as PydanticCoreValidationError
8
+
9
+
10
+ def to_snake_case(name: str) -> str:
11
+ s = re.sub(r"(?<!^)(?=[A-Z])", "_", name)
12
+ return s.lower()
13
+
14
+
15
+ def module_exists(name: str) -> bool:
16
+ """Checks if a module exists without importing it"""
17
+ return importlib.util.find_spec(name) is not None
18
+
19
+
20
+ def import_and_create_module(module_name: str, class_name: str) -> Any:
21
+ """
22
+ Dynamically import a module and create an instance of a specified class.
23
+
24
+ Args:
25
+ module_name: String name of the module (e.g., 'aidbox.hl7_fhir_r4_core.patient')
26
+ class_name: String name of the class (e.g., 'Patient')
27
+
28
+ Returns:
29
+ Instance of the specified class
30
+ """
31
+ try:
32
+ module = importlib.import_module(module_name)
33
+ class_obj = getattr(module, class_name)
34
+ return class_obj
35
+
36
+ except (ImportError, AttributeError) as e:
37
+ raise ImportError(f"Could not import {class_name} from {module_name}: {e}")
38
+
39
+
40
+ def import_and_create_module_if_exists(package: str, class_name: str) -> Any:
41
+ """
42
+ Dynamically import a module and create an instance of a specified class if the module exists.
43
+
44
+ Args:
45
+ package: String name of the package (e.g., 'aidbox.hl7_fhir_r4_core')
46
+ class_name: String name of the class (e.g., 'Patient')
47
+
48
+ Returns:
49
+ Instance of the specified class or None if the module does not exist
50
+ """
51
+ module_name = package + "." + to_snake_case(class_name)
52
+ if module_exists(module_name):
53
+ return import_and_create_module(module_name, class_name)
54
+ else:
55
+ return None
56
+
57
+
58
+ def validate_and_downcast(
59
+ resource_data: dict[str, Any], package_list: List[str], family: List[str]
60
+ ) -> Any:
61
+ """
62
+ Validates and downcasts ResourceFamily to the appropriate FHIR resource class
63
+
64
+ Args:
65
+ resource_data: Input value (dict)
66
+ package_list: List of package names to search for resource classes (e.g., ['aidbox.hl7_fhir_r4_core', 'aidbox.hl7_fhir_r4_extras'])
67
+ family: List of valid resource types (e.g., 'Group' or 'Patient')
68
+
69
+ Returns:
70
+ Instance of the appropriate FHIR resource class
71
+ """
72
+
73
+ # Extract and validate resource type
74
+ resource_type = resource_data.get("resourceType")
75
+ if not resource_type:
76
+ raise ValueError("Missing 'resourceType' field in resource")
77
+
78
+ if resource_type not in family:
79
+ raise ValueError(f"Invalid resourceType '{resource_type}'. ")
80
+
81
+ # Dynamically import and instantiate the appropriate class
82
+ target_class = None
83
+ for package in package_list:
84
+ target_class = import_and_create_module_if_exists(package, resource_type)
85
+ if target_class is not None:
86
+ break
87
+ if target_class is None:
88
+ raise ImportError(
89
+ f"Could not find class for resourceType '{resource_type}' in packages {package_list}"
90
+ )
91
+
92
+ return target_class.model_validate(resource_data)