fargopy 0.4.0__py3-none-any.whl → 1.0.1__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.
fargopy/base.py ADDED
@@ -0,0 +1,377 @@
1
+ ###############################################################
2
+ # Package documentation
3
+ ###############################################################
4
+ """
5
+ FARGOpy Base Module
6
+ ===================
7
+
8
+ This module contains the base classes, configuration, and initialization logic for FARGOpy.
9
+ It is separated from __init__.py to avoid circular import issues with other modules like sys.py.
10
+ """
11
+
12
+ import warnings
13
+ import os
14
+ import json
15
+ import sys
16
+ import numpy as np
17
+
18
+ # Version
19
+ __version__ = '1.0.1'
20
+
21
+ __all__ = [
22
+ "__version__",
23
+ "Debug",
24
+ "Dictobj",
25
+ "Fargobj",
26
+ "Conf",
27
+ "initialize",
28
+ "DEG",
29
+ "RAD",
30
+ "IN_COLAB",
31
+ "_welcome",
32
+ ]
33
+
34
+ ###############################################################
35
+ # Constants
36
+ ###############################################################
37
+ DEG = np.pi / 180
38
+ RAD = 1 / DEG
39
+
40
+ # Check if we are in colab
41
+ IN_COLAB = "google.colab" in sys.modules
42
+
43
+
44
+ ###############################################################
45
+ # Base classes
46
+ ###############################################################
47
+ class Debug(object):
48
+ """The Debug class controls the debugging messages of the package.
49
+
50
+ Attributes
51
+ ----------
52
+ VERBOSE : bool
53
+ If True all the trace messages are shown. Default is False.
54
+
55
+ Methods
56
+ -------
57
+ trace(msg)
58
+ Show a debugging message if VERBOSE=True.
59
+ """
60
+
61
+ VERBOSE = False
62
+
63
+ @staticmethod
64
+ def trace(msg):
65
+ if Debug.VERBOSE:
66
+ print("::" + msg)
67
+
68
+
69
+ class Dictobj(object):
70
+ """Convert a dictionary to an object
71
+
72
+ Initialization attributes:
73
+ dict: dictionary:
74
+ Dictionary containing the attributes.
75
+
76
+ Attributes:
77
+ All the keys in the initialization dictionary.
78
+
79
+ Methods:
80
+ keys():
81
+ It works like the keys() method of a dictionary.
82
+ item(key):
83
+ Recover the value of an attribute as it was a dictionary.
84
+ print_keys():
85
+ Print a list of keys
86
+ """
87
+
88
+ def __init__(self, **kwargs):
89
+ if "dict" in kwargs.keys():
90
+ kwargs.update(kwargs["dict"])
91
+ for key, value in kwargs.items():
92
+ if key == "dict":
93
+ continue
94
+ setattr(self, key, value)
95
+
96
+ def __getitem__(self, key):
97
+ return self.item(str(key))
98
+
99
+ def keys(self):
100
+ """Show the list of attributes of Dictobj"""
101
+ props = []
102
+ for i, prop in enumerate(self.__dict__.keys()):
103
+ if "__" in prop:
104
+ continue
105
+ props += [prop]
106
+ return props
107
+
108
+ def item(self, key):
109
+ """Get the value of an item of a Dictobj."""
110
+ if key not in self.keys():
111
+ raise ValueError(f"Key 'key' not in Dictobj")
112
+ return self.__dict__[key]
113
+
114
+ def __getitem__(self, key):
115
+ return self.item(str(key))
116
+
117
+ def print_keys(self):
118
+ """Print all the keys of a Dictobj."""
119
+ prop_list = ""
120
+ for i, prop in enumerate(self.keys()):
121
+ prop_list += f"{prop}, "
122
+ if ((i + 1) % 10) == 0:
123
+ prop_list += "\n"
124
+ print(prop_list.strip(", "))
125
+
126
+ def __str__(self):
127
+ return str(self.__dict__)
128
+
129
+ def __repr__(self):
130
+ return self.__str__()
131
+
132
+
133
+ class Fargobj(object):
134
+ def __init__(self, **kwargs):
135
+ self.fobject = True
136
+ self.kwargs = kwargs
137
+
138
+ def save_object(self, filename=None, verbose=False):
139
+ """Save Fargobj into a filename in JSON format"""
140
+ if filename is None:
141
+ object_hash = str(abs(hash(str(self.__dict__))))
142
+ filename = f"/tmp/fargobj_{object_hash}.json"
143
+ if verbose:
144
+ print(f"Saving object to {filename}...")
145
+ with open(filename, "w") as file_object:
146
+ file_object.write(
147
+ json.dumps(self.__dict__, default=lambda obj: "<not serializable>")
148
+ )
149
+ file_object.close()
150
+
151
+ def set_property(self, property, default, method=lambda prop: prop):
152
+ """Set a property of object using a given method"""
153
+ if property in self.kwargs.keys():
154
+ method(self.kwargs[property])
155
+ self.__dict__[property] = self.kwargs[property]
156
+ return True
157
+ else:
158
+ method(default)
159
+ self.__dict__[property] = default
160
+ return False
161
+
162
+ def has(self, key):
163
+ """Check if a key is an attribute of Fargobj object"""
164
+ if key in self.__dict__.keys():
165
+ return True
166
+ else:
167
+ return False
168
+
169
+
170
+ ###############################################################
171
+ # Package configuration
172
+ ###############################################################
173
+ # Basic (unmodifiable) variables
174
+ Conf = Dictobj()
175
+
176
+ # Cross-platform home directory detection
177
+ if os.name == "nt": # Windows
178
+ Conf.FP_HOME = os.environ.get("USERPROFILE", os.path.expanduser("~"))
179
+ else: # Unix-like systems (Linux, macOS)
180
+ Conf.FP_HOME = os.environ.get("HOME", os.path.expanduser("~"))
181
+
182
+ Conf.FP_DOTDIR = os.path.join(Conf.FP_HOME, ".fargopy")
183
+ Conf.FP_RCFILE = os.path.join(Conf.FP_DOTDIR, "fargopyrc")
184
+
185
+ # Default configuration file content
186
+ Conf.FP_CONFIGURATION = f"""# This is the configuration variables for FARGOpy
187
+ # Package
188
+ FP_VERSION = '{__version__}'
189
+ # System
190
+ FP_HOME = '{Conf.FP_HOME}/'
191
+ # Directories
192
+ FP_DOTDIR = '{Conf.FP_DOTDIR}'
193
+ FP_RCFILE = '{Conf.FP_RCFILE}'
194
+ # Behavior
195
+ FP_VERBOSE = False
196
+ # FARGO3D variables
197
+ FP_FARGO3D_CLONECMD = 'GIT_TERMINAL_PROMPT=0 git clone https://bitbucket.org/fargo3d/public.git'
198
+ FP_FARGO3D_BASEDIR = '{Conf.FP_HOME}'
199
+ FP_FARGO3D_PACKDIR = 'fargo3d/'
200
+ FP_FARGO3D_BINARY = 'fargo3d'
201
+ FP_FARGO3D_HEADER = 'src/fargo3d.h'
202
+ """
203
+
204
+ # Default initialization script
205
+ Conf.FP_INITIAL_SCRIPT = """
206
+ import sys
207
+ import fargopy as fp
208
+ get_ipython().run_line_magic('load_ext','autoreload')
209
+ get_ipython().run_line_magic('autoreload','2')
210
+ fp.initialize(' '.join(sys.argv))
211
+ """
212
+
213
+
214
+ def initialize(options="", force=False, **kwargs):
215
+ """Initialization routine
216
+
217
+ Args:
218
+ options: string, default = '':
219
+ Action(s) to be performed. Valid actions include:
220
+ 'configure': configure the package.
221
+ 'download': download FARGO3D directory.
222
+ 'check': attempt to compile FARGO3D in the machine.
223
+ 'all': all actions.
224
+
225
+ force: bool, default = False:
226
+ If True, force any action that depends on a previous condition.
227
+ For instance if options = 'configure' and force = True it will
228
+ override FARGOpy directory.
229
+ """
230
+
231
+ # Import fargopy inside the function to avoid circular imports
232
+ # when accessing fargopy.Sys or other components that might rely on base.py
233
+ import fargopy
234
+
235
+ if ("configure" in options) or ("all" in options):
236
+ # Create configuration directory
237
+ if not os.path.isdir(Conf.FP_DOTDIR) or force:
238
+ Debug.trace(f"Configuring FARGOpy at {Conf.FP_DOTDIR}...")
239
+ # Create directory
240
+ os.system(f"mkdir -p {Conf.FP_DOTDIR}")
241
+ # Create configuration variables
242
+ f = open(f"{Conf.FP_DOTDIR}/fargopyrc", "w")
243
+ f.write(Conf.FP_CONFIGURATION)
244
+ f.close()
245
+ # Create initialization script
246
+ f = open(f"{Conf.FP_DOTDIR}/ifargopy.py", "w")
247
+ f.write(Conf.FP_INITIAL_SCRIPT)
248
+ f.close()
249
+ else:
250
+ Debug.trace(f"Configuration already in place.")
251
+
252
+ if ("download" in options) or ("all" in options):
253
+ fargo_dir = f"{Conf.FP_FARGO3D_BASEDIR}/{Conf.FP_FARGO3D_PACKDIR}".replace(
254
+ "//", "/"
255
+ )
256
+
257
+ print("Downloading FARGOpy...")
258
+ if not os.path.isdir(fargo_dir) or force:
259
+ if os.path.isdir(fargo_dir):
260
+ print(f"Directory '{fargo_dir}' already exists. Removing it...")
261
+ os.system(f"rm -rf {fargo_dir}")
262
+ # Ensure GIT_TERMINAL_PROMPT=0 is set to avoid stalling if authentication is requested
263
+ clone_cmd = Conf.FP_FARGO3D_CLONECMD
264
+ if "GIT_TERMINAL_PROMPT" not in clone_cmd:
265
+ clone_cmd = "GIT_TERMINAL_PROMPT=0 " + clone_cmd
266
+
267
+ fargopy.Sys.simple(
268
+ f"cd {Conf.FP_FARGO3D_BASEDIR} && {clone_cmd} {Conf.FP_FARGO3D_PACKDIR}"
269
+ )
270
+ print(f"\tFARGO3D downloaded to {fargo_dir}")
271
+ else:
272
+ print(f"\tFARGO3D directory already present in '{fargo_dir}'")
273
+
274
+ fargo_header = f"{fargo_dir}/{Conf.FP_FARGO3D_HEADER}"
275
+ if not os.path.isfile(fargo_header):
276
+ print(f"No header file for fargo found in '{fargo_header}'")
277
+ else:
278
+ print(f"Header file for FARGO3D found in the fargo directory {fargo_dir}")
279
+
280
+ if ("check" in options) or ("all" in options):
281
+ fargo_dir = f"{Conf.FP_FARGO3D_BASEDIR}/{Conf.FP_FARGO3D_PACKDIR}".replace(
282
+ "//", "/"
283
+ )
284
+
285
+ print("Test compilation of FARGO3D")
286
+ if not os.path.isdir(fargo_dir):
287
+ print(
288
+ f"Directory '{fargo_dir}' does not exist. Please download it with fargopy.initialize('download')"
289
+ )
290
+
291
+ cmd_fun = lambda options, mode: (
292
+ f"make -C {fargo_dir} clean mrproper all {options} 2>&1 |tee /tmp/fargo_{mode}.log"
293
+ )
294
+
295
+ for option, mode in zip(
296
+ ["PARALLEL=0 GPU=0", "PARALLEL=0 GPU=1", "PARALLEL=1 GPU=0"],
297
+ ["regular", "gpu", "parallel"],
298
+ ):
299
+ # Verify if you want to check this mode
300
+ if (mode in kwargs.keys()) and (kwargs[mode] == 0):
301
+ print(f"\tSkipping {mode} compilation")
302
+ exec(f"Conf.FP_FARGO3D_{mode.upper()} = 0")
303
+ continue
304
+
305
+ cmd = cmd_fun(option, mode)
306
+ print(f"\tChecking normal compilation.\n\tRunning '{cmd}':")
307
+ # fargopy.Sys is used here
308
+ error, output = fargopy.Sys.run(cmd)
309
+ if not os.path.isfile(f"{fargo_dir}/{Conf.FP_FARGO3D_BINARY}"):
310
+ print(
311
+ f"\t\tCompilation failed for '{mode}'. Check log file '/tmp/fargo_{mode}.log'"
312
+ )
313
+ exec(f"Conf.FP_FARGO3D_{mode.upper()} = 0")
314
+ else:
315
+ print(f"\t\tCompilation in mode {mode} successful.")
316
+ exec(f"Conf.FP_FARGO3D_{mode.upper()} = 1")
317
+
318
+ print(f"Summary of compilation modes:")
319
+ print(f"\tRegular: {Conf.FP_FARGO3D_REGULAR}")
320
+ print(f"\tGPU: {Conf.FP_FARGO3D_GPU}")
321
+ print(f"\tParallel: {Conf.FP_FARGO3D_PARALLEL}")
322
+
323
+
324
+ # Showing version
325
+ def _welcome():
326
+ """Welcome message"""
327
+ print(f"Running FARGOpy version {__version__}")
328
+
329
+
330
+ ###############################################################
331
+ # Initialization logic (moved from __init__.py)
332
+ ###############################################################
333
+ # Avoid warnings
334
+ warnings.filterwarnings("ignore")
335
+
336
+ # Read FARGOpy configuration variables
337
+ if not os.path.isdir(Conf.FP_DOTDIR):
338
+ print(f"Configuring FARGOpy for the first time")
339
+ initialize("configure")
340
+ Debug.trace(f"::Reading configuration variables")
341
+
342
+ # Load configuration variables into Conf
343
+ conf_dict = dict()
344
+ if os.path.isfile(Conf.FP_RCFILE):
345
+ exec(open(f"{Conf.FP_RCFILE}").read(), dict(), conf_dict)
346
+ Conf.__dict__.update(conf_dict)
347
+
348
+ # Derivative configuration variables
349
+ Debug.VERBOSE = Conf.FP_VERBOSE
350
+ Conf.FP_FARGO3D_DIR = (Conf.FP_FARGO3D_BASEDIR + "/" + Conf.FP_FARGO3D_PACKDIR).replace(
351
+ "//", "/"
352
+ )
353
+ Conf.FP_FARGO3D_LOCKFILE = f"{Conf.FP_DOTDIR}/fargopy.lock"
354
+
355
+ # Check if version in RCFILE is different from installed FARGOpy version
356
+ if Conf.FP_VERSION != __version__:
357
+ print(
358
+ f"Your configuration file version '{Conf.FP_VERSION}' it is different than the installed version of FARGOpy '{__version__}'"
359
+ )
360
+ # Interactive check only if stream is a TTY? Or just skip if not?
361
+ # Original code asks for input. This might be blocking in some envs.
362
+ # We'll keep it as is, but rely on it not triggering often in CI/tests if version matches.
363
+ # However, since I'm creating a new file, I'll assume users might encounter this.
364
+ try:
365
+ # Check if sys.stdin has interactive capabilities
366
+ if sys.stdin.isatty():
367
+ ans = input(
368
+ f"Do you want to update configuration file '{Conf.FP_RCFILE}'? [Y/n]: "
369
+ )
370
+ if ans and ("Y" not in ans.upper()):
371
+ if "N" in ans.upper():
372
+ print("We will keeping asking you this until you update it, sorry!")
373
+ else:
374
+ os.system(f"cp -rf {Conf.FP_RCFILE} {Conf.FP_RCFILE}.save")
375
+ initialize("configure", force=True)
376
+ except Exception:
377
+ pass
fargopy/bin/ifargopy ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python3
2
+ import sys
3
+ import os
4
+ import subprocess
5
+ import argparse
6
+
7
+
8
+ def get_version():
9
+ try:
10
+ try:
11
+ from importlib.metadata import version
12
+ return version("fargopy")
13
+ except ImportError:
14
+ # Fallback for Python < 3.8
15
+ import pkg_resources
16
+ return pkg_resources.get_distribution("fargopy").version
17
+ except Exception:
18
+ # Fallback if package not installed or in development
19
+ try:
20
+ import fargopy.version
21
+ return fargopy.version.version
22
+ except ImportError:
23
+ return "unknown"
24
+
25
+ def run_verify():
26
+ try:
27
+ import fargopy
28
+ v = get_version()
29
+ print(f"fargopy {v} is successfully installed.")
30
+ print(f"Location: {os.path.dirname(fargopy.__file__)}")
31
+ except ImportError:
32
+ print("Error: fargopy is not installed or cannot be imported.")
33
+ sys.exit(1)
34
+
35
+ def run_tests():
36
+ try:
37
+ import fargopy
38
+ package_dir = os.path.dirname(fargopy.__file__)
39
+ test_dir = os.path.join(package_dir, 'tests')
40
+ if not os.path.exists(test_dir):
41
+ print(f"Error: tests directory not found at {test_dir}")
42
+ sys.exit(1)
43
+
44
+ print(f"Running tests in {test_dir}...")
45
+ subprocess.check_call([sys.executable, "-m", "pytest", test_dir])
46
+ except ImportError:
47
+ print("Error: fargopy is not installed.")
48
+ sys.exit(1)
49
+ except subprocess.CalledProcessError as e:
50
+ sys.exit(e.returncode)
51
+
52
+ def main():
53
+ parser = argparse.ArgumentParser(description="fargopy interactive shell and utilities")
54
+ parser.add_argument("--verify", action="store_true", help="Verify installation and show version")
55
+ parser.add_argument("--test", action="store_true", help="Run the distributed tests")
56
+
57
+ # If no arguments are provided, or just unknown args (which arguably should be passed to ipython?
58
+ # but for now let's stick to the requested logic: optional args for cmd, else launch ipython)
59
+ # The issue is that argparse will parse all args. If the user wants to pass args to ipython, this might conflict.
60
+ # However, standard pattern is usually wrapper handles its flags, pass others.
61
+ # But ifargopy historically just wraps ipython.
62
+
63
+ # Let's check if we have specific flags.
64
+ if "--verify" in sys.argv:
65
+ run_verify()
66
+ return
67
+ if "--test" in sys.argv:
68
+ run_tests()
69
+ return
70
+
71
+ # Original behavior: launch IPython
72
+ user_home = os.path.expanduser("~")
73
+ fp_dotdir = os.path.join(user_home, ".fargopy")
74
+
75
+ if not os.path.isdir(fp_dotdir):
76
+ # First time initialization logic
77
+ init_script = "/tmp/ifargopy_initialize.py"
78
+ with open(init_script, "w") as f:
79
+ f.write("import fargopy as fp\n")
80
+ f.write("fp.initialize('configure')\n")
81
+ f.write("print('We have configured fargopy for the first time. Run it again.')\n")
82
+
83
+ subprocess.call(["ipython", "-i", init_script])
84
+ else:
85
+ startup_script = os.path.join(fp_dotdir, "ifargopy.py")
86
+ # Pass all arguments to ipython except the script name itself
87
+ cmd = ["ipython", "-i", startup_script] + sys.argv[1:]
88
+ subprocess.call(cmd)
89
+
90
+ if __name__ == "__main__":
91
+ main()