// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================
// Import
// ========================================
///|
pub struct PyModule {
priv obj : PyObject
priv mut name : String?
priv attrs : Map[String, PyObjectEnum]
}
///|
pub fn PyModule::create(obj : PyObject) -> PyModule raise PyRuntimeError {
guard obj.is_module() else { raise TypeMismatchError }
PyModule::{ obj, name: None, attrs: Map::new() }
}
///|
fn PyModule::create_unchecked(obj : PyObject) -> PyModule {
PyModule::{ obj, name: None, attrs: Map::new() }
}
///|
pub fn PyModule::create_by_ref(
obj_ref : @cpython.PyObjectRef,
) -> PyModule raise PyRuntimeError {
guard @cpython.py_module_check(obj_ref) else { raise TypeMismatchError }
PyModule::{ obj: PyObject::create(obj_ref), name: None, attrs: Map::new() }
}
///|
fn PyModule::create_by_ref_unchecked(
obj_ref : @cpython.PyObjectRef,
) -> PyModule {
PyModule::{ obj: PyObject::create(obj_ref), name: None, attrs: Map::new() }
}
///|
test {
ignore(PyModule::create_by_ref_unchecked)
}
// REVIEW: what if user call pyimport twice or more?
///|
/// Import a python module
///
/// ## Example
///
/// ```moonbit
/// let os = @python.pyimport("os")
///
/// assert_true(os is Some(_))
/// ```
pub fn pyimport(name : String, print_err? : Bool = false) -> PyModule? {
let obj = @cpython.py_import_import_module(name)
if obj.is_null() {
if print_err {
println("module \{name} not found")
@cpython.py_err_print()
}
@cpython.py_err_clear()
return None
}
PyModule::{ obj: PyObject::create(obj), name: None, attrs: Map::new() }
|> Some
}
///|
/// Get the name of the module.
///
/// ## Example
///
/// ```moonbit
/// let os = @python.pyimport("os").unwrap()
///
/// inspect(os.get_name(), content="os")
/// ```
pub fn PyModule::get_name(self : PyModule) -> String {
if self.name is Some(n) {
return n
}
let attr = self.get_attr("__name__")
if attr is None {
println("module has no attribute __name__")
panic()
}
let attr = attr.unwrap()
guard attr is PyString(s) else {
println("module __name__ is not a string")
panic()
}
let n = s.to_string() |> strip_quot
self.name = Some(n)
n
}
///|
pub fn PyModule::dump(self : PyModule) -> Unit {
self.obj.dump()
}
///|
/// Get attribute by name.
///
/// ## Example
///
/// ```moonbit
/// let collections = pyimport("collections").unwrap()
/// guard collections.get_attr("Counter").unwrap() is PyCallable(counter)
///
/// let list = [1L, 2L, 2L, 3L, 3L, 3L].map(PyInteger::from) |> PyList::from
/// let args = PyTuple::new(1)
/// args .. set(0, list)
/// guard counter.invoke(args~) is Some(PyDict(cnt))
/// inspect(cnt, content="Counter({3: 3, 2: 2, 1: 1})")
///
/// guard cnt.obj().get_attr("total") is Some(PyCallable(total))
/// inspect(total.invoke().unwrap(), content="PyInteger(6)")
/// ```
///
/// // The above code is equivalent to the following python code:
///
/// ```python
/// import collections
/// from collections import Counter
///
/// list = [1, 2, 2, 3, 3, 3]
/// cnt = Counter(list)
/// print(cnt) # Counter({3: 3, 2: 2, 1: 1})
///
/// total = cnt.total()
/// print(total) # 6
/// ```
pub fn PyModule::get_attr(
self : PyModule,
attr : String,
print_err? : Bool = false,
) -> PyObjectEnum? {
if self.attrs.contains(attr) {
return self.attrs.get(attr)
}
let f = @cpython.py_object_get_attr_string(self.obj_ref(), attr)
if f.is_null() {
if print_err {
println("module \{self.get_name()} has no attribute \{attr}")
@cpython.py_err_print()
}
@cpython.py_err_clear()
return None
}
let c = PyObjectEnum::create_by_ref(f)
self.attrs.set(attr, c)
Some(c)
}
///|
pub impl IsPyObject for PyModule with obj(self) {
self.obj
}
///|
pub impl Show for PyModule with to_string(self) {
self.obj.to_string()
}
///|
pub impl Show for PyModule with output(self, logger) {
logger.write_string(self.to_string())
}