Public demo snapshot of an in-house pyRevit extension for precast concrete shop-ticket QC (37 checks / 46 checklist rows) and a standalone panel handling solver (PCI Chapter 8). - No client, job, plant or product data: knowledge layers ship as structural templates only. - Ticket QC's model-read escalation targets a LAN model server endpoint (configurable), not a cloud provider. - Built from the private development repo via tools/make_dist.py.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Makes the Revit-free parts of `vibe` importable on host CPython.
|
||||
|
||||
`vibe/__init__.py` does `from vibe.helpers import (...)` at module scope, and
|
||||
`helpers` imports the Revit API the moment it is loaded. That is correct for
|
||||
the extension -- every button script and every `/vibe/exec` snippet gets `q`,
|
||||
`p`, `tx` and friends for free -- but it means a plain `import vibe.knowledge`
|
||||
from an ordinary CPython interpreter runs `vibe/__init__.py` first and dies
|
||||
with `ImportError: No module named Autodesk` before it ever reaches
|
||||
`knowledge.py`.
|
||||
|
||||
Most of `vibe` does not actually touch Revit at module scope. `knowledge.py`,
|
||||
`liftexport.py` and `configdump.py` only reach for the Revit API, or for
|
||||
`vibe.designspec` / `vibe.ticketqc` (which do), *inside* the functions that
|
||||
need a live document -- on purpose, precisely so the rest of the file can be
|
||||
exercised without one. `vibe.compat` has no Revit dependency at all.
|
||||
|
||||
THE TRICK, and why it works
|
||||
----------------------------
|
||||
We never execute `vibe/__init__.py`. Instead we register a bare, empty module
|
||||
object as `sys.modules['vibe']` with its `__path__` pointed at the real
|
||||
`WillTools.extension/lib/vibe` directory, then get out of the way. From then
|
||||
on, ordinary `import vibe.knowledge` / `from vibe import knowledge` statements
|
||||
-- inside test files AND inside the vibe modules themselves, e.g.
|
||||
`liftexport.py`'s `from vibe import knowledge` -- are resolved by Python's
|
||||
normal import machinery: `vibe` is already in `sys.modules` (so its
|
||||
`__init__.py` is never run again, and never run at all), and `vibe.knowledge`
|
||||
is found and executed as an ordinary submodule via that `__path__`.
|
||||
|
||||
USING THIS IN A NEW TEST
|
||||
-------------------------
|
||||
Nothing to import from this file. Once pytest has loaded this conftest.py
|
||||
(automatic for anything under `WillTools.extension/tests/`), plain imports
|
||||
just work:
|
||||
|
||||
from vibe import knowledge
|
||||
from vibe import liftexport
|
||||
from vibe import configdump
|
||||
from vibe import compat
|
||||
|
||||
A module that reaches for Revit or for `vibe.designspec` / `vibe.ticketqc` at
|
||||
MODULE SCOPE (rather than inside a function) will still fail to import this
|
||||
way -- that is not a bug in this trick, it is this trick correctly reporting
|
||||
that the module has a hard Revit dependency. `vibe.ticketqc` is one such
|
||||
module (it does `from Autodesk.Revit.DB import (...)` at the top), and so is
|
||||
`vibe.helpers`. Nothing under test here should ever need either.
|
||||
|
||||
`liftexport.export()` and anything else that needs a live `doc` is out of
|
||||
scope for this suite by the same logic -- see the docstring in
|
||||
`test_liftexport.py`.
|
||||
"""
|
||||
import sys
|
||||
import types
|
||||
from pathlib import Path
|
||||
|
||||
VIBE_DIR = Path(__file__).resolve().parents[1] / 'lib' / 'vibe'
|
||||
assert VIBE_DIR.is_dir(), 'expected to find vibe at {0}'.format(VIBE_DIR)
|
||||
|
||||
if 'vibe' not in sys.modules:
|
||||
_stub = types.ModuleType('vibe')
|
||||
_stub.__path__ = [str(VIBE_DIR)] # makes it a package, not a module
|
||||
_stub.__file__ = str(VIBE_DIR / '__init__.py')
|
||||
sys.modules['vibe'] = _stub
|
||||
|
||||
# The pyRevit YAML reader is faked in `pyrevit_yaml_fake.py`, NOT here, and
|
||||
# the module name is deliberately long: `panellift/tests/` has a conftest of
|
||||
# its own, so a whole-repo `python -m pytest` run leaves exactly ONE module
|
||||
# named `conftest` importable and it is whichever pytest reached first.
|
||||
# `from conftest import ...` inside a test file is therefore a coin flip.
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Appended by tools/make_dist.py -- knowledge-content test exclusion
|
||||
# ============================================================================
|
||||
# See knowledge/catalogue.yaml's own header (build_catalogue_carveout() in
|
||||
# tools/make_dist.py) for the full accounting. Short version: these 7
|
||||
# tests read `catalogue.inserts` and assert the owner's real published
|
||||
# manufacturer numbers, which this distribution does not ship. Skipped by
|
||||
# name, not faked with invented numbers standing in for real knowledge.
|
||||
#
|
||||
# AUTO-DETECTING: if `catalogue.inserts` is ever non-empty in the
|
||||
# knowledge/catalogue.yaml this test run actually finds -- e.g. a colleague
|
||||
# who has since been handed the owner's real knowledge layers -- these run
|
||||
# normally, unskipped, no edit needed here.
|
||||
_KNOWLEDGE_CONTENT_TESTS = ['test_the_shipped_catalogue_file_agrees_with_the_defaults', 'test_the_factor_resolves_per_part_and_is_never_borrowed', 'test_the_spacing_minimum_is_derived_from_the_part_s_own_edge_distance', 'test_a_published_spacing_overrides_the_multiple', 'test_a_part_with_no_edge_distance_to_double_stands_down_naming_it', 'test_st100_carries_the_owners_number_and_st50_still_does_not', 'test_qc589_plant_override_beats_the_public_catalogue']
|
||||
|
||||
|
||||
def _dist_real_inserts_present():
|
||||
import io as _io
|
||||
import os as _os
|
||||
here = _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__)))
|
||||
path = _os.path.join(_os.path.dirname(here), 'knowledge', 'catalogue.yaml')
|
||||
try:
|
||||
import yaml as _yaml
|
||||
with _io.open(path, encoding='utf-8') as fh:
|
||||
data = _yaml.safe_load(fh.read()) or {}
|
||||
except Exception:
|
||||
return False
|
||||
return bool(((data.get('catalogue') or {}).get('inserts')) or None)
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
import pytest as _pytest
|
||||
if _dist_real_inserts_present():
|
||||
return
|
||||
skip = _pytest.mark.skip(
|
||||
reason='knowledge-content test: asserts the owner\'s real product '
|
||||
'capacity data, which this distribution does not ship -- '
|
||||
'see knowledge/catalogue.yaml\'s header and CONTRIBUTING.md')
|
||||
for item in items:
|
||||
if item.originalname in _KNOWLEDGE_CONTENT_TESTS or item.name in _KNOWLEDGE_CONTENT_TESTS:
|
||||
item.add_marker(skip)
|
||||
Reference in New Issue
Block a user