Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ jobs:
energyplus-version: ${{ matrix.energyplus-version }}
energyplus-sha: ${{ matrix.energyplus-sha }}
energyplus-install: ${{ matrix.energyplus-install }}


- name: Install libgfortran3
run: sudo apt-get update && sudo apt-get install -y libgfortran5

- name: Test with tox
run: tox -- -n 2 --doctest-modules tests --cov --cov-config=pyproject.toml --cov-report=xml
env:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ install: ## Install the poetry environment and install the pre-commit hooks
@echo "🚀 Creating virtual environment using pyenv and poetry"
@poetry install
@poetry run pre-commit install
@poetry shell
@poetry env activate

.PHONY: check
check: ## Run code quality tools.
Expand Down
132 changes: 126 additions & 6 deletions archetypal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from __future__ import annotations

import logging as lg
import os
import shutil
from pathlib import Path
from typing import Any, ClassVar, Literal, Optional

Expand Down Expand Up @@ -195,6 +197,124 @@ def initialize_units(cls, v):
settings = Settings()
settings.unit_registry = unit_registry


# --- pathlib.Path compatibility shims (legacy path.Path API) ---
# These helpers allow older code to keep working after switching to pathlib.
def _expand(self: Path) -> Path:
return self.expanduser()


def _files(self: Path, pattern: str = "*"):
# Raise if directory doesn't exist to mimic legacy semantics used in tests
if not self.exists():
raise FileNotFoundError(str(self))
return list(self.glob(pattern))


def _walkfiles(self: Path, pattern: str = "*"):
return self.rglob(pattern)


def _basename(self: Path) -> str:
return self.name


def _dirname(self: Path) -> Path:
return self.parent


def _makedirs_p(self: Path) -> Path:
self.mkdir(parents=True, exist_ok=True)
return self


def _mkdir_p(self: Path) -> Path:
self.mkdir(parents=True, exist_ok=True)
return self


def _mkdir(self: Path, *args, **kwargs) -> Path: # type: ignore[override]
# Preserve typical semantics but return self for chaining.
Path.mkdir(self, *args, **kwargs)
return self


def _rmtree(self: Path, ignore_errors: bool = False):
shutil.rmtree(self, ignore_errors=ignore_errors)


def _rmtree_p(self: Path):
shutil.rmtree(self, ignore_errors=True)


def _copy(self: Path, dest) -> Path:
dest = Path(dest)
target = dest / self.name if dest.exists() and dest.is_dir() else dest
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(self, target)
return target


def _copytree(self: Path, dest) -> Path:
dest = Path(dest)
shutil.copytree(self, dest)
return dest


def _relpath(self: Path, start: Path | str | None = None) -> Path:
base = start if start is not None else os.getcwd()
return Path(os.path.relpath(self, start=base))


def _stripext(self: Path) -> str:
return self.stem


def _endswith(self: Path, suffix) -> bool:
return str(self).endswith(suffix)


def _len(self: Path) -> int:
# Some third-party libraries (e.g., click's test runner) may call len() on
# arguments; provide a best-effort length of the string form.
return len(str(self))


def _getitem(self: Path, item):
return str(self).__getitem__(item)


def _startswith(self: Path, prefix, *args) -> bool:
return str(self).startswith(prefix, *args)


# Attach shims if not already present
for name, func in {
"expand": _expand,
"files": _files,
"walkfiles": _walkfiles,
"basename": _basename,
"dirname": _dirname,
"makedirs_p": _makedirs_p,
"mkdir_p": _mkdir_p,
"mkdir": _mkdir,
"rmtree": _rmtree,
"rmtree_p": _rmtree_p,
"copy": _copy,
"copytree": _copytree,
"relpath": _relpath,
"stripext": _stripext,
"endswith": _endswith,
"__getitem__": _getitem,
"startswith": _startswith,
}.items():
if not hasattr(Path, name):
setattr(Path, name, func)

# Provide len() on Path for better interop in some callers
if not hasattr(Path, "__len__"):
Path.__len__ = _len # type: ignore[attr-defined]

# After settings are loaded, import other modules
from .eplus_interface.version import EnergyPlusVersion # noqa: E402
from .idfclass import IDF # noqa: E402
Expand All @@ -219,16 +339,16 @@ def initialize_units(cls, v):
warn_if_not_compatible()

__all__ = [
"settings",
"Settings",
"__version__",
"utils",
"dataportal",
"IDF",
"EnergyPlusVersion",
"BuildingTemplate",
"EnergyPlusVersion",
"Settings",
"UmiTemplateLibrary",
"__version__",
"clear_cache",
"config",
"dataportal",
"parallel_process",
"settings",
"utils",
]
28 changes: 14 additions & 14 deletions archetypal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
################################################################################
import os
import time
from pathlib import Path

import click
from path import Path

from archetypal import __version__, settings
from archetypal.idfclass import IDF
Expand Down Expand Up @@ -203,15 +203,15 @@ def reduce(ctx, idf, output, weather, cores, all_zones, as_version):
output = Path(output)
name = output.stem
ext = output.suffix if output.suffix == ".json" else ".json"
dir_ = output.dirname()
dir_ = output.parent

file_paths = list(set_filepaths(idf))
file_list = "\n".join([f"{i}. " + str(file.name) for i, file in enumerate(file_paths)])
log(
f"executing {len(file_paths)} file(s):\n{file_list}",
)
weather, *_ = set_filepaths([weather])
log(f"using the '{weather.basename()}' weather file\n")
log(f"using the '{weather.name}' weather file\n")

# Call UmiTemplateLibrary constructor with list of IDFs
template = UmiTemplateLibrary.from_idf_files(
Expand All @@ -224,8 +224,8 @@ def reduce(ctx, idf, output, weather, cores, all_zones, as_version):
annual=True,
)
# Save json file
final_path: Path = dir_ / name + ext
template.save(path_or_buf=final_path)
final_path: Path = dir_ / f"{name}{ext}"
template.save(path_or_buf=str(final_path))
log(
f"Successfully created template file at {final_path.absolute()}",
)
Expand Down Expand Up @@ -321,11 +321,11 @@ def transition(idf, to_version, cores, yes):
for idf in results.values():
if isinstance(idf, IDF):
if overwrite:
file_list.append(idf.original_idfname)
file_list.append(str(idf.original_idfname))
idf.saveas(str(idf.original_idfname))
else:
full_path = idf.original_idfname.dirname() / idf.original_idfname.stem + f"V{to_version}.idf"
file_list.append(full_path)
full_path = idf.original_idfname.parent / f"{idf.original_idfname.stem}V{to_version}.idf"
file_list.append(str(full_path))
idf.saveas(full_path)
log(
f"Successfully transitioned to version '{to_version}' in "
Expand All @@ -348,13 +348,13 @@ def set_filepaths(idf):
"""
if not isinstance(idf, (list, tuple)):
raise TypeError("A list must be passed")
idf = tuple(Path(file_or_path).expand() for file_or_path in idf) # make Paths
idf = tuple(Path(file_or_path).expanduser() for file_or_path in idf) # make Paths
file_paths = () # Placeholder for tuple of paths
for file_or_path in idf:
if file_or_path.is_file(): # if a file, concatenate into file_paths
file_paths += (file_or_path,)
elif file_or_path.is_dir(): # if a directory, walkdir (recursive) and get *.idf
file_paths += tuple(file_or_path.walkfiles("*.idf"))
file_paths += tuple(file_or_path.rglob("*.idf"))
else:
# has wildcard
excluded_dirs = [
Expand All @@ -363,12 +363,12 @@ def set_filepaths(idf):
settings.imgs_folder,
settings.logs_folder,
]
top = file_or_path.absolute().dirname()
top = file_or_path.absolute().parent
for root, _, _ in walkdirs(top, excluded_dirs):
pattern = file_or_path.basename()
file_paths += tuple(Path(root).files(pattern))
pattern = file_or_path.name
file_paths += tuple(Path(root).glob(pattern))

file_paths = {f.relpath().expand() for f in file_paths} # Only keep unique
file_paths = {f.resolve() for f in file_paths} # Only keep unique
# values
if file_paths:
return file_paths
Expand Down
4 changes: 2 additions & 2 deletions archetypal/eplus_interface/basement.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import subprocess
import time
from io import StringIO
from pathlib import Path
from threading import Thread

from packaging.version import Version
from path import Path
from tqdm.contrib.logging import tqdm_logging_redirect

from ..eplus_interface.exceptions import EnergyPlusProcessError
Expand Down Expand Up @@ -59,7 +59,7 @@ def run(self):

# The BasementGHTin.idf file is copied from the self.include list
self.include = [Path(file).copy(self.run_dir) for file in self.idf.include]
if "BasementGHTIn.idf" not in [p.basename() for p in self.include]:
if "BasementGHTIn.idf" not in [p.name for p in self.include]:
self.cleanup_callback()
return

Expand Down
Loading
Loading